mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
ext/sqlite3: explain statement support addition.
similar to what have been done for pdo/sqlite as having statement explain support to simulate how a query would operate or for more advanced users, analysing the VM routines used for possible optimisations. close GH-18853
This commit is contained in:
parent
e9310171f7
commit
22bd2ae63f
5 changed files with 500 additions and 1 deletions
2
NEWS
2
NEWS
|
@ -247,6 +247,8 @@ PHP NEWS
|
|||
- Sqlite:
|
||||
. Added Sqlite3Stmt::busy to check if a statement is still being executed.
|
||||
(David Carlier)
|
||||
. Added Sqlite3Stmt::explain to produce a explain query plan from
|
||||
the statement. (David Carlier)
|
||||
|
||||
- Standard:
|
||||
. Fixed crypt() tests on musl when using --with-external-libcrypt
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
#include "SAPI.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h>
|
||||
#endif
|
||||
|
||||
#include "zend_exceptions.h"
|
||||
#include "sqlite3_arginfo.h"
|
||||
|
@ -1500,6 +1503,60 @@ PHP_METHOD(SQLite3Stmt, busy)
|
|||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
PHP_METHOD(SQLite3Stmt, explain)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (__builtin_available(macOS 14.2, *)) {
|
||||
#endif
|
||||
php_sqlite3_stmt *stmt_obj;
|
||||
zval *object = ZEND_THIS;
|
||||
stmt_obj = Z_SQLITE3_STMT_P(object);
|
||||
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3);
|
||||
SQLITE3_CHECK_INITIALIZED_STMT(stmt_obj->stmt, SQLite3Stmt);
|
||||
|
||||
RETURN_LONG((zend_long)sqlite3_stmt_isexplain(stmt_obj->stmt));
|
||||
#ifdef __APPLE__
|
||||
} else {
|
||||
zend_throw_error(NULL, "explain statement unsupported");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PHP_METHOD(SQLite3Stmt, setExplain)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (__builtin_available(macOS 14.2, *)) {
|
||||
#endif
|
||||
php_sqlite3_stmt *stmt_obj;
|
||||
zend_long mode;
|
||||
zval *object = ZEND_THIS;
|
||||
stmt_obj = Z_SQLITE3_STMT_P(object);
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
Z_PARAM_LONG(mode)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
if (mode < 0 || mode > 2) {
|
||||
zend_argument_value_error(1, "must be one of the SQLite3Stmt::EXPLAIN_MODE_* constants");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3);
|
||||
SQLITE3_CHECK_INITIALIZED_STMT(stmt_obj->stmt, SQLite3Stmt);
|
||||
|
||||
RETURN_BOOL(sqlite3_stmt_explain(stmt_obj->stmt, (int)mode) == SQLITE_OK);
|
||||
#ifdef __APPLE__
|
||||
} else {
|
||||
zend_throw_error(NULL, "explain statement unsupported");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/* bind parameters to a statement before execution */
|
||||
static int php_sqlite3_bind_params(php_sqlite3_stmt *stmt_obj) /* {{{ */
|
||||
{
|
||||
|
|
|
@ -274,6 +274,15 @@ class SQLite3Stmt
|
|||
public function reset(): bool {}
|
||||
|
||||
public function busy(): bool {}
|
||||
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
public const int EXPLAIN_MODE_PREPARED = 0;
|
||||
public const int EXPLAIN_MODE_EXPLAIN = 1;
|
||||
public const int EXPLAIN_MODE_EXPLAIN_QUERY_PLAN = 2;
|
||||
|
||||
public function explain(): int {}
|
||||
public function setExplain(int $mode): bool {}
|
||||
#endif
|
||||
}
|
||||
|
||||
/** @not-serializable */
|
||||
|
|
43
ext/sqlite3/sqlite3_arginfo.h
generated
43
ext/sqlite3/sqlite3_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 28132e0e4df61f19dc4b23a7c9f79be6b3e40a8e */
|
||||
* Stub hash: c3216eada9881743cbd3aa1510f1200b7ce0d942 */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3___construct, 0, 0, 1)
|
||||
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
|
||||
|
@ -147,6 +147,15 @@ ZEND_END_ARG_INFO()
|
|||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SQLite3Stmt_busy, 0, 0, _IS_BOOL, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SQLite3Stmt_explain, 0, 0, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SQLite3Stmt_setExplain, 0, 1, _IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, mode, IS_LONG, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
#endif
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3Result___construct, 0, 0, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
|
@ -206,6 +215,10 @@ ZEND_METHOD(SQLite3Stmt, paramCount);
|
|||
ZEND_METHOD(SQLite3Stmt, readOnly);
|
||||
ZEND_METHOD(SQLite3Stmt, reset);
|
||||
ZEND_METHOD(SQLite3Stmt, busy);
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
ZEND_METHOD(SQLite3Stmt, explain);
|
||||
ZEND_METHOD(SQLite3Stmt, setExplain);
|
||||
#endif
|
||||
ZEND_METHOD(SQLite3Result, __construct);
|
||||
ZEND_METHOD(SQLite3Result, numColumns);
|
||||
ZEND_METHOD(SQLite3Result, columnName);
|
||||
|
@ -258,6 +271,10 @@ static const zend_function_entry class_SQLite3Stmt_methods[] = {
|
|||
ZEND_ME(SQLite3Stmt, readOnly, arginfo_class_SQLite3Stmt_readOnly, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(SQLite3Stmt, reset, arginfo_class_SQLite3Stmt_reset, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(SQLite3Stmt, busy, arginfo_class_SQLite3Stmt_busy, ZEND_ACC_PUBLIC)
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
ZEND_ME(SQLite3Stmt, explain, arginfo_class_SQLite3Stmt_explain, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(SQLite3Stmt, setExplain, arginfo_class_SQLite3Stmt_setExplain, ZEND_ACC_PUBLIC)
|
||||
#endif
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
|
@ -540,6 +557,30 @@ static zend_class_entry *register_class_SQLite3Stmt(void)
|
|||
|
||||
INIT_CLASS_ENTRY(ce, "SQLite3Stmt", class_SQLite3Stmt_methods);
|
||||
class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_NOT_SERIALIZABLE);
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
|
||||
zval const_EXPLAIN_MODE_PREPARED_value;
|
||||
ZVAL_LONG(&const_EXPLAIN_MODE_PREPARED_value, 0);
|
||||
zend_string *const_EXPLAIN_MODE_PREPARED_name = zend_string_init_interned("EXPLAIN_MODE_PREPARED", sizeof("EXPLAIN_MODE_PREPARED") - 1, 1);
|
||||
zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_PREPARED_name, &const_EXPLAIN_MODE_PREPARED_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
|
||||
zend_string_release(const_EXPLAIN_MODE_PREPARED_name);
|
||||
#endif
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
|
||||
zval const_EXPLAIN_MODE_EXPLAIN_value;
|
||||
ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_value, 1);
|
||||
zend_string *const_EXPLAIN_MODE_EXPLAIN_name = zend_string_init_interned("EXPLAIN_MODE_EXPLAIN", sizeof("EXPLAIN_MODE_EXPLAIN") - 1, 1);
|
||||
zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_EXPLAIN_name, &const_EXPLAIN_MODE_EXPLAIN_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
|
||||
zend_string_release(const_EXPLAIN_MODE_EXPLAIN_name);
|
||||
#endif
|
||||
#if SQLITE_VERSION_NUMBER >= 3043000
|
||||
|
||||
zval const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value;
|
||||
ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value, 2);
|
||||
zend_string *const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name = zend_string_init_interned("EXPLAIN_MODE_EXPLAIN_QUERY_PLAN", sizeof("EXPLAIN_MODE_EXPLAIN_QUERY_PLAN") - 1, 1);
|
||||
zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name, &const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
|
||||
zend_string_release(const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name);
|
||||
#endif
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
|
390
ext/sqlite3/tests/sqlite3_explain.phpt
Normal file
390
ext/sqlite3/tests/sqlite3_explain.phpt
Normal file
|
@ -0,0 +1,390 @@
|
|||
--TEST--
|
||||
Sqlite3Stmt::explain/setExplain usage
|
||||
--EXTENSIONS--
|
||||
sqlite3
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (PHP_OS_FAMILY === "Darwin") die("skip on darwin for now");
|
||||
$version = SQLite3::version()['versionNumber'];
|
||||
if ($version <= 3043000) die("skip for sqlite3 < 3.43.0");
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require_once(__DIR__ . '/new_db.inc');
|
||||
|
||||
$db->exec('CREATE TABLE test_explain (a string);');
|
||||
$stmt = $db->prepare('INSERT INTO test_explain VALUES ("first insert"), ("second_insert")');
|
||||
$stmt->setExplain(Sqlite3Stmt::EXPLAIN_MODE_EXPLAIN);
|
||||
var_dump($stmt->explain() == Sqlite3Stmt::EXPLAIN_MODE_EXPLAIN);
|
||||
$r = $stmt->execute();
|
||||
$result = [];
|
||||
while (($arr = $r->fetchArray(SQLITE3_ASSOC)) !== false) $result[] = $arr;
|
||||
var_dump($result);
|
||||
$stmts = $db->prepare('SELECT * FROM test_explain');
|
||||
$stmts->setExplain(Sqlite3Stmt::EXPLAIN_MODE_EXPLAIN_QUERY_PLAN);
|
||||
$r = $stmts->execute();
|
||||
$result = [];
|
||||
while (($arr = $r->fetchArray(SQLITE3_ASSOC)) !== false) $result[] = $arr;
|
||||
var_dump($result);
|
||||
|
||||
$stmt = $db->prepare('INSERT INTO test_explain VALUES ("first insert"), ("second_insert")');
|
||||
$stmt->setExplain(Sqlite3Stmt::EXPLAIN_MODE_PREPARED);
|
||||
$stmt->execute();
|
||||
$stmts = $db->prepare('SELECT * FROM test_explain');
|
||||
$stmts->setExplain(Sqlite3Stmt::EXPLAIN_MODE_PREPARED);
|
||||
$r = $stmts->execute();
|
||||
$result = [];
|
||||
while (($arr = $r->fetchArray(SQLITE3_ASSOC)) !== false) $result[] = $arr;
|
||||
var_dump($result);
|
||||
|
||||
try {
|
||||
$stmts->setExplain(-1);
|
||||
} catch (\ValueError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmts->setExplain(256);
|
||||
} catch (\ValueError $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
var_dump($stmts->explain() == Sqlite3Stmt::EXPLAIN_MODE_PREPARED);
|
||||
?>
|
||||
--EXPECTF--
|
||||
bool(true)
|
||||
array(%d) {
|
||||
[0]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(0)
|
||||
["opcode"]=>
|
||||
string(4) "Init"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(%d)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[1]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(1)
|
||||
["opcode"]=>
|
||||
string(13) "InitCoroutine"
|
||||
["p1"]=>
|
||||
int(3)
|
||||
["p2"]=>
|
||||
int(%d)
|
||||
["p3"]=>
|
||||
int(2)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
%A
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(7) "String8"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(2)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
string(12) "first insert"
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
%A
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(5) "Yield"
|
||||
["p1"]=>
|
||||
int(3)
|
||||
["p2"]=>
|
||||
int(0)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
%A
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(7) "String8"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(2)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
string(13) "second_insert"
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(5) "Yield"
|
||||
["p1"]=>
|
||||
int(3)
|
||||
["p2"]=>
|
||||
int(0)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(12) "EndCoroutine"
|
||||
["p1"]=>
|
||||
int(3)
|
||||
["p2"]=>
|
||||
int(0)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(9) "OpenWrite"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(2)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
string(1) "1"
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(5) "Yield"
|
||||
["p1"]=>
|
||||
int(3)
|
||||
["p2"]=>
|
||||
int(%d)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(8) "NewRowid"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(1)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(10) "MakeRecord"
|
||||
["p1"]=>
|
||||
int(2)
|
||||
["p2"]=>
|
||||
int(1)
|
||||
["p3"]=>
|
||||
int(4)
|
||||
["p4"]=>
|
||||
string(1) "C"
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(6) "Insert"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(4)
|
||||
["p3"]=>
|
||||
int(1)
|
||||
["p4"]=>
|
||||
string(12) "test_explain"
|
||||
["p5"]=>
|
||||
int(57)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(4) "Goto"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(%d)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(4) "Halt"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(0)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(11) "Transaction"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(1)
|
||||
["p3"]=>
|
||||
int(1)
|
||||
["p4"]=>
|
||||
string(1) "0"
|
||||
["p5"]=>
|
||||
int(1)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
[%d]=>
|
||||
array(8) {
|
||||
["addr"]=>
|
||||
int(%d)
|
||||
["opcode"]=>
|
||||
string(4) "Goto"
|
||||
["p1"]=>
|
||||
int(0)
|
||||
["p2"]=>
|
||||
int(1)
|
||||
["p3"]=>
|
||||
int(0)
|
||||
["p4"]=>
|
||||
NULL
|
||||
["p5"]=>
|
||||
int(0)
|
||||
["comment"]=>
|
||||
%a
|
||||
}
|
||||
}
|
||||
array(1) {
|
||||
[0]=>
|
||||
array(4) {
|
||||
["id"]=>
|
||||
int(2)
|
||||
["parent"]=>
|
||||
int(0)
|
||||
["notused"]=>
|
||||
int(0)
|
||||
["detail"]=>
|
||||
string(17) "SCAN test_explain"
|
||||
}
|
||||
}
|
||||
array(2) {
|
||||
[0]=>
|
||||
array(1) {
|
||||
["a"]=>
|
||||
string(12) "first insert"
|
||||
}
|
||||
[1]=>
|
||||
array(1) {
|
||||
["a"]=>
|
||||
string(13) "second_insert"
|
||||
}
|
||||
}
|
||||
SQLite3Stmt::setExplain(): Argument #1 ($mode) must be one of the SQLite3Stmt::EXPLAIN_MODE_* constants
|
||||
SQLite3Stmt::setExplain(): Argument #1 ($mode) must be one of the SQLite3Stmt::EXPLAIN_MODE_* constants
|
||||
bool(true)
|
Loading…
Add table
Add a link
Reference in a new issue