diff --git a/phpstan-baseline.php b/phpstan-baseline.php index a49319ffd2f8..e31dfd77df42 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -2396,11 +2396,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Database/BaseConnection.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Property CodeIgniter\\\\Database\\\\BaseConnection\\:\\:\\$aliasedTables type has no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseConnection.php', -]; $ignoreErrors[] = [ 'message' => '#^Property CodeIgniter\\\\Database\\\\BaseConnection\\:\\:\\$dataCache type has no value type specified in iterable type array\\.$#', 'count' => 1, diff --git a/system/Commands/Database/ShowTableInfo.php b/system/Commands/Database/ShowTableInfo.php index 05dcfe4d67a3..2d44e932ee10 100644 --- a/system/Commands/Database/ShowTableInfo.php +++ b/system/Commands/Database/ShowTableInfo.php @@ -16,6 +16,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\TableName; use Config\Database; use InvalidArgumentException; @@ -194,7 +195,7 @@ private function showDataOfTable(string $tableName, int $limitRows, int $limitFi CLI::newLine(); $this->removeDBPrefix(); - $thead = $this->db->getFieldNames($tableName); + $thead = $this->db->getFieldNames(TableName::fromActualName($this->db, $tableName)); $this->restoreDBPrefix(); // If there is a field named `id`, sort by it. @@ -253,7 +254,7 @@ private function makeTableRows( $this->tbody = []; $this->removeDBPrefix(); - $builder = $this->db->table($tableName); + $builder = $this->db->table(TableName::fromActualName($this->db, $tableName)); $builder->limit($limitRows); if ($sortField !== null) { $builder->orderBy($sortField, $this->sortDesc ? 'DESC' : 'ASC'); diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 3995dffdf7d4..27924e4c542b 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -298,7 +298,7 @@ class BaseBuilder /** * Constructor * - * @param array|string $tableName tablename or tablenames with or without aliases + * @param array|string|TableName $tableName tablename or tablenames with or without aliases * * Examples of $tableName: `mytable`, `jobs j`, `jobs j, users u`, `['jobs j','users u']` * @@ -315,15 +315,20 @@ public function __construct($tableName, ConnectionInterface $db, ?array $options */ $this->db = $db; + if ($tableName instanceof TableName) { + $this->tableName = $tableName->getTableName(); + $this->QBFrom[] = $this->db->escapeIdentifier($tableName); + $this->db->addTableAlias($tableName->getAlias()); + } // If it contains `,`, it has multiple tables - if (is_string($tableName) && ! str_contains($tableName, ',')) { + elseif (is_string($tableName) && ! str_contains($tableName, ',')) { $this->tableName = $tableName; // @TODO remove alias if exists + $this->from($tableName); } else { $this->tableName = ''; + $this->from($tableName); } - $this->from($tableName); - if ($options !== null && $options !== []) { foreach ($options as $key => $value) { if (property_exists($this, $key)) { diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index 5f8c01b9703e..6c8e14df1f0a 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -900,7 +900,7 @@ abstract protected function _transRollback(): bool; /** * Returns a non-shared new instance of the query builder for this connection. * - * @param array|string $tableName + * @param array|string|TableName $tableName * * @return BaseBuilder * @@ -1029,10 +1029,10 @@ public function getConnectDuration(int $decimals = 6): string * insert the table prefix (if it exists) in the proper position, and escape only * the correct identifiers. * - * @param array|int|string $item - * @param bool $prefixSingle Prefix a table name with no segments? - * @param bool $protectIdentifiers Protect table or column names? - * @param bool $fieldExists Supplied $item contains a column name? + * @param array|int|string|TableName $item + * @param bool $prefixSingle Prefix a table name with no segments? + * @param bool $protectIdentifiers Protect table or column names? + * @param bool $fieldExists Supplied $item contains a column name? * * @return array|string * @phpstan-return ($item is array ? array : string) @@ -1053,6 +1053,10 @@ public function protectIdentifiers($item, bool $prefixSingle = false, ?bool $pro return $escapedArray; } + if ($item instanceof TableName) { + return $this->escapeTableName($item); + } + // If you pass `['column1', 'column2']`, `$item` will be int because the array keys are int. $item = (string) $item; @@ -1195,14 +1199,18 @@ private function protectDotItem(string $item, string $alias, bool $protectIdenti * * This function escapes single identifier. * - * @param non-empty-string $item + * @param non-empty-string|TableName $item */ - public function escapeIdentifier(string $item): string + public function escapeIdentifier($item): string { if ($item === '') { return ''; } + if ($item instanceof TableName) { + return $this->escapeTableName($item); + } + return $this->escapeChar . str_replace( $this->escapeChar, @@ -1212,6 +1220,11 @@ public function escapeIdentifier(string $item): string . $this->escapeChar; } + private function escapeTableName(TableName $tableName): string + { + return $this->escapeIdentifier($tableName->getActualTableName()); + } + /** * Escape the SQL Identifiers * @@ -1513,12 +1526,16 @@ public function tableExists(string $tableName, bool $cached = true): bool /** * Fetch Field Names * + * @param string|TableName $tableName + * * @return array|false * * @throws DatabaseException */ - public function getFieldNames(string $table) + public function getFieldNames($tableName) { + $table = ($tableName instanceof TableName) ? $tableName->getTableName() : $tableName; + // Is there a cached result? if (isset($this->dataCache['field_names'][$table])) { return $this->dataCache['field_names'][$table]; @@ -1528,7 +1545,7 @@ public function getFieldNames(string $table) $this->initialize(); } - if (false === ($sql = $this->_listColumns($table))) { + if (false === ($sql = $this->_listColumns($tableName))) { if ($this->DBDebug) { throw new DatabaseException('This feature is not available for the database you are using.'); } @@ -1744,9 +1761,11 @@ abstract protected function _listTables(bool $constrainByPrefix = false, ?string /** * Generates a platform-specific query string so that the column names can be fetched. * + * @param string|TableName $table + * * @return false|string */ - abstract protected function _listColumns(string $table = ''); + abstract protected function _listColumns($table = ''); /** * Platform-specific field data information. diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index c2ef98adb0b3..0b84b61e8a2f 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -15,6 +15,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Database\TableName; use LogicException; use mysqli; use mysqli_result; @@ -404,10 +405,19 @@ protected function _listTables(bool $prefixLimit = false, ?string $tableName = n /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { - return 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, true, null, false); + $tableName = $this->protectIdentifiers( + $table, + true, + null, + false + ); + + return 'SHOW COLUMNS FROM ' . $tableName; } /** diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index be7eb37cb9a9..51d0ccb27e63 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -16,6 +16,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Query; +use CodeIgniter\Database\TableName; use ErrorException; use stdClass; @@ -266,18 +267,25 @@ protected function _listTables(bool $prefixLimit = false, ?string $tableName = n /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { - if (str_contains($table, '.')) { - sscanf($table, '%[^.].%s', $owner, $table); + if ($table instanceof TableName) { + $tableName = $this->escape(strtoupper($table->getActualTableName())); + $owner = $this->username; + } elseif (str_contains($table, '.')) { + sscanf($table, '%[^.].%s', $owner, $tableName); + $tableName = $this->escape(strtoupper($this->DBPrefix . $tableName)); } else { - $owner = $this->username; + $owner = $this->username; + $tableName = $this->escape(strtoupper($this->DBPrefix . $table)); } return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . ' - AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($this->DBPrefix . $table)); + AND UPPER(TABLE_NAME) = ' . $tableName; } /** diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 45e30eb65365..d03694825474 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -16,6 +16,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\RawSql; +use CodeIgniter\Database\TableName; use ErrorException; use PgSql\Connection as PgSqlConnection; use PgSql\Result as PgSqlResult; @@ -286,13 +287,20 @@ protected function _listTables(bool $prefixLimit = false, ?string $tableName = n /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { + if ($table instanceof TableName) { + $tableName = $this->escape($table->getActualTableName()); + } else { + $tableName = $this->escape($this->DBPrefix . strtolower($table)); + } + return 'SELECT "column_name" FROM "information_schema"."columns" - WHERE LOWER("table_name") = ' - . $this->escape($this->DBPrefix . strtolower($table)) + WHERE LOWER("table_name") = ' . $tableName . ' ORDER BY "ordinal_position"'; } diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php index 411470d8e34c..92c6ac887e99 100755 --- a/system/Database/SQLSRV/Connection.php +++ b/system/Database/SQLSRV/Connection.php @@ -15,6 +15,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Database\TableName; use stdClass; /** @@ -221,12 +222,20 @@ protected function _listTables(bool $prefixLimit = false, ?string $tableName = n /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { + if ($table instanceof TableName) { + $tableName = $this->escape(strtolower($table->getActualTableName())); + } else { + $tableName = $this->escape($this->DBPrefix . strtolower($table)); + } + return 'SELECT [COLUMN_NAME] ' . ' FROM [INFORMATION_SCHEMA].[COLUMNS]' - . ' WHERE [TABLE_NAME] = ' . $this->escape($this->DBPrefix . $table) + . ' WHERE [TABLE_NAME] = ' . $tableName . ' AND [TABLE_SCHEMA] = ' . $this->escape($this->schema); } diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index d15c64213952..8e7b15a87a41 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -15,6 +15,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Database\TableName; use Exception; use SQLite3; use SQLite3Result; @@ -202,19 +203,31 @@ protected function _listTables(bool $prefixLimit = false, ?string $tableName = n /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { - return 'PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')'; + if ($table instanceof TableName) { + $tableName = $this->escapeIdentifier($table); + } else { + $tableName = $this->protectIdentifiers($table, true, null, false); + } + + return 'PRAGMA TABLE_INFO(' . $tableName . ')'; } /** + * @param string|TableName $tableName + * * @return array|false * * @throws DatabaseException */ - public function getFieldNames(string $table) + public function getFieldNames($tableName) { + $table = ($tableName instanceof TableName) ? $tableName->getTableName() : $tableName; + // Is there a cached result? if (isset($this->dataCache['field_names'][$table])) { return $this->dataCache['field_names'][$table]; @@ -224,7 +237,7 @@ public function getFieldNames(string $table) $this->initialize(); } - $sql = $this->_listColumns($table); + $sql = $this->_listColumns($tableName); $query = $this->query($sql); $this->dataCache['field_names'][$table] = []; diff --git a/system/Test/Mock/MockConnection.php b/system/Test/Mock/MockConnection.php index 83826c347c98..a7ef482a4cf6 100644 --- a/system/Test/Mock/MockConnection.php +++ b/system/Test/Mock/MockConnection.php @@ -17,6 +17,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\Query; +use CodeIgniter\Database\TableName; /** * @extends BaseConnection @@ -197,8 +198,10 @@ protected function _listTables(bool $constrainByPrefix = false, ?string $tableNa /** * Generates a platform-specific query string so that the column names can be fetched. + * + * @param string|TableName $table */ - protected function _listColumns(string $table = ''): string + protected function _listColumns($table = ''): string { return ''; }