Skip to content

Commit 42e5b69

Browse files
authored
Merge pull request #6364 from sclubricants/DbTableExists
Add $cached param to BaseConnection::tableExists()
2 parents cc8758b + b2f2f61 commit 42e5b69

File tree

11 files changed

+128
-31
lines changed

11 files changed

+128
-31
lines changed

system/Database/BaseConnection.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,10 +1405,41 @@ public function listTables(bool $constrainByPrefix = false)
14051405

14061406
/**
14071407
* Determine if a particular table exists
1408+
*
1409+
* @param bool $cached Whether to use data cache
14081410
*/
1409-
public function tableExists(string $tableName): bool
1411+
public function tableExists(string $tableName, bool $cached = true): bool
14101412
{
1411-
return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables(), true);
1413+
if ($cached === true) {
1414+
return in_array($this->protectIdentifiers($tableName, true, false, false), $this->listTables(), true);
1415+
}
1416+
1417+
if (false === ($sql = $this->_listTables(false, $tableName))) {
1418+
if ($this->DBDebug) {
1419+
throw new DatabaseException('This feature is not available for the database you are using.');
1420+
}
1421+
1422+
return false;
1423+
}
1424+
1425+
$tableExists = $this->query($sql)->getResultArray() !== [];
1426+
1427+
// if cache has been built already
1428+
if (! empty($this->dataCache['table_names'])) {
1429+
$key = array_search(
1430+
strtolower($tableName),
1431+
array_map('strtolower', $this->dataCache['table_names']),
1432+
true
1433+
);
1434+
1435+
// table doesn't exist but still in cache - lets reset cache, it can be rebuilt later
1436+
// OR if table does exist but is not found in cache
1437+
if (($key !== false && ! $tableExists) || ($key === false && $tableExists)) {
1438+
$this->resetDataCache();
1439+
}
1440+
}
1441+
1442+
return $tableExists;
14121443
}
14131444

14141445
/**
@@ -1575,9 +1606,11 @@ abstract public function insertID();
15751606
/**
15761607
* Generates the SQL for listing tables in a platform-dependent manner.
15771608
*
1609+
* @param string|null $tableName If $tableName is provided will return only this table if exists.
1610+
*
15781611
* @return false|string
15791612
*/
1580-
abstract protected function _listTables(bool $constrainByPrefix = false);
1613+
abstract protected function _listTables(bool $constrainByPrefix = false, ?string $tableName = null);
15811614

15821615
/**
15831616
* Generates a platform-specific query string so that the column names can be fetched.

system/Database/Forge.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ public function createTable(string $table, bool $ifNotExists = false, array $att
498498
}
499499

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

504504
return true;

system/Database/MySQLi/Connection.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,17 @@ public function escapeLikeStringDirect($str)
368368
/**
369369
* Generates the SQL for listing tables in a platform-dependent manner.
370370
* Uses escapeLikeStringDirect().
371+
*
372+
* @param string|null $tableName If $tableName is provided will return only this table if exists.
371373
*/
372-
protected function _listTables(bool $prefixLimit = false): string
374+
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
373375
{
374376
$sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database);
375377

378+
if ($tableName !== null) {
379+
return $sql . ' LIKE ' . $this->escape($tableName);
380+
}
381+
376382
if ($prefixLimit !== false && $this->DBPrefix !== '') {
377383
return $sql . " LIKE '" . $this->escapeLikeStringDirect($this->DBPrefix) . "%'";
378384
}

system/Database/OCI8/Connection.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,17 @@ public function affectedRows(): int
242242

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

252+
if ($tableName !== null) {
253+
return $sql . ' WHERE "TABLE_NAME" LIKE ' . $this->escape($tableName);
254+
}
255+
250256
if ($prefixLimit !== false && $this->DBPrefix !== '') {
251257
return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' "
252258
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);

system/Database/Postgre/Connection.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,17 @@ protected function _escapeString(string $str): string
204204

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

