You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
542 lines
13 KiB
542 lines
13 KiB
<?php
|
|
|
|
/**
|
|
* CodeIgniter
|
|
*
|
|
* An open source application development framework for PHP
|
|
*
|
|
* This content is released under the MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
|
* Copyright (c) 2019 CodeIgniter Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* @package CodeIgniter
|
|
* @author CodeIgniter Dev Team
|
|
* @copyright 2019 CodeIgniter Foundation
|
|
* @license https://opensource.org/licenses/MIT MIT License
|
|
* @link https://codeigniter.com
|
|
* @since Version 4.0.0
|
|
* @filesource
|
|
*/
|
|
|
|
namespace CodeIgniter\Database\SQLite3;
|
|
|
|
use CodeIgniter\Database\BaseConnection;
|
|
use CodeIgniter\Database\ConnectionInterface;
|
|
use CodeIgniter\Database\Exceptions\DatabaseException;
|
|
|
|
/**
|
|
* Connection for SQLite3
|
|
*/
|
|
class Connection extends BaseConnection implements ConnectionInterface
|
|
{
|
|
|
|
/**
|
|
* Database driver
|
|
*
|
|
* @var string
|
|
*/
|
|
public $DBDriver = 'SQLite3';
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* ORDER BY random keyword
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_random_keyword = [
|
|
'RANDOM()',
|
|
];
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Connect to the database.
|
|
*
|
|
* @param boolean $persistent
|
|
*
|
|
* @return mixed
|
|
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
|
|
*/
|
|
public function connect(bool $persistent = false)
|
|
{
|
|
if ($persistent && $this->db->DBDebug)
|
|
{
|
|
throw new DatabaseException('SQLite3 doesn\'t support persistent connections.');
|
|
}
|
|
try
|
|
{
|
|
return (! $this->password)
|
|
? new \SQLite3($this->database)
|
|
: new \SQLite3($this->database, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $this->password);
|
|
}
|
|
catch (\Exception $e)
|
|
{
|
|
throw new DatabaseException('SQLite3 error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Keep or establish the connection if no queries have been sent for
|
|
* a length of time exceeding the server's idle timeout.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function reconnect()
|
|
{
|
|
$this->close();
|
|
$this->initialize();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Close the database connection.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _close()
|
|
{
|
|
$this->connID->close();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Select a specific database table to use.
|
|
*
|
|
* @param string $databaseName
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function setDatabase(string $databaseName): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns a string containing the version of the database being used.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getVersion(): string
|
|
{
|
|
if (isset($this->dataCache['version']))
|
|
{
|
|
return $this->dataCache['version'];
|
|
}
|
|
|
|
$version = \SQLite3::version();
|
|
|
|
return $this->dataCache['version'] = $version['versionString'];
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Execute the query
|
|
*
|
|
* @param string $sql
|
|
*
|
|
* @return mixed \SQLite3Result object or bool
|
|
*/
|
|
public function execute(string $sql)
|
|
{
|
|
return $this->isWriteType($sql)
|
|
? $this->connID->exec($sql)
|
|
: $this->connID->query($sql);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the total number of rows affected by this query.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function affectedRows(): int
|
|
{
|
|
return $this->connID->changes();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Platform-dependant string escape
|
|
*
|
|
* @param string $str
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _escapeString(string $str): string
|
|
{
|
|
return $this->connID->escapeString($str);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Generates the SQL for listing tables in a platform-dependent manner.
|
|
*
|
|
* @param boolean $prefixLimit
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _listTables(bool $prefixLimit = false): string
|
|
{
|
|
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
|
|
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
|
|
. (($prefixLimit !== false && $this->DBPrefix !== '')
|
|
? ' AND "NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . '%\' ' . sprintf($this->likeEscapeStr,
|
|
$this->likeEscapeChar)
|
|
: '');
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Generates a platform-specific query string so that the column names can be fetched.
|
|
*
|
|
* @param string $table
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _listColumns(string $table = ''): string
|
|
{
|
|
return 'PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')';
|
|
}
|
|
|
|
/**
|
|
* Fetch Field Names
|
|
*
|
|
* @param string $table Table name
|
|
*
|
|
* @return array|false
|
|
* @throws DatabaseException
|
|
*/
|
|
public function getFieldNames(string $table)
|
|
{
|
|
// Is there a cached result?
|
|
if (isset($this->dataCache['field_names'][$table]))
|
|
{
|
|
return $this->dataCache['field_names'][$table];
|
|
}
|
|
|
|
if (empty($this->connID))
|
|
{
|
|
$this->initialize();
|
|
}
|
|
|
|
if (false === ($sql = $this->_listColumns($table)))
|
|
{
|
|
if ($this->DBDebug)
|
|
{
|
|
throw new DatabaseException(lang('Database.featureUnavailable'));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
$query = $this->query($sql);
|
|
$this->dataCache['field_names'][$table] = [];
|
|
|
|
foreach ($query->getResultArray() as $row)
|
|
{
|
|
// Do we know from where to get the column's name?
|
|
if (! isset($key))
|
|
{
|
|
if (isset($row['column_name']))
|
|
{
|
|
$key = 'column_name';
|
|
}
|
|
elseif (isset($row['COLUMN_NAME']))
|
|
{
|
|
$key = 'COLUMN_NAME';
|
|
}
|
|
elseif (isset($row['name']))
|
|
{
|
|
$key = 'name';
|
|
}
|
|
else
|
|
{
|
|
// We have no other choice but to just get the first element's key.
|
|
$key = key($row);
|
|
}
|
|
}
|
|
|
|
$this->dataCache['field_names'][$table][] = $row[$key];
|
|
}
|
|
|
|
return $this->dataCache['field_names'][$table];
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns an array of objects with field data
|
|
*
|
|
* @param string $table
|
|
* @return \stdClass[]
|
|
* @throws DatabaseException
|
|
*/
|
|
public function _fieldData(string $table): array
|
|
{
|
|
if (($query = $this->query('PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null,
|
|
false) . ')')) === false)
|
|
{
|
|
throw new DatabaseException(lang('Database.failGetFieldData'));
|
|
}
|
|
$query = $query->getResultObject();
|
|
|
|
if (empty($query))
|
|
{
|
|
return [];
|
|
}
|
|
$retVal = [];
|
|
for ($i = 0, $c = count($query); $i < $c; $i++)
|
|
{
|
|
$retVal[$i] = new \stdClass();
|
|
$retVal[$i]->name = $query[$i]->name;
|
|
$retVal[$i]->type = $query[$i]->type;
|
|
$retVal[$i]->max_length = null;
|
|
$retVal[$i]->default = $query[$i]->dflt_value;
|
|
$retVal[$i]->primary_key = isset($query[$i]->pk) ? (bool)$query[$i]->pk : false;
|
|
$retVal[$i]->nullable = isset($query[$i]->notnull) ? ! (bool)$query[$i]->notnull : false;
|
|
}
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns an array of objects with index data
|
|
*
|
|
* @param string $table
|
|
* @return \stdClass[]
|
|
* @throws DatabaseException
|
|
*/
|
|
public function _indexData(string $table): array
|
|
{
|
|
// Get indexes
|
|
// Don't use PRAGMA index_list, so we can preserve index order
|
|
$sql = "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name=" . $this->escape(strtolower($table));
|
|
if (($query = $this->query($sql)) === false)
|
|
{
|
|
throw new DatabaseException(lang('Database.failGetIndexData'));
|
|
}
|
|
$query = $query->getResultObject();
|
|
|
|
$retVal = [];
|
|
foreach ($query as $row)
|
|
{
|
|
$obj = new \stdClass();
|
|
$obj->name = $row->name;
|
|
|
|
// Get fields for index
|
|
$obj->fields = [];
|
|
if (($fields = $this->query('PRAGMA index_info(' . $this->escape(strtolower($row->name)) . ')')) === false)
|
|
{
|
|
throw new DatabaseException(lang('Database.failGetIndexData'));
|
|
}
|
|
$fields = $fields->getResultObject();
|
|
|
|
foreach ($fields as $field)
|
|
{
|
|
$obj->fields[] = $field->name;
|
|
}
|
|
|
|
$retVal[$obj->name] = $obj;
|
|
}
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns an array of objects with Foreign key data
|
|
*
|
|
* @param string $table
|
|
* @return \stdClass[]
|
|
*/
|
|
public function _foreignKeyData(string $table): array
|
|
{
|
|
if ($this->supportsForeignKeys() !== true)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$tables = $this->listTables();
|
|
|
|
if (empty($tables))
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$retVal = [];
|
|
|
|
foreach ($tables as $table)
|
|
{
|
|
$query = $this->query("PRAGMA foreign_key_list({$table})")->getResult();
|
|
|
|
foreach ($query as $row)
|
|
{
|
|
$obj = new \stdClass();
|
|
$obj->constraint_name = $row->from . ' to ' . $row->table . '.' . $row->to;
|
|
$obj->table_name = $table;
|
|
$obj->foreign_table_name = $row->table;
|
|
$obj->sequence = $row->seq;
|
|
|
|
$retVal[] = $obj;
|
|
}
|
|
}
|
|
|
|
return $retVal;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns platform-specific SQL to disable foreign key checks.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _disableForeignKeyChecks()
|
|
{
|
|
return 'PRAGMA foreign_keys = OFF';
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns platform-specific SQL to enable foreign key checks.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _enableForeignKeyChecks()
|
|
{
|
|
return 'PRAGMA foreign_keys = ON';
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the last error code and message.
|
|
*
|
|
* Must return an array with keys 'code' and 'message':
|
|
*
|
|
* return ['code' => null, 'message' => null);
|
|
*
|
|
* @return array
|
|
*/
|
|
public function error(): array
|
|
{
|
|
return [
|
|
'code' => $this->connID->lastErrorCode(),
|
|
'message' => $this->connID->lastErrorMsg(),
|
|
];
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Insert ID
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function insertID(): int
|
|
{
|
|
return $this->connID->lastInsertRowID();
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Begin Transaction
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function _transBegin(): bool
|
|
{
|
|
return $this->connID->exec('BEGIN TRANSACTION');
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Commit Transaction
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function _transCommit(): bool
|
|
{
|
|
return $this->connID->exec('END TRANSACTION');
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Rollback Transaction
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function _transRollback(): bool
|
|
{
|
|
return $this->connID->exec('ROLLBACK');
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Determines if the statement is a write-type query or not.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isWriteType($sql): bool
|
|
{
|
|
return (bool)preg_match(
|
|
'/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX)\s/i',
|
|
$sql);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Checks to see if the current install supports Foreign Keys
|
|
* and has them enabled.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function supportsForeignKeys(): bool
|
|
{
|
|
$result = $this->simpleQuery('PRAGMA foreign_keys');
|
|
|
|
return (bool)$result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
}
|
|
|