Download the PHP package vuthaihoc/laravel-xtdb2 without Composer
On this page you can find all versions of the php package vuthaihoc/laravel-xtdb2. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download vuthaihoc/laravel-xtdb2
More information about vuthaihoc/laravel-xtdb2
Files in vuthaihoc/laravel-xtdb2
Package laravel-xtdb2
Short Description An XTDB 2 database driver for Laravel: Eloquent, Query Builder and migrations over the PostgreSQL wire protocol, with bitemporal queries
License MIT
Homepage https://github.com/vuthaihoc/laravel-xtdb2
Informations about the package laravel-xtdb2
XTDB driver for Laravel
A Laravel 12 / 13 database driver for XTDB 2: Eloquent, the query builder and migrations over XTDB's PostgreSQL wire protocol.
Documentation: https://vuthaihoc.github.io/laravel-xtdb2/ — including Bitemporal data: concepts and use cases.
Pre-release. Requires XTDB 2.2 (tested on
2.2.0-beta3): migrations need itsCREATE TABLE.
XTDB is not PostgreSQL: tables are schemaless, every row has an _id, all history is kept (bitemporal), and a
transaction either reads or writes. The driver maps Laravel onto that model; the limits are listed below.
Installation
Run XTDB locally:
Models
Every XTDB row is identified by _id, and XTDB generates no keys. Use HasXtdbKey: the key is _id, a ULID
generated when the model is created.
- Relations, soft deletes, casts, pagination,
chunk(),cursor(),firstOrCreate()andupdateOrCreate()work as usual. Foreign keys are ordinary columns holding the related_id. - A model keeping Laravel's incrementing
idgets a random positive 64-bit integer, stored asidand_id. DB::table()->insert()adds a ULID_idto rows without one;insertGetId()returns the generated key.- Laravel's
array/jsoncasts store JSON text, whose fields XTDB cannot query: useAsDocument, or bindnew LaravelXtdb\Query\Document([...])in the query builder.
Bitemporal queries
See docs/docs/bitemporal.md for the concepts and real-world use cases (scheduled price changes, back-dated salary corrections, reproducible reports, historical addresses, coverage periods, GDPR erasure, month-end snapshots).
XTDB keeps every version of every row along two time axes: valid time (when a fact is true in your domain, which you can set, e.g. a price valid from next month or a correction of the past) and system time (when XTDB recorded it, which you cannot change). By default queries read the rows valid now, as currently recorded.
| Method | SQL |
|---|---|
asOfValidTime($t), validBetween($from, $to = null), forAllValidTime() |
FOR VALID_TIME AS OF / FROM ... TO / FOR ALL VALID_TIME on the from table |
asOfSystemTime($t) (= asOfTime()), forAllSystemTime() |
FOR SYSTEM_TIME AS OF / FOR ALL SYSTEM_TIME |
withValidTime(), history() |
selects _valid_from, _valid_to; history() = all valid time, oldest first |
validFrom($from, $to = null), validTo($to) |
insert: _valid_from/_valid_to values; update/delete: FOR PORTION OF VALID_TIME FROM ... TO ... |
erase() |
ERASE FROM ... WHERE ... |
readCurrent() |
drops the valid-time and system-time clauses |
Times are a DateTimeInterface, a timestamp (UTC unless it has an offset) or a duration such as '-10s'. The clauses
apply to the from table: joined tables and relation subqueries (whereHas()) read the current rows. An update or
delete takes validFrom()/validTo() or forAllValidTime(), not asOfValidTime(). upsert() cannot take a valid
time (XTDB's PATCH rejects _valid_from).
Models
Migrations
XTDB tables are schemaless. A migration declares a table and its columns, so queries can name them before any row exists (XTDB rejects unknown tables and columns):
| Blueprint | On XTDB |
|---|---|
create(), table() adding columns |
create table t (columns): declares the table and the columns, types are ignored |
types, nullable(), default(), change() |
nothing (defaults are not applied: set them in the model's $attributes) |
indexes, primary(), unique(), foreign keys |
nothing: XTDB has none |
drop(), dropIfExists(), truncate(), migrate:fresh |
erase from t where true: rows and their history are removed, the table name stays |
dropColumn() |
nothing: existing values stay |
rename(), renameColumn() |
UnsupportedFeatureException |
Migrations do not run in transactions (XTDB DDL is not transactional).
Values and types
XTDB rejects untyped parameters in writes and keeps values sent as text as text, so the driver writes every binding
as a typed XTDB literal: TRUE/FALSE, numbers, TIMESTAMP '...' for DateTimeInterface, {...} / [...] for
documents, and escaped E'...' strings. Eloquent formats dates to strings (2026-01-02 03:04:05), so strings with a
date and a time are sent as timestamps too; set 'dates_from_strings' => false to keep them text.
toRawSql() shows the SQL the driver sends.
Keep one type per column: XTDB compares values by type, and remembers a column's types even after its rows are
erased (an _id that was once an integer and once a string cannot be sorted).
Transactions
An XTDB transaction is read-only or read-write, decided by its first statement, and a read-write transaction runs
no queries. Inside DB::transaction(), only write:
A query after a write, or a write after a query, throws LaravelXtdb\Exceptions\XtdbTransactionException with this
advice. Nested transactions are part of the outer one (XTDB has no savepoints).
Limits
| Laravel | XTDB |
|---|---|
update() / delete() return values |
always 0 (XTDB reports no affected rows) |
insert() of an existing _id |
replaces the whole row |
upsert() |
PATCH, matched by _id only; existing rows get every given column |
insertOrIgnore(), inRandomOrder(), JSON path updates (update(['a->b' => ...])), array indexes in JSON paths |
UnsupportedFeatureException |
lockForUpdate(), sharedLock() |
ignored (no row locks) |
whereLike() / ilike |
lower(col) like lower(?) |
window functions, HAVING on select aliases, subqueries in UPDATE ... SET |
not supported by XTDB |
min/max/sum/avg of a column that never held a value |
null (XTDB raises an error; the driver returns null) |
XTDB 2.2.0-beta3 issues the driver works around: ? = any(list) followed by AND/OR evaluates to false (XTDB
parses the rest of the condition into the ANY; compiled as (? = any(list)) is true), and LIKE ... ESCAPE matches nothing, so %
and _ cannot be matched literally with like (the search helpers use position()).
laravel-db-portable
The driver requires laravel-db-portable, and its query builder implements that package's contracts:
HistoricalReads:asOfTime($time)reads thefromtable as XTDB had stored it then (FOR SYSTEM_TIME AS OF; a DateTimeInterface, a timestamp or'-10s'),readCurrent()undoes it,readStale()reads current data (reads do not contend with writes).SearchBox:whereStartsWith(),whereContains()andsuggest()comparelower()values withposition(); XTDB has no full-text search, sosearchFullText()and*FullTextRelevance()throwUnsupportedFeatureException.
Testing
License
MIT
All versions of laravel-xtdb2 with dependencies
ext-pdo Version *
ext-pdo_pgsql Version *
illuminate/database Version ^12.0 || ^13.0
illuminate/support Version ^12.0 || ^13.0
symfony/uid Version ^7.0 || ^8.0
vuthaihoc/laravel-db-portable Version ^0.7