Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add $cached param to BaseConnection::tableExists() #6364

Merged
merged 6 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions system/Database/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1405,10 +1405,41 @@ public function listTables(bool $constrainByPrefix = false)

/**
* Determine if a particular table exists
sclubricants marked this conversation as resolved.
Show resolved Hide resolved
*
* @param bool $cached Whether to use data cache
*/
public function tableExists(string $tableName): bool
public function tableExists(string $tableName, bool $cached = true): bool
kenjis marked this conversation as resolved.
Show resolved Hide resolved
{
return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables(), true);
if ($cached === true) {
return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables(), true);
}

if (false === ($sql = $this->_listTables(false, $tableName))) {
if ($this->DBDebug) {
throw new DatabaseException('This feature is not available for the database you are using.');
}

return false;
}

$tableExists = $this->query($sql)->getResultArray() !== [];

// if cache has been built already
if (! empty($this->dataCache['table_names'])) {
$key = array_search(
strtolower($tableName),
array_map('strtolower', $this->dataCache['table_names']),
true
);

// table doesn't exist but still in cache - lets reset cache, it can be rebuilt later
// OR if table does exist but is not found in cache
if (($key !== false && ! $tableExists) || ($key === false && $tableExists)) {
$this->resetDataCache();
}
}

return $tableExists;
}

/**
Expand Down Expand Up @@ -1575,9 +1606,11 @@ abstract public function insertID();
/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*
* @return false|string
*/
abstract protected function _listTables(bool $constrainByPrefix = false);
abstract protected function _listTables(bool $constrainByPrefix = false, ?string $tableName = null);
kenjis marked this conversation as resolved.
Show resolved Hide resolved

