diff --git a/.travis.yml b/.travis.yml
index e043fbb41fbc..d07890f543ab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,6 +21,7 @@ dist: precise
env:
- DB=mysqli
- DB=postgres
+ - DB=sqlite
services:
- memcached
diff --git a/php_errors.log b/php_errors.log
new file mode 100644
index 000000000000..f246f51007b5
--- /dev/null
+++ b/php_errors.log
@@ -0,0 +1 @@
+[30-Jan-2018 23:57:36 America/Chicago] PHP Fatal error: Class 'z' not found in /Users/kilishan/WebSites/CodeIgniter4/tests/system/Validation/RulesTest.php on line 5
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 34d737b5a7b0..c3738f0a7c06 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -16,7 +16,10 @@
./tests/system
-
+ ./tests/system/Database
+
+
+ ./tests/system/Database
diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index c4b0847b3656..77809da67554 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -188,6 +188,21 @@ class BaseBuilder
*/
protected $binds = [];
+ /**
+ * Some databases, like SQLite, do not by default
+ * allow limiting of delete clauses.
+ *
+ * @var bool
+ */
+ protected $canLimitDeletes = true;
+
+ /**
+ * Some databases do not by default
+ * allow limit update queries with WHERE.
+ * @var bool
+ */
+ protected $canLimitWhereUpdates = true;
+
//--------------------------------------------------------------------
/**
@@ -1908,6 +1923,11 @@ public function update($set = null, $where = null, int $limit = null, $test = fa
if ( ! empty($limit))
{
+ if (! $this->canLimitWhereUpdates)
+ {
+ throw new DatabaseException('This driver does not allow LIMITs on UPDATE queries using WHERE.');
+ }
+
$this->limit($limit);
}
@@ -2292,6 +2312,11 @@ public function delete($where = '', $limit = null, $reset_data = true, $returnSQ
if ( ! empty($this->QBLimit))
{
+ if (! $this->canLimitDeletes)
+ {
+ throw new DatabaseException('SQLite3 does not allow LIMITs on DELETE queries.');
+ }
+
$sql = $this->_limit($sql);
}
diff --git a/system/Database/Forge.php b/system/Database/Forge.php
index 2b94a1c5d907..e33ad79a0f6b 100644
--- a/system/Database/Forge.php
+++ b/system/Database/Forge.php
@@ -59,6 +59,7 @@ class Forge
/**
* List of keys.
+ *
* @var array
*/
protected $keys = [];
@@ -76,7 +77,7 @@ class Forge
*/
protected $primaryKeys = [];
- /**
+ /**
* List of foreign keys.
*
* @var type
@@ -174,7 +175,7 @@ class Forge
*/
public function __construct(ConnectionInterface $db)
{
- $this->db = & $db;
+ $this->db = &$db;
}
//--------------------------------------------------------------------
@@ -210,7 +211,7 @@ public function createDatabase($db_name)
return false;
}
- elseif ( ! $this->db->query(sprintf($this->createDatabaseStr, $db_name, $this->db->charset, $this->db->DBCollat))
+ elseif (! $this->db->query(sprintf($this->createDatabaseStr, $db_name, $this->db->charset, $this->db->DBCollat))
)
{
if ($this->db->DBDebug)
@@ -221,7 +222,7 @@ public function createDatabase($db_name)
return false;
}
- if ( ! empty($this->db->dataCache['db_names']))
+ if (! empty($this->db->dataCache['db_names']))
{
$this->db->dataCache['db_names'][] = $db_name;
}
@@ -250,7 +251,7 @@ public function dropDatabase($db_name)
return false;
}
- elseif ( ! $this->db->query(sprintf($this->dropDatabaseStr, $db_name)))
+ elseif (! $this->db->query(sprintf($this->dropDatabaseStr, $db_name)))
{
if ($this->db->DBDebug)
{
@@ -260,7 +261,7 @@ public function dropDatabase($db_name)
return false;
}
- if ( ! empty($this->db->dataCache['db_names']))
+ if (! empty($this->db->dataCache['db_names']))
{
$key = array_search(strtolower($db_name), array_map('strtolower', $this->db->dataCache['db_names']), true);
if ($key !== false)
@@ -287,7 +288,7 @@ public function addKey($key, bool $primary = false, bool $unique = false)
{
if ($primary === true)
{
- foreach ((array) $key as $one)
+ foreach ((array)$key as $one)
{
$this->primaryKeys[] = $one;
}
@@ -350,8 +351,8 @@ public function addField($field)
{
$this->addField([
'id' => [
- 'type' => 'INT',
- 'constraint' => 9,
+ 'type' => 'INT',
+ 'constraint' => 9,
'auto_increment' => true,
],
]);
@@ -387,25 +388,25 @@ public function addField($field)
* @param bool $onUpdate
* @param bool $onDelete
*
- * @return \CodeIgniter\Database\Forge
+ * @return \CodeIgniter\Database\Forge
+ * @throws \CodeIgniter\Database\Exceptions\DatabaseException
*/
- public function addForeignKey($fieldName= '',$tableName = '', $tableField = '', $onUpdate = false, $onDelete = false)
+ public function addForeignKey($fieldName = '', $tableName = '', $tableField = '', $onUpdate = false, $onDelete = false)
{
+ if (! isset($this->fields[$fieldName]))
+ {
+ throw new DatabaseException(lang('Database.fieldNotExists', [$fieldName]));
+ }
- if( ! isset($this->fields[$fieldName]))
- {
- throw new \RuntimeException('Field "'.$fieldName.'" not exist');
- }
-
- $this->foreignKeys[$fieldName] = [
- 'table' => $tableName,
- 'field' => $tableField,
- 'onDelete' => strtoupper($onDelete),
- 'onUpdate' => strtoupper($onUpdate)
- ];
+ $this->foreignKeys[$fieldName] = [
+ 'table' => $tableName,
+ 'field' => $tableField,
+ 'onDelete' => strtoupper($onDelete),
+ 'onUpdate' => strtoupper($onUpdate),
+ ];
- return $this;
+ return $this;
}
//--------------------------------------------------------------------
@@ -421,8 +422,8 @@ public function addForeignKey($fieldName= '',$tableName = '', $tableField = '',
*/
public function dropForeignKey($table, $foreign_name)
{
-
- $sql = sprintf($this->dropConstraintStr,$this->db->escapeIdentifiers($this->db->DBPrefix.$table),$this->db->escapeIdentifiers($this->db->DBPrefix.$foreign_name));
+ $sql = sprintf($this->dropConstraintStr, $this->db->escapeIdentifiers($this->db->DBPrefix.$table),
+ $this->db->escapeIdentifiers($this->db->DBPrefix.$foreign_name));
if ($sql === false)
{
@@ -484,9 +485,9 @@ public function createTable($table, $if_not_exists = false, array $attributes =
empty($this->db->dataCache['table_names']) || $this->db->dataCache['table_names'][] = $table;
// Most databases don't support creating indexes from within the CREATE TABLE statement
- if ( ! empty($this->keys))
+ if (! empty($this->keys))
{
- for ($i = 0, $sqls = $this->_processIndexes($table), $c = count($sqls); $i < $c; $i ++ )
+ for ($i = 0, $sqls = $this->_processIndexes($table), $c = count($sqls); $i < $c; $i++)
{
$this->db->query($sqls[$i]);
}
@@ -522,18 +523,20 @@ protected function _createTable($table, $if_not_exists, $attributes)
$if_not_exists = false;
}
- $sql = ($if_not_exists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table)) : 'CREATE TABLE';
+ $sql = ($if_not_exists) ? sprintf($this->createTableIfStr, $this->db->escapeIdentifiers($table))
+ : 'CREATE TABLE';
$columns = $this->_processFields(true);
- for ($i = 0, $c = count($columns); $i < $c; $i ++ )
+ for ($i = 0, $c = count($columns); $i < $c; $i++)
{
- $columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t" . $columns[$i]['_literal'] : "\n\t" . $this->_processColumn($columns[$i]);
+ $columns[$i] = ($columns[$i]['_literal'] !== false) ? "\n\t".$columns[$i]['_literal']
+ : "\n\t".$this->_processColumn($columns[$i]);
}
- $columns = implode(',', $columns);
+ $columns = implode(',', $columns);
- $columns .= $this->_processPrimaryKeys($table);
- $columns .= $this->_processForeignKeys($table);
+ $columns .= $this->_processPrimaryKeys($table);
+ $columns .= $this->_processForeignKeys($table);
// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
if ($this->createTableKeys === true)
@@ -542,7 +545,8 @@ protected function _createTable($table, $if_not_exists, $attributes)
}
// createTableStr will usually have the following format: "%s %s (%s\n)"
- $sql = sprintf($this->createTableStr . '%s', $sql, $this->db->escapeIdentifiers($table), $columns, $this->_createTableAttributes($attributes));
+ $sql = sprintf($this->createTableStr.'%s', $sql, $this->db->escapeIdentifiers($table), $columns,
+ $this->_createTableAttributes($attributes));
return $sql;
}
@@ -578,7 +582,7 @@ protected function _createTableAttributes($attributes)
*
* @param string $table_name Table name
* @param bool $if_exists Whether to add an IF EXISTS condition
- * @param bool $cascade Whether to add an CASCADE condition
+ * @param bool $cascade Whether to add an CASCADE condition
*
* @return mixed
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
@@ -602,7 +606,7 @@ public function dropTable($table_name, $if_exists = false, $cascade = false)
$table_name = substr($table_name, strlen($this->db->DBPrefix));
}
- if (($query = $this->_dropTable($this->db->DBPrefix . $table_name, $if_exists, $cascade)) === true)
+ if (($query = $this->_dropTable($this->db->DBPrefix.$table_name, $if_exists, $cascade)) === true)
{
return true;
}
@@ -612,7 +616,8 @@ public function dropTable($table_name, $if_exists = false, $cascade = false)
// Update table list cache
if ($query && ! empty($this->db->dataCache['table_names']))
{
- $key = array_search(strtolower($this->db->DBPrefix . $table_name), array_map('strtolower', $this->db->dataCache['table_names']), true);
+ $key = array_search(strtolower($this->db->DBPrefix.$table_name),
+ array_map('strtolower', $this->db->dataCache['table_names']), true);
if ($key !== false)
{
unset($this->db->dataCache['table_names'][$key]);
@@ -631,7 +636,7 @@ public function dropTable($table_name, $if_exists = false, $cascade = false)
*
* @param string $table Table name
* @param bool $if_exists Whether to add an IF EXISTS condition
- * @param bool $cascade Whether to add an CASCADE condition
+ * @param bool $cascade Whether to add an CASCADE condition
*
* @return string
*/
@@ -643,7 +648,7 @@ protected function _dropTable($table, $if_exists, $cascade)
{
if ($this->dropTableIfStr === false)
{
- if ( ! $this->db->tableExists($table))
+ if (! $this->db->tableExists($table))
{
return true;
}
@@ -654,7 +659,7 @@ protected function _dropTable($table, $if_exists, $cascade)
}
}
- $sql = $sql . ' ' . $this->db->escapeIdentifiers($table);
+ $sql = $sql.' '.$this->db->escapeIdentifiers($table);
return $sql;
}
@@ -686,15 +691,18 @@ public function renameTable($table_name, $new_table_name)
return false;
}
- $result = $this->db->query(sprintf($this->renameTableStr, $this->db->escapeIdentifiers($this->db->DBPrefix . $table_name), $this->db->escapeIdentifiers($this->db->DBPrefix . $new_table_name))
+ $result = $this->db->query(sprintf($this->renameTableStr,
+ $this->db->escapeIdentifiers($this->db->DBPrefix.$table_name),
+ $this->db->escapeIdentifiers($this->db->DBPrefix.$new_table_name))
);
if ($result && ! empty($this->db->dataCache['table_names']))
{
- $key = array_search(strtolower($this->db->DBPrefix . $table_name), array_map('strtolower', $this->db->dataCache['table_names']), true);
+ $key = array_search(strtolower($this->db->DBPrefix.$table_name),
+ array_map('strtolower', $this->db->dataCache['table_names']), true);
if ($key !== false)
{
- $this->db->dataCache['table_names'][$key] = $this->db->DBPrefix . $new_table_name;
+ $this->db->dataCache['table_names'][$key] = $this->db->DBPrefix.$new_table_name;
}
}
@@ -706,11 +714,11 @@ public function renameTable($table_name, $new_table_name)
/**
* Column Add
*
- * @param string $table Table name
- * @param array $field Column definition
+ * @param string $table Table name
+ * @param array $field Column definition
*
* @return bool
- * @throws \CodeIgniter\Database\Exceptions\DatabaseException
+ * @throws \CodeIgniter\Database\Exceptions\DatabaseException
*/
public function addColumn($table, $field)
{
@@ -722,7 +730,7 @@ public function addColumn($table, $field)
$this->addField([$k => $field[$k]]);
}
- $sqls = $this->_alterTable('ADD', $this->db->DBPrefix . $table, $this->_processFields());
+ $sqls = $this->_alterTable('ADD', $this->db->DBPrefix.$table, $this->_processFields());
$this->_reset();
if ($sqls === false)
{
@@ -734,7 +742,7 @@ public function addColumn($table, $field)
return false;
}
- for ($i = 0, $c = count($sqls); $i < $c; $i ++ )
+ for ($i = 0, $c = count($sqls); $i < $c; $i++)
{
if ($this->db->query($sqls[$i]) === false)
{
@@ -758,7 +766,7 @@ public function addColumn($table, $field)
*/
public function dropColumn($table, $column_name)
{
- $sql = $this->_alterTable('DROP', $this->db->DBPrefix . $table, $column_name);
+ $sql = $this->_alterTable('DROP', $this->db->DBPrefix.$table, $column_name);
if ($sql === false)
{
if ($this->db->DBDebug)
@@ -798,7 +806,7 @@ public function modifyColumn($table, $field)
throw new \RuntimeException('Field information is required');
}
- $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix . $table, $this->_processFields());
+ $sqls = $this->_alterTable('CHANGE', $this->db->DBPrefix.$table, $this->_processFields());
$this->_reset();
if ($sqls === false)
{
@@ -810,7 +818,7 @@ public function modifyColumn($table, $field)
return false;
}
- for ($i = 0, $c = count($sqls); $i < $c; $i ++ )
+ for ($i = 0, $c = count($sqls); $i < $c; $i++)
{
if ($this->db->query($sqls[$i]) === false)
{
@@ -834,21 +842,21 @@ public function modifyColumn($table, $field)
*/
protected function _alterTable($alter_type, $table, $field)
{
- $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table) . ' ';
+ $sql = 'ALTER TABLE '.$this->db->escapeIdentifiers($table).' ';
// DROP has everything it needs now.
if ($alter_type === 'DROP')
{
- return $sql . 'DROP COLUMN ' . $this->db->escapeIdentifiers($field);
+ return $sql.'DROP COLUMN '.$this->db->escapeIdentifiers($field);
}
- $sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type . ' COLUMN ';
+ $sql .= ($alter_type === 'ADD') ? 'ADD ' : $alter_type.' COLUMN ';
$sqls = [];
- for ($i = 0, $c = count($field); $i < $c; $i ++ )
+ for ($i = 0, $c = count($field); $i < $c; $i++)
{
$sqls[] = $sql
- . ($field[$i]['_literal'] !== false ? $field[$i]['_literal'] : $this->_processColumn($field[$i]));
+ .($field[$i]['_literal'] !== false ? $field[$i]['_literal'] : $this->_processColumn($field[$i]));
}
return $sqls;
@@ -885,16 +893,16 @@ protected function _processFields($create_table = false)
isset($attributes['TYPE']) && $this->_attributeType($attributes);
$field = [
- 'name' => $key,
- 'new_name' => $attributes['NAME'] ?? null,
- 'type' => $attributes['TYPE'] ?? null,
- 'length' => '',
- 'unsigned' => '',
- 'null' => '',
- 'unique' => '',
- 'default' => '',
+ 'name' => $key,
+ 'new_name' => isset($attributes['NAME']) ? $attributes['NAME'] : null,
+ 'type' => isset($attributes['TYPE']) ? $attributes['TYPE'] : null,
+ 'length' => '',
+ 'unsigned' => '',
+ 'null' => '',
+ 'unique' => '',
+ 'default' => '',
'auto_increment' => '',
- '_literal' => false,
+ '_literal' => false,
];
isset($attributes['TYPE']) && $this->_attributeUnsigned($attributes, $field);
@@ -907,7 +915,7 @@ protected function _processFields($create_table = false)
}
elseif (isset($attributes['FIRST']))
{
- $field['first'] = (bool) $attributes['FIRST'];
+ $field['first'] = (bool)$attributes['FIRST'];
}
}
@@ -917,7 +925,7 @@ protected function _processFields($create_table = false)
{
if ($attributes['NULL'] === true)
{
- $field['null'] = empty($this->null) ? '' : ' ' . $this->null;
+ $field['null'] = empty($this->null) ? '' : ' '.$this->null;
}
else
{
@@ -944,10 +952,12 @@ protected function _processFields($create_table = false)
case 'ENUM':
case 'SET':
$attributes['CONSTRAINT'] = $this->db->escape($attributes['CONSTRAINT']);
- $field['length'] = is_array($attributes['CONSTRAINT']) ? "('" . implode("','", $attributes['CONSTRAINT']) . "')" : '(' . $attributes['CONSTRAINT'] . ')';
+ $field['length'] = is_array($attributes['CONSTRAINT']) ? "('".implode("','",
+ $attributes['CONSTRAINT'])."')" : '('.$attributes['CONSTRAINT'].')';
break;
default:
- $field['length'] = is_array($attributes['CONSTRAINT']) ? '(' . implode(',', $attributes['CONSTRAINT']) . ')' : '(' . $attributes['CONSTRAINT'] . ')';
+ $field['length'] = is_array($attributes['CONSTRAINT']) ? '('.implode(',',
+ $attributes['CONSTRAINT']).')' : '('.$attributes['CONSTRAINT'].')';
break;
}
}
@@ -970,12 +980,12 @@ protected function _processFields($create_table = false)
protected function _processColumn($field)
{
return $this->db->escapeIdentifiers($field['name'])
- . ' ' . $field['type'] . $field['length']
- . $field['unsigned']
- . $field['default']
- . $field['null']
- . $field['auto_increment']
- . $field['unique'];
+ .' '.$field['type'].$field['length']
+ .$field['unsigned']
+ .$field['default']
+ .$field['null']
+ .$field['auto_increment']
+ .$field['unique'];
}
//--------------------------------------------------------------------
@@ -1068,15 +1078,15 @@ protected function _attributeDefault(&$attributes, &$field)
{
if ($attributes['DEFAULT'] === null)
{
- $field['default'] = empty($this->null) ? '' : $this->default . $this->null;
+ $field['default'] = empty($this->null) ? '' : $this->default.$this->null;
// Override the NULL attribute if that's our default
$attributes['NULL'] = true;
- $field['null'] = empty($this->null) ? '' : ' ' . $this->null;
+ $field['null'] = empty($this->null) ? '' : ' '.$this->null;
}
else
{
- $field['default'] = $this->default . $this->db->escape($attributes['DEFAULT']);
+ $field['default'] = $this->default.$this->db->escape($attributes['DEFAULT']);
}
}
}
@@ -1093,7 +1103,7 @@ protected function _attributeDefault(&$attributes, &$field)
*/
protected function _attributeUnique(&$attributes, &$field)
{
- if ( ! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === true)
+ if (! empty($attributes['UNIQUE']) && $attributes['UNIQUE'] === true)
{
$field['unique'] = ' UNIQUE';
}
@@ -1111,8 +1121,8 @@ protected function _attributeUnique(&$attributes, &$field)
*/
protected function _attributeAutoIncrement(&$attributes, &$field)
{
- if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true &&
- stripos($field['type'], 'int') !== false
+ if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true
+ && stripos($field['type'], 'int') !== false
)
{
$field['auto_increment'] = ' AUTO_INCREMENT';
@@ -1132,9 +1142,9 @@ protected function _processPrimaryKeys($table)
{
$sql = '';
- for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i ++ )
+ for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i++)
{
- if ( ! isset($this->fields[$this->primaryKeys[$i]]))
+ if (! isset($this->fields[$this->primaryKeys[$i]]))
{
unset($this->primaryKeys[$i]);
}
@@ -1142,8 +1152,8 @@ protected function _processPrimaryKeys($table)
if (count($this->primaryKeys) > 0)
{
- $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers('pk_' . $table)
- . ' PRIMARY KEY(' . implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)) . ')';
+ $sql .= ",\n\tCONSTRAINT ".$this->db->escapeIdentifiers('pk_'.$table)
+ .' PRIMARY KEY('.implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)).')';
}
return $sql;
@@ -1162,13 +1172,13 @@ protected function _processIndexes($table)
{
$sqls = [];
- for ($i = 0, $c = count($this->keys); $i < $c; $i ++ )
+ for ($i = 0, $c = count($this->keys); $i < $c; $i++)
{
- $this->keys[$i] = (array) $this->keys[$i];
+ $this->keys[$i] = (array)$this->keys[$i];
- for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2 ++ )
+ for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
{
- if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+ if (! isset($this->fields[$this->keys[$i][$i2]]))
{
unset($this->keys[$i][$i2]);
}
@@ -1180,22 +1190,23 @@ protected function _processIndexes($table)
if (in_array($i, $this->uniqueKeys))
{
- $sqls[] = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table)
- . ' ADD CONSTRAINT ' . $this->db->escapeIdentifiers($table . '_' . implode('_', $this->keys[$i]))
- . ' UNIQUE (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ');';
+ $sqls[] = 'ALTER TABLE '.$this->db->escapeIdentifiers($table)
+ .' ADD CONSTRAINT '.$this->db->escapeIdentifiers($table.'_'.implode('_', $this->keys[$i]))
+ .' UNIQUE ('.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])) . ');';
+ $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;
}
//--------------------------------------------------------------------
- /**
+
+ /**
* Process foreign keys
*
* @param string $table Table name
@@ -1203,30 +1214,31 @@ protected function _processIndexes($table)
* @return string
*/
protected function _processForeignKeys($table) {
- $sql = '';
+ $sql = '';
- $allowActions = array('CASCADE','SET NULL','NO ACTION','RESTRICT','SET DEFAULT');
+ $allowActions = array('CASCADE','SET NULL','NO ACTION','RESTRICT','SET DEFAULT');
- if (count($this->foreignKeys) > 0){
- foreach ($this->foreignKeys as $field => $fkey) {
- $name_index = $table.'_'.$field.'_foreign';
+ if (count($this->foreignKeys) > 0){
+ foreach ($this->foreignKeys as $field => $fkey) {
+ $name_index = $table.'_'.$field.'_foreign';
- $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($name_index)
- . ' FOREIGN KEY(' . $this->db->escapeIdentifiers($field) . ') REFERENCES '.$this->db->escapeIdentifiers($this->db->DBPrefix.$fkey['table']).' ('.$this->db->escapeIdentifiers($fkey['field']).')';
+ $sql .= ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers($name_index)
+ . ' FOREIGN KEY(' . $this->db->escapeIdentifiers($field) . ') REFERENCES '.$this->db->escapeIdentifiers($this->db->DBPrefix.$fkey['table']).' ('.$this->db->escapeIdentifiers($fkey['field']).')';
- if($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions)){
- $sql .= " ON DELETE ".$fkey['onDelete'];
- }
-
- if($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions)){
- $sql .= " ON UPDATE ".$fkey['onUpdate'];
- }
+ if($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions)){
+ $sql .= " ON DELETE ".$fkey['onDelete'];
+ }
+ if($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions)){
+ $sql .= " ON UPDATE ".$fkey['onUpdate'];
}
- }
- return $sql;
+ }
}
+
+ return $sql;
+ }
+
//--------------------------------------------------------------------
/**
diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php
new file mode 100644
index 000000000000..1c6f4c2eed07
--- /dev/null
+++ b/system/Database/SQLite3/Builder.php
@@ -0,0 +1,107 @@
+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 mixed
+ */
+ 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 mixed
+ */
+ public function setDatabase(string $databaseName)
+ {
+ return false;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns a string containing the version of the database being used.
+ *
+ * @return mixed
+ */
+ public function getVersion()
+ {
+ 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($sql)
+ {
+ return $this->isWriteType($sql)
+ ? $this->connID->exec($sql)
+ : $this->connID->query($sql);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns the total number of rows affected by this query.
+ *
+ * @return mixed
+ */
+ 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 bool $prefixLimit
+ *
+ * @return string
+ */
+ protected function _listTables($prefixLimit = false): string
+ {
+ return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
+ .(($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($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('This feature is not available for the database you are using.');
+ }
+
+ 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 object with field data
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function _fieldData(string $table)
+ {
+
+ if (($query = $this->query('PRAGMA TABLE_INFO('.$this->protectIdentifiers($table, true, null,
+ false).')')) === false)
+ {
+ return false;
+ }
+ $query = $query->getResultObject();
+ if (empty($query))
+ {
+ return false;
+ }
+ $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) ? (int)$query[$i]->pk : 0;
+ }
+
+ return $retval;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns an object with index data
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function _indexData(string $table)
+ {
+ // 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)
+ {
+ return false;
+ }
+ $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)
+ {
+ return false;
+ }
+ $fields = $fields->getResultObject();
+
+ foreach ($fields as $field)
+ {
+ $obj->fields[] = $field->name;
+ }
+
+ $retval[] = $obj;
+ }
+
+ return $retval;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * 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 int
+ */
+ public function insertID(): int
+ {
+ return $this->connID->lastInsertRowID();
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Begin Transaction
+ *
+ * @return bool
+ */
+ protected function _transBegin(): bool
+ {
+ return $this->connID->exec('BEGIN TRANSACTION');
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Commit Transaction
+ *
+ * @return bool
+ */
+ protected function _transCommit(): bool
+ {
+ return $this->connID->exec('END TRANSACTION');
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Rollback Transaction
+ *
+ * @return bool
+ */
+ protected function _transRollback(): bool
+ {
+ return $this->connID->exec('ROLLBACK');
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Determines if the statement is a write-type query or not.
+ *
+ * @return bool
+ */
+ 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 bool
+ */
+ protected function supportsForeignKeys(): bool
+ {
+ $result = $this->simpleQuery("PRAGMA foreign_keys");
+
+ return (bool)$result;
+ }
+
+ /**
+ * Returns an object with Foreign key data
+ *
+ * @param string $table
+ * @return array
+ */
+ public function _foreignKeyData(string $table)
+ {
+ 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;
+
+ $retval[] = $obj;
+ }
+ }
+
+ return $retval;
+ }
+
+ //--------------------------------------------------------------------
+}
diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php
new file mode 100644
index 000000000000..8de90fc74e3e
--- /dev/null
+++ b/system/Database/SQLite3/Forge.php
@@ -0,0 +1,295 @@
+db->getVersion(), '3.3', '<'))
+ {
+ $this->createTableIfStr = false;
+ $this->dropTableIfStr = false;
+ }
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Create database
+ *
+ * @param string $db_name
+ *
+ * @return bool
+ */
+ public function createDatabase($db_name): 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
+ *
+ * @param string $db_name
+ *
+ * @return bool
+ * @throws \CodeIgniter\DatabaseException
+ */
+ public function dropDatabase($db_name): bool
+ {
+ // In SQLite, a database is dropped when we delete a file
+ if (! file_exists($db_name))
+ {
+ 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($db_name))
+ {
+ 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($db_name), array_map('strtolower', $this->db->dataCache['db_names']), true);
+ if ($key !== false)
+ {
+ unset($this->db->dataCache['db_names'][$key]);
+ }
+ }
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * ALTER TABLE
+ *
+ * @todo implement drop_column(), modify_column()
+ *
+ * @param string $alter_type ALTER type
+ * @param string $table Table name
+ * @param mixed $field Column definition
+ *
+ * @return string|array
+ */
+ protected function _alterTable($alter_type, $table, $field)
+ {
+ if (in_array($alter_type, ['DROP', 'CHANGE'], true))
+ {
+ return false;
+ }
+
+ return parent::_alterTable($alter_type, $table, $field);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Process column
+ *
+ * @param array $field
+ *
+ * @return string
+ */
+ protected function _processColumn($field)
+ {
+ return $this->db->escapeIdentifiers($field['name'])
+ .' '.$field['type']
+ .$field['auto_increment']
+ .$field['null']
+ .$field['unique']
+ .$field['default'];
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Process indexes
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ protected function _processIndexes($table)
+ {
+ $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))
+ {
+ $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.
+ *
+ * @param array &$attributes
+ *
+ * @return void
+ */
+ protected function _attributeType(&$attributes)
+ {
+ switch (strtoupper($attributes['TYPE']))
+ {
+ case 'ENUM':
+ case 'SET':
+ $attributes['TYPE'] = 'TEXT';
+
+ return;
+ default:
+ return;
+ }
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Field attribute AUTO_INCREMENT
+ *
+ * @param array &$attributes
+ * @param array &$field
+ *
+ * @return void
+ */
+ protected function _attributeAutoIncrement(&$attributes, &$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
+ *
+ * @param string $table Table name
+ * @param string $foreign_name Foreign name
+ *
+ * @return bool
+ * @throws \CodeIgniter\Database\Exceptions\DatabaseException
+ */
+ public function dropForeignKey($table, $foreign_name)
+ {
+ throw new DatabaseException(lang('Database.dropForeignKeyUnsupported'));
+ }
+
+ //--------------------------------------------------------------------
+
+}
diff --git a/system/Database/SQLite3/PreparedQuery.php b/system/Database/SQLite3/PreparedQuery.php
new file mode 100644
index 000000000000..af33a7d4d0f7
--- /dev/null
+++ b/system/Database/SQLite3/PreparedQuery.php
@@ -0,0 +1,134 @@
+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()
+ *
+ * @param array $data
+ *
+ * @return bool
+ */
+ public function _execute($data)
+ {
+ if (is_null($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_integer($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;
+ }
+
+ //--------------------------------------------------------------------
+
+}
diff --git a/system/Database/SQLite3/Result.php b/system/Database/SQLite3/Result.php
new file mode 100644
index 000000000000..6afa78b676d4
--- /dev/null
+++ b/system/Database/SQLite3/Result.php
@@ -0,0 +1,196 @@
+resultID->numColumns();
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Generates an array of column names in the result set.
+ *
+ * @return array
+ */
+ 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.
+ *
+ * @return array
+ */
+ public function getFieldData(): array
+ {
+ static $data_types = [
+ SQLITE3_INTEGER => 'integer',
+ SQLITE3_FLOAT => 'float',
+ SQLITE3_TEXT => 'text',
+ SQLITE3_BLOB => 'blob',
+ SQLITE3_NULL => 'null'
+ ];
+
+ $retval = [];
+
+ 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 = isset($data_types[$type]) ? $data_types[$type] : $type;
+ $retval[$i]->max_length = NULL;
+ }
+
+ return $retval;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Frees the current result.
+ *
+ * @return mixed
+ */
+ 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.
+ *
+ * @param int $n
+ *
+ * @return mixed
+ * @throws \CodeIgniter\DatabaseException
+ */
+ public function dataSeek($n = 0)
+ {
+ if ($n != 0)
+ {
+ if ($this->db->DBDebug) {
+ throw new DatabaseException('SQLite3 doesn\'t support seeking to other offset.');
+ }
+ return false;
+ }
+ return $this->resultID->reset();
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns the result set as an array.
+ *
+ * Overridden by driver classes.
+ *
+ * @return array
+ */
+ protected function fetchAssoc()
+ {
+ return $this->resultID->fetchArray(SQLITE3_ASSOC);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns the result set as an object.
+ *
+ * Overridden by child classes.
+ *
+ * @param string $className
+ *
+ * @return object
+ */
+ protected function fetchObject($className = 'stdClass')
+ {
+ // No native support for fetching rows as objects
+ if (($row = $this->fetchAssoc()) === FALSE)
+ {
+ return FALSE;
+ }
+ elseif ($className === 'stdClass')
+ {
+ return (object) $row;
+ }
+
+ $classObj = new $className();
+ $classSet = \Closure::bind(function ($key, $value) {
+ $this->$key = $value;
+ }, $classObj, $className
+ );
+ foreach (array_keys($row) as $key)
+ {
+ $classSet($key, $row[$key]);
+ }
+ return $classObj;
+ }
+
+ //--------------------------------------------------------------------
+}
diff --git a/system/Database/SQLite3/Utils.php b/system/Database/SQLite3/Utils.php
new file mode 100644
index 000000000000..9df3b665ca47
--- /dev/null
+++ b/system/Database/SQLite3/Utils.php
@@ -0,0 +1,68 @@
+db->DBDriver == 'SQLite3' ? 'unique' : 'auto_increment';
+
// User Table
$this->forge->addField([
- 'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
+ 'id' => ['type' => 'INTEGER', 'constraint' => 3, $unique_or_auto => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 80,],
'email' => ['type' => 'VARCHAR', 'constraint' => 100],
'country' => ['type' => 'VARCHAR', 'constraint' => 40,],
@@ -17,7 +20,7 @@ public function up()
// Job Table
$this->forge->addField([
- 'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
+ 'id' => ['type' => 'INTEGER', 'constraint' => 3, $unique_or_auto => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 40],
'description' => ['type' => 'TEXT'],
'created_at' => ['type' => 'DATETIME', 'null' => true]
@@ -27,7 +30,7 @@ public function up()
// Misc Table
$this->forge->addField([
- 'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true ],
+ 'id' => ['type' => 'INTEGER', 'constraint' => 3, $unique_or_auto => true],
'key' => ['type' => 'VARCHAR', 'constraint' => 40],
'value' => ['type' => 'TEXT'],
]);
@@ -36,7 +39,7 @@ public function up()
// Empty Table
$this->forge->addField([
- 'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
+ 'id' => ['type' => 'INTEGER', 'constraint' => 3, $unique_or_auto => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 40,],
]);
$this->forge->addKey('id', true);
diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php
index 55e7112ad00b..bf289e0e9009 100644
--- a/tests/system/Database/Live/ForgeTest.php
+++ b/tests/system/Database/Live/ForgeTest.php
@@ -1,5 +1,6 @@
forge->dropTable('forge_test_table', true);
$this->forge->addField([
- 'id' => [
+ 'id' => [
'type' => 'INTEGER',
'constraint' => 11,
'unsigned' => false,
@@ -41,6 +42,11 @@ public function testCreateTable()
public function testCreateTableWithAttributes()
{
+ if ($this->db->DBDriver == 'SQLite3')
+ {
+ $this->markTestSkipped('SQLite3 does not support comments on tables or columns.');
+ }
+
$this->forge->dropTable('forge_test_attributes', true);
$this->forge->addField('id');
@@ -86,7 +92,7 @@ public function testAddFields()
]);
$this->forge->addKey('id', true);
- $this->forge->addKey(['username', 'active'], false, true);
+ $this->forge->addUniqueKey(['username', 'active']);
$create = $this->forge->createTable('forge_test_fields', true);
//Check Field names
@@ -96,7 +102,6 @@ public function testAddFields()
$this->assertContains('name', $fieldsNames);
$this->assertContains('active', $fieldsNames);
-
$fieldsData = $this->db->getFieldData('forge_test_fields');
$this->assertContains($fieldsData[0]->name, ['id', 'name', 'username', 'active']);
@@ -129,6 +134,13 @@ public function testAddFields()
$this->assertEquals($fieldsData[1]->max_length, 255);
}
+ elseif ($this->db->DBDriver === 'SQLite3')
+ {
+ $this->assertEquals(strtolower($fieldsData[0]->type), 'integer');
+ $this->assertEquals(strtolower($fieldsData[1]->type), 'varchar');
+
+ $this->assertEquals($fieldsData[1]->default, null);
+ }
else
{
$this->assertTrue(false, "DB Driver not supported");
@@ -140,11 +152,14 @@ public function testAddFields()
public function testCompositeKey()
{
+ // SQLite3 uses auto increment different
+ $unique_or_auto = $this->db->DBDriver == 'SQLite3' ? 'unique' : 'auto_increment';
+
$this->forge->addField([
'id' => [
- 'type' => 'INTEGER',
- 'constraint' => 3,
- 'auto_increment' => true,
+ 'type' => 'INTEGER',
+ 'constraint' => 3,
+ $unique_or_auto => true,
],
'code' => [
'type' => 'VARCHAR',
@@ -196,7 +211,6 @@ public function testCompositeKey()
public function testForeignKey()
{
-
$attributes = [];
if ($this->db->DBDriver == 'MySQLi')
@@ -238,8 +252,14 @@ public function testForeignKey()
$foreignKeyData = $this->db->getForeignKeyData('forge_test_invoices');
- $this->assertEquals($foreignKeyData[0]->constraint_name,
- $this->db->DBPrefix.'forge_test_invoices_users_id_foreign');
+ if ($this->db->DBDriver == 'SQLite3')
+ {
+ $this->assertEquals($foreignKeyData[0]->constraint_name, 'users_id to db_forge_test_users.id');
+ }
+ else
+ {
+ $this->assertEquals($foreignKeyData[0]->constraint_name,$this->db->DBPrefix.'forge_test_invoices_users_id_foreign');
+ }
$this->assertEquals($foreignKeyData[0]->table_name, $this->db->DBPrefix.'forge_test_invoices');
$this->assertEquals($foreignKeyData[0]->foreign_table_name, $this->db->DBPrefix.'forge_test_users');
@@ -257,6 +277,10 @@ public function testDropForeignKey()
{
$attributes = ['ENGINE' => 'InnoDB'];
}
+ if ($this->db->DBDriver == 'SQLite3')
+ {
+ $this->expectException(DatabaseException::class);
+ }
$this->forge->addField([
'id' => [
diff --git a/tests/system/Validation/RulesTest.php b/tests/system/Validation/RulesTest.php
index 62f871cee216..71fa51180d9e 100644
--- a/tests/system/Validation/RulesTest.php
+++ b/tests/system/Validation/RulesTest.php
@@ -6,7 +6,6 @@
class RulesTest extends \CIUnitTestCase
{
-
/**
* @var Validation
*/
diff --git a/tests/system/Validation/UniqueRulesTest.php b/tests/system/Validation/UniqueRulesTest.php
new file mode 100644
index 000000000000..4d9d2d2ab60b
--- /dev/null
+++ b/tests/system/Validation/UniqueRulesTest.php
@@ -0,0 +1,115 @@
+ [
+ \CodeIgniter\Validation\Rules::class,
+ \CodeIgniter\Validation\FormatRules::class,
+ \CodeIgniter\Validation\FileRules::class,
+ \CodeIgniter\Validation\CreditCardRules::class,
+ \Tests\Support\Validation\TestRules::class,
+ ],
+ 'groupA' => [
+ 'foo' => 'required|min_length[5]',
+ ],
+ 'groupA_errors' => [
+ 'foo' => [
+ 'min_length' => 'Shame, shame. Too short.',
+ ],
+ ],
+ ];
+
+ //--------------------------------------------------------------------
+
+ public function setUp()
+ {
+ parent::setUp();
+ $this->validation = new Validation((object)$this->config, \Config\Services::renderer());
+ $this->validation->reset();
+
+ $_FILES = [];
+ }
+
+ /**
+ * @group DatabaseLive
+ */
+ public function testIsUniqueFalse()
+ {
+ $this->hasInDatabase('user', [
+ 'name' => 'Derek',
+ 'email' => 'derek@world.com',
+ 'country' => 'USA',
+ ]);
+
+ $data = [
+ 'email' => 'derek@world.com',
+ ];
+
+ $this->validation->setRules([
+ 'email' => 'is_unique[user.email]',
+ ]);
+
+ $this->assertFalse($this->validation->run($data));
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * @group DatabaseLive
+ */
+ public function testIsUniqueTrue()
+ {
+ $data = [
+ 'email' => 'derek@world.co.uk',
+ ];
+
+ $this->validation->setRules([
+ 'email' => 'is_unique[user.email]',
+ ]);
+
+ $this->assertTrue($this->validation->run($data));
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * @group DatabaseLive
+ */
+ public function testIsUniqueIgnoresParams()
+ {
+ $this->hasInDatabase('user', [
+ 'name' => 'Derek',
+ 'email' => 'derek@world.co.uk',
+ 'country' => 'GB',
+ ]);
+
+ $db = Database::connect();
+ $row = $db->table('user')
+ ->limit(1)
+ ->get()
+ ->getRow();
+
+ $data = [
+ 'email' => 'derek@world.co.uk',
+ ];
+
+ $this->validation->setRules([
+ 'email' => "is_unique[user.email,id,{$row->id}]",
+ ]);
+
+ $this->assertTrue($this->validation->run($data));
+ }
+
+ //--------------------------------------------------------------------
+}
diff --git a/tests/travis/Database.php b/tests/travis/Database.php
index 109cfdcb838a..397c608c93c1 100644
--- a/tests/travis/Database.php
+++ b/tests/travis/Database.php
@@ -42,6 +42,27 @@
'compress' => false,
'strictOn' => false,
'failover' => [],
- ]
+ ],
+
+ 'sqlite' => [
+ 'DSN' => '',
+ 'hostname' => 'localhost',
+ 'username' => '',
+ 'password' => '',
+ 'database' => ':memory:',
+ 'DBDriver' => 'SQLite3',
+ 'DBPrefix' => 'db_',
+ 'pConnect' => false,
+ 'DBDebug' => (ENVIRONMENT !== 'production'),
+ 'cacheOn' => false,
+ 'cacheDir' => '',
+ 'charset' => 'utf8',
+ 'DBCollat' => 'utf8_general_ci',
+ 'swapPre' => '',
+ 'encrypt' => false,
+ 'compress' => false,
+ 'strictOn' => false,
+ 'failover' => [],
+ ]
];
diff --git a/user_guide_src/source/database/forge.rst b/user_guide_src/source/database/forge.rst
index 35e465834e12..e72f462d9290 100644
--- a/user_guide_src/source/database/forge.rst
+++ b/user_guide_src/source/database/forge.rst
@@ -183,6 +183,9 @@ and unique keys with specific methods::
$forge->addPrimaryKey('blog_id');
// gives PRIMARY KEY `blog_id` (`blog_id`)
+Foreign Keys help to enforce relationships and actions across your tables. For tables that support Foreign Keys,
+you may add them directly in forge::
+
$forge->addUniqueKey(array('blog_id', 'uri'));
// gives UNIQUE KEY `blog_id_uri` (`blog_id`, `uri`)
@@ -195,9 +198,7 @@ Adding Foreign Keys
$forge->addForeignKey('users_id','users','id');
// gives CONSTRAINT `TABLENAME_users_foreign` FOREIGN KEY(`users_id`) REFERENCES `users`(`id`)
-You can specify the desired action for the "on delete" and "on update" properties of the constraint:
-
-::
+You can specify the desired action for the "on delete" and "on update" properties of the constraint::
$forge->addForeignKey('users_id','users','id','CASCADE','CASCADE');
// gives CONSTRAINT `TABLENAME_users_foreign` FOREIGN KEY(`users_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
@@ -254,6 +255,8 @@ Execute a DROP FOREIGN KEY.
// Produces: ALTER TABLE 'tablename' DROP FOREIGN KEY 'users_foreign'
$forge->dropForeignKey('tablename','users_foreign');
+.. note:: SQlite database driver does not support dropping of foreign keys.
+
Renaming a table
================
diff --git a/user_guide_src/source/intro/requirements.rst b/user_guide_src/source/intro/requirements.rst
index 5cf31ae50da2..68d3f12406c1 100644
--- a/user_guide_src/source/intro/requirements.rst
+++ b/user_guide_src/source/intro/requirements.rst
@@ -9,6 +9,7 @@ Currently supported databases are:
- MySQL (5.1+) via the *MySQLi* driver
- PostgreSQL via the *Postgre* driver
+ - SqLite3 via the *SQLite3* driver
Not all of the drivers have been converted/rewritten for CodeIgniter4.
The list below shows the outstanding ones.
@@ -17,7 +18,7 @@ The list below shows the outstanding ones.
- Oracle via the *oci8* and *pdo* drivers
- PostgreSQL via the *pdo* driver
- MS SQL via the *mssql*, *sqlsrv* (version 2005 and above only) and *pdo* drivers
- - SQLite via the *sqlite* (version 2), *sqlite3* (version 3) and *pdo* drivers
+ - SQLite via the *sqlite* (version 2) and *pdo* drivers
- CUBRID via the *cubrid* and *pdo* drivers
- Interbase/Firebird via the *ibase* and *pdo* drivers
- ODBC via the *odbc* and *pdo* drivers (you should know that ODBC is actually an abstraction layer)