Skip to content

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 behaviourWhat the driver does
Boolean expressions return "true"/"false" stringsexists(), hasTable(), index unique and schema default flags are computed as integers
No insert ... as alias row aliasupsert() uses values(col)
Assigning a primary key on duplicate key is rejectedupsert() never updates its uniqueBy columns (Laravel's database cache upserts every column)
No skip locked / nowaitDropped from lock clauses; the database queue's FOR UPDATE SKIP LOCKED becomes for update
RAND() accepts no seedThe seed is dropped
No performance_schemathreadCount() counts information_schema.processlist
Conflicts and deadlocks abort the whole transaction with SQLSTATE HY000 and MatrixOne error numbersRecognized as concurrency errors: DB::transaction() and statements outside a transaction are retried with backoff (retry_attempts, see Eloquent › Conflicts and deadlocks)
No ROLLBACK TO SAVEPOINTNested 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 documentsThe JSON path is applied with json_extract()
SET assignments all read the original rowJSON path updates of one column share one json_set()
LIKE ignores _ci collationslike / whereLike() compile to cast(col as text) ilike
ILIKE rejects numbers and datesColumns are cast to text first
Natural language full-text mode only matches words appearing togetherFullTextQuery::anyOf() and the Scout engine use boolean mode (any word)
Full-text has no stemming, stopwords or accent foldingFullTextQuery::anyOf(prefix: true), TextNormalizer::foldAccents(), matrixone-index options; see Full-text › Languages
CLUSTER BY refuses tables with a primary key and cannot be added laterclusterBy() throws on a primary key or outside Schema::create()
A time window cannot be combined with GROUP BY / HAVING; units are second, minute, hour, daytimeWindow() 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 quotesSnapshot and PITR names are validated (letters, digits, _, -)
MATCH ... AGAINST cannot be OR-ed with other conditionsThe Scout engine uses a subquery; see Full-text › Limitations
LAST_INSERT_ID() is wrong on tables with a FULLTEXT indexinsertGetId() uses insert ... returning
Identifiers are limited to 64 charactersLong index names are shortened consistently
JSON booleans cannot be compared with SQL trueCompared as unquoted text
TRUNCATE refused on tables referenced by foreign keysFalls back to DELETE
Unconditional DELETE with foreign key checks disabled corrupts FK metadatawhere 1 = 1 is appended
ALTER TABLE ... AUTO_INCREMENT = n unsupportedStarting values are set in CREATE TABLE
No RENAME INDEXDrop and re-create
USING BTREE rejectedIndex algorithm omitted
Several tables cannot be dropped in one statementdb:wipe drops one table at a time
information_schema.key_column_usage is empty for FKsForeign keys read from mo_catalog.mo_foreign_keys
Hidden __mo_* columns and system databasesExcluded from introspection
Placeholders rejected in MATCH ... AGAINST with native preparesEmulated prepares are the default
Native UUID column type unreadable by mysqlnduuid() stays char(36)
No YEAR typeyear() becomes smallint

Minor differences ​

  • Fractional-second columns: MatrixOne may omit a zero fraction when returning values (2026-01-01 00:00:00 instead of 2026-01-01 00:00:00.000000), e.g. for datetime(6). Eloquent date casts parse both forms; compare raw strings with care.

  • As with Laravel's MySQL driver, DateTime/Carbon bindings are formatted as Y-m-d H:i:s (no microseconds). Pass a formatted string to compare fractional seconds.

  • No fuzzy search: no trigram similarity, levenshtein() or unaccent(), and the ngram parser splits only CJK text into n-grams. whereStartsWith(), whereContains() and suggest() 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_defaults is set)
  • Indexes on JSON columns (unless ignore_json_indexes is 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:

bash
composer test:known-issues
  • Correlated subqueries with LIMIT return wrong results, silently. addSelect(['last_post' => Post::select('title')->whereColumn('user_id', 'users.id')->latest()->limit(1)]) yields NULL for most rows. Use a relationship with latestOfMany() / ofMany() (compiled with aggregates and joins, which work) or an aggregate subquery (selectRaw('(select max(created_at) ...)')).
  • A bare NULL in a UNION is typed VARCHAR, so aggregates over the union fail. Cast placeholders: cast(null as double).
  • DELETE affected rows include rows removed by ON 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 _ci collations; the driver cannot change this without losing index use. Use the Lowercase cast or whereIgnoreCase() (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 SNAPSHOT is a syntax error on 4.2.4. Copy the rows back with insertUsing() and asOfSnapshot() (Analytics › Restoring data).
  • A DESC key after a boolean ORDER BY key is ignored: order by score is null, score desc returns rows in insertion order. To sort NULLs last, order by score desc already puts them last on MatrixOne; for ascending order order by score is null, score asc works. Wrapping the descending key in coalesce() 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 BIGINT primary key and are maintained asynchronously (sync with alter reindex ... hnsw force_sync). vectorIndex() builds IVF-Flat unless ->hnsw() is called.

Released under the MIT License.