This commit is contained in:
Markus
2022-04-28 09:40:10 +02:00
commit 795794f992
9586 changed files with 1146991 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseBuilder;
/**
* Builder for SQLite3
*/
class Builder extends BaseBuilder
{
/**
* Default installs of SQLite typically do not
* support limiting delete clauses.
*
* @var bool
*/
protected $canLimitDeletes = false;
/**
* Default installs of SQLite do no support
* limiting update queries in combo with WHERE.
*
* @var bool
*/
protected $canLimitWhereUpdates = false;
/**
* ORDER BY random keyword
*
* @var array
*/
protected $randomKeyword = [
'RANDOM()',
];
/**
* @var array
*/
protected $supportedIgnoreStatements = [
'insert' => 'OR IGNORE',
];
/**
* Replace statement
*
* Generates a platform-specific replace string from the supplied data
*/
protected function _replace(string $table, array $keys, array $values): string
{
return 'INSERT OR ' . parent::_replace($table, $keys, $values);
}
/**
* Generates a platform-specific truncate string from the supplied data
*
* If the database does not support the TRUNCATE statement,
* then this method maps to 'DELETE FROM table'
*/
protected function _truncate(string $table): string
{
return 'DELETE FROM ' . $table;
}
}

View File

@@ -0,0 +1,396 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use ErrorException;
use Exception;
use SQLite3;
use stdClass;
/**
* Connection for SQLite3
*/
class Connection extends BaseConnection
{
/**
* Database driver
*
* @var string
*/
public $DBDriver = 'SQLite3';
/**
* Identifier escape character
*
* @var string
*/
public $escapeChar = '`';
/**
* Connect to the database.
*
* @throws DatabaseException
*
* @return mixed
*/
public function connect(bool $persistent = false)
{
if ($persistent && $this->DBDebug) {
throw new DatabaseException('SQLite3 doesn\'t support persistent connections.');
}
try {
if ($this->database !== ':memory:' && strpos($this->database, DIRECTORY_SEPARATOR) === false) {
$this->database = WRITEPATH . $this->database;
}
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.
*/
public function reconnect()
{
$this->close();
$this->initialize();
}
/**
* Close the database connection.
*/
protected function _close()
{
$this->connID->close();
}
/**
* Select a specific database table to use.
*/
public function setDatabase(string $databaseName): bool
{
return false;
}
/**
* Returns a string containing the version of the database being used.
*/
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
*
* @return mixed \SQLite3Result object or bool
*/
protected function execute(string $sql)
{
try {
return $this->isWriteType($sql)
? $this->connID->exec($sql)
: $this->connID->query($sql);
} catch (ErrorException $e) {
log_message('error', $e);
if ($this->DBDebug) {
throw $e;
}
}
return false;
}
/**
* Returns the total number of rows affected by this query.
*/
public function affectedRows(): int
{
return $this->connID->changes();
}
/**
* Platform-dependant string escape
*/
protected function _escapeString(string $str): string
{
return $this->connID->escapeString($str);
}
/**
* Generates the SQL for listing tables in a platform-dependent manner.
*/
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.
*/
protected function _listColumns(string $table = ''): string
{
return 'PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')';
}
/**
* @throws DatabaseException
*
* @return array|false
*/
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();
}
$sql = $this->_listColumns($table);
$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
*
* @throws DatabaseException
*
* @return stdClass[]
*/
protected function _fieldData(string $table): array
{
if (false === $query = $this->query('PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, 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;
$retVal[$i]->nullable = isset($query[$i]->notnull) && ! (bool) $query[$i]->notnull;
}
return $retVal;
}
/**
* Returns an array of objects with index data
*
* @throws DatabaseException
*
* @return stdClass[]
*/
protected 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 (false === $fields = $this->query('PRAGMA index_info(' . $this->escape(strtolower($row->name)) . ')')) {
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
*
* @return stdClass[]
*/
protected 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 this format: ['code' => string|int, 'message' => string]
* intval(code) === 0 means "no error".
*
* @return array<string, int|string>
*/
public function error(): array
{
return [
'code' => $this->connID->lastErrorCode(),
'message' => $this->connID->lastErrorMsg(),
];
}
/**
* Insert ID
*/
public function insertID(): int
{
return $this->connID->lastInsertRowID();
}
/**
* Begin Transaction
*/
protected function _transBegin(): bool
{
return $this->connID->exec('BEGIN TRANSACTION');
}
/**
* Commit Transaction
*/
protected function _transCommit(): bool
{
return $this->connID->exec('END TRANSACTION');
}
/**
* Rollback Transaction
*/
protected function _transRollback(): bool
{
return $this->connID->exec('ROLLBACK');
}
/**
* Checks to see if the current install supports Foreign Keys
* and has them enabled.
*/
public function supportsForeignKeys(): bool
{
$result = $this->simpleQuery('PRAGMA foreign_keys');
return (bool) $result;
}
}

View File

@@ -0,0 +1,254 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Forge as BaseForge;
/**
* Forge for SQLite3
*/
class Forge extends BaseForge
{
/**
* DROP INDEX statement
*
* @var string
*/
protected $dropIndexStr = 'DROP INDEX %s';
/**
* @var Connection
*/
protected $db;
/**
* UNSIGNED support
*
* @var array|bool
*/
protected $_unsigned = false;
/**
* NULL value representation in CREATE/ALTER TABLE statements
*
* @var string
*
* @internal
*/
protected $null = 'NULL';
/**
* Constructor.
*/
public function __construct(BaseConnection $db)
{
parent::__construct($db);
if (version_compare($this->db->getVersion(), '3.3', '<')) {
$this->createTableIfStr = false;
$this->dropTableIfStr = false;
}
}
/**
* Create database
*
* @param bool $ifNotExists Whether to add IF NOT EXISTS condition
*/
public function createDatabase(string $dbName, bool $ifNotExists = false): bool
{
// In SQLite, a database is created when you connect to the database.
// We'll return TRUE so that an error isn't generated.
return true;
}
/**
* Drop database
*
* @throws DatabaseException
*/
public function dropDatabase(string $dbName): bool
{
// In SQLite, a database is dropped when we delete a file
if (! is_file($dbName)) {
if ($this->db->DBDebug) {
throw new DatabaseException('Unable to drop the specified database.');
}
return false;
}
// We need to close the pseudo-connection first
$this->db->close();
if (! @unlink($dbName)) {
if ($this->db->DBDebug) {
throw new DatabaseException('Unable to drop the specified database.');
}
return false;
}
if (! empty($this->db->dataCache['db_names'])) {
$key = array_search(strtolower($dbName), array_map('strtolower', $this->db->dataCache['db_names']), true);
if ($key !== false) {
unset($this->db->dataCache['db_names'][$key]);
}
}
return true;
}
/**
* @param mixed $field
*
* @return array|string|null
*/
protected function _alterTable(string $alterType, string $table, $field)
{
switch ($alterType) {
case 'DROP':
$sqlTable = new Table($this->db, $this);
$sqlTable->fromTable($table)
->dropColumn($field)
->run();
return '';
case 'CHANGE':
(new Table($this->db, $this))
->fromTable($table)
->modifyColumn($field)
->run();
return null;
default:
return parent::_alterTable($alterType, $table, $field);
}
}
/**
* Process column
*/
protected function _processColumn(array $field): string
{
if ($field['type'] === 'TEXT' && strpos($field['length'], "('") === 0) {
$field['type'] .= ' CHECK(' . $this->db->escapeIdentifiers($field['name'])
. ' IN ' . $field['length'] . ')';
}
return $this->db->escapeIdentifiers($field['name'])
. ' ' . $field['type']
. $field['auto_increment']
. $field['null']
. $field['unique']
. $field['default'];
}
/**
* Process indexes
*/
protected function _processIndexes(string $table): array
{
$sqls = [];
for ($i = 0, $c = count($this->keys); $i < $c; $i++) {
$this->keys[$i] = (array) $this->keys[$i];
for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) {
if (! isset($this->fields[$this->keys[$i][$i2]])) {
unset($this->keys[$i][$i2]);
}
}
if (count($this->keys[$i]) <= 0) {
continue;
}
if (in_array($i, $this->uniqueKeys, true)) {
$sqls[] = 'CREATE UNIQUE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
. ' ON ' . $this->db->escapeIdentifiers($table)
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
continue;
}
$sqls[] = 'CREATE INDEX ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
. ' ON ' . $this->db->escapeIdentifiers($table)
. ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
}
return $sqls;
}
/**
* Field attribute TYPE
*
* Performs a data type mapping between different databases.
*/
protected function _attributeType(array &$attributes)
{
switch (strtoupper($attributes['TYPE'])) {
case 'ENUM':
case 'SET':
$attributes['TYPE'] = 'TEXT';
break;
case 'BOOLEAN':
$attributes['TYPE'] = 'INT';
break;
default:
break;
}
}
/**
* Field attribute AUTO_INCREMENT
*/
protected function _attributeAutoIncrement(array &$attributes, array &$field)
{
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true
&& stripos($field['type'], 'int') !== false) {
$field['type'] = 'INTEGER PRIMARY KEY';
$field['default'] = '';
$field['null'] = '';
$field['unique'] = '';
$field['auto_increment'] = ' AUTOINCREMENT';
$this->primaryKeys = [];
}
}
/**
* Foreign Key Drop
*
* @throws DatabaseException
*/
public function dropForeignKey(string $table, string $foreignName): bool
{
// If this version of SQLite doesn't support it, we're done here
if ($this->db->supportsForeignKeys() !== true) {
return true;
}
// Otherwise we have to copy the table and recreate
// without the foreign key being involved now
$sqlTable = new Table($this->db, $this);
return $sqlTable->fromTable($this->db->DBPrefix . $table)
->dropForeignKey($foreignName)
->run();
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
/**
* Prepared query for SQLite3
*/
class PreparedQuery extends BasePreparedQuery
{
/**
* The SQLite3Result resource, or false.
*
* @var bool|Result
*/
protected $result;
/**
* Prepares the query against the database, and saves the connection
* info necessary to execute the query later.
*
* NOTE: This version is based on SQL code. Child classes should
* override this method.
*
* @param array $options Passed to the connection's prepare statement.
* Unused in the MySQLi driver.
*
* @return $this
*/
public function _prepare(string $sql, array $options = [])
{
if (! ($this->statement = $this->db->connID->prepare($sql))) {
$this->errorCode = $this->db->connID->lastErrorCode();
$this->errorString = $this->db->connID->lastErrorMsg();
}
return $this;
}
/**
* Takes a new set of data and runs it against the currently
* prepared query. Upon success, will return a Results object.
*
* @todo finalize()
*/
public function _execute(array $data): bool
{
if (! isset($this->statement)) {
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
}
foreach ($data as $key => $item) {
// Determine the type string
if (is_int($item)) {
$bindType = SQLITE3_INTEGER;
} elseif (is_float($item)) {
$bindType = SQLITE3_FLOAT;
} else {
$bindType = SQLITE3_TEXT;
}
// Bind it
$this->statement->bindValue($key + 1, $item, $bindType);
}
$this->result = $this->statement->execute();
return $this->result !== false;
}
/**
* Returns the result object for the prepared query.
*
* @return mixed
*/
public function _getResult()
{
return $this->result;
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use Closure;
use CodeIgniter\Database\BaseResult;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Entity\Entity;
use stdClass;
/**
* Result for SQLite3
*/
class Result extends BaseResult
{
/**
* Gets the number of fields in the result set.
*/
public function getFieldCount(): int
{
return $this->resultID->numColumns();
}
/**
* Generates an array of column names in the result set.
*/
public function getFieldNames(): array
{
$fieldNames = [];
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
$fieldNames[] = $this->resultID->columnName($i);
}
return $fieldNames;
}
/**
* Generates an array of objects representing field meta-data.
*/
public function getFieldData(): array
{
static $dataTypes = [
SQLITE3_INTEGER => 'integer',
SQLITE3_FLOAT => 'float',
SQLITE3_TEXT => 'text',
SQLITE3_BLOB => 'blob',
SQLITE3_NULL => 'null',
];
$retVal = [];
$this->resultID->fetchArray(SQLITE3_NUM);
for ($i = 0, $c = $this->getFieldCount(); $i < $c; $i++) {
$retVal[$i] = new stdClass();
$retVal[$i]->name = $this->resultID->columnName($i);
$type = $this->resultID->columnType($i);
$retVal[$i]->type = $type;
$retVal[$i]->type_name = $dataTypes[$type] ?? null;
$retVal[$i]->max_length = null;
$retVal[$i]->length = null;
}
$this->resultID->reset();
return $retVal;
}
/**
* Frees the current result.
*/
public function freeResult()
{
if (is_object($this->resultID)) {
$this->resultID->finalize();
$this->resultID = false;
}
}
/**
* Moves the internal pointer to the desired offset. This is called
* internally before fetching results to make sure the result set
* starts at zero.
*
* @throws DatabaseException
*
* @return mixed
*/
public function dataSeek(int $n = 0)
{
if ($n !== 0) {
throw new DatabaseException('SQLite3 doesn\'t support seeking to other offset.');
}
return $this->resultID->reset();
}
/**
* Returns the result set as an array.
*
* Overridden by driver classes.
*
* @return mixed
*/
protected function fetchAssoc()
{
return $this->resultID->fetchArray(SQLITE3_ASSOC);
}
/**
* Returns the result set as an object.
*
* Overridden by child classes.
*
* @return bool|object
*/
protected function fetchObject(string $className = 'stdClass')
{
// No native support for fetching rows as objects
if (($row = $this->fetchAssoc()) === false) {
return false;
}
if ($className === 'stdClass') {
return (object) $row;
}
$classObj = new $className();
if (is_subclass_of($className, Entity::class)) {
return $classObj->setAttributes($row);
}
$classSet = Closure::bind(function ($key, $value) {
$this->{$key} = $value;
}, $classObj, $className);
foreach (array_keys($row) as $key) {
$classSet($key, $row[$key]);
}
return $classObj;
}
}

View File

@@ -0,0 +1,363 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\Exceptions\DataException;
/**
* Class Table
*
* Provides missing features for altering tables that are common
* in other supported databases, but are missing from SQLite.
* These are needed in order to support migrations during testing
* when another database is used as the primary engine, but
* SQLite in memory databases are used for faster test execution.
*/
class Table
{
/**
* All of the fields this table represents.
*
* @var array
*/
protected $fields = [];
/**
* All of the unique/primary keys in the table.
*
* @var array
*/
protected $keys = [];
/**
* All of the foreign keys in the table.
*
* @var array
*/
protected $foreignKeys = [];
/**
* The name of the table we're working with.
*
* @var string
*/
protected $tableName;
/**
* The name of the table, with database prefix
*
* @var string
*/
protected $prefixedTableName;
/**
* Database connection.
*
* @var Connection
*/
protected $db;
/**
* Handle to our forge.
*
* @var Forge
*/
protected $forge;
/**
* Table constructor.
*/
public function __construct(Connection $db, Forge $forge)
{
$this->db = $db;
$this->forge = $forge;
}
/**
* Reads an existing database table and
* collects all of the information needed to
* recreate this table.
*
* @return Table
*/
public function fromTable(string $table)
{
$this->prefixedTableName = $table;
$prefix = $this->db->DBPrefix;
if (! empty($prefix) && strpos($table, $prefix) === 0) {
$table = substr($table, strlen($prefix));
}
if (! $this->db->tableExists($this->prefixedTableName)) {
throw DataException::forTableNotFound($this->prefixedTableName);
}
$this->tableName = $table;
$this->fields = $this->formatFields($this->db->getFieldData($table));
$this->keys = array_merge($this->keys, $this->formatKeys($this->db->getIndexData($table)));
$this->foreignKeys = $this->db->getForeignKeyData($table);
return $this;
}
/**
* Called after `fromTable` and any actions, like `dropColumn`, etc,
* to finalize the action. It creates a temp table, creates the new
* table with modifications, and copies the data over to the new table.
* Resets the connection dataCache to be sure changes are collected.
*/
public function run(): bool
{
$this->db->query('PRAGMA foreign_keys = OFF');
$this->db->transStart();
$this->forge->renameTable($this->tableName, "temp_{$this->tableName}");
$this->forge->reset();
$this->createTable();
$this->copyData();
$this->forge->dropTable("temp_{$this->tableName}");
$success = $this->db->transComplete();
$this->db->query('PRAGMA foreign_keys = ON');
$this->db->resetDataCache();
return $success;
}
/**
* Drops columns from the table.
*
* @param array|string $columns
*
* @return Table
*/
public function dropColumn($columns)
{
if (is_string($columns)) {
$columns = explode(',', $columns);
}
foreach ($columns as $column) {
$column = trim($column);
if (isset($this->fields[$column])) {
unset($this->fields[$column]);
}
}
return $this;
}
/**
* Modifies a field, including changing data type,
* renaming, etc.
*
* @return Table
*/
public function modifyColumn(array $field)
{
$field = $field[0];
$oldName = $field['name'];
unset($field['name']);
$this->fields[$oldName] = $field;
return $this;
}
/**
* Drops a foreign key from this table so that
* it won't be recreated in the future.
*
* @return Table
*/
public function dropForeignKey(string $column)
{
if (empty($this->foreignKeys)) {
return $this;
}
for ($i = 0; $i < count($this->foreignKeys); $i++) {
if ($this->foreignKeys[$i]->table_name !== $this->tableName) {
continue;
}
// The column name should be the first thing in the constraint name
if (strpos($this->foreignKeys[$i]->constraint_name, $column) !== 0) {
continue;
}
unset($this->foreignKeys[$i]);
}
return $this;
}
/**
* Creates the new table based on our current fields.
*
* @return mixed
*/
protected function createTable()
{
$this->dropIndexes();
$this->db->resetDataCache();
// Handle any modified columns.
$fields = [];
foreach ($this->fields as $name => $field) {
if (isset($field['new_name'])) {
$fields[$field['new_name']] = $field;
continue;
}
$fields[$name] = $field;
}
$this->forge->addField($fields);
// Unique/Index keys
if (is_array($this->keys)) {
foreach ($this->keys as $key) {
switch ($key['type']) {
case 'primary':
$this->forge->addPrimaryKey($key['fields']);
break;
case 'unique':
$this->forge->addUniqueKey($key['fields']);
break;
case 'index':
$this->forge->addKey($key['fields']);
break;
}
}
}
return $this->forge->createTable($this->tableName);
}
/**
* Copies data from our old table to the new one,
* taking care map data correctly based on any columns
* that have been renamed.
*/
protected function copyData()
{
$exFields = [];
$newFields = [];
foreach ($this->fields as $name => $details) {
$newFields[] = $details['new_name'] ?? $name;
$exFields[] = $name;
}
$exFields = implode(', ', $exFields);
$newFields = implode(', ', $newFields);
$this->db->query("INSERT INTO {$this->prefixedTableName}({$newFields}) SELECT {$exFields} FROM {$this->db->DBPrefix}temp_{$this->tableName}");
}
/**
* Converts fields retrieved from the database to
* the format needed for creating fields with Forge.
*
* @param array|bool $fields
*
* @return mixed
*/
protected function formatFields($fields)
{
if (! is_array($fields)) {
return $fields;
}
$return = [];
foreach ($fields as $field) {
$return[$field->name] = [
'type' => $field->type,
'default' => $field->default,
'null' => $field->nullable,
];
if ($field->primary_key) {
$this->keys[$field->name] = [
'fields' => [$field->name],
'type' => 'primary',
];
}
}
return $return;
}
/**
* Converts keys retrieved from the database to
* the format needed to create later.
*
* @param mixed $keys
*
* @return mixed
*/
protected function formatKeys($keys)
{
if (! is_array($keys)) {
return $keys;
}
$return = [];
foreach ($keys as $name => $key) {
$return[$name] = [
'fields' => $key->fields,
'type' => 'index',
];
}
return $return;
}
/**
* Attempts to drop all indexes and constraints
* from the database for this table.
*/
protected function dropIndexes()
{
if (! is_array($this->keys) || $this->keys === []) {
return;
}
foreach ($this->keys as $name => $key) {
if ($key['type'] === 'primary' || $key['type'] === 'unique') {
continue;
}
$this->db->query("DROP INDEX IF EXISTS '{$name}'");
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseUtils;
use CodeIgniter\Database\Exceptions\DatabaseException;
/**
* Utils for SQLite3
*/
class Utils extends BaseUtils
{
/**
* OPTIMIZE TABLE statement
*
* @var string
*/
protected $optimizeTable = 'REINDEX %s';
/**
* Platform dependent version of the backup function.
*
* @return mixed
*/
public function _backup(?array $prefs = null)
{
throw new DatabaseException('Unsupported feature of the database platform you are using.');
}
}