@@ -45,13 +45,39 @@ class SQLite extends MariaDB
4545 private const MARIADB_MEDIUMTEXT_BYTES = '16777215 ' ;
4646 private const MARIADB_LONGTEXT_BYTES = '4294967295 ' ;
4747
48+ /**
49+ * When enabled, the adapter reports MariaDB-shaped column metadata,
50+ * advertises MariaDB-only capabilities (upserts, attribute resizing,
51+ * PCRE regex via the registered UDF), and declares schema-internal
52+ * columns (e.g. `_tenant`) using MariaDB-style types so callers that
53+ * inspect INFORMATION_SCHEMA-style results behave identically across
54+ * both adapters. Off by default — vanilla SQLite stays vanilla.
55+ */
56+ protected bool $ emulateMySQL = false ;
57+
4858 public function __construct (mixed $ pdo )
4959 {
5060 parent ::__construct ($ pdo );
5161
5262 $ this ->registerUserFunctions ();
5363 }
5464
65+ /**
66+ * Toggle MariaDB/MySQL emulation. See $emulateMySQL for what this
67+ * actually changes.
68+ */
69+ public function setEmulateMySQL (bool $ emulate ): static
70+ {
71+ $ this ->emulateMySQL = $ emulate ;
72+
73+ return $ this ;
74+ }
75+
76+ public function getEmulateMySQL (): bool
77+ {
78+ return $ this ->emulateMySQL ;
79+ }
80+
5581 /**
5682 * SQLite has no built-in regex operator — the inherited REGEXP path
5783 * relies on the connection supplying one. Register a PHP-implemented
@@ -209,7 +235,13 @@ public function createCollection(string $name, array $attributes = [], array $in
209235 $ attributeStrings [$ key ] = "` {$ attrId }` {$ attrType }, " ;
210236 }
211237
212- $ tenantQuery = $ this ->sharedTables ? '`_tenant` "INT(11) UNSIGNED" DEFAULT NULL, ' : '' ;
238+ // SQLite stores integers regardless of declared type, but
239+ // testSchemaAttributes asserts the columnType reads back as
240+ // `int(11) unsigned` to match MariaDB. Quote the declaration so
241+ // PRAGMA table_info echoes the exact string under emulation;
242+ // otherwise use INTEGER, the affinity-correct vanilla form.
243+ $ tenantType = $ this ->emulateMySQL ? '"INT(11) UNSIGNED" ' : 'INTEGER ' ;
244+ $ tenantQuery = $ this ->sharedTables ? "`_tenant` {$ tenantType } DEFAULT NULL, " : '' ;
213245
214246 $ collection = "
215247 CREATE TABLE {$ this ->getSQLTable ($ id )} (
@@ -408,10 +440,11 @@ public function updateAttribute(string $collection, string $id, string $type, in
408440
409441 // SQLite is dynamically typed — `ALTER TABLE ... MODIFY COLUMN` is
410442 // not supported and a smaller declared size silently accepts
411- // larger values. To keep parity with the MariaDB contract that
412- // resize-down rejects when data exceeds the new size, scan the
413- // column ourselves and raise the same TruncateException.
414- if ($ type === Database::VAR_STRING && $ size > 0 && !$ array ) {
443+ // larger values. Under MySQL emulation, scan the column and
444+ // raise the same TruncateException MariaDB throws. Off-
445+ // emulation the declared size is metadata-only, so skip the
446+ // scan and let the rename branch (if any) handle the rest.
447+ if ($ this ->emulateMySQL && $ type === Database::VAR_STRING && $ size > 0 && !$ array ) {
415448 $ name = $ this ->filter ($ collection );
416449 $ column = $ this ->filter ($ id );
417450 $ sql = "SELECT 1 FROM {$ this ->getSQLTable ($ name )} WHERE LENGTH(` {$ column }`) > :max LIMIT 1 " ;
@@ -1215,11 +1248,11 @@ public function getSupportForUpdateLock(): bool
12151248 */
12161249 public function getSupportForAttributeResizing (): bool
12171250 {
1218- // SQLite is dynamically typed with no MODIFY COLUMN, but
1219- // updateAttribute scans the column on resize-down and raises
1220- // TruncateException when any row exceeds the new size — same
1221- // contract MariaDB enforces via the engine .
1222- return true ;
1251+ // SQLite is dynamically typed with no MODIFY COLUMN. When
1252+ // emulating MySQL, updateAttribute scans the column on
1253+ // resize-down and raises TruncateException to match MariaDB's
1254+ // contract. Off-emulation, declared sizes are metadata-only .
1255+ return $ this -> emulateMySQL ;
12231256 }
12241257
12251258 /**
@@ -1254,6 +1287,7 @@ public function getSupportForSchemaIndexes(): bool
12541287 */
12551288 public function getSupportForUpserts (): bool
12561289 {
1290+ // ON CONFLICT DO UPDATE is native SQLite, not MariaDB emulation.
12571291 return true ;
12581292 }
12591293
@@ -2197,6 +2231,9 @@ public function getSupportNonUtfCharacters(): bool
21972231 */
21982232 public function getSupportForPCRERegex (): bool
21992233 {
2234+ // The REGEXP UDF is registered unconditionally in the
2235+ // constructor and runs preg_match (PCRE) — same surface as
2236+ // MariaDB's native operator from the caller's perspective.
22002237 return true ;
22012238 }
22022239
@@ -2581,10 +2618,11 @@ private function parseSqliteColumnType(string $declaration): array
25812618 }
25822619
25832620 $ dataType = \strtolower ($ base );
2584- // SQLite spells INT and INTEGER interchangeably for declared types,
2585- // but MariaDB's INFORMATION_SCHEMA always reports `int`. Canonicalise
2586- // so getSchemaAttributes matches the parent contract.
2587- if ($ dataType === 'integer ' ) {
2621+ // SQLite spells INT and INTEGER interchangeably for declared types.
2622+ // Under emulation, canonicalise to MariaDB's reported `int` so
2623+ // getSchemaAttributes matches that adapter's contract; otherwise
2624+ // keep the verbatim form the user declared.
2625+ if ($ this ->emulateMySQL && $ dataType === 'integer ' ) {
25882626 $ dataType = 'int ' ;
25892627 }
25902628
@@ -2596,6 +2634,12 @@ private function parseSqliteColumnType(string $declaration): array
25962634 'datetimePrecision ' => null ,
25972635 ];
25982636
2637+ // VARCHAR / CHAR / DATETIME(n) / DECIMAL(p,s) length+precision
2638+ // come straight from the declaration — that's true for vanilla
2639+ // SQLite too. The MariaDB byte ceilings (TEXT/MEDIUMTEXT/etc.)
2640+ // and the integer/float precision defaults are MariaDB-specific
2641+ // INFORMATION_SCHEMA conventions, so report them only under
2642+ // emulation.
25992643 switch ($ dataType ) {
26002644 case 'varchar ' :
26012645 case 'char ' :
@@ -2604,19 +2648,6 @@ private function parseSqliteColumnType(string $declaration): array
26042648 }
26052649 break ;
26062650
2607- case 'text ' :
2608- $ result ['characterMaximumLength ' ] = self ::MARIADB_TEXT_BYTES ;
2609- break ;
2610-
2611- case 'mediumtext ' :
2612- $ result ['characterMaximumLength ' ] = self ::MARIADB_MEDIUMTEXT_BYTES ;
2613- break ;
2614-
2615- case 'longtext ' :
2616- case 'json ' :
2617- $ result ['characterMaximumLength ' ] = self ::MARIADB_LONGTEXT_BYTES ;
2618- break ;
2619-
26202651 case 'datetime ' :
26212652 case 'timestamp ' :
26222653 case 'time ' :
@@ -2625,40 +2656,70 @@ private function parseSqliteColumnType(string $declaration): array
26252656 }
26262657 break ;
26272658
2628- case 'tinyint ' :
2629- $ result ['numericPrecision ' ] = '3 ' ;
2659+ case 'decimal ' :
2660+ case 'numeric ' :
2661+ if ($ argument !== null ) {
2662+ $ result ['numericPrecision ' ] = (string ) $ argument ;
2663+ }
2664+ if ($ secondArgument !== null ) {
2665+ $ result ['numericScale ' ] = (string ) $ secondArgument ;
2666+ } elseif ($ this ->emulateMySQL && $ argument !== null ) {
2667+ $ result ['numericScale ' ] = '0 ' ;
2668+ }
26302669 break ;
2670+ }
26312671
2632- case 'smallint ' :
2633- $ result ['numericPrecision ' ] = '5 ' ;
2634- break ;
2672+ if ($ this ->emulateMySQL ) {
2673+ switch ($ dataType ) {
2674+ case 'text ' :
2675+ $ result ['characterMaximumLength ' ] = self ::MARIADB_TEXT_BYTES ;
2676+ break ;
26352677
2636- case 'mediumint ' :
2637- $ result ['numericPrecision ' ] = ' 7 ' ;
2638- break ;
2678+ case 'mediumtext ' :
2679+ $ result ['characterMaximumLength ' ] = self :: MARIADB_MEDIUMTEXT_BYTES ;
2680+ break ;
26392681
2640- case 'int ' :
2641- case 'integer ' :
2642- $ result ['numericPrecision ' ] = ' 10 ' ;
2643- break ;
2682+ case 'longtext ' :
2683+ case 'json ' :
2684+ $ result ['characterMaximumLength ' ] = self :: MARIADB_LONGTEXT_BYTES ;
2685+ break ;
26442686
2645- case 'bigint ' :
2646- $ result ['numericPrecision ' ] = '19 ' ;
2647- break ;
2687+ case 'tinyint ' :
2688+ $ result ['numericPrecision ' ] = '3 ' ;
2689+ break ;
26482690
2649- case 'decimal ' :
2650- case 'numeric ' :
2651- $ result ['numericPrecision ' ] = $ argument !== null ? (string ) $ argument : '10 ' ;
2652- $ result ['numericScale ' ] = $ secondArgument !== null ? (string ) $ secondArgument : '0 ' ;
2653- break ;
2691+ case 'smallint ' :
2692+ $ result ['numericPrecision ' ] = '5 ' ;
2693+ break ;
26542694
2655- case 'float ' :
2656- $ result ['numericPrecision ' ] = '12 ' ;
2657- break ;
2695+ case 'mediumint ' :
2696+ $ result ['numericPrecision ' ] = '7 ' ;
2697+ break ;
26582698
2659- case 'double ' :
2660- $ result ['numericPrecision ' ] = '22 ' ;
2661- break ;
2699+ case 'int ' :
2700+ case 'integer ' :
2701+ $ result ['numericPrecision ' ] = '10 ' ;
2702+ break ;
2703+
2704+ case 'bigint ' :
2705+ $ result ['numericPrecision ' ] = '19 ' ;
2706+ break ;
2707+
2708+ case 'decimal ' :
2709+ case 'numeric ' :
2710+ if ($ result ['numericPrecision ' ] === null ) {
2711+ $ result ['numericPrecision ' ] = '10 ' ;
2712+ }
2713+ break ;
2714+
2715+ case 'float ' :
2716+ $ result ['numericPrecision ' ] = '12 ' ;
2717+ break ;
2718+
2719+ case 'double ' :
2720+ $ result ['numericPrecision ' ] = '22 ' ;
2721+ break ;
2722+ }
26622723 }
26632724
26642725 return $ result ;
0 commit comments