MatrixOne Compatibility
MatrixOne speaks the MySQL 8.0 protocol but implements a subset of MySQL. This page lists every difference the driver handles, verified against MatrixOne 4.2.4 (and 3.0.9 where noted).
Handled transparently
| MatrixOne behaviour | What the driver does |
|---|---|
Boolean expressions return "true"/"false" strings | exists(), hasTable(), index unique and schema default flags are computed as integers |
No insert ... as alias row alias | upsert() uses values(col) |
Assigning a primary key on duplicate key is rejected | upsert() never updates its uniqueBy columns (Laravel's database cache upserts every column) |
No skip locked / nowait | Dropped from lock clauses; the database queue's FOR UPDATE SKIP LOCKED becomes for update |
RAND() accepts no seed | The seed is dropped |
No performance_schema | threadCount() counts information_schema.processlist |
Conflicts and deadlocks abort the whole transaction with SQLSTATE HY000 and MatrixOne error numbers | Recognized as concurrency errors: DB::transaction() and statements outside a transaction are retried with backoff (retry_attempts, see Eloquent › Conflicts and deadlocks) |
No ROLLBACK TO SAVEPOINT | Nested transactions are flattened, or fail the outer commit with 'nested_transactions' => 'rollback_only' (see Eloquent › Transactions) |
A FULLTEXT index needs a primary key (primary key cannot be empty for fulltext index) | fullText() on a table without a primary key throws a clear error |
json_overlaps() takes exactly two documents | The JSON path is applied with json_extract() |
SET assignments all read the original row | JSON path updates of one column share one json_set() |
LIKE ignores _ci collations | like / whereLike() compile to cast(col as text) ilike |
ILIKE rejects numbers and dates | Columns are cast to text first |
| Natural language full-text mode only matches words appearing together | FullTextQuery::anyOf() and the Scout engine use boolean mode (any word) |
| Full-text has no stemming, stopwords or accent folding | FullTextQuery::anyOf(prefix: true), TextNormalizer::foldAccents(), matrixone-index options; see Full-text › Languages |
CLUSTER BY refuses tables with a primary key and cannot be added later | clusterBy() throws on a primary key or outside Schema::create() |
A time window cannot be combined with GROUP BY / HAVING; units are second, minute, hour, day | timeWindow() validates units and throws on groups; aggregates such as count() run over the windows |
SAMPLE() takes * but not table.* | sample() compiles Eloquent's table.* to * |
| Snapshot names reject quotes | Snapshot and PITR names are validated (letters, digits, _, -) |
MATCH ... AGAINST cannot be OR-ed with other conditions | The Scout engine uses a subquery; see Full-text › Limitations |
LAST_INSERT_ID() is wrong on tables with a FULLTEXT index | insertGetId() uses insert ... returning |
| Identifiers are limited to 64 characters | Long index names are shortened consistently |
JSON booleans cannot be compared with SQL true | Compared as unquoted text |
TRUNCATE refused on tables referenced by foreign keys | Falls back to DELETE |
Unconditional DELETE with foreign key checks disabled corrupts FK metadata | where 1 = 1 is appended |
ALTER TABLE ... AUTO_INCREMENT = n unsupported | Starting values are set in CREATE TABLE |
No RENAME INDEX | Drop and re-create |
USING BTREE rejected | Index algorithm omitted |
| Several tables cannot be dropped in one statement | db:wipe drops one table at a time |
information_schema.key_column_usage is empty for FKs | Foreign keys read from mo_catalog.mo_foreign_keys |
Hidden __mo_* columns and system databases | Excluded from introspection |
Placeholders rejected in MATCH ... AGAINST with native prepares | Emulated prepares are the default |
Native UUID column type unreadable by mysqlnd | uuid() stays char(36) |
No YEAR type | year() becomes smallint |
Minor differences
Fractional-second columns: MatrixOne may omit a zero fraction when returning values (
2026-01-01 00:00:00instead of2026-01-01 00:00:00.000000), e.g. fordatetime(6). Eloquent date casts parse both forms; compare raw strings with care.As with Laravel's MySQL driver,
DateTime/Carbon bindings are formatted asY-m-d H:i:s(no microseconds). Pass a formatted string to compare fractional seconds.No fuzzy search: no trigram similarity,
levenshtein()orunaccent(), and thengramparser splits only CJK text into n-grams.whereStartsWith(),whereContains()andsuggest()match prefixes and substrings (see Suggestions).
Not supported (throws)
- Full-text query expansion (
whereFullText(..., ['expanded' => true])) set(),geometry(),geography()columns- Default values on JSON columns (unless
ignore_json_defaultsis set) - Indexes on JSON columns (unless
ignore_json_indexesis set) joinLateral()schema:dump- Changing the auto-increment start of an existing table
Known MatrixOne issues
The package's tests/KnownIssues suite reproduces each open issue in plain SQL and asserts MySQL's behaviour, so it fails while the issue exists. Run it against a new MatrixOne release to see what got fixed:
composer test:known-issues- Correlated subqueries with
LIMITreturn wrong results, silently.addSelect(['last_post' => Post::select('title')->whereColumn('user_id', 'users.id')->latest()->limit(1)])yieldsNULLfor most rows. Use a relationship withlatestOfMany()/ofMany()(compiled with aggregates and joins, which work) or an aggregate subquery (selectRaw('(select max(created_at) ...)')). - A bare
NULLin aUNIONis typedVARCHAR, so aggregates over the union fail. Cast placeholders:cast(null as double). DELETEaffected rows include rows removed byON DELETE CASCADE(MySQL counts only the statement's rows).- 4.2.4 panic on correlated counts.
loadCount()/withCount()on a single parent selected by primary key (find($id),whereIn('id', [$id])) crashes the server when the related query has an extra condition, e.g. SoftDeletes (Error reading result set's header). Count on the relation instead:$user->posts()->count(). The driver drops the broken connection, forgetting its transaction, so later queries reconnect instead of blocking on the row locks the dead session still holds. =compares strings case-sensitively even on_cicollations; the driver cannot change this without losing index use. Use theLowercasecast orwhereIgnoreCase()(Query Builder › Case-insensitive equality).- 4.2.4: inserting into a table with both a foreign key and a FULLTEXT index panics in the query planner. Keep full-text indexes on tables without their own foreign keys.
RESTORE ... FROM SNAPSHOTis a syntax error on 4.2.4. Copy the rows back withinsertUsing()andasOfSnapshot()(Analytics › Restoring data).- A
DESCkey after a booleanORDER BYkey is ignored:order by score is null, score descreturns rows in insertion order. To sort NULLs last,order by score descalready puts them last on MatrixOne; for ascending orderorder by score is null, score ascworks. Wrapping the descending key incoalesce()also avoids the bug:order by has_sync desc, coalesce(ratio, 0) desc. - FULLTEXT2 indexes (4.2.2+) are experimental and are not indexed synchronously; the driver does not use them.
- HNSW vector indexes are experimental: they need a signed
BIGINTprimary key and are maintained asynchronously (sync withalter reindex ... hnsw force_sync).vectorIndex()builds IVF-Flat unless->hnsw()is called.