214+
if ($tableName !== null) {
215+
return $sql . ' AND "table_name" LIKE ' . $this->escape($tableName);
216+
}
217+
212218
if ($prefixLimit !== false && $this->DBPrefix !== '') {
213219
return $sql . ' AND "table_name" LIKE \''
214220
. $this->escapeLikeString($this->DBPrefix) . "%' "

system/Database/SQLSRV/Connection.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,20 @@ public function insertID(): int
183183

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

196+
if ($tableName !== null) {
197+
return $sql .= ' AND [TABLE_NAME] LIKE ' . $this->escape($tableName);
198+
}
199+
194200
if ($prefixLimit === true && $this->DBPrefix !== '') {
195201
$sql .= " AND [TABLE_NAME] LIKE '" . $this->escapeLikeString($this->DBPrefix) . "%' "
196202
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);

system/Database/SQLite3/Connection.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,17 @@ protected function _escapeString(string $str): string
160160

161161
/**
162162
* Generates the SQL for listing tables in a platform-dependent manner.
163+
*
164+
* @param string|null $tableName If $tableName is provided will return only this table if exists.
163165
*/
164-
protected function _listTables(bool $prefixLimit = false): string
166+
protected function _listTables(bool $prefixLimit = false, ?string $tableName = null): string
165167
{
168+
if ($tableName !== null) {
169+
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
170+
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
171+
. ' AND "NAME" LIKE ' . $this->escape($tableName);
172+
}
173+
166174
return 'SELECT "NAME" FROM "SQLITE_MASTER" WHERE "TYPE" = \'table\''
167175
. ' AND "NAME" NOT LIKE \'sqlite!_%\' ESCAPE \'!\''
168176
. (($prefixLimit !== false && $this->DBPrefix !== '')

system/Test/Mock/MockConnection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,10 @@ public function insertID(): int
179179

180180
/**
181181
* Generates the SQL for listing tables in a platform-dependent manner.
182+
*
183+
* @param string|null $tableName If $tableName is provided will return only this table if exists.
182184
*/
183-
protected function _listTables(bool $constrainByPrefix = false): string
185+
protected function _listTables(bool $constrainByPrefix = false, ?string $tableName = null): string
184186
{
185187
return '';
186188
}

tests/system/Database/Forge/CreateTableTest.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,6 @@
2121
*/
2222
final class CreateTableTest extends CIUnitTestCase
2323
{
24-
public function testCreateTableWithExists()
25-
{
26-
$dbMock = $this->getMockBuilder(MockConnection::class)
27-
->setConstructorArgs([[]])
28-
->onlyMethods(['listTables'])
29-
->getMock();
30-
$dbMock
31-
->method('listTables')
32-
->willReturn(['foo']);
33-
34-
$forge = new class ($dbMock) extends Forge {
35-
protected $createTableIfStr = false;
36-
};
37-
38-
$forge->addField('id');
39-
$actual = $forge->createTable('foo', true);
40-
41-
$this->assertTrue($actual);
42-
}
43-
4424
public function testCreateTableWithDefaultRawSql()
4525
{
4626
$sql = <<<'SQL'

tests/system/Database/Live/ForgeTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,55 @@ public function testCreateTable()
155155
$this->forge->dropTable('forge_test_table', true);
156156
}
157157

158+
public function testCreateTableWithExists()
159+
{
160+
// create table so that it exists in database
161+
$this->forge->addField([
162+
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
163+
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
164+
])->addKey('id', true)->createTable('test_exists', true);
165+
166+
// table exists in cache
167+
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', true));
168+
169+
// table exists without cached results
170+
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', false));
171+
172+
// try creating table when table exists
173+
$result = $this->forge->addField([
174+
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
175+
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
176+
])->addKey('id', true)->createTable('test_exists', true);
177+
178+
$this->assertTrue($result);
179+
180+
// Delete table outside of forge. This should leave table in cache as existing.
181+
$this->forge->getConnection()->query('DROP TABLE ' . $this->forge->getConnection()->protectIdentifiers('db_test_exists', true, null, false));
182+
183+
// table stil exists in cache
184+
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', true));
185+
186+
// table does not exist without cached results - this will update the cache
187+
$this->assertFalse($this->forge->getConnection()->tableExists('db_test_exists', false));
188+
189+
// the call above should update the cache - table should not exist in cache anymore
190+
$this->assertFalse($this->forge->getConnection()->tableExists('db_test_exists', true));
191+
192+
// try creating table when table does not exist but still in cache
193+
$result = $this->forge->addField([
194+
'id' => ['type' => 'INTEGER', 'constraint' => 3, 'auto_increment' => true],
195+
'name' => ['type' => 'VARCHAR', 'constraint' => 80],
196+
])->addKey('id', true)->createTable('test_exists', true);
197+
198+
$this->assertTrue($result);
199+
200+
// check that the table does now exist without cached results
201+
$this->assertTrue($this->forge->getConnection()->tableExists('db_test_exists', false));
202+
203+
// drop table so that it doesn't mess up other tests
204+
$this->forge->dropTable('test_exists');
205+
}
206+
158207
public function testCreateTableApplyBigInt()
159208
{
160209
$this->forge->dropTable('forge_test_table', true);

user_guide_src/source/changelogs/v4.2.5.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ Release Date: Unreleased
1212
BREAKING
1313
********
1414

15-
none.
15+
- 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.
16+
- 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.
1617

1718
Enhancements
1819
************

0 commit comments

Comments
 (0)