PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.0 2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 All 43 releases
← All changes | vendor/wpfluent/framework/src/WPFluent/Database/Schema.php +503 -63 1.40 → 2.2.0 View file →
@@ -8,11 +8,18 @@
8 8 {
9 9 use MaintainsDatabase;
10 10
11 11 /**
12 + * Keep track of custom tables when unit testing
13 + *
14 + * @var array
15 + */
16 + public static $customTempTables = [];
17 +
18 + /**
12 19 * Get the global $wpdb instance
13 20 *
14 - * @return global $wpdb instance
21 + * @return \wpdb The global $wpdb
15 22 */
16 23 public static function db()
17 24 {
18 25 return $GLOBALS['wpdb'];
@@ -26,17 +33,36 @@
26 33 public static function getInfo($key = null)
27 34 {
28 35 $db = static::db();
29 36
37 + if (static::isSqlite()) {
38 + $info = [
39 + 'dbname' => 'sqlite',
40 + 'prefix' => $db->prefix,
41 + 'dbhost' => '',
42 + 'username' => '',
43 + 'password' => '',
44 + 'charset' => 'utf8',
45 + 'collation' => '',
46 + 'tables' => static::getTableList(),
47 + ];
48 +
49 + return $key ? ($info[$key] ?? null) : $info;
50 + }
51 +
30 52 $info = [
31 - 'dbname' => $db->dbname,
32 - 'prefix' => $db->prefix,
33 - 'dbhost' => $db->dbhost,
53 + // @phpstan-ignore-next-line
54 + 'dbname' => $db->dbname,
55 + 'prefix' => $db->prefix,
56 + // @phpstan-ignore-next-line
57 + 'dbhost' => $db->dbhost,
58 + // @phpstan-ignore-next-line
34 59 'username' => $db->dbuser,
60 + // @phpstan-ignore-next-line
35 61 'password' => $db->dbpassword,
36 62 'charset' => $db->charset,
37 63 'collation' => $db->collate,
38 - 'tables' => static::getTableList()
64 + 'tables' => static::getTableList(),
39 65 ];
40 66
41 67 return $key ? $info[$key] : $info;
42 68 }
@@ -53,11 +79,11 @@
53 79 public static function migrate($table, $sql = null)
54 80 {
55 81 if (!$sql && is_array($table)) {
56 82 $result = [];
57 - foreach ($table as $t => $sql) {
83 + foreach ($table as $t => $s) {
58 84 $result = array_merge(
59 - $result, (array) static::createTable($t, $sql)
85 + $result, (array) static::createTable($t, $s)
60 86 );
61 87 }
62 88 return $result;
63 89 } else {
@@ -124,16 +150,18 @@
124 150 // error and then turn off the error from being shown, so
125 151 // error will be not shown if there is no temporary
126 152 // table, then restore the error state.
127 153 $isErrorSuppressed = $wpdb->suppress_errors;
128 -
154 +
129 155 $wpdb->suppress_errors = true;
130 156
131 - $result = static::query("SELECT 1 FROM %{$table}% WHERE 0");
157 + static::query("SELECT 1 FROM %{$table}% WHERE 0");
132 158
159 + $hasError = !empty($wpdb->last_error);
160 +
133 161 $wpdb->suppress_errors = $isErrorSuppressed;
134 162
135 - return $result === 0;
163 + return !$hasError;
136 164 }
137 165
138 166 /**
139 167 * Resolves the table prefix and makes the table name with prefix
@@ -176,8 +204,36 @@
176 204
177 205 return $sql;
178 206 }
179 207
208 + /**
209 + * Clean the sql string by removing the comments.
210 + *
211 + * @param string $sql
212 + * @return string
213 + */
214 + public static function cleanUp($sql)
215 + {
216 + $sql = static::sql($sql);
217 +
218 + // Remove inline -- comments
219 + $sql = preg_replace('/--.*$/m', '', $sql);
220 +
221 + // Remove inline # comments
222 + $sql = preg_replace('/#.*$/m', '', $sql);
223 +
224 + // Remove block /* ... */
225 + $sql = preg_replace('/\/\*.*?\*\//s', '', $sql);
226 +
227 + // Collapse multiple spaces & newlines
228 + // $sql = preg_replace('/\s+/', ' ', $sql);
229 +
230 + // Remove dangling commas before closing parenthesis
231 + $sql = preg_replace('/,\s*\)/', ')', $sql);
232 +
233 + return trim($sql);
234 + }
235 +
180 236 /**
181 237 * Creates a new table using dbDelta function or alters the table if exists.
182 238 *
183 239 * @param string $table The table name without the prefix
@@ -189,10 +245,14 @@
189 245 public static function createTable($table, $sql)
190 246 {
191 247 $table = static::table($table);
192 248
193 - $sql = static::sql($sql);
249 + $sql = static::cleanUp($sql);
194 250
251 + if (static::isSqlite()) {
252 + $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
253 + }
254 +
195 255 $collate = static::db()->get_charset_collate();
196 256
197 257 return static::callDBDelta(
198 258 "CREATE TABLE $table (
@@ -218,13 +278,13 @@
218 278 }
219 279
220 280 /**
221 281 * Alters an existing table
222 - *
282 + *
223 283 * @param string $table The table name without the prefix
224 284 * @param string $sql The sql to create table or an absolute path of a
225 285 * .sql file containing the column definations for creating the new table.
226 - *
286 + *
227 287 * @return string message
228 288 */
229 289 public static function alterTable($table, $sql)
230 290 {
@@ -229,14 +289,27 @@
229 289 public static function alterTable($table, $sql)
230 290 {
231 291 $table = static::table($table);
232 292
233 - $sql = static::sql($sql);
293 + $sql = static::cleanUp($sql);
234 294
235 - $sql = array_map(function($i) { return trim($i);}, explode(',', $sql));
236 -
295 + if (static::isSqlite()) {
296 + $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
297 + }
298 +
299 + $parts = array_filter(array_map('trim', static::splitAlterClauses($sql)));
300 +
301 + if (static::isSqlite()) {
302 + // SQLite only supports one ALTER TABLE operation per statement.
303 + $result = null;
304 + foreach ($parts as $part) {
305 + $result = static::query("ALTER TABLE $table {$part};");
306 + }
307 + return $result;
308 + }
309 +
237 310 $sql = "ALTER TABLE $table ".PHP_EOL.rtrim(
238 - trim(implode(','.PHP_EOL, $sql)), ';'
311 + trim(implode(','.PHP_EOL, $parts)), ';'
239 312 ).";";
240 313
241 314 return static::query($sql);
242 315 }
@@ -241,8 +314,42 @@
241 314 return static::query($sql);
242 315 }
243 316
244 317 /**
318 + * Split a comma-separated ALTER TABLE clause list, respecting parentheses
319 + * so that DECIMAL(10,2) and similar types are not split mid-definition.
320 + *
321 + * @param string $sql
322 + * @return string[]
323 + */
324 + protected static function splitAlterClauses($sql)
325 + {
326 + $parts = [];
327 + $depth = 0;
328 + $current = '';
329 +
330 + for ($i = 0, $len = strlen($sql); $i < $len; $i++) {
331 + $char = $sql[$i];
332 + if ($char === '(') {
333 + $depth++;
334 + } elseif ($char === ')') {
335 + $depth--;
336 + } elseif ($char === ',' && $depth === 0) {
337 + $parts[] = $current;
338 + $current = '';
339 + continue;
340 + }
341 + $current .= $char;
342 + }
343 +
344 + if ($current !== '') {
345 + $parts[] = $current;
346 + }
347 +
348 + return $parts;
349 + }
350 +
351 + /**
245 352 * Alters an existing table using dbDelta function if exists, otherwise creates
246 353 * it. Alters an existing table but takes the table creation column defination.
247 354 * This is because, the dbDelta functioin can create or update a table using
248 355 * the table creation defination. In this, case, if a table exists and the
@@ -260,52 +367,141 @@
260 367 * @return string message
261 368 */
262 369 public static function updateTable($table, $sql)
263 370 {
264 - $result = static::createTable(
265 - $table, implode(",\n", array_map('trim', explode(',', $sql)))
266 - );
371 + $sql = static::cleanUp($sql);
267 372
268 - if ($existingColumns = static::getColumns($table)) {
373 + if (static::isSqlite()) {
374 + $sql = preg_replace('/\bjson\b/i', 'longtext', $sql);
375 + }
269 376
270 - $columns = array_map(function($l) {
271 - if (preg_match('/(\S+)/', $l, $matches)) return $matches[1];
272 - }, array_map('trim', explode(',', $sql)));
377 + $columnsDefinitions = array_map('trim', static::splitAlterClauses($sql));
378 + $schemaSql = implode(",\n", $columnsDefinitions);
273 379
274 - foreach ($existingColumns as $column) {
275 - if (!in_array($column, $columns)) {
276 - $tbl = static::table($table);
277 - static::query("alter table $tbl drop column $column");
278 - $tblColumn = $tbl.'.'.$column;
279 - $result[$tblColumn] = "Dropped column {$tblColumn}";
280 - }
281 - }
282 - }
380 + // Extract desired column names (skip constraint/index clauses)
381 + $columns = [];
382 + foreach ($columnsDefinitions as $definition) {
383 + if (preg_match('/^(?:PRIMARY\s+KEY|UNIQUE(?:(?:\s+KEY|\s+INDEX))?|KEY|INDEX|CONSTRAINT|FOREIGN\s+KEY|FULLTEXT|SPATIAL)\b/i', ltrim($definition))) {
384 + continue;
385 + }
283 386
284 - return $result;
387 + if (preg_match('/^`?(\w+)`?\s+/i', $definition, $matches)) {
388 + $columns[$matches[1]] = $definition;
389 + }
390 + }
391 +
392 + $tbl = static::table($table);
393 +
394 + // SQLite cannot drop PRIMARY KEY columns or columns referenced by indexes
395 + // via native ALTER TABLE DROP COLUMN.
396 + //
397 + // Workarounds:
398 + // - Indexed columns: drop the index first with DROP INDEX,
399 + // then drop the column.
400 + // - PRIMARY KEY columns: use CHANGE COLUMN to rebuild the table without
401 + // the PK attribute (renaming the column to a temp name), then drop
402 + // the renamed column.
403 + if (static::isSqlite()) {
404 + // Collect PRIMARY KEY columns and per-column index key names.
405 + $indexRows = (array) static::db()->get_results("SHOW INDEX FROM {$tbl}");
406 + $pkColumns = [];
407 + $columnIndexes = []; // column_name => [key_name, ...]
408 + foreach ($indexRows as $row) {
409 + if ($row->Key_name === 'PRIMARY') {
410 + $pkColumns[] = $row->Column_name;
411 + } else {
412 + $columnIndexes[$row->Column_name][] = $row->Key_name;
413 + }
414 + }
415 +
416 + $existingColumns = static::getColumns($table) ?: [];
417 +
418 + // 1. Add any desired columns not yet in the table.
419 + foreach ($columns as $colName => $definition) {
420 + if (!in_array($colName, $existingColumns)) {
421 + static::db()->query("ALTER TABLE {$tbl} ADD COLUMN {$definition}");
422 + }
423 + }
424 +
425 + // 2. Drop every column that is not in the desired schema.
426 + foreach ($existingColumns as $column) {
427 + if (isset($columns[$column])) {
428 + continue; // desired — keep it
429 + }
430 +
431 + // Drop non-primary indexes referencing this column first, otherwise
432 + // native SQLite ALTER TABLE DROP COLUMN fails.
433 + foreach ($columnIndexes[$column] ?? [] as $keyName) {
434 + static::db()->query("ALTER TABLE {$tbl} DROP INDEX {$keyName}");
435 + }
436 +
437 + if (in_array($column, $pkColumns)) {
438 + // Native SQLite ALTER TABLE DROP COLUMN rejects PRIMARY KEY columns.
439 + // Use CHANGE COLUMN to trigger an internal table rebuild that strips
440 + // the PK attribute, then drop the (now ordinary) renamed column.
441 + $tmpCol = $column . '_wpf_drop';
442 + static::db()->query("ALTER TABLE {$tbl} CHANGE COLUMN {$column} {$tmpCol} INT NULL");
443 + static::db()->query("ALTER TABLE {$tbl} DROP COLUMN {$tmpCol}");
444 + } else {
445 + static::db()->query("ALTER TABLE {$tbl} DROP COLUMN {$column}");
446 + }
447 + }
448 +
449 + return [$tbl => "Updated table structure"];
450 + }
451 +
452 + // MySQL path: use dbDelta to create/update, then drop extra columns.
453 + $result = static::createTable($table, $schemaSql);
454 + $existingColumns = static::getColumns($table) ?: [];
455 +
456 + // Add missing columns
457 + foreach ($columns as $colName => $definition) {
458 + if (!in_array($colName, $existingColumns)) {
459 + static::query("ALTER TABLE $tbl ADD COLUMN $definition");
460 + $result[$tbl.'.'.$colName] = "Added column {$tbl}.{$colName}";
461 + }
462 + }
463 +
464 + // Drop extra columns
465 + foreach ($existingColumns as $column) {
466 + if (!isset($columns[$column])) {
467 + static::query("ALTER TABLE $tbl DROP COLUMN $column");
468 + $result[$tbl.'.'.$column] = "Dropped column {$tbl}.{$column}";
469 + }
470 + }
471 +
472 + return $result;
285 473 }
286 474
475 + /**
476 + * Drops/deletes an existing table.
477 + *
478 + * @param string $table The table name without the prefix
479 + * @param bool $disableForeignKeyCheck Optional.
480 + * @return bool
481 + */
482 + public static function dropTable($table, $disableForeignKeyCheck = true)
483 + {
484 + return static::db()->query('DROP TABLE ' . static::table($table));
485 + }
486 +
287 487 /**
288 488 * Drops/deletes an existing table if exists
289 489 *
290 490 * @param string $table The table name without the prefix
291 - * @param bool $disableForeignKeyCheck Optional. Whether to disable foreign key checks before dropping the table. Default is true. *
491 + * @param bool $disableForeignKeyCheck Optional.
292 492 * @return bool
293 493 */
294 -
295 494 public static function dropTableIfExists($table, $disableForeignKeyCheck = true)
296 495 {
297 496 if (static::hasTable($table)) {
298 - if ($disableForeignKeyCheck) {
299 - static::db()->query("ALTER TABLE " . static::table($table) . " DISABLE KEYS;");
300 - }
301 - return static::db()->query('DROP TABLE ' . static::table($table));
497 + return static::dropTable($table, $disableForeignKeyCheck);
302 498 }
303 499 }
304 500
305 501 /**
306 502 * Truncate a table.
307 - *
503 + *
308 504 * @param string $table
309 505 * @return bool
310 506 */
311 507 public static function truncate($table)
@@ -311,14 +507,23 @@
311 507 public static function truncate($table)
312 508 {
313 509 $table = static::table($table);
314 510
315 - return static::db()->query("TRUNCATE TABLE $table");
511 + $result = static::db()->query("TRUNCATE TABLE $table");
512 +
513 + if (static::isSqlite()) {
514 + // SQLite translates TRUNCATE TABLE to DELETE FROM, which does NOT reset
515 + // the auto-increment counter. Manually clear the sqlite_sequence row so
516 + // that the next INSERT starts at id=1 (matching MySQL TRUNCATE behaviour).
517 + static::db()->query("DELETE FROM sqlite_sequence WHERE name='{$table}'");
518 + }
519 +
520 + return $result;
316 521 }
317 522
318 523 /**
319 524 * Truncate a table if exists.
320 - *
525 + *
321 526 * @param string $table
322 527 * @return bool
323 528 */
324 529 public static function truncateTableIfExists($table)
@@ -350,8 +555,13 @@
350 555 * @see https://developer.wordpress.org/reference/functions/drop_index
351 556 */
352 557 public static function dropIndex($table, $index)
353 558 {
559 + if (static::isSqlite()) {
560 + $tbl = static::table($table);
561 + return static::db()->query("ALTER TABLE {$tbl} DROP INDEX {$index}");
562 + }
563 +
354 564 return drop_index(static::table($table), $index);
355 565 }
356 566
357 567 /**
@@ -358,35 +568,39 @@
358 568 * Makes raw query and can resolve the table name from the query
359 569 * and can form a full table name including the table prefix if
360 570 * the table name is wrapped like: %table_name% in the query.
361 571 *
362 - * @param straing $query
572 + * @param string $query
363 573 * @return mixed
364 574 */
365 575 public static function query($query)
366 576 {
367 - if (preg_match('/%.*%/', $query, $m)) {
368 - $query = str_replace(
369 - $m[0], static::table(trim($m[0], '%')), $query
370 - );
371 - }
577 + $query = preg_replace_callback('/(?<![\'"])%([a-zA-Z0-9_-]+)%(?![\'"])/', function ($matches) {
578 + return static::table($matches[1]);
579 + }, $query);
372 580
581 + if (preg_match('/^(SELECT|SHOW|DESCRIBE|EXPLAIN)\s+/i', trim($query))) {
582 + return static::db()->get_results($query, OBJECT);
583 + }
584 +
373 585 return static::db()->query($query);
374 586 }
375 587
376 588 /**
377 589 * Get a list of all columns from the given table name.
378 - *
590 + *
379 591 * @param string $table The table name without the prefix
380 - * @return array
592 + * @return array|null
381 593 */
382 594 public static function getColumns($table)
383 595 {
384 - if (static::hasTable($table)) {
385 - return static::db()->get_col(
386 - 'DESC ' . static::table($table), 0
387 - );
596 + if (!static::hasTable($table)) {
597 + return null;
388 598 }
599 +
600 + return static::db()->get_col(
601 + 'DESCRIBE ' . static::table($table), 0
602 + );
389 603 }
390 604
391 605 /**
392 606 * Gets a list of all columns including column information
@@ -393,14 +607,52 @@
393 607 *
394 608 * @param string $table The table name without the prefix
395 609 * @return array
396 610 */
397 - public static function getColumnsWithTypes($table)
611 + protected static function protectedGetColumnsWithTypes($table)
398 612 {
399 613 if (!static::hasTable($table)) return;
400 -
614 +
615 + $table = static::table($table);
616 +
617 + if (static::isSqlite()) {
618 + // INFORMATION_SCHEMA is not available in SQLite.
619 + // Use DESCRIBE which the WP SQLite plugin translates with
620 + // proper MySQL type mapping from its data type cache.
621 + $columns = (array) static::db()->get_results("DESCRIBE {$table}");
622 + return array_map(function ($col, $pos) {
623 + $extra = $col->Extra ?? '';
624 + // SQLite INTEGER PRIMARY KEY is auto_increment but DESCRIBE
625 + // doesn't report it in Extra — detect from Key + Type.
626 + if (empty($extra) && ($col->Key ?? '') === 'PRI') {
627 + $type = strtoupper($col->Type ?? '');
628 + if (in_array($type, ['INTEGER', 'BIGINT', 'BIGINT(20)', 'BIGINT(20) UNSIGNED', 'INT'])) {
629 + $extra = 'auto_increment';
630 + }
631 + }
632 +
633 + return [
634 + 'column_name' => $col->Field,
635 + 'ordinal_position' => $pos + 1,
636 + 'column_default' => $col->Default,
637 + 'is_nullable' => ($col->Null === 'YES' ? 'YES' : 'NO'),
638 + 'data_type' => strtolower(
639 + preg_replace('/\(.*/', '', $col->Type)
640 + ),
641 + 'character_maximum_length' => preg_match(
642 + '/\((\d+)\)/', $col->Type, $matches
643 + ) ? (int)$matches[1] : null,
644 + 'numeric_precision' => null,
645 + 'numeric_scale' => null,
646 + 'column_key' => $col->Key ?? '',
647 + 'extra' => $extra,
648 + ];
649 + }, $columns, array_keys($columns));
650 + }
651 +
652 + // @phpstan-ignore-next-line
401 653 $db = static::db()->dbname;
402 - $table = static::table($table);
654 +
403 655 $fields = [
404 656 'COLUMN_NAME',
405 657 'ORDINAL_POSITION',
406 658 'COLUMN_DEFAULT',
@@ -411,9 +663,9 @@
411 663 'NUMERIC_SCALE',
412 664 'COLUMN_KEY',
413 665 'EXTRA',
414 666 ];
415 -
667 +
416 668 $sql = "SELECT " . implode(',', $fields) . " FROM INFORMATION_SCHEMA.COLUMNS";
417 669 $sql .= " WHERE TABLE_NAME = '".$table."' AND TABLE_SCHEMA = '".$db."'";
418 670
419 671 return array_map(function($i) {
@@ -421,12 +673,112 @@
421 673 foreach ((array) $i as $key => $value) {
422 674 $item[strtolower($key)] = $value;
423 675 }
424 676 return $item;
425 - }, static::db()->get_results($sql));
677 + }, (array) static::db()->get_results($sql));
426 678 }
427 679
680 + public static function getColumnsWithTypes($table)
681 + {
682 + $columns = static::protectedGetColumnsWithTypes($table);
683 +
684 + if (!empty($columns)) {
685 + return $columns;
686 + }
687 +
688 + if (static::isSqlite()) {
689 + return [];
690 + }
691 +
692 + $columns = static::db()->get_results(
693 + 'SHOW COLUMNS FROM `'.static::table($table).'`'
694 + );
695 +
696 + return array_map(function ($col) {
697 + return [
698 + 'column_name' => $col->Field,
699 + 'ordinal_position' => null,
700 + 'column_default' => $col->Default,
701 + 'is_nullable' => ($col->Null === 'YES' ? 'YES' : 'NO'),
702 + 'data_type' => strtolower(
703 + preg_replace('/\(.*/', '', $col->Type)
704 + ),
705 + 'character_maximum_length' => preg_match(
706 + '/\((\d+)\)/', $col->Type, $matches
707 + ) ? (int)$matches[1] : null,
708 + 'numeric_precision' => null,
709 + 'numeric_scale' => null,
710 + 'column_key' => $col->Key,
711 + 'extra' => $col->Extra,
712 + ];
713 + }, (array) $columns);
714 + }
715 +
428 716 /**
717 + * Gets a list of all columns including column information
718 + *
719 + * @param string $table The table name without the prefix
720 + * @return array
721 + */
722 + public static function describeTable($table)
723 + {
724 + return static::getColumnsWithTypes($table);
725 + }
726 +
727 + /**
728 + * Gets a list of all foreign keys from the given table name.
729 + *
730 + * @param string $table
731 + * @return array
732 + */
733 + public static function getTableForeignKeys($table)
734 + {
735 + $table = static::table($table);
736 +
737 + if (static::isSqlite()) {
738 + // Parse foreign keys from the CREATE TABLE statement in sqlite_master
739 + // since PRAGMA queries cannot go through $wpdb.
740 + $row = static::db()->get_row(
741 + "SELECT sql FROM sqlite_master WHERE type='table' AND name='{$table}'"
742 + );
743 + if (!$row || empty($row->sql)) {
744 + return [];
745 + }
746 + $fks = [];
747 + if (preg_match_all(
748 + '/FOREIGN\s+KEY\s*\(\s*[`"]?(\w+)[`"]?\s*\)\s*REFERENCES\s+[`"]?(\w+)[`"]?\s*\(\s*[`"]?(\w+)[`"]?\s*\)/i',
749 + $row->sql, $matches, PREG_SET_ORDER
750 + )) {
751 + foreach ($matches as $m) {
752 + $fks[] = [
753 + 'column_name' => $m[1],
754 + 'referenced_table' => $m[2],
755 + 'referenced_column' => $m[3],
756 + ];
757 + }
758 + }
759 + return $fks;
760 + }
761 +
762 + // @phpstan-ignore-next-line
763 + $db = static::db()->dbname;
764 +
765 + $sql = "SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
766 + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
767 + WHERE TABLE_NAME = '{$table}'
768 + AND TABLE_SCHEMA = '{$db}'
769 + AND REFERENCED_TABLE_NAME IS NOT NULL";
770 +
771 + return array_map(function ($i) {
772 + return [
773 + 'column_name' => $i->COLUMN_NAME,
774 + 'referenced_table' => $i->REFERENCED_TABLE_NAME,
775 + 'referenced_column' => $i->REFERENCED_COLUMN_NAME,
776 + ];
777 + }, (array) static::db()->get_results($sql));
778 + }
779 +
780 + /**
429 781 * Retrieves the list of all available built-in tables from
430 782 * the database using WordPress' $wpdb->tables native method.
431 783 *
432 784 * @param string $scope
@@ -459,18 +811,81 @@
459 811 * @return array
460 812 */
461 813 public static function getTableList($dbname = null)
462 814 {
815 + if (static::isSqlite()) {
816 + return array_map(function ($i) {
817 + return $i->name;
818 + }, (array) static::db()->get_results(
819 + "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
820 + ));
821 + }
822 +
823 + // @phpstan-ignore-next-line
463 824 $dbname = $dbname ?: static::db()->dbname;
464 825 $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES";
465 826 $sql .= " WHERE TABLE_SCHEMA = '".$dbname."'";
466 -
827 +
467 828 return array_map(function($i) {
468 829 return $i->TABLE_NAME;
469 - }, static::db()->get_results($sql));
830 + }, (array) static::db()->get_results($sql));
470 831 }
471 832
472 833 /**
834 + * Retrieves the list of all available views in the database.
835 + *
836 + * @param string $dbname optional
837 + * @return array
838 + */
839 + public static function getViews($dbname = null)
840 + {
841 + if (static::isSqlite()) {
842 + return array_map(function ($i) {
843 + return $i->name;
844 + }, (array) static::db()->get_results(
845 + "SELECT name FROM sqlite_master WHERE type='view'"
846 + ));
847 + }
848 +
849 + // @phpstan-ignore-next-line
850 + $dbname = $dbname ?: static::db()->dbname;
851 + $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS";
852 + $sql .= " WHERE TABLE_SCHEMA = '".$dbname."'";
853 +
854 + return array_map(function ($i) {
855 + return $i->TABLE_NAME;
856 + }, (array) static::db()->get_results($sql));
857 + }
858 +
859 + /**
860 + * Retrieves the SQL definition of a specific view.
861 + *
862 + * @param string $name The view name
863 + * @param string $dbname optional
864 + * @return string|null
865 + */
866 + public static function getView($name, $dbname = null)
867 + {
868 + if (static::isSqlite()) {
869 + $result = static::db()->get_row(
870 + "SELECT sql FROM sqlite_master WHERE type='view' AND name='".$name."'"
871 + );
872 +
873 + return $result ? $result->sql : null;
874 + }
875 +
876 + // @phpstan-ignore-next-line
877 + $dbname = $dbname ?: static::db()->dbname;
878 + $result = static::db()->get_row(
879 + "SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS"
880 + . " WHERE TABLE_SCHEMA = '".$dbname."'"
881 + . " AND TABLE_NAME = '".$name."'"
882 + );
883 +
884 + return $result ? $result->VIEW_DEFINITION : null;
885 + }
886 +
887 + /**
473 888 * Determine if the connected database is a sqlite database.
474 889 *
475 890 * @return bool
476 891 */
@@ -485,8 +900,12 @@
485 900 * @return bool
486 901 */
487 902 public static function isMaria()
488 903 {
904 + if (static::isSqlite()) {
905 + return false;
906 + }
907 +
489 908 return str_contains(
490 909 static::db()->get_var('SELECT VERSION()'), 'MariaDB'
491 910 );
492 911 }
@@ -498,8 +917,12 @@
498 917 * @return string
499 918 */
500 919 public static function getEngine($table)
501 920 {
921 + if (static::isSqlite()) {
922 + return 'sqlite';
923 + }
924 +
502 925 return static::db()->get_var(
503 926 'SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = "' . static::table($table) . '"'
504 927 );
505 928 }
@@ -515,7 +938,24 @@
515 938 if (!function_exists('dbDelta')) {
516 939 require (ABSPATH . 'wp-admin/includes/upgrade.php');
517 940 }
518 941
519 - return dbDelta($sql);
942 + $result = dbDelta($sql);
943 +
944 + if (php_sapi_name() === 'cli') {
945 + $key = array_key_first($result);
946 + if ($key && !str_contains($key, '.')) {
947 + static::$customTempTables[] = $key;
948 + }
949 + }
950 +
951 + return $result;
520 952 }
953 +
954 + /**
955 + * Helper to get the driver-specific JSON type.
956 + */
957 + public static function jsonType()
958 + {
959 + return static::isSqlite() ? 'longtext' : 'json';
960 + }
521 961 }