Exception:Array
(
[message] => Unable to prepare statement: database is locked
SELECT *, _.name as name FROM taxonomy_item_content AS _
LEFT JOIN taxonomy_item as ti ON (_.taxonomy_item_id = ti.taxonomy_item_id)
LEFT JOIN taxonomy t ON (ti.taxonomy_id = t.taxonomy_id)
LEFT JOIN taxonomy_to_site tt ON (tt.taxonomy_item_id = ti.taxonomy_item_id)
WHERE 1 = 1
AND slug = 'tag-1'
AND language_id = 2
AND t.post_type = 'post'
AND tt.site_id = 1
LIMIT 1
- SELECT *, _.name as name FROM taxonomy_item_content AS _
LEFT JOIN taxonomy_item as ti ON (_.taxonomy_item_id = ti.taxonomy_item_id)
LEFT JOIN taxonomy t ON (ti.taxonomy_id = t.taxonomy_id)
LEFT JOIN taxonomy_to_site tt ON (tt.taxonomy_item_id = ti.taxonomy_item_id)
WHERE 1 = 1
AND slug = :slug
AND language_id = :language_id
AND t.post_type = :post_type
AND tt.site_id = :site_id
LIMIT 1
- Array
(
[0] => Array
(
[file] => /var/www/html/storage/model/app/categorysql.sqlite.php
[line] => 1109
[function] => execute
[class] => Vvveb\System\Db\Sqlite
[type] => ->
)
)
[file] => /var/www/html/system/db/sqlite.php
[line_no] => 444
[line] => throw new \Exception($message, $e->getCode()); // <==
[lines] => Array
(
[0] => list($parameters, $types) = $this->paramsToQmark($sql, $params, $paramTypes);
[1] =>
[2] => try {
[3] => $stmt = self::$link->prepare($sql);
[4] => } catch (\Exception $e) {
[5] => $message = $e->getMessage() . "\n" . $this->debugSql($origSql, $params, $paramTypes) . "\n - " . $origSql . "\n - " . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[6] =>
[7] => throw new \Exception($message, $e->getCode()); // <==
[8] => }
[9] =>
[10] => if ($stmt/* && ! empty($paramTypes)*/) {
[11] => foreach ($parameters as $key => $value) {
[12] => $bindType = match ($types[$key] ?? 's') {
[13] => 'i' => SQLITE3_INTEGER,
)
[trace] => #0 /var/www/html/storage/model/app/categorysql.sqlite.php(1109): Vvveb\System\Db\Sqlite->execute('SELECT *, _.nam...', Array, Array)
#1 /var/www/html/app/controller/content/category.php(64): Vvveb\Sql\CategorySQL->getCategoryBySlug(Array)
#2 [internal function]: Vvveb\Controller\Content\Category->index()
#3 /var/www/html/system/core/frontcontroller.php(252): call_user_func(Array)
#4 /var/www/html/system/core/frontcontroller.php(322): Vvveb\System\Core\FrontController::call('Vvveb\\Controlle...', 'index', '/var/www/html/a...')
#5 /var/www/html/system/core/frontcontroller.php(396): Vvveb\System\Core\FrontController::redirect('Content/Categor...', 'index')
#6 /var/www/html/system/core/startup.php(521): Vvveb\System\Core\FrontController::dispatch(Array)
#7 /var/www/html/index.php(170): Vvveb\System\Core\start()
#8 /var/www/html/public/index.php(28): include('/var/www/html/i...')
#9 {main}
[code] => Array
(
[0] =>
[2] => /**
[3] => * Vvveb
[4] => *
[5] => * Copyright (C) 2022 Ziadin Givan
[6] => *
[7] => * This program is free software: you can redistribute it and/or modify
[8] => * it under the terms of the GNU Affero General Public License as
[9] => * published by the Free Software Foundation, either version 3 of the
[10] => * License, or (at your option) any later version.
[11] => *
[12] => * This program is distributed in the hope that it will be useful,
[13] => * but WITHOUT ANY WARRANTY; without even the implied warranty of
[14] => * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
[15] => * GNU Affero General Public License for more details.
[16] => *
[17] => * You should have received a copy of the GNU Affero General Public License
[18] => * along with this program. If not, see .
[19] => *
[20] => */
[21] =>
[22] => namespace Vvveb\System\Db;
[23] =>
[24] => use Vvveb\System\Event;
[25] =>
[26] => /**
[27] => * SQLite database driver using the SQLite3 extension.
[28] => *
[29] => * Provides connection management, query execution with parameterised statements,
[30] => * result fetching, escaping, and schema introspection for SQLite databases.
[31] => */
[32] => class Sqlite extends DBDriver {
[33] => /** @var ?\SQLite3 The shared database connection (singleton per process). */
[34] => private static ?\SQLite3 $link = null;
[35] =>
[36] => /** @var ?\SQLite3Stmt The current prepared statement, if any. */
[37] => private ?\SQLite3Stmt $stmt = null;
[38] =>
[39] => /** @var int Number of rows affected by the last query. */
[40] => public int $affected_rows = 0;
[41] =>
[42] => /** @var int|bool Number of rows returned by the last query, or 0 if not applicable. */
[43] => public int|bool $num_rows = 0;
[44] =>
[45] => /** @var ?int The row ID of the last inserted row, or null if none. */
[46] => public ?int $insert_id = null;
[47] =>
[48] => /** @var string Table name prefix. */
[49] => public string $prefix = ''; //'vv_';
[50] =>
[51] => /** @var string Identifier quote character for SQLite. */
[52] => public string $quote = '`';
[53] =>
[54] => /**
[55] => * Get the SQLite library version number.
[56] => *
[57] => * @return int|false The version number (e.g. 3039000), or false if not connected.
[58] => */
[59] => public static function version(): int|false {
[60] => if (self :: $link) {
[61] => return \SQLite3::version()['versionNumber'] ?? false;
[62] => }
[63] =>
[64] => return false;
[65] => }
[66] =>
[67] => /**
[68] => * Get the SQLite library version string.
[69] => *
[70] => * @return string|false The version string (e.g. '3.39.0'), or false if not connected.
[71] => */
[72] => public static function info(): string|false {
[73] => if (self :: $link) {
[74] => return \SQLite3::version()['versionString'] ?? false;
[75] => }
[76] =>
[77] => return false;
[78] => }
[79] =>
[80] => /**
[81] => * Get the error message from the last SQLite operation.
[82] => *
[83] => * @return string The error message, or empty string if not connected.
[84] => */
[85] => public function error(): string {
[86] => if (self :: $link) {
[87] => return self :: $link->lastErrorMsg() ?? '';
[88] => }
[89] =>
[90] => return '';
[91] => }
[92] =>
[93] => /**
[94] => * Get the error code from the last SQLite operation.
[95] => *
[96] => * @return int The error code, or 0 if not connected.
[97] => */
[98] => public function errorCode(): int {
[99] => if (self :: $link) {
[100] => return self :: $link->lastErrorCode() ?? 0;
[101] => }
[102] =>
[103] => return 0;
[104] => }
[105] =>
[106] => /**
[107] => * Pass through the statement result unchanged.
[108] => *
[109] => * @param mixed $stmt The statement result.
[110] => *
[111] => * @return mixed The same statement result.
[112] => */
[113] => public function get_result(mixed $stmt): mixed {
[114] => return $stmt;
[115] => }
[116] =>
[117] => /**
[118] => * @param string $filename The SQLite database file path.
[119] => * @param string $dbname Database name (unused for SQLite).
[120] => * @param string $user Database user (unused for SQLite).
[121] => * @param string $pass Database password (unused for SQLite).
[122] => * @param ?string $port Port (unused for SQLite).
[123] => * @param string $prefix Table name prefix.
[124] => */
[125] => public function __construct(string $filename = DB_HOST, string $dbname = DB_NAME, string $user = DB_USER, string $pass = DB_PASS, ?string $port = null, string $prefix = DB_PREFIX) {
[126] => //return $this->connect($host, $dbname, $user, $pass, $port, $prefix);
[127] => }
[128] =>
[129] => /**
[130] => * Connect to the SQLite database.
[131] => *
[132] => * Uses a singleton pattern — returns the existing connection if already open.
[133] => * Enables exceptions for error handling.
[134] => *
[135] => * @param string $filename The SQLite database file path.
[136] => * @param string $dbname Database name (unused for SQLite).
[137] => * @param string $user Database user (unused for SQLite).
[138] => * @param string $pass Database password (unused for SQLite).
[139] => * @param ?string $port Port (unused for SQLite).
[140] => * @param string $prefix Table name prefix.
[141] => *
[142] => * @return \SQLite3 The database connection.
[143] => *
[144] => * @throws \Exception On connection failure.
[145] => */
[146] => public function connect(string $filename = DB_HOST, string $dbname = DB_NAME, string $user = DB_USER, string $pass = DB_PASS, ?string $port = null, string $prefix = DB_PREFIX): \SQLite3 {
[147] => if (self :: $link) {
[148] => return self :: $link;
[149] => }
[150] =>
[151] => try {
[152] => self :: $link = new \SQLite3($filename); //, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
[153] => } catch (\Exception $e) {
[154] => $errorMessage = str_replace($pass,'*****', $e->getMessage());
[155] =>
[156] => throw new \Exception($errorMessage, $e->getCode());
[157] => }
[158] =>
[159] => self :: $link->enableExceptions(true);
[160] =>
[161] => return self :: $link;
[162] => }
[163] =>
[164] => /**
[165] => * Get all columns for a table used for sanitizing input.
[166] => *
[167] => * @param string $tableName The table name to introspect.
[168] => * @param bool $comment Whether to include column comments (unused for SQLite).
[169] => *
[170] => * @return array|false
[171] => * Associative array keyed by column name, or false on failure.
[172] => */
[173] => public function getColumnsMeta(string $tableName, bool $comment = false): array|false {
[174] => $sql =
[175] => "SELECT type as t, name, dflt_value as d, `notnull` as n FROM pragma_table_info('$tableName');";
[176] =>
[177] => if ($result = $this->query($sql)) {
[178] => $columns = [];
[179] =>
[180] => while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
[181] => $columns[$row['name']] = $row;
[182] => }
[183] =>
[184] => /* free result set */
[185] => $result->finalize();
[186] =>
[187] => return $columns;
[188] => }
[189] =>
[190] => return false;
[191] => }
[192] =>
[193] => /**
[194] => * Get all table names in the database.
[195] => *
[196] => * @param string $db The database name (unused for SQLite — reads sqlite_master).
[197] => *
[198] => * @return array|false List of table names, or false on failure.
[199] => */
[200] => public function getTableNames(string $db = DB_NAME): array|false {
[201] => $sql = "SELECT name FROM sqlite_master WHERE type ='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_search%' ORDER BY name";
[202] =>
[203] => if ($result = $this->query($sql)) {
[204] => $names = [];
[205] =>
[206] => while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
[207] => //$names[] = $row;
[208] => $names[] = $row['name'];
[209] => }
[210] =>
[211] => /* free result set */
[212] => $result->finalize();
[213] =>
[214] => return $names;
[215] => }
[216] =>
[217] => return false;
[218] => }
[219] =>
[220] => /**
[221] => * Escape a value for safe inclusion in a SQL query.
[222] => *
[223] => * Strings are escaped with SQLite3::escapeString. Null values become 'null'.
[224] => * All other types are returned as-is.
[225] => *
[226] => * @param mixed $string The value to escape.
[227] => *
[228] => * @return mixed The escaped value.
[229] => */
[230] => public function escape(mixed $string): mixed {
[231] => if (is_string($string)) {
[232] => return self :: $link->escapeString($string);
[233] => }
[234] =>
[235] => if (is_null($string)) {
[236] => return 'null';
[237] => }
[238] =>
[239] => return $string;
[240] => }
[241] =>
[242] => /**
[243] => * Escape a string value for use in a SQL literal.
[244] => *
[245] => * @param string $string The string to escape.
[246] => *
[247] => * @return string The escaped string.
[248] => */
[249] => public function escapeLiteral(string $string): string {
[250] => return $this->escape($string);
[251] => }
[252] =>
[253] => /**
[254] => * Fetch a single scalar value from a result.
[255] => *
[256] => * @param \SQLite3Result $result The query result.
[257] => *
[258] => * @return mixed The first column value, or null if no row or the value is null.
[259] => */
[260] => public function fetchOne(\SQLite3Result $result): mixed {
[261] => $return = false;
[262] =>
[263] => if ($result) {
[264] => $return = $result->fetchArray(SQLITE3_NUM)[0] ?? null;
[265] => }
[266] =>
[267] => return $return;
[268] => }
[269] =>
[270] => /**
[271] => * Fetch a single row from a result.
[272] => *
[273] => * @param \SQLite3Result $result The query result.
[274] => *
[275] => * @return array|false The row as an associative array, or false if no results.
[276] => */
[277] => public function fetchArray(\SQLite3Result $result): array|false {
[278] => $return = false;
[279] =>
[280] => if ($result) {
[281] => $return = $result->fetchArray(SQLITE3_ASSOC);
[282] => }
[283] =>
[284] => return $return;
[285] => }
[286] =>
[287] => /**
[288] => * Fetch all rows from a result.
[289] => *
[290] => * @param \SQLite3Result $result The query result.
[291] => *
[292] => * @return array> All result rows.
[293] => */
[294] => public function fetchAll(\SQLite3Result $result): array {
[295] => $return = [];
[296] =>
[297] => if ($result) {
[298] => while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
[299] => $return[] = $row;
[300] => }
[301] => }
[302] =>
[303] => return $return;
[304] => }
[305] =>
[306] => /**
[307] => * Execute a raw SQL query.
[308] => *
[309] => * Automatically connects if not connected. Updates affected_rows, insert_id,
[310] => * and num_rows on success.
[311] => *
[312] => * @param string $sql The SQL query to execute.
[313] => *
[314] => * @return \SQLite3Result|false The query result, or false on failure.
[315] => *
[316] => * @throws \Exception On query failure.
[317] => */
[318] => public function query(string $sql): \SQLite3Result|false {
[319] => if (! self :: $link) {
[320] => $this->connect();
[321] => }
[322] =>
[323] => $result = false;
[324] =>
[325] => try {
[326] => $result = self :: $link->query($sql);
[327] =>
[328] => if (LOG_SQL_QUERIES) {
[329] => error_log($sql);
[330] => }
[331] =>
[332] => if ($result) {
[333] => $this->affected_rows = self :: $link->changes();
[334] => $this->insert_id = self :: $link->lastInsertRowID();
[335] => $this->num_rows = $result->numColumns() && $result->columnType(0) != SQLITE3_NULL;
[336] => //$result->finalize();
[337] => } else {
[338] => $message = $this->error() . "\n" . $sql . "\n - " . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[339] =>
[340] => throw new \Exception($message, $this->errorCode());
[341] => }
[342] => } catch (\Exception $e) {
[343] => $message = $e->getMessage() . "\n$sql\n" . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[344] =>
[345] => throw new \Exception($message, $e->getCode());
[346] => }
[347] =>
[348] => return $result;
[349] => }
[350] =>
[351] => /**
[352] => * Execute multiple SQL statements by splitting on semicolons.
[353] => *
[354] => * @param string $sql The SQL statements to execute (semicolon-separated).
[355] => *
[356] => * @return array Array of results from each statement.
[357] => *
[358] => * @throws \Exception On any statement failure.
[359] => */
[360] => public function multi_query(string $sql): array {
[361] => $results = [];
[362] =>
[363] => $queries = preg_split('/;\n/', $sql);
[364] =>
[365] => foreach ($queries as $query) {
[366] => if (empty(trim($query))) {
[367] => continue;
[368] => }
[369] =>
[370] => if (LOG_SQL_QUERIES) {
[371] => error_log($sql);
[372] => }
[373] =>
[374] => try {
[375] => $result = self :: $link->query("$query;");
[376] =>
[377] => if ($result) {
[378] => $this->affected_rows = self :: $link->changes();
[379] => $this->insert_id = self :: $link->lastInsertRowID();
[380] => $this->num_rows = $result->numColumns() && $result->columnType(0) != SQLITE3_NULL;
[381] => //$result->finalize();
[382] => } else {
[383] => $message = $this->error() . "\n" . $sql . "\n - " . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[384] =>
[385] => throw new \Exception($message, $this->errorCode());
[386] => }
[387] =>
[388] => $results[] = $result;
[389] => } catch (\Exception $e) {
[390] => $message = $e->getMessage() . "\n$sql\n" . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[391] =>
[392] => throw new \Exception($message, $e->getCode());
[393] => }
[394] => }
[395] =>
[396] => return $results;
[397] => }
[398] =>
[399] => /**
[400] => * Close the database connection.
[401] => *
[402] => * @return bool|null True on success, null if not connected.
[403] => */
[404] => public function close(): bool|null {
[405] => if (self :: $link) {
[406] => return self :: $link->close();
[407] => }
[408] =>
[409] => return null;
[410] => }
[411] =>
[412] => /**
[413] => * Prepare and execute a parameterised query.
[414] => *
[415] => * Fires an Event::trigger hook before execution. Converts named parameters
[416] => * to positional ? placeholders, binds values with appropriate SQLite3 type
[417] => * constants, and executes the statement. Updates affected_rows, insert_id,
[418] => * and num_rows on success.
[419] => *
[420] => * @param string $sql The SQL query with :named parameters.
[421] => * @param array $params Named parameter values.
[422] => * @param array $paramTypes Optional explicit type overrides.
[423] => *
[424] => * @return \SQLite3Result|false|null The query result, false, or null on failure.
[425] => *
[426] => * @throws \Exception On prepare or execute failure.
[427] => */
[428] => public function execute(string $sql, array $params = [], array $paramTypes = []): \SQLite3Result|false|null {
[429] => list($sql, $params) = Event::trigger(__CLASS__,__FUNCTION__, $sql, $params);
[430] => //save orig sql for debugging info
[431] => $origSql = $sql;
[432] =>
[433] => if (! self :: $link) {
[434] => $this->connect();
[435] => }
[436] =>
[437] => list($parameters, $types) = $this->paramsToQmark($sql, $params, $paramTypes);
[438] =>
[439] => try {
[440] => $stmt = self::$link->prepare($sql);
[441] => } catch (\Exception $e) {
[442] => $message = $e->getMessage() . "\n" . $this->debugSql($origSql, $params, $paramTypes) . "\n - " . $origSql . "\n - " . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[443] =>
[444] => throw new \Exception($message, $e->getCode()); // <==
[445] => }
[446] =>
[447] => if ($stmt/* && ! empty($paramTypes)*/) {
[448] => foreach ($parameters as $key => $value) {
[449] => $bindType = match ($types[$key] ?? 's') {
[450] => 'i' => SQLITE3_INTEGER,
[451] => 'd' => SQLITE3_FLOAT,
[452] => 'b' => SQLITE3_BLOB,
[453] => default => SQLITE3_TEXT,
[454] => };
[455] => $index = (int)$key + 1;
[456] => $stmt->bindValue($index, $value, $bindType);
[457] => }
[458] => } else {
[459] => if (DEBUG) {
[460] => error_log((self :: $link->lastErrorMsg ?? '') . ' ' . $this->debugSql($origSql, $params, $paramTypes));
[461] => }
[462] => }
[463] =>
[464] => if (LOG_SQL_QUERIES) {
[465] => error_log($this->debugSql($origSql, $params, $paramTypes));
[466] => }
[467] =>
[468] => if ($stmt) {
[469] => $result = null;
[470] =>
[471] => try {
[472] => if ($result = $stmt->execute()) {
[473] => $this->affected_rows = self :: $link->changes();
[474] => $this->insert_id = self :: $link->lastInsertRowID();
[475] => $this->num_rows = $result->numColumns() && $result->columnType(0) != SQLITE3_NULL;
[476] =>
[477] => return $result;
[478] => } else {
[479] => error_log(print_r($result, 1));
[480] => error_log($this->debugSql($sql, $params, $paramTypes));
[481] => }
[482] => } catch (\Exception $e) {
[483] => $message = $e->getMessage() . "\n" . $origSql . "\n" . $this->debugSql($origSql, $params, $paramTypes) . "\n" . print_r($parameters, 1) . $types . "\n" . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1), true);
[484] =>
[485] => throw new \Exception($message, $e->getCode());
[486] => }
[487] => } else {
[488] => error_log(print_r($stmt, 1));
[489] => error_log($this->debugSql($origSql, $params, $paramTypes));
[490] => }
[491] =>
[492] => return $stmt;
[493] => }
[494] =>
[495] => /**
[496] => * Bind a parameter value to the current prepared statement.
[497] => *
[498] => * @param string|int $param The parameter index or name.
[499] => * @param mixed $value The value to bind.
[500] => * @param int|null $type The SQLite3 type constant, or null for auto-detection.
[501] => *
[502] => * @return void
[503] => */
[504] => public function bind(string|int $param, mixed $value, ?int $type = null): void {
[505] => $this->stmt->bindValue($param, $value, $type);
[506] => }
[507] => }
)
)