/**
* Generates a platform-specific query string so that the column names can be fetched.
Expand Down
2 changes: 1 addition & 1 deletion system/Database/Forge.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ public function createTable(string $table, bool $ifNotExists = false, array $att
}

// If table exists lets stop here
if ($ifNotExists === true && $this->db->tableExists($table)) {
if ($ifNotExists === true && $this->db->tableExists($table, false)) {
$this->reset();

return true;
Expand Down
8 changes: 7 additions & 1 deletion system/Database/MySQLi/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,17 @@ public function escapeLikeStringDirect($str)
/**
* Generates the SQL for listing tables in a platform-dependent manner.
* Uses escapeLikeStringDirect().
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $prefixLimit = false): string
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
{
$sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database);

if ($tableName !== null) {
return $sql . ' LIKE ' . $this->escape($tableName);
}

if ($prefixLimit !== false && $this->DBPrefix !== '') {
return $sql . " LIKE '" . $this->escapeLikeStringDirect($this->DBPrefix) . "%'";
}
Expand Down
8 changes: 7 additions & 1 deletion system/Database/OCI8/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,17 @@ public function affectedRows(): int

/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $prefixLimit = false): string
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
{
$sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"';

if ($tableName !== null) {
return $sql . ' WHERE "TABLE_NAME" LIKE ' . $this->escape($tableName);
}

if ($prefixLimit !== false && $this->DBPrefix !== '') {
return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' "
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
Expand Down
8 changes: 7 additions & 1 deletion system/Database/Postgre/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,17 @@ protected function _escapeString(string $str): string

/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $prefixLimit = false): string
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
{
$sql = 'SELECT "table_name" FROM "information_schema"."tables" WHERE "table_schema" = \'' . $this->schema . "'";

if ($tableName !== null) {
return $sql . ' AND "table_name" LIKE ' . $this->escape($tableName);
}

if ($prefixLimit !== false && $this->DBPrefix !== '') {
return $sql . ' AND "table_name" LIKE \''
. $this->escapeLikeString($this->DBPrefix) . "%' "
Expand Down
8 changes: 7 additions & 1 deletion system/Database/SQLSRV/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,20 @@ public function insertID(): int

/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $prefixLimit = false): string
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
{
$sql = 'SELECT [TABLE_NAME] AS "name"'
. ' FROM [INFORMATION_SCHEMA].[TABLES] '
. ' WHERE '
. " [TABLE_SCHEMA] = '" . $this->schema . "' ";

if ($tableName !== null) {
return $sql .= ' AND [TABLE_NAME] LIKE ' . $this->escape($tableName);
}

if ($prefixLimit === true && $this->DBPrefix !== '') {
$sql .= " AND [TABLE_NAME] LIKE '" . $this->escapeLikeString($this->DBPrefix) . "%' "
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
Expand Down
10 changes: 9 additions & 1 deletion system/Database/SQLite3/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,17 @@ protected function _escapeString(string $str): string

/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $prefixLimit = false): string
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
{
if ($tableName !== null) {
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
. ' AND "NAME" LIKE ' . $this->escape($tableName);
}

return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
. (($prefixLimit !== false && $this->DBPrefix !== '')
Expand Down
4 changes: 3 additions & 1 deletion system/Test/Mock/MockConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ public function insertID(): int

/**
* Generates the SQL for listing tables in a platform-dependent manner.
*
* @param string|null $tableName If $tableName is provided will return only this table if exists.
*/
protected function _listTables(bool $constrainByPrefix = false): string
protected function _listTables(bool $constrainByPrefix = false, ?string $tableName = null): string
{
return '';
}
Expand Down
20 changes: 0 additions & 20 deletions tests/system/Database/Forge/CreateTableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,6 @@
*/
final class CreateTableTest extends CIUnitTestCase
{
public function testCreateTableWithExists()
{
$dbMock = $this->getMockBuilder(MockConnection::class)
->setConstructorArgs([[]])
->onlyMethods(['listTables'])
->getMock();
$dbMock
->method('listTables')
->willReturn(['foo']);

$forge = new class ($dbMock) extends Forge {
protected $createTableIfStr = false;
};

$forge->addField('id');
$actual = $forge->createTable('foo', true);

$this->assertTrue($actual);
}

public function testCreateTableWithDefaultRawSql()
{
$sql = <<<'SQL'
Expand Down
49 changes: 49 additions & 0 deletions tests/system/Database/Live/ForgeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,55 @@ public function testCreateTable()
$this->forge->dropTable('forge_test_table', true);
}

public function testCreateTableWithExists()
{
// create table so that it exists in database
$this->forge->addField([
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
])->addKey('id', true)->createTable('test_exists', true);

// table exists in cache
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', true));

// table exists without cached results
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', false));

// try creating table when table exists
$result = $this->forge->addField([
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
])->addKey('id', true)->createTable('test_exists', true);

$this->assertTrue($result);

// Delete table outside of forge. This should leave table in cache as existing.
$this->forge->getConnection()->query('DROP TABLE ' . $this->forge->getConnection()->protectIdentifiers('db_test_exists', true, null, false));

// table stil exists in cache
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', true));

// table does not exist without cached results - this will update the cache
$this->assertFalse($this->forge->getConnection()->tableExists('db_test_exists', false));

// the call above should update the cache - table should not exist in cache anymore
$this->assertFalse($this->forge->getConnection()->tableExists('db_test_exists', true));

// try creating table when table does not exist but still in cache
$result = $this->forge->addField([
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
])->addKey('id', true)->createTable('test_exists', true);

$this->assertTrue($result);

// check that the table does now exist without cached results
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', false));

// drop table so that it doesn't mess up other tests
$this->forge->dropTable('test_exists');
}

public function testCreateTableApplyBigInt()
{
$this->forge->dropTable('forge_test_table', true);
Expand Down
3 changes: 2 additions & 1 deletion user_guide_src/source/changelogs/v4.2.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Release Date: Unreleased
BREAKING
********

none.
- The method signature of ``BaseConnection::tableExists()`` has been changed. A second optional parameter ``$cached`` was added. This directs whether to use cache data or not. Default is ``true``, use cache data.
- The abstract method signature of ``BaseBuilder::_listTables()`` has been changed. A second optional parameter ``$tableName`` was added. Providing a table name will generate SQL listing only that table.

Enhancements
************
Expand Down