Differences from Laravel
Orbit follows Laravel's Eloquent and query builder, with these exceptions. Each one is listed here so it is not a surprise to someone who knows Laravel.
first()has no default order. As in Laravel,first()takes the first row the database returns. Callorder_bywhen the row matters. Laravel'ssole,chunkandlazyenforce an order; Orbit does not add one.- Columns outside
FILLABLEare an error.insertandupdatereturnError::NotFillablefor a column that is not in the model'sFILLABLElist. Laravel'sModel::createdrops such an attribute without a message, so a misspelt column is ignored. Orbit stops instead, so the mistake shows up at once. Laravel's query builder does not check$fillableat all; Orbit checks it on every write. insertreturns the id. It returns the auto-increment id asu64. Laravel's query builderinsertreturns a boolean, andinsertGetIdreturns the id.where_eq(column, None)isIS NULL, andOperator::NewithNoneisIS NOT NULL, as in Laravel. Other operators withNonematch nothing, where Laravel throws.- Terminal methods take the database.
get(&db)and the other terminal methods take theDatabaseexplicitly. Laravel uses a default connection. - Transactions take an async closure that receives the transaction.
Database::transactionpasses the transaction astx, and queries run on it when given&mut *tx. Laravel'sDB::transactiontakes no argument and uses the default connection. The closure's error type must convert fromError. - Transactions do not retry. Laravel's
DB::transaction($callback, $attempts)retries on a deadlock. Orbit returnsError::Deadlockand leaves the retry to the caller. - Grouped conditions are
where_groupandor_where_group. They take a closure that receives an empty query, as Laravel'swhere(fn)does. Only the conditions are kept from the group;order_by,limitand the other settings inside it are ignored. - Errors are typed. Unique and foreign key violations, deadlocks and lock wait timeouts are variants of
Error. Laravel'sQueryExceptioncarries the SQLSTATE code instead. A unique violation's index name comes from the server's message, because the MySQL driver does not return it as a separate field. replaceis Diesel'sreplace_into, not Laravel'supsert.Query::replaceissues MySQL'sREPLACE INTO, with Diesel's semantics. It deletes the row that has the same unique key or primary key, then inserts the new one. So the auto-increment id changes, columns not in the values take their defaults, and the returned count is the rows affected as MySQL reports them (2 for a replaced row). Laravel'supsertisINSERT ... ON DUPLICATE KEY UPDATE, which updates the existing row in place and keeps its id. That is a different operation, and Orbit does not implement it.- The blocking API refuses async threads.
orbit_orm::blocking(featureblocking) runs the async queries on a runtime that theDatabaseowns. Laravel has no sync or async split, so this follows Diesel, which Ignite uses. A blocking call on a thread that runs async code returnsError::BlockingInRuntime, rather than panicking inside Tokio or stalling the runtime. Call it fromtokio::task::spawn_blocking, as Ignite does. Each call blocks its thread, so the blocking pool is the place for it. TheDatabaseshuts its runtime down in the background when dropped, so dropping it on an async thread is safe.
Matches Laravel, with limits
for_updateruns outside a transaction. Outside a transaction,for_updaterunsSELECT ... FOR UPDATEin autocommit, and the lock lasts for that statement. This matches Laravel'slockForUpdate().- The default table name follows Laravel's inflector, with gaps. Without
table, the name isStr::snake(Str::pluralStudly(name)), as in Eloquent: only the last word is pluralized, soUserProfilegivesuser_profilesandPersongivespeople. The plural rules are ported from doctrine/inflector 2.1.x, the English rules Laravel'sPluralizeruses, with the same uncountable words, irregular words and suffix rules in the same order. Checked against Laravel v13.42.0 (illuminate/support13.42.0), which requiresdoctrine/inflector ^2.0.5and resolves to 2.1.1: 1,693 distinct class names (1,862 rows over two lists) give the sameStr::snake(Str::pluralStudly(name))as Orbit. Before the fixes from that check, 13 of them differed (Media,People,Trivia,MalteseandTestis, alone and in compounds); those are fixed. Gaps: the rules are ASCII only, so a name that ends in a non-ASCII letter gets a plains; the check covered doctrine/inflector 2.1.1 only, and names outside the tested set are not proven to match every doctrine rule; a word that matches no rule gets ans, as Laravel does.table = "..."overrides the default, and an explicit name is used as written.
Orbit