From d02d8e4af905e44880d209b82d509625b4b8b314 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 13:51:16 +0900 Subject: [PATCH 001/184] feat: copy MySQLi to OCI8. --- system/Database/OCI8/Builder.php | 76 +++ system/Database/OCI8/Connection.php | 731 +++++++++++++++++++++++++ system/Database/OCI8/Forge.php | 271 +++++++++ system/Database/OCI8/PreparedQuery.php | 136 +++++ system/Database/OCI8/Result.php | 172 ++++++ system/Database/OCI8/Utils.php | 79 +++ 6 files changed, 1465 insertions(+) create mode 100644 system/Database/OCI8/Builder.php create mode 100644 system/Database/OCI8/Connection.php create mode 100644 system/Database/OCI8/Forge.php create mode 100644 system/Database/OCI8/PreparedQuery.php create mode 100644 system/Database/OCI8/Result.php create mode 100644 system/Database/OCI8/Utils.php diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php new file mode 100644 index 000000000000..23f14ea7e33c --- /dev/null +++ b/system/Database/OCI8/Builder.php @@ -0,0 +1,76 @@ +QBJoin) && count($this->QBFrom) > 1) + { + return '('.implode(', ', $this->QBFrom).')'; + } + + return implode(', ', $this->QBFrom); + } + +} diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php new file mode 100644 index 000000000000..059c86009324 --- /dev/null +++ b/system/Database/OCI8/Connection.php @@ -0,0 +1,731 @@ +hostname[0] === '/') + { + $hostname = null; + $port = null; + $socket = $this->hostname; + } + else + { + $hostname = ($persistent === true) ? 'p:' . $this->hostname : $this->hostname; + $port = empty($this->port) ? null : $this->port; + $socket = null; + } + + $client_flags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0; + $this->mysqli = mysqli_init(); + + mysqli_report(MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX); + + $this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10); + + if (isset($this->strictOn)) + { + if ($this->strictOn) + { + $this->mysqli->options(MYSQLI_INIT_COMMAND, + 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'); + } + else + { + $this->mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( + @@sql_mode, + "STRICT_ALL_TABLES,", ""), + ",STRICT_ALL_TABLES", ""), + "STRICT_ALL_TABLES", ""), + "STRICT_TRANS_TABLES,", ""), + ",STRICT_TRANS_TABLES", ""), + "STRICT_TRANS_TABLES", "")' + ); + } + } + + if (is_array($this->encrypt)) + { + $ssl = []; + empty($this->encrypt['ssl_key']) || $ssl['key'] = $this->encrypt['ssl_key']; + empty($this->encrypt['ssl_cert']) || $ssl['cert'] = $this->encrypt['ssl_cert']; + empty($this->encrypt['ssl_ca']) || $ssl['ca'] = $this->encrypt['ssl_ca']; + empty($this->encrypt['ssl_capath']) || $ssl['capath'] = $this->encrypt['ssl_capath']; + empty($this->encrypt['ssl_cipher']) || $ssl['cipher'] = $this->encrypt['ssl_cipher']; + + if (! empty($ssl)) + { + if (isset($this->encrypt['ssl_verify'])) + { + if ($this->encrypt['ssl_verify']) + { + defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && + $this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true); + } + // Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT + // to FALSE didn't do anything, so PHP 5.6.16 introduced yet another + // constant ... + // + // https://secure.php.net/ChangeLog-5.php#5.6.16 + // https://bugs.php.net/bug.php?id=68344 + elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT') && version_compare($this->mysqli->client_info, '5.6', '>=')) + { + $client_flags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + } + } + + $client_flags += MYSQLI_CLIENT_SSL; + $this->mysqli->ssl_set( + $ssl['key'] ?? null, $ssl['cert'] ?? null, $ssl['ca'] ?? null, + $ssl['capath'] ?? null, $ssl['cipher'] ?? null + ); + } + } + + try + { + if ($this->mysqli->real_connect($hostname, $this->username, $this->password, + $this->database, $port, $socket, $client_flags) + ) + { + // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails + if (($client_flags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, '5.7.3', '<=') + && empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'") + ->fetch_object()->Value) + ) + { + $this->mysqli->close(); + $message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!'; + log_message('error', $message); + + if ($this->DBDebug) + { + throw new DatabaseException($message); + } + + return false; + } + + if (! $this->mysqli->set_charset($this->charset)) + { + log_message('error', + "Database: Unable to set the configured connection charset ('{$this->charset}')."); + $this->mysqli->close(); + + if ($this->db->debug) + { + throw new DatabaseException('Unable to set client connection character set: ' . $this->charset); + } + + return false; + } + + return $this->mysqli; + } + } + catch (\Throwable $e) + { + // Clean sensitive information from errors. + $msg = $e->getMessage(); + + $msg = str_replace($this->username, '****', $msg); + $msg = str_replace($this->password, '****', $msg); + + throw new \mysqli_sql_exception($msg, $e->getCode(), $e); + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return void + */ + 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 boolean + */ + public function setDatabase(string $databaseName): bool + { + if ($databaseName === '') + { + $databaseName = $this->database; + } + + if (empty($this->connID)) + { + $this->initialize(); + } + + if ($this->connID->select_db($databaseName)) + { + $this->database = $databaseName; + + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Returns a string containing the version of the database being used. + * + * @return string + */ + public function getVersion(): string + { + if (isset($this->dataCache['version'])) + { + return $this->dataCache['version']; + } + + if (empty($this->mysqli)) + { + $this->initialize(); + } + + return $this->dataCache['version'] = $this->mysqli->server_info; + } + + //-------------------------------------------------------------------- + + /** + * Executes the query against the database. + * + * @param string $sql + * + * @return mixed + */ + public function execute(string $sql) + { + while ($this->connID->more_results()) + { + $this->connID->next_result(); + if ($res = $this->connID->store_result()) + { + $res->free(); + } + } + + return $this->connID->query($this->prepQuery($sql)); + } + + //-------------------------------------------------------------------- + + /** + * Prep the query + * + * If needed, each database adapter can prep the query string + * + * @param string $sql an SQL query + * + * @return string + */ + protected function prepQuery(string $sql): string + { + // mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack + // modifies the query so that it a proper number of affected rows is returned. + if ($this->deleteHack === true && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) + { + return trim($sql) . ' WHERE 1=1'; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * Returns the total number of rows affected by this query. + * + * @return integer + */ + public function affectedRows(): int + { + return $this->connID->affected_rows ?? 0; + } + + //-------------------------------------------------------------------- + + /** + * Platform-dependant string escape + * + * @param string $str + * @return string + */ + protected function _escapeString(string $str): string + { + if (is_bool($str)) + { + return $str; + } + + if (! $this->connID) + { + $this->initialize(); + } + + return $this->connID->real_escape_string($str); + } + + //-------------------------------------------------------------------- + + /** + * Escape Like String Direct + * There are a few instances where MySQLi queries cannot take the + * additional "ESCAPE x" parameter for specifying the escape character + * in "LIKE" strings, and this handles those directly with a backslash. + * + * @param string|string[] $str Input string + * @return string|string[] + */ + public function escapeLikeStringDirect($str) + { + if (is_array($str)) + { + foreach ($str as $key => $val) + { + $str[$key] = $this->escapeLikeStringDirect($val); + } + + return $str; + } + + $str = $this->_escapeString($str); + + // Escape LIKE condition wildcards + return str_replace([ + $this->likeEscapeChar, + '%', + '_', + ], [ + '\\' . $this->likeEscapeChar, + '\\' . '%', + '\\' . '_', + ], $str + ); + + return $str; + } + + //-------------------------------------------------------------------- + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + * Uses escapeLikeStringDirect(). + * + * @param boolean $prefixLimit + * + * @return string + */ + protected function _listTables(bool $prefixLimit = false): string + { + $sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database); + + if ($prefixLimit !== false && $this->DBPrefix !== '') + { + return $sql . " LIKE '" . $this->escapeLikeStringDirect($this->DBPrefix) . "%'"; + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * 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 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, true, null, false); + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of objects with field data + * + * @param string $table + * @return \stdClass[] + * @throws DatabaseException + */ + public function _fieldData(string $table): array + { + $table = $this->protectIdentifiers($table, true, null, false); + + if (($query = $this->query('SHOW COLUMNS FROM ' . $table)) === false) + { + throw new DatabaseException(lang('Database.failGetFieldData')); + } + $query = $query->getResultObject(); + + $retVal = []; + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retVal[$i] = new \stdClass(); + $retVal[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', $retVal[$i]->type, $retVal[$i]->max_length); + + $retVal[$i]->default = $query[$i]->Default; + $retVal[$i]->primary_key = (int)($query[$i]->Key === 'PRI'); + } + + return $retVal; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of objects with index data + * + * @param string $table + * @return \stdClass[] + * @throws DatabaseException + * @throws \LogicException + */ + public function _indexData(string $table): array + { + $table = $this->protectIdentifiers($table, true, null, false); + + if (($query = $this->query('SHOW INDEX FROM ' . $table)) === false) + { + throw new DatabaseException(lang('Database.failGetIndexData')); + } + + if (! $indexes = $query->getResultArray()) + { + return []; + } + + $keys = []; + + foreach ($indexes as $index) + { + if (empty($keys[$index['Key_name']])) + { + $keys[$index['Key_name']] = new \stdClass(); + $keys[$index['Key_name']]->name = $index['Key_name']; + + if ($index['Key_name'] === 'PRIMARY') + { + $type = 'PRIMARY'; + } + elseif ($index['Index_type'] === 'FULLTEXT') + { + $type = 'FULLTEXT'; + } + elseif ($index['Non_unique']) + { + if ($index['Index_type'] === 'SPATIAL') + { + $type = 'SPATIAL'; + } + else + { + $type = 'INDEX'; + } + } + else + { + $type = 'UNIQUE'; + } + + $keys[$index['Key_name']]->type = $type; + } + + $keys[$index['Key_name']]->fields[] = $index['Column_name']; + } + + return $keys; + } + + //-------------------------------------------------------------------- + + /** + * Returns an array of objects with Foreign key data + * + * @param string $table + * @return \stdClass[] + * @throws DatabaseException + */ + public function _foreignKeyData(string $table): array + { + $sql = ' + SELECT + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, + kcu.COLUMN_NAME, + rc.REFERENCED_TABLE_NAME, + kcu.REFERENCED_COLUMN_NAME + FROM information_schema.TABLE_CONSTRAINTS AS tc + INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc + ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + INNER JOIN information_schema.KEY_COLUMN_USAGE AS kcu + ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME + WHERE + tc.CONSTRAINT_TYPE = ' . $this->escape('FOREIGN KEY') . ' AND + tc.TABLE_SCHEMA = ' . $this->escape($this->database) . ' AND + tc.TABLE_NAME = ' . $this->escape($table); + + if (($query = $this->query($sql)) === false) + { + throw new DatabaseException(lang('Database.failGetForeignKeyData')); + } + $query = $query->getResultObject(); + + $retVal = []; + foreach ($query as $row) + { + $obj = new \stdClass(); + $obj->constraint_name = $row->CONSTRAINT_NAME; + $obj->table_name = $row->TABLE_NAME; + $obj->column_name = $row->COLUMN_NAME; + $obj->foreign_table_name = $row->REFERENCED_TABLE_NAME; + $obj->foreign_column_name = $row->REFERENCED_COLUMN_NAME; + + $retVal[] = $obj; + } + + return $retVal; + } + + //-------------------------------------------------------------------- + + /** + * Returns platform-specific SQL to disable foreign key checks. + * + * @return string + */ + protected function _disableForeignKeyChecks() + { + return 'SET FOREIGN_KEY_CHECKS=0'; + } + + //-------------------------------------------------------------------- + + /** + * Returns platform-specific SQL to enable foreign key checks. + * + * @return string + */ + protected function _enableForeignKeyChecks() + { + return 'SET FOREIGN_KEY_CHECKS=1'; + } + + //-------------------------------------------------------------------- + + /** + * 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 + { + if (! empty($this->mysqli->connect_errno)) + { + return [ + 'code' => $this->mysqli->connect_errno, + 'message' => $this->mysqli->connect_error, + ]; + } + + return [ + 'code' => $this->connID->errno, + 'message' => $this->connID->error, + ]; + } + + //-------------------------------------------------------------------- + + /** + * Insert ID + * + * @return integer + */ + public function insertID(): int + { + return $this->connID->insert_id; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return boolean + */ + protected function _transBegin(): bool + { + $this->connID->autocommit(false); + + return $this->connID->begin_transaction(); + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return boolean + */ + protected function _transCommit(): bool + { + if ($this->connID->commit()) + { + $this->connID->autocommit(true); + + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return boolean + */ + protected function _transRollback(): bool + { + if ($this->connID->rollback()) + { + $this->connID->autocommit(true); + + return true; + } + + return false; + } + //-------------------------------------------------------------------- +} diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php new file mode 100644 index 000000000000..52168afde4c9 --- /dev/null +++ b/system/Database/OCI8/Forge.php @@ -0,0 +1,271 @@ +_quoted_table_options)) + { + $sql .= $this->db->escape($attributes[$key]); + } + else + { + $sql .= $this->db->escapeString($attributes[$key]); + } + } + } + + if (! empty($this->db->charset) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) + { + $sql .= ' DEFAULT CHARACTER SET = ' . $this->db->escapeString($this->db->charset); + } + + if (! empty($this->db->DBCollat) && ! strpos($sql, 'COLLATE')) + { + $sql .= ' COLLATE = ' . $this->db->escapeString($this->db->DBCollat); + } + + return $sql; + } + + //-------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alterTable(string $alter_type, string $table, $field) + { + if ($alter_type === 'DROP') + { + return parent::_alterTable($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + foreach ($field as $i => $data) + { + if ($data['_literal'] !== false) + { + $field[$i] = ($alter_type === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; + } + else + { + if ($alter_type === 'ADD') + { + $field[$i]['_literal'] = "\n\tADD "; + } + else + { + $field[$i]['_literal'] = empty($data['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; + } + + $field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]); + } + } + + return [$sql . implode(',', $field)]; + } + + //-------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _processColumn(array $field): string + { + $extra_clause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === true) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escapeIdentifiers($field['name']) + . (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['null'] + . $field['default'] + . $field['auto_increment'] + . $field['unique'] + . (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) + . $extra_clause; + } + + //-------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _processIndexes(string $table): string + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i ++) + { + if (is_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]); + continue; + } + } + } + elseif (! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) || $this->keys[$i] = [$this->keys[$i]]; + + $unique = in_array($i, $this->uniqueKeys) ? 'UNIQUE ' : ''; + + $sql .= ",\n\t{$unique}KEY " . $this->db->escapeIdentifiers(implode('_', $this->keys[$i])) + . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ')'; + } + + $this->keys = []; + + return $sql; + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php new file mode 100644 index 000000000000..a510a3702877 --- /dev/null +++ b/system/Database/OCI8/PreparedQuery.php @@ -0,0 +1,136 @@ +statement = $this->db->mysqli->prepare($sql)) + { + $this->errorCode = $this->db->mysqli->errno; + $this->errorString = $this->db->mysqli->error; + } + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + * + * @param array $data + * + * @return boolean + */ + public function _execute(array $data): bool + { + if (is_null($this->statement)) + { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + // First off -bind the parameters + $bindTypes = ''; + + // Determine the type string + foreach ($data as $item) + { + if (is_integer($item)) + { + $bindTypes .= 'i'; + } + elseif (is_numeric($item)) + { + $bindTypes .= 'd'; + } + else + { + $bindTypes .= 's'; + } + } + + // Bind it + $this->statement->bind_param($bindTypes, ...$data); + + $success = $this->statement->execute(); + + return $success; + } + + //-------------------------------------------------------------------- + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->statement->get_result(); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php new file mode 100644 index 000000000000..630a4f7f3788 --- /dev/null +++ b/system/Database/OCI8/Result.php @@ -0,0 +1,172 @@ +resultID->field_count; + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of column names in the result set. + * + * @return array + */ + public function getFieldNames(): array + { + $fieldNames = []; + $this->resultID->field_seek(0); + while ($field = $this->resultID->fetch_field()) + { + $fieldNames[] = $field->name; + } + + return $fieldNames; + } + + //-------------------------------------------------------------------- + + /** + * Generates an array of objects representing field meta-data. + * + * @return array + */ + public function getFieldData(): array + { + $retVal = []; + $fieldData = $this->resultID->fetch_fields(); + + foreach ($fieldData as $i => $data) + { + $retVal[$i] = new \stdClass(); + $retVal[$i]->name = $data->name; + $retVal[$i]->type = $data->type; + $retVal[$i]->max_length = $data->max_length; + $retVal[$i]->primary_key = (int) ($data->flags & 2); + $retVal[$i]->default = $data->def; + } + + return $retVal; + } + + //-------------------------------------------------------------------- + + /** + * Frees the current result. + * + * @return void + */ + public function freeResult() + { + if (is_object($this->resultID)) + { + $this->resultID->free(); + $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 integer $n + * + * @return mixed + */ + public function dataSeek(int $n = 0) + { + return $this->resultID->data_seek($n); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an array. + * + * Overridden by driver classes. + * + * @return mixed + */ + protected function fetchAssoc() + { + return $this->resultID->fetch_assoc(); + } + + //-------------------------------------------------------------------- + + /** + * Returns the result set as an object. + * + * Overridden by child classes. + * + * @param string $className + * + * @return object|boolean|Entity + */ + protected function fetchObject(string $className = 'stdClass') + { + if (is_subclass_of($className, Entity::class)) + { + return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data); + } + return $this->resultID->fetch_object($className); + } + + //-------------------------------------------------------------------- +} diff --git a/system/Database/OCI8/Utils.php b/system/Database/OCI8/Utils.php new file mode 100644 index 000000000000..5ebe7fbb9f1b --- /dev/null +++ b/system/Database/OCI8/Utils.php @@ -0,0 +1,79 @@ + Date: Tue, 31 Dec 2019 13:55:25 +0900 Subject: [PATCH 002/184] fix: namespace MySQLi to OCI8. --- system/Database/OCI8/Builder.php | 2 +- system/Database/OCI8/Forge.php | 2 +- system/Database/OCI8/PreparedQuery.php | 2 +- system/Database/OCI8/Result.php | 4 ++-- system/Database/OCI8/Utils.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 23f14ea7e33c..d81b91d074e7 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -36,7 +36,7 @@ * @filesource */ -namespace CodeIgniter\Database\MySQLi; +namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\BaseBuilder; diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 52168afde4c9..02c132bf4a81 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -36,7 +36,7 @@ * @filesource */ -namespace CodeIgniter\Database\MySQLi; +namespace CodeIgniter\Database\OCI8; /** * Forge for MySQLi diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index a510a3702877..6ef0d9e7bc80 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -36,7 +36,7 @@ * @filesource */ -namespace CodeIgniter\Database\MySQLi; +namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 630a4f7f3788..e1cb7425f716 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -36,14 +36,14 @@ * @filesource */ -namespace CodeIgniter\Database\MySQLi; +namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; /** - * Result for MySQLi + * Result for OCI */ class Result extends BaseResult implements ResultInterface { diff --git a/system/Database/OCI8/Utils.php b/system/Database/OCI8/Utils.php index 5ebe7fbb9f1b..070c0d5d7348 100644 --- a/system/Database/OCI8/Utils.php +++ b/system/Database/OCI8/Utils.php @@ -36,7 +36,7 @@ * @filesource */ -namespace CodeIgniter\Database\MySQLi; +namespace CodeIgniter\Database\OCI8; use CodeIgniter\Database\BaseUtils; use CodeIgniter\Database\Exceptions\DatabaseException; From 1e412ca2aef76292e78e2a10a5f87415612f7622 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 14:30:47 +0900 Subject: [PATCH 003/184] feat: add build DNS process. --- system/Database/OCI8/Connection.php | 284 +++++++++++++--------------- 1 file changed, 136 insertions(+), 148 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 059c86009324..3925337c8eee 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -1,5 +1,4 @@ '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS + // Easy Connect string (Oracle 10g+) + 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', + 'in' => '/^[a-z0-9$_]+$/i' // Instance name (defined in tnsnames.ora) + ]; + //-------------------------------------------------------------------- /** - * Connect to the database. - * - * @param boolean $persistent + * Reset $stmtId flag * - * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * Used by storedProcedure() to prevent execute() from + * re-setting the statement ID. */ - public function connect(bool $persistent = false) - { - // Do we have a socket path? - if ($this->hostname[0] === '/') - { - $hostname = null; - $port = null; - $socket = $this->hostname; - } - else - { - $hostname = ($persistent === true) ? 'p:' . $this->hostname : $this->hostname; - $port = empty($this->port) ? null : $this->port; - $socket = null; - } + protected $resetStmtId = true; - $client_flags = ($this->compress === true) ? MYSQLI_CLIENT_COMPRESS : 0; - $this->mysqli = mysqli_init(); + /** + * Statement ID + * + * @var resource + */ + public $stmtId; - mysqli_report(MYSQLI_REPORT_ALL & ~MYSQLI_REPORT_INDEX); + /** + * Commit mode flag + * + * @var int + */ + public $commitMode = OCI_COMMIT_ON_SUCCESS; - $this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 10); + /** + * Cursor ID + * + * @var resource + */ + public $cursorId; - if (isset($this->strictOn)) + /** + * confirm DNS format. + * + * @return bool + */ + private function isValidDSN() : bool + { + foreach ($this->validDSNs as $regexp) { - if ($this->strictOn) + if (preg_match($regexp, $this->DSN)) { - $this->mysqli->options(MYSQLI_INIT_COMMAND, - 'SET SESSION sql_mode = CONCAT(@@sql_mode, ",", "STRICT_ALL_TABLES")'); - } - else - { - $this->mysqli->options(MYSQLI_INIT_COMMAND, 'SET SESSION sql_mode = - REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( - @@sql_mode, - "STRICT_ALL_TABLES,", ""), - ",STRICT_ALL_TABLES", ""), - "STRICT_ALL_TABLES", ""), - "STRICT_TRANS_TABLES,", ""), - ",STRICT_TRANS_TABLES", ""), - "STRICT_TRANS_TABLES", "")' - ); + return true; } } - if (is_array($this->encrypt)) - { - $ssl = []; - empty($this->encrypt['ssl_key']) || $ssl['key'] = $this->encrypt['ssl_key']; - empty($this->encrypt['ssl_cert']) || $ssl['cert'] = $this->encrypt['ssl_cert']; - empty($this->encrypt['ssl_ca']) || $ssl['ca'] = $this->encrypt['ssl_ca']; - empty($this->encrypt['ssl_capath']) || $ssl['capath'] = $this->encrypt['ssl_capath']; - empty($this->encrypt['ssl_cipher']) || $ssl['cipher'] = $this->encrypt['ssl_cipher']; - - if (! empty($ssl)) - { - if (isset($this->encrypt['ssl_verify'])) - { - if ($this->encrypt['ssl_verify']) - { - defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT') && - $this->mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true); - } - // Apparently (when it exists), setting MYSQLI_OPT_SSL_VERIFY_SERVER_CERT - // to FALSE didn't do anything, so PHP 5.6.16 introduced yet another - // constant ... - // - // https://secure.php.net/ChangeLog-5.php#5.6.16 - // https://bugs.php.net/bug.php?id=68344 - elseif (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT') && version_compare($this->mysqli->client_info, '5.6', '>=')) - { - $client_flags += MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; - } - } - - $client_flags += MYSQLI_CLIENT_SSL; - $this->mysqli->ssl_set( - $ssl['key'] ?? null, $ssl['cert'] ?? null, $ssl['ca'] ?? null, - $ssl['capath'] ?? null, $ssl['cipher'] ?? null - ); - } - } + return false; + } - try + /** + * Connect to the database. + * + * @param boolean $persistent + * @return mixed + */ + public function connect(bool $persistent = false) + { + if (empty($this->DSN) && !$this->isValidDSN()) { - if ($this->mysqli->real_connect($hostname, $this->username, $this->password, - $this->database, $port, $socket, $client_flags) - ) - { - // Prior to version 5.7.3, MySQL silently downgrades to an unencrypted connection if SSL setup fails - if (($client_flags & MYSQLI_CLIENT_SSL) && version_compare($this->mysqli->client_info, '5.7.3', '<=') - && empty($this->mysqli->query("SHOW STATUS LIKE 'ssl_cipher'") - ->fetch_object()->Value) - ) - { - $this->mysqli->close(); - $message = 'MySQLi was configured for an SSL connection, but got an unencrypted connection instead!'; - log_message('error', $message); - - if ($this->DBDebug) - { - throw new DatabaseException($message); - } - - return false; - } - - if (! $this->mysqli->set_charset($this->charset)) - { - log_message('error', - "Database: Unable to set the configured connection charset ('{$this->charset}')."); - $this->mysqli->close(); - - if ($this->db->debug) - { - throw new DatabaseException('Unable to set client connection character set: ' . $this->charset); - } - - return false; - } - - return $this->mysqli; - } + $this->buildDSN(); } - catch (\Throwable $e) - { - // Clean sensitive information from errors. - $msg = $e->getMessage(); - $msg = str_replace($this->username, '****', $msg); - $msg = str_replace($this->password, '****', $msg); + $func = ($persistent === true) ? 'oci_pconnect' : 'oci_connect'; - throw new \mysqli_sql_exception($msg, $e->getCode(), $e); - } - - return false; + return empty($this->charset) + ? $func($this->username, $this->password, $this->DSN) + : $func($this->username, $this->password, $this->DSN, $this->charset); } //-------------------------------------------------------------------- @@ -678,6 +598,74 @@ public function insertID(): int //-------------------------------------------------------------------- + /** + * Build a DSN from the provided parameters + * + * @return void + */ + protected function buildDSN() + { + $this->DSN === '' || $this->DSN = ''; + + // Legacy support for TNS in the hostname configuration field + $this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname); + + if (preg_match($this->validDSNs['tns'], $this->hostname)) + { + $this->DSN = $this->hostname; + return; + } + + $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE; + $easyConnectablePort = (( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : ''); + $easyConnectableDatabase = ($this->database !== '' ? '/'.ltrim($this->database, '/') : ''); + + if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) + { + /* If the hostname field isn't empty, doesn't contain + * ':' and/or '/' and if port and/or database aren't + * empty, then the hostname field is most likely indeed + * just a hostname. Therefore we'll try and build an + * Easy Connect string from these 3 settings, assuming + * that the database field is a service name. + */ + $this->DSN = $this->hostname + .$easyConnectablePort + .$easyConnectableDatabase; + + if (preg_match($this->validDSNs['ec'], $this->DSN)) + { + return; + } + } + + /* At this point, we can only try and validate the hostname and + * database fields separately as DSNs. + */ + if (preg_match($this->validDSNs['ec'], $this->hostname) OR preg_match($this->validDSNs['in'], $this->hostname)) + { + $this->DSN = $this->hostname; + return; + } + + $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database); + foreach ($valid_dsns as $regexp) + { + if (preg_match($regexp, $this->database)) + { + return; + } + } + + /* Well - OK, an empty string should work as well. + * PHP will try to use environment variables to + * determine which Oracle instance to connect to. + */ + $this->DSN = ''; + } + + //-------------------------------------------------------------------- + /** * Begin Transaction * From 96350b8af341595cf897615976950dbd0d2f462a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:40:17 +0900 Subject: [PATCH 004/184] style: fix style. --- system/Database/OCI8/Connection.php | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 3925337c8eee..674bc8812230 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -69,13 +69,16 @@ class Connection extends BaseConnection implements ConnectionInterface * * @var array */ - protected $reservedIdentifiers = ['*', 'rownum']; + protected $reservedIdentifiers = [ + '*', + 'rownum', + ]; protected $validDSNs = [ - 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS + 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS // Easy Connect string (Oracle 10g+) - 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', - 'in' => '/^[a-z0-9$_]+$/i' // Instance name (defined in tnsnames.ora) + 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', + 'in' => '/^[a-z0-9$_]+$/i',// Instance name (defined in tnsnames.ora) ]; //-------------------------------------------------------------------- @@ -91,28 +94,28 @@ class Connection extends BaseConnection implements ConnectionInterface /** * Statement ID * - * @var resource + * @var resource */ public $stmtId; /** * Commit mode flag * - * @var int + * @var integer */ public $commitMode = OCI_COMMIT_ON_SUCCESS; /** * Cursor ID * - * @var resource + * @var resource */ public $cursorId; /** * confirm DNS format. * - * @return bool + * @return boolean */ private function isValidDSN() : bool { @@ -135,7 +138,7 @@ private function isValidDSN() : bool */ public function connect(bool $persistent = false) { - if (empty($this->DSN) && !$this->isValidDSN()) + if (empty($this->DSN) && ! $this->isValidDSN()) { $this->buildDSN(); } @@ -616,9 +619,9 @@ protected function buildDSN() return; } - $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === FALSE && strpos($this->hostname, ':') === FALSE; - $easyConnectablePort = (( ! empty($this->port) && ctype_digit($this->port)) ? ':'.$this->port : ''); - $easyConnectableDatabase = ($this->database !== '' ? '/'.ltrim($this->database, '/') : ''); + $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === false && strpos($this->hostname, ':') === false; + $easyConnectablePort = (( ! empty($this->port) && ctype_digit($this->port)) ? ':' . $this->port : ''); + $easyConnectableDatabase = ($this->database !== '' ? '/' . ltrim($this->database, '/') : ''); if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) { @@ -630,8 +633,8 @@ protected function buildDSN() * that the database field is a service name. */ $this->DSN = $this->hostname - .$easyConnectablePort - .$easyConnectableDatabase; + . $easyConnectablePort + . $easyConnectableDatabase; if (preg_match($this->validDSNs['ec'], $this->DSN)) { @@ -642,7 +645,7 @@ protected function buildDSN() /* At this point, we can only try and validate the hostname and * database fields separately as DSNs. */ - if (preg_match($this->validDSNs['ec'], $this->hostname) OR preg_match($this->validDSNs['in'], $this->hostname)) + if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) { $this->DSN = $this->hostname; return; From b5e028692c752aac68d103ebcb69f23819bfba2e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:41:21 +0900 Subject: [PATCH 005/184] feat: add get forgen key data method. --- system/Database/OCI8/Connection.php | 41 +++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 674bc8812230..b2742ee31c12 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -497,22 +497,24 @@ public function _indexData(string $table): array */ public function _foreignKeyData(string $table): array { - $sql = ' - SELECT - tc.CONSTRAINT_NAME, - tc.TABLE_NAME, - kcu.COLUMN_NAME, - rc.REFERENCED_TABLE_NAME, - kcu.REFERENCED_COLUMN_NAME - FROM information_schema.TABLE_CONSTRAINTS AS tc - INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc - ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME - INNER JOIN information_schema.KEY_COLUMN_USAGE AS kcu - ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME - WHERE - tc.CONSTRAINT_TYPE = ' . $this->escape('FOREIGN KEY') . ' AND - tc.TABLE_SCHEMA = ' . $this->escape($this->database) . ' AND - tc.TABLE_NAME = ' . $this->escape($table); + $sql = 'SELECT + acc.constraint_name, + acc.table_name, + acc.column_name, + ccu.table_name foreign_table_name, + accu.column_name foreign_column_name + FROM all_cons_columns acc + JOIN all_constraints ac + ON acc.owner = ac.owner + AND acc.constraint_name = ac.constraint_name + JOIN all_constraints ccu + ON ac.r_owner = ccu.owner + AND ac.r_constraint_name = ccu.constraint_name + JOIN all_cons_columns accu + ON accu.constraint_name = ccu.constraint_name + AND accu.table_name = ccu.table_name + WHERE ac.constraint_type = ' . $this->escape('R') . ' + AND acc.table_name = ' . $this->escape($table); if (($query = $this->query($sql)) === false) { @@ -527,10 +529,9 @@ public function _foreignKeyData(string $table): array $obj->constraint_name = $row->CONSTRAINT_NAME; $obj->table_name = $row->TABLE_NAME; $obj->column_name = $row->COLUMN_NAME; - $obj->foreign_table_name = $row->REFERENCED_TABLE_NAME; - $obj->foreign_column_name = $row->REFERENCED_COLUMN_NAME; - - $retVal[] = $obj; + $obj->foreign_table_name = $row->FOREIGN_TABLE_NAME; + $obj->foreign_column_name = $row->FOREIGN_COLUMN_NAME; + $retVal[] = $obj; } return $retVal; From c6dcfda35457459075cd4f4791f4904e60f56bbe Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:54:57 +0900 Subject: [PATCH 006/184] feat: add close method. --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index b2742ee31c12..808fa424fdf4 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -173,7 +173,7 @@ public function reconnect() */ protected function _close() { - $this->connID->close(); + oci_close($this->connID); } //-------------------------------------------------------------------- From c4362a94ab562ee8c7d674146a32b976a16d1fa4 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:55:22 +0900 Subject: [PATCH 007/184] feat: add reconnect method. --- system/Database/OCI8/Connection.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 808fa424fdf4..fee3e74ec24d 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -160,8 +160,6 @@ public function connect(bool $persistent = false) */ public function reconnect() { - $this->close(); - $this->initialize(); } //-------------------------------------------------------------------- From 8444672f502a618e38f5f6c6a2f61ee4caae51e0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:55:40 +0900 Subject: [PATCH 008/184] feat: add setDatabase method. --- system/Database/OCI8/Connection.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index fee3e74ec24d..c0e5d795a4b6 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -185,23 +185,6 @@ protected function _close() */ public function setDatabase(string $databaseName): bool { - if ($databaseName === '') - { - $databaseName = $this->database; - } - - if (empty($this->connID)) - { - $this->initialize(); - } - - if ($this->connID->select_db($databaseName)) - { - $this->database = $databaseName; - - return true; - } - return false; } From 2ec92fed353324975dc3a85b09a6ec60fc49febd Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:56:14 +0900 Subject: [PATCH 009/184] feat: add get version method. --- system/Database/OCI8/Connection.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index c0e5d795a4b6..b52544c839e7 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -202,12 +202,16 @@ public function getVersion(): string return $this->dataCache['version']; } - if (empty($this->mysqli)) + if (! $this->connID || ($version_string = oci_server_version($this->connID)) === false) { - $this->initialize(); + return false; + } + elseif (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) + { + return $this->dataCache['version'] = $match[1]; } - return $this->dataCache['version'] = $this->mysqli->server_info; + return false; } //-------------------------------------------------------------------- From 4ea5d58f48fbfc183a563c50f42dc671b5e53613 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:56:38 +0900 Subject: [PATCH 010/184] feat: add exec method. --- system/Database/OCI8/Connection.php | 37 +++++++---------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index b52544c839e7..f5a9f7e0a720 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -221,43 +221,22 @@ public function getVersion(): string * * @param string $sql * - * @return mixed + * @return resource */ public function execute(string $sql) { - while ($this->connID->more_results()) + if ($this->resetStmtId === true) { - $this->connID->next_result(); - if ($res = $this->connID->store_result()) + $sql = rtrim($sql, ';'); + if (strpos('BEGIN', ltrim($sql)) === 0) { - $res->free(); + $sql .= ';'; } + $this->stmtId = oci_parse($this->connID, $sql); } - return $this->connID->query($this->prepQuery($sql)); - } - - //-------------------------------------------------------------------- - - /** - * Prep the query - * - * If needed, each database adapter can prep the query string - * - * @param string $sql an SQL query - * - * @return string - */ - protected function prepQuery(string $sql): string - { - // mysqli_affected_rows() returns 0 for "DELETE FROM TABLE" queries. This hack - // modifies the query so that it a proper number of affected rows is returned. - if ($this->deleteHack === true && preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $sql)) - { - return trim($sql) . ' WHERE 1=1'; - } - - return $sql; + oci_set_prefetch($this->stmtId, 1000); + return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; } //-------------------------------------------------------------------- From f1c68a16fd9aa7ca49d04da088538288421f65a4 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:57:36 +0900 Subject: [PATCH 011/184] feat: add get affected rows method. --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index f5a9f7e0a720..443600ee7372 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -248,7 +248,7 @@ public function execute(string $sql) */ public function affectedRows(): int { - return $this->connID->affected_rows ?? 0; + return oci_num_rows($this->stmtId); } //-------------------------------------------------------------------- From 359817fa2c32f2ad45b172e49891526f99585ff2 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:58:04 +0900 Subject: [PATCH 012/184] feat: remove escape method. --- system/Database/OCI8/Connection.php | 62 ----------------------------- 1 file changed, 62 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 443600ee7372..955fb75a2eef 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -253,72 +253,10 @@ public function affectedRows(): int //-------------------------------------------------------------------- - /** - * Platform-dependant string escape - * - * @param string $str - * @return string - */ - protected function _escapeString(string $str): string - { - if (is_bool($str)) - { - return $str; - } - - if (! $this->connID) - { - $this->initialize(); - } - - return $this->connID->real_escape_string($str); - } - //-------------------------------------------------------------------- - /** - * Escape Like String Direct - * There are a few instances where MySQLi queries cannot take the - * additional "ESCAPE x" parameter for specifying the escape character - * in "LIKE" strings, and this handles those directly with a backslash. - * - * @param string|string[] $str Input string - * @return string|string[] - */ - public function escapeLikeStringDirect($str) - { - if (is_array($str)) - { - foreach ($str as $key => $val) - { - $str[$key] = $this->escapeLikeStringDirect($val); - } - - return $str; - } - - $str = $this->_escapeString($str); - - // Escape LIKE condition wildcards - return str_replace([ - $this->likeEscapeChar, - '%', - '_', - ], [ - '\\' . $this->likeEscapeChar, - '\\' . '%', - '\\' . '_', - ], $str - ); - - return $str; - } - - //-------------------------------------------------------------------- - /** * Generates the SQL for listing tables in a platform-dependent manner. - * Uses escapeLikeStringDirect(). * * @param boolean $prefixLimit * From bdd528c3cb605fe51a23af3c812706fc1f133156 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:58:30 +0900 Subject: [PATCH 013/184] feat: add get table list method. --- system/Database/OCI8/Connection.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 955fb75a2eef..b10b79a65aee 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -264,11 +264,12 @@ public function affectedRows(): int */ protected function _listTables(bool $prefixLimit = false): string { - $sql = 'SHOW TABLES FROM ' . $this->escapeIdentifiers($this->database); + $sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"'; if ($prefixLimit !== false && $this->DBPrefix !== '') { - return $sql . " LIKE '" . $this->escapeLikeStringDirect($this->DBPrefix) . "%'"; + return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' " + . sprintf($this->likeEscapeStr, $this->likeEscapeChar); } return $sql; From e43a5f2881aca3cddf633a0a3c273cdebc3cd30b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:59:01 +0900 Subject: [PATCH 014/184] feat: add column list method. --- system/Database/OCI8/Connection.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index b10b79a65aee..580b6fe79f37 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -286,7 +286,18 @@ protected function _listTables(bool $prefixLimit = false): string */ protected function _listColumns(string $table = ''): string { - return 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, true, null, false); + if (strpos($table, '.') !== false) + { + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; + } + + 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)); } //-------------------------------------------------------------------- From 31a9f1236d9434d9dfe0c4888628c2313d67baf1 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:59:32 +0900 Subject: [PATCH 015/184] feat: add get field list method. --- system/Database/OCI8/Connection.php | 41 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 580b6fe79f37..81bd889a81ff 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -311,27 +311,50 @@ protected function _listColumns(string $table = ''): string */ public function _fieldData(string $table): array { - $table = $this->protectIdentifiers($table, true, null, false); + if (strpos($table, '.') !== false) + { + sscanf($table, '%[^.].%s', $owner, $table); + } + else + { + $owner = $this->username; + } - if (($query = $this->query('SHOW COLUMNS FROM ' . $table)) === false) + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE + FROM ALL_TAB_COLUMNS + WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . ' + AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === false) { throw new DatabaseException(lang('Database.failGetFieldData')); } $query = $query->getResultObject(); - $retVal = []; + $retval = []; for ($i = 0, $c = count($query); $i < $c; $i++) { - $retVal[$i] = new \stdClass(); - $retVal[$i]->name = $query[$i]->Field; + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; - sscanf($query[$i]->Type, '%[a-z](%d)', $retVal[$i]->type, $retVal[$i]->max_length); + $length = ($query[$i]->CHAR_LENGTH > 0) + ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; + if ($length === null) + { + $length = $query[$i]->DATA_LENGTH; + } + $retval[$i]->max_length = $length; - $retVal[$i]->default = $query[$i]->Default; - $retVal[$i]->primary_key = (int)($query[$i]->Key === 'PRI'); + $default = $query[$i]->DATA_DEFAULT; + if ($default === null && $query[$i]->NULLABLE === 'N') + { + $default = ''; + } + $retval[$i]->default = $default; } - return $retVal; + return $retval; } //-------------------------------------------------------------------- From 15343145c8bd243b1febe79fb66b15c09ed9596a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 15:59:49 +0900 Subject: [PATCH 016/184] feat: add get index list method. --- system/Database/OCI8/Connection.php | 71 ++++++++++++----------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 81bd889a81ff..ad9b7fb9b6a5 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -365,62 +365,51 @@ public function _fieldData(string $table): array * @param string $table * @return \stdClass[] * @throws DatabaseException - * @throws \LogicException */ public function _indexData(string $table): array { - $table = $this->protectIdentifiers($table, true, null, false); - - if (($query = $this->query('SHOW INDEX FROM ' . $table)) === false) + if (strpos($table, '.') !== false) { - throw new DatabaseException(lang('Database.failGetIndexData')); + sscanf($table, '%[^.].%s', $owner, $table); } - - if (! $indexes = $query->getResultArray()) + else { - return []; + $owner = $this->username; } - $keys = []; + $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME ' + . ' FROM ALL_IND_COLUMNS uic ' + . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME ' + . 'WHERE TABLE_NAME = ' . $this->escape(strtolower($this->DBPrefix . $table)) . ' ' + . 'AND TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' ' + . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION'; + + if (($query = $this->query($sql)) === false) + { + throw new DatabaseException(lang('Database.failGetIndexData')); + } + $query = $query->getResultObject(); - foreach ($indexes as $index) + $retVal = []; + $constraintTypes = [ + 'P' => 'PRIMARY', + 'U' => 'UNIQUE', + ]; + foreach ($query as $row) { - if (empty($keys[$index['Key_name']])) + if (isset($retVal[$row->INDEX_NAME])) { - $keys[$index['Key_name']] = new \stdClass(); - $keys[$index['Key_name']]->name = $index['Key_name']; - - if ($index['Key_name'] === 'PRIMARY') - { - $type = 'PRIMARY'; - } - elseif ($index['Index_type'] === 'FULLTEXT') - { - $type = 'FULLTEXT'; - } - elseif ($index['Non_unique']) - { - if ($index['Index_type'] === 'SPATIAL') - { - $type = 'SPATIAL'; - } - else - { - $type = 'INDEX'; - } - } - else - { - $type = 'UNIQUE'; - } - - $keys[$index['Key_name']]->type = $type; + $retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME; + continue; } - $keys[$index['Key_name']]->fields[] = $index['Column_name']; + $retVal[$row->INDEX_NAME] = new \stdClass(); + $retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME; + $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME]; + $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX'; } - return $keys; + return $retVal; } //-------------------------------------------------------------------- From 19d1a1866273a6416525326402286ecec17917a5 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:09:13 +0900 Subject: [PATCH 017/184] feat: add disable foreign key method. --- system/Database/OCI8/Connection.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index ad9b7fb9b6a5..b1b75ec9d2be 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -472,7 +472,21 @@ public function _foreignKeyData(string $table): array */ protected function _disableForeignKeyChecks() { - return 'SET FOREIGN_KEY_CHECKS=0'; + return << Date: Tue, 31 Dec 2019 16:10:12 +0900 Subject: [PATCH 018/184] feat: add enable foreign key method. --- system/Database/OCI8/Connection.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index b1b75ec9d2be..b363d47e127e 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -498,7 +498,21 @@ protected function _disableForeignKeyChecks() */ protected function _enableForeignKeyChecks() { - return 'SET FOREIGN_KEY_CHECKS=1'; + return << Date: Tue, 31 Dec 2019 16:11:04 +0900 Subject: [PATCH 019/184] feat: add get cursor method. --- system/Database/OCI8/Connection.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index b363d47e127e..4f0693d626cb 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -517,6 +517,18 @@ protected function _enableForeignKeyChecks() //-------------------------------------------------------------------- + /** + * Get cursor. Returns a cursor from the database + * + * @return resource + */ + public function getCursor() + { + return $this->cursorId = oci_new_cursor($this->connID); + } + + //-------------------------------------------------------------------- + /** * Returns the last error code and message. * From 6c4ef9b49f4a01528a14d08be145b847de0d7a88 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:11:50 +0900 Subject: [PATCH 020/184] feat: add call stored procedure method. --- system/Database/OCI8/Connection.php | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 4f0693d626cb..7b897a376bd4 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -529,6 +529,55 @@ public function getCursor() //-------------------------------------------------------------------- + /** + * Stored Procedure. Executes a stored procedure + * + * @param string package name in which the stored procedure is in + * @param string stored procedure name to execute + * @param array parameters + * @return mixed + * + * params array keys + * + * KEY OPTIONAL NOTES + * name no the name of the parameter should be in : format + * value no the value of the parameter. If this is an OUT or IN OUT parameter, + * this should be a reference to a variable + * type yes the type of the parameter + * length yes the max size of the parameter + */ + public function storedProcedure(string $package, string $procedure, array $params) + { + if ($package === '' || $procedure === '') + { + throw new DatabaseException(lang('Database.invalidArgument', [$package . $procedure])); + } + + // Build the query string + $sql = 'BEGIN ' . $package . '.' . $procedure . '('; + + $have_cursor = false; + foreach ($params as $param) + { + $sql .= $param['name'] . ','; + + if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) + { + $have_cursor = true; + } + } + $sql = trim($sql, ',') . '); END;'; + + $this->resetStmtId = false; + $this->stmtId = oci_parse($this->connID, $sql); + $this->bindParams($params); + $result = $this->query($sql, false, $have_cursor); + $this->resetStmtId = true; + return $result; + } + + // -------------------------------------------------------------------- + /** * Returns the last error code and message. * From f7880e54c3120ece06f51c63f5d3cb21f4c915b4 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:12:28 +0900 Subject: [PATCH 021/184] feat: add bind parameter method. --- system/Database/OCI8/Connection.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 7b897a376bd4..f02dbf192bfc 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -578,6 +578,35 @@ public function storedProcedure(string $package, string $procedure, array $param // -------------------------------------------------------------------- + /** + * Bind parameters + * + * @param array $params + * @return void + */ + protected function bindParams($params) + { + if (! is_array($params) || ! is_resource($this->stmtId)) + { + return; + } + + foreach ($params as $param) + { + foreach (['name', 'value', 'type', 'length'] as $val) + { + if (! isset($param[$val])) + { + $param[$val] = ''; + } + } + + oci_bind_by_name($this->stmtId, $param['name'], $param['value'], $param['length'], $param['type']); + } + } + + // -------------------------------------------------------------------- + /** * Returns the last error code and message. * From 1de69671f1d8ab481a231efddf479d1375af4028 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:12:49 +0900 Subject: [PATCH 022/184] feat: add error method. --- system/Database/OCI8/Connection.php | 32 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index f02dbf192bfc..7c59ca1843f4 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -618,18 +618,32 @@ protected function bindParams($params) */ public function error(): array { - if (! empty($this->mysqli->connect_errno)) + // oci_error() returns an array that already contains + // 'code' and 'message' keys, but it can return false + // if there was no error .... + if (is_resource($this->cursorId)) { - return [ - 'code' => $this->mysqli->connect_errno, - 'message' => $this->mysqli->connect_error, - ]; + $error = oci_error($this->cursorId); + } + elseif (is_resource($this->stmtId)) + { + $error = oci_error($this->stmtId); + } + elseif (is_resource($this->connID)) + { + $error = oci_error($this->connID); + } + else + { + $error = oci_error(); } - return [ - 'code' => $this->connID->errno, - 'message' => $this->connID->error, - ]; + return is_array($error) + ? $error + : [ + 'code' => '', + 'message' => '', + ]; } //-------------------------------------------------------------------- From 194e68345c6b7db315f9f6d20d1f0afecc1bac56 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:26:57 +0900 Subject: [PATCH 023/184] feat: add insert_id method. --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 7c59ca1843f4..e620874869e2 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -655,7 +655,7 @@ public function error(): array */ public function insertID(): int { - return $this->connID->insert_id; + throw new DatabaseException(lang('Database.featureUnavailable')); } //-------------------------------------------------------------------- From eed3529f9b5ad846cefd152b98e0c3a4ce2b141a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:27:29 +0900 Subject: [PATCH 024/184] feat: add transaction start method. --- system/Database/OCI8/Connection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index e620874869e2..ee3a3ea5ec89 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -735,12 +735,12 @@ protected function buildDSN() */ protected function _transBegin(): bool { - $this->connID->autocommit(false); + $this->commitMode = OCI_NO_AUTO_COMMIT; - return $this->connID->begin_transaction(); + return true; } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Commit Transaction From f940c360b3e68c0bf60d104a987988f6177e50b0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:28:07 +0900 Subject: [PATCH 025/184] feat: add transaction commit method. --- system/Database/OCI8/Connection.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index ee3a3ea5ec89..a1a6835b4617 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -749,17 +749,12 @@ protected function _transBegin(): bool */ protected function _transCommit(): bool { - if ($this->connID->commit()) - { - $this->connID->autocommit(true); - - return true; - } + $this->commitMode = OCI_COMMIT_ON_SUCCESS; - return false; + return oci_commit($this->connID); } - //-------------------------------------------------------------------- + // -------------------------------------------------------------------- /** * Rollback Transaction From e7600ebfaf98f06216f4869bef72a387f2ab586d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:28:38 +0900 Subject: [PATCH 026/184] feat: add transaction rollback method. --- system/Database/OCI8/Connection.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index a1a6835b4617..e201502776a4 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -763,14 +763,10 @@ protected function _transCommit(): bool */ protected function _transRollback(): bool { - if ($this->connID->rollback()) - { - $this->connID->autocommit(true); - - return true; - } + $this->commitMode = OCI_COMMIT_ON_SUCCESS; - return false; + return oci_rollback($this->connID); } - //-------------------------------------------------------------------- + + // -------------------------------------------------------------------- } From 917e7ec2db445feed5a7770e8216538c0328c943 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:33:32 +0900 Subject: [PATCH 027/184] feat: add necessary property. --- system/Database/OCI8/Builder.php | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index d81b91d074e7..8ffd06092249 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -45,13 +45,41 @@ */ class Builder extends BaseBuilder { - /** * Identifier escape character * * @var string */ - protected $escapeChar = '`'; + protected $escapeChar = '"'; + + /** + * ORDER BY random keyword + * + * @var array + */ + protected $randomKeyword = [ + 'DBMS_RANDOM.RANDOM' // @todo: 未検証 + ]; + + /** + * COUNT string + * + * @used-by CI_DB_driver::count_all() + * @used-by BaseBuilder::count_all_results() + * + * @var string + */ + protected $countString = 'SELECT COUNT(1) AS '; + + /** + * Limit used flag + * + * If we use LIMIT, we'll add a field that will + * throw off num_fields later. + * + * @var boolean + */ + protected $limitUsed = false; /** * FROM tables From 44bd4cabe131e981f0d51490cc18729d7fa7db05 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:37:13 +0900 Subject: [PATCH 028/184] feat: add insert batch method. --- system/Database/OCI8/Builder.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 8ffd06092249..0cc2097c8fd2 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -82,23 +82,29 @@ class Builder extends BaseBuilder protected $limitUsed = false; /** - * FROM tables + * Insert batch statement * - * Groups tables in FROM clauses if needed, so there is no confusion - * about operator precedence. + * Generates a platform-specific insert string from the supplied data. * - * Note: This is only used (and overridden) by MySQL. + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values * * @return string */ - protected function _fromTables(): string + protected function _insertBatch(string $table, array $keys, array $values): string { - if ( ! empty($this->QBJoin) && count($this->QBFrom) > 1) + $keys = implode(', ', $keys); + $sql = "INSERT ALL\n"; + + for ($i = 0, $c = count($values); $i < $c; $i++) { - return '('.implode(', ', $this->QBFrom).')'; + $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; } - return implode(', ', $this->QBFrom); + return $sql . 'SELECT * FROM dual'; + } + } } From 626e53d9f7d01692b73c2b8a00a8e125b7e2390e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:38:07 +0900 Subject: [PATCH 029/184] feat: add truncate method. --- system/Database/OCI8/Builder.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 0cc2097c8fd2..402426639aca 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -105,6 +105,23 @@ protected function _insertBatch(string $table, array $keys, array $values): stri return $sql . 'SELECT * FROM dual'; } + //-------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string $table The table name + * + * @return string + */ + protected function _truncate(string $table): string + { + return 'TRUNCATE TABLE ' . $table; } } From 945d03dd8942f029bde13041c9195c3772e165c6 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:38:36 +0900 Subject: [PATCH 030/184] feat: add delete method. --- system/Database/OCI8/Builder.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 402426639aca..c556383b7537 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -124,4 +124,27 @@ protected function _truncate(string $table): string return 'TRUNCATE TABLE ' . $table; } + //-------------------------------------------------------------------- + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param mixed $where The where clause + * @param integer $limit The limit clause + * @param boolean $reset_data + * + * @return mixed + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + */ + public function delete($where = '', int $limit = null, bool $reset_data = true) + { + if (! empty($limit)) + { + $this->QBLimit = $limit; + } + + return parent::delete($where, null, $reset_data); + } } From 836928eca25f3cd4607445083e713893b8a59b58 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:39:05 +0900 Subject: [PATCH 031/184] feat: add delete method. --- system/Database/OCI8/Builder.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index c556383b7537..f350aee36c29 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -147,4 +147,26 @@ public function delete($where = '', int $limit = null, bool $reset_data = true) return parent::delete($where, null, $reset_data); } + + //-------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table The table name + * + * @return string + */ + protected function _delete(string $table): string + { + if ($this->QBLimit) + { + $this->where('rownum <= ', $this->QBLimit, false); + $this->QBLimit = false; + } + + return parent::_delete($table); + } } From d6b52235b8f48584c395c26f27b7cd361cf80aa0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:39:23 +0900 Subject: [PATCH 032/184] feat: add limit method. --- system/Database/OCI8/Builder.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index f350aee36c29..6b7f3d86e1e2 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -169,4 +169,30 @@ protected function _delete(string $table): string return parent::_delete($table); } + + //-------------------------------------------------------------------- + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + * + * @return string + */ + protected function _limit(string $sql): string + { + if (version_compare($this->db->getVersion(), '12.1', '>=')) + { + // OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->QBOrderBy) && $sql .= ' ORDER BY 1'; + + return $sql . ' OFFSET ' . (int) $this->QBOffset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; + } + + $this->limitUsed = true; + return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($this->QBOffset + $this->QBLimit + 1) . ')' + . ($this->QBOffset ? ' WHERE rnum >= ' . ($this->QBOffset + 1) : ''); + } } From 10b6d80b7e7b543dc2f6e729a24d18fe1570cbd9 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:39:34 +0900 Subject: [PATCH 033/184] feat: add reset select method. --- system/Database/OCI8/Builder.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 6b7f3d86e1e2..be206e330b70 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -195,4 +195,13 @@ protected function _limit(string $sql): string return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($this->QBOffset + $this->QBLimit + 1) . ')' . ($this->QBOffset ? ' WHERE rnum >= ' . ($this->QBOffset + 1) : ''); } + + /** + * Resets the query builder values. Called by the get() function + */ + protected function resetSelect() + { + $this->limitUsed = false; + parent::resetSelect(); + } } From 26277b8ba87e15c3f208508951d1270911f5921d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:50:50 +0900 Subject: [PATCH 034/184] remove: unncessary method and property. --- system/Database/OCI8/Forge.php | 74 ---------------------------------- 1 file changed, 74 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 02c132bf4a81..aa9eaeb55ad6 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -49,108 +49,34 @@ class Forge extends \CodeIgniter\Database\Forge * * @var string */ - protected $createDatabaseStr = 'CREATE DATABASE %s CHARACTER SET %s COLLATE %s'; /** - * DROP CONSTRAINT statement * * @var string */ - protected $dropConstraintStr = 'ALTER TABLE %s DROP FOREIGN KEY %s'; /** - * CREATE TABLE keys flag * - * Whether table keys are created from within the - * CREATE TABLE statement. - * - * @var boolean */ - protected $createTableKeys = true; /** - * UNSIGNED support * - * @var array */ - protected $_unsigned = [ - 'TINYINT', - 'SMALLINT', - 'MEDIUMINT', - 'INT', - 'INTEGER', - 'BIGINT', - 'REAL', - 'DOUBLE', - 'DOUBLE PRECISION', - 'FLOAT', - 'DECIMAL', - 'NUMERIC', - ]; /** - * Table Options list which required to be quoted * - * @var array */ - protected $_quoted_table_options = [ - 'COMMENT', - 'COMPRESSION', - 'CONNECTION', - 'DATA DIRECTORY', - 'INDEX DIRECTORY', - 'ENCRYPTION', - 'PASSWORD', - ]; /** * NULL value representation in CREATE/ALTER TABLE statements * * @var string */ - protected $_null = 'NULL'; - - //-------------------------------------------------------------------- /** - * CREATE TABLE attributes * - * @param array $attributes Associative array of table attributes - * @return string */ - protected function _createTableAttributes(array $attributes): string - { - $sql = ''; - foreach (array_keys($attributes) as $key) - { - if (is_string($key)) - { - $sql .= ' ' . strtoupper($key) . ' = '; - - if (in_array(strtoupper($key), $this->_quoted_table_options)) - { - $sql .= $this->db->escape($attributes[$key]); - } - else - { - $sql .= $this->db->escapeString($attributes[$key]); - } - } - } - - if (! empty($this->db->charset) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET')) - { - $sql .= ' DEFAULT CHARACTER SET = ' . $this->db->escapeString($this->db->charset); - } - - if (! empty($this->db->DBCollat) && ! strpos($sql, 'COLLATE')) - { - $sql .= ' COLLATE = ' . $this->db->escapeString($this->db->DBCollat); - } - - return $sql; - } //-------------------------------------------------------------------- From 08fc17056d32af2a85c40060938c759467ebde7c Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:51:40 +0900 Subject: [PATCH 035/184] feat: add necessary properties. --- system/Database/OCI8/Forge.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index aa9eaeb55ad6..e149edbba4b2 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -49,34 +49,56 @@ class Forge extends \CodeIgniter\Database\Forge * * @var string */ + protected $createDatabaseStr = false; /** + * CREATE TABLE IF statement * * @var string */ + protected $createTableIfStr = false; /** + * DROP TABLE IF EXISTS statement * + * @var string */ + protected $dropTableIfStr = false; /** + * DROP DATABASE statement * + * @var string */ + protected $dropDatabaseStr = false; /** + * UNSIGNED support * + * @var boolean|array */ + protected $unsigned = false; /** * NULL value representation in CREATE/ALTER TABLE statements * * @var string */ + protected $null = 'NULL'; /** + * RENAME TABLE statement * + * @var string */ + protected $renameTableStr = 'ALTER TABLE %s RENAME TO %s'; + /** + * DROP CONSTRAINT statement + * + * @var string + */ + protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s'; //-------------------------------------------------------------------- From 02c86e0a3966b80545f3741d3b78b12b740a1ef1 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:51:59 +0900 Subject: [PATCH 036/184] style: fix doc comment style. --- system/Database/OCI8/Forge.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index e149edbba4b2..c538136edd6f 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -105,9 +105,10 @@ class Forge extends \CodeIgniter\Database\Forge /** * ALTER TABLE * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * * @return string|string[] */ protected function _alterTable(string $alter_type, string $table, $field) From e18141884c3e0573b308a5533b604867e461a2fd Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:52:57 +0900 Subject: [PATCH 037/184] feat: add alter table method. --- system/Database/OCI8/Forge.php | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index c538136edd6f..7e58c853d617 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -117,30 +117,48 @@ protected function _alterTable(string $alter_type, string $table, $field) { return parent::_alterTable($alter_type, $table, $field); } + elseif ($alter_type === 'CHANGE') + { + $alter_type = 'MODIFY'; + } - $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); - foreach ($field as $i => $data) + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + $sqls = []; + for ($i = 0, $c = count($field); $i < $c; $i++) { - if ($data['_literal'] !== false) + if ($field[$i]['_literal'] !== false) { - $field[$i] = ($alter_type === 'ADD') ? "\n\tADD " . $data['_literal'] : "\n\tMODIFY " . $data['_literal']; + $field[$i] = "\n\t" . $field[$i]['_literal']; } else { - if ($alter_type === 'ADD') + $field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]); + + if (! empty($field[$i]['comment'])) { - $field[$i]['_literal'] = "\n\tADD "; + $sqls[] = 'COMMENT ON COLUMN ' + . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name']) + . ' IS ' . $field[$i]['comment']; } - else + + if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) { - $field[$i]['_literal'] = empty($data['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; + $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); } - $field[$i] = $field[$i]['_literal'] . $this->_processColumn($field[$i]); + $field[$i] = "\n\t" . $field[$i]['_literal']; } } - return [$sql . implode(',', $field)]; + $sql .= ' ' . $alter_type . ' '; + $sql .= (count($field) === 1) + ? $field[0] + : '(' . implode(',', $field) . ')'; + + // RENAME COLUMN must be executed after MODIFY + array_unshift($sqls, $sql); + return $sqls; } //-------------------------------------------------------------------- From 0ce7e2ed781a9ba769451dcac31ab5d22c71f19f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 16:55:28 +0900 Subject: [PATCH 038/184] feat: add auto increment method. --- system/Database/OCI8/Forge.php | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 7e58c853d617..247ed01f55e0 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -163,6 +163,27 @@ protected function _alterTable(string $alter_type, string $table, $field) //-------------------------------------------------------------------- + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeAutoIncrement(array &$attributes, array &$field) + { + if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true + && stripos($field['type'], 'NUMBER') !== false + && version_compare($this->db->getVersion(), '12.1', '>=') + ) + { + $field['auto_increment'] = ' GENERATED ALWAYS AS IDENTITY'; + } + } + + //-------------------------------------------------------------------- + /** * Process column * @@ -173,21 +194,7 @@ protected function _processColumn(array $field): string { $extra_clause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; - if (empty($extra_clause) && isset($field['first']) && $field['first'] === true) - { - $extra_clause = ' FIRST'; - } - return $this->db->escapeIdentifiers($field['name']) - . (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name'])) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['null'] - . $field['default'] - . $field['auto_increment'] - . $field['unique'] - . (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment']) - . $extra_clause; } //-------------------------------------------------------------------- From 9b0a9d823733e7285ab93e6e406e1e24f3ac53a2 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:04:49 +0900 Subject: [PATCH 039/184] feat: add processColumn method. --- system/Database/OCI8/Forge.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 247ed01f55e0..94077c9fbfb7 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -187,12 +187,20 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) /** * Process column * - * @param array $field + * @param array $field + * * @return string */ protected function _processColumn(array $field): string { - $extra_clause = isset($field['after']) ? ' AFTER ' . $this->db->escapeIdentifiers($field['after']) : ''; + return $this->db->escapeIdentifiers($field['name']) + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['default'] + . $field['auto_increment'] + . $field['null'] + . $field['unique']; + } } From 0538af6ac68d9851cc13937ddc5299b50f887078 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:06:47 +0900 Subject: [PATCH 040/184] feat: merge column type method. see: https://docs.oracle.com/cd/E12151_01/doc.150/e12155/oracle_mysql_compared.htm#BABHHAJC --- system/Database/OCI8/Forge.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 94077c9fbfb7..e47d53fce2c6 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -202,7 +202,41 @@ protected function _processColumn(array $field): string . $field['unique']; } + //-------------------------------------------------------------------- + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * + * @return void + */ + protected function _attributeType(array &$attributes) + { + // Reset field lengths for data types that don't support it + // Usually overridden by drivers + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'INT': + case 'INTEGER': + case 'BIGINT': + case 'NUMERIC': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'DATETIME': + $attributes['TYPE'] = 'DATE'; + return; + case 'TEXT': + case 'VARCHAR': + $attributes['TYPE'] = 'VARCHAR2'; + return; + default: return; + } } //-------------------------------------------------------------------- From 3f8d25286a7d307d6c8c04a24de0ac9536e31b7e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:10:48 +0900 Subject: [PATCH 041/184] feat: add drop table method. --- system/Database/OCI8/Forge.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index e47d53fce2c6..e65648b5be11 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -241,6 +241,35 @@ protected function _attributeType(array &$attributes) //-------------------------------------------------------------------- + /** + * Drop Table + * + * Generates a platform-specific DROP TABLE string + * + * @param string $table Table name + * @param boolean $if_exists Whether to add an IF EXISTS condition + * @param boolean $cascade + * + * @return string + */ + protected function _dropTable(string $table, bool $if_exists, bool $cascade): string + { + $sql = parent::_dropTable($table, $if_exists, $cascade); + + if ($sql !== '' && $cascade === true) + { + $sql .= ' CASCADE CONSTRAINTS PURGE'; + } + elseif ($sql !== '') + { + $sql .= ' PURGE'; + } + + return $sql; + } + + //-------------------------------------------------------------------- + /** * Process indexes * From 53e44213a3b8731074012f874357825c6582c0b8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:14:09 +0900 Subject: [PATCH 042/184] feat: add forgen key method. --- system/Database/OCI8/Forge.php | 46 ++++++++++++++-------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index e65648b5be11..85a5bdc3d3e2 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -271,46 +271,38 @@ protected function _dropTable(string $table, bool $if_exists, bool $cascade): st //-------------------------------------------------------------------- /** - * Process indexes + * Process foreign keys + * + * @param string $table Table name * - * @param string $table (ignored) * @return string */ - protected function _processIndexes(string $table): string + protected function _processForeignKeys(string $table): string { $sql = ''; - for ($i = 0, $c = count($this->keys); $i < $c; $i ++) + $allowActions = [ + 'CASCADE', + 'SET NULL', + 'NO ACTION', + ]; + + if (count($this->foreignKeys) > 0) { - if (is_array($this->keys[$i])) + foreach ($this->foreignKeys as $field => $fkey) { - for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2 ++) + $name_index = $table . '_' . $field . '_fk'; + + $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)) { - if (! isset($this->fields[$this->keys[$i][$i2]])) - { - unset($this->keys[$i][$i2]); - continue; - } + $sql .= ' ON DELETE ' . $fkey['onDelete']; } } - elseif (! isset($this->fields[$this->keys[$i]])) - { - unset($this->keys[$i]); - continue; - } - - is_array($this->keys[$i]) || $this->keys[$i] = [$this->keys[$i]]; - - $unique = in_array($i, $this->uniqueKeys) ? 'UNIQUE ' : ''; - - $sql .= ",\n\t{$unique}KEY " . $this->db->escapeIdentifiers(implode('_', $this->keys[$i])) - . ' (' . implode(', ', $this->db->escapeIdentifiers($this->keys[$i])) . ')'; } - $this->keys = []; - return $sql; } - - //-------------------------------------------------------------------- } From 4236bae0faa758f128bbc01b3928499c19db1971 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:15:03 +0900 Subject: [PATCH 043/184] feat: add prepear method. --- system/Database/OCI8/PreparedQuery.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 6ef0d9e7bc80..b4999f4bc6a2 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -62,14 +62,17 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface */ public function _prepare(string $sql, array $options = []) { - // Mysqli driver doesn't like statements - // with terminating semicolons. $sql = rtrim($sql, ';'); + if (strpos('BEGIN', ltrim($sql)) === 0) + { + $sql .= ';'; + } - if (! $this->statement = $this->db->mysqli->prepare($sql)) + if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) { - $this->errorCode = $this->db->mysqli->errno; - $this->errorString = $this->db->mysqli->error; + $error = oci_error($this->db->connID); + $this->errorCode = $error['code'] ?? 0; + $this->errorString = $error['message'] ?? ''; } return $this; From d2fc77ee4d77cb6ff0937919a9fbf28263888980 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:17:58 +0900 Subject: [PATCH 044/184] feat: add bind process. --- system/Database/OCI8/PreparedQuery.php | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index b4999f4bc6a2..c2691ac80f9e 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -95,32 +95,12 @@ public function _execute(array $data): bool throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } - // First off -bind the parameters - $bindTypes = ''; - - // Determine the type string - foreach ($data as $item) + foreach ($data as $key => $val) { - if (is_integer($item)) - { - $bindTypes .= 'i'; - } - elseif (is_numeric($item)) - { - $bindTypes .= 'd'; - } - else - { - $bindTypes .= 's'; - } + oci_bind_by_name($this->statement, ':' . $key, $val); } - // Bind it - $this->statement->bind_param($bindTypes, ...$data); - - $success = $this->statement->execute(); - - return $success; + return oci_execute($this->statement, $this->db->commitMode); } //-------------------------------------------------------------------- From 08153a5c36067979c5c80bfef2d761a4a8f40ce3 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:19:52 +0900 Subject: [PATCH 045/184] feat: add get result method. --- system/Database/OCI8/PreparedQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index c2691ac80f9e..fc35d15e44ef 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -112,7 +112,7 @@ public function _execute(array $data): bool */ public function _getResult() { - return $this->statement->get_result(); + return $this->statement; } //-------------------------------------------------------------------- From 0c7fc2f60a4f31920fe7319a1b2ae371be95ac5d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:20:15 +0900 Subject: [PATCH 046/184] feat: add parameterize method. --- system/Database/OCI8/PreparedQuery.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index fc35d15e44ef..ff5808846d96 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -116,4 +116,25 @@ public function _getResult() } //-------------------------------------------------------------------- + + /** + * Replaces the ? placeholders with :1, :2, etc parameters for use + * within the prepared query. + * + * @param string $sql + * + * @return string + */ + public function parameterize(string $sql): string + { + // Track our current value + $count = 0; + + $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { + $count ++; + return ":{$count}"; + }, $sql); + + return $sql; + } } From 6dea7b1206b52dd81d7027baf920179129c3959d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:21:17 +0900 Subject: [PATCH 047/184] feat: add get field count method. --- system/Database/OCI8/Result.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index e1cb7425f716..1b82b57aea47 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -55,7 +55,7 @@ class Result extends BaseResult implements ResultInterface */ public function getFieldCount(): int { - return $this->resultID->field_count; + return oci_num_fields($this->resultID); } //-------------------------------------------------------------------- From e9bd2ca1598537b63782f5b88a8c8d06ab43b33e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:22:16 +0900 Subject: [PATCH 048/184] feat: add get fieldNames method. --- system/Database/OCI8/Result.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 1b82b57aea47..581754de0b7d 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -67,14 +67,9 @@ public function getFieldCount(): int */ public function getFieldNames(): array { - $fieldNames = []; - $this->resultID->field_seek(0); - while ($field = $this->resultID->fetch_field()) - { - $fieldNames[] = $field->name; - } - - return $fieldNames; + return array_map(function ($field_index) { + return oci_field_name($this->resultID, $field_index); + }, range(1, $this->getFieldCount())); } //-------------------------------------------------------------------- From 739c74eec9b04fa2c60b627276b576c7f4a349ad Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:22:53 +0900 Subject: [PATCH 049/184] feat: add get field data method. --- system/Database/OCI8/Result.php | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 581754de0b7d..b2a8255bb3ae 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -81,20 +81,15 @@ public function getFieldNames(): array */ public function getFieldData(): array { - $retVal = []; - $fieldData = $this->resultID->fetch_fields(); - - foreach ($fieldData as $i => $data) - { - $retVal[$i] = new \stdClass(); - $retVal[$i]->name = $data->name; - $retVal[$i]->type = $data->type; - $retVal[$i]->max_length = $data->max_length; - $retVal[$i]->primary_key = (int) ($data->flags & 2); - $retVal[$i]->default = $data->def; - } - - return $retVal; + return array_map(function ($field_index) { + return (object) [ + 'name' => oci_field_name($this->resultID, $field_index), + 'type' => oci_field_type($this->resultID, $field_index), + 'max_length' => oci_field_size($this->resultID, $field_index), + // 'primary_key' = (int) ($data->flags & 2), + // 'default' = $data->def, + ]; + }, range(1, $this->getFieldCount())); } //-------------------------------------------------------------------- From dfdb6e0141d56843f175855cf4042a1e85bbffdb Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:23:20 +0900 Subject: [PATCH 050/184] feat: add free result method. --- system/Database/OCI8/Result.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index b2a8255bb3ae..a475215481bf 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -103,8 +103,8 @@ public function freeResult() { if (is_object($this->resultID)) { - $this->resultID->free(); - $this->resultID = false; + oci_free_statement($this->resultID); + $this->resultID = null; } } From da3674ba8f4da0cc0a4537a55bfa81d841a531e8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:23:36 +0900 Subject: [PATCH 051/184] feat: add data Seek method. --- system/Database/OCI8/Result.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index a475215481bf..ae6fab6bec26 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -121,7 +121,8 @@ public function freeResult() */ public function dataSeek(int $n = 0) { - return $this->resultID->data_seek($n); + // We can't support data seek by oci + return false; } //-------------------------------------------------------------------- From 0e379350a1869db8b9b8ef5914b674786d0581cf Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:23:56 +0900 Subject: [PATCH 052/184] feat: fetchAssoc method. --- system/Database/OCI8/Result.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index ae6fab6bec26..1fcf0b5c608e 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -136,7 +136,7 @@ public function dataSeek(int $n = 0) */ protected function fetchAssoc() { - return $this->resultID->fetch_assoc(); + return oci_fetch_assoc($this->resultID); } //-------------------------------------------------------------------- From 3c42fd22c1452d67092c9786568b12e924ebbcc2 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:24:08 +0900 Subject: [PATCH 053/184] feat: add fetchObject method. --- system/Database/OCI8/Result.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 1fcf0b5c608e..85faae1db6a2 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -150,13 +150,26 @@ protected function fetchAssoc() * * @return object|boolean|Entity */ - protected function fetchObject(string $className = 'stdClass') + protected function fetchObject(string $className = \stdClass::class) { - if (is_subclass_of($className, Entity::class)) + $row = oci_fetch_object($this->resultID); + + if ($className === 'stdClass' || ! $row) + { + return $row; + } + elseif (is_subclass_of($className, Entity::class)) { - return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data); + return (new $className())->setAttributes((array) $row); } - return $this->resultID->fetch_object($className); + + $instance = new $className(); + foreach ($row as $key => $value) + { + $instance->$key = $value; + } + + return $instance; } //-------------------------------------------------------------------- From 9fe2661c8b1c4f0be0cb60b0a578d80ba345dc35 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 17:25:04 +0900 Subject: [PATCH 054/184] feat: define property. --- system/Database/OCI8/Utils.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/system/Database/OCI8/Utils.php b/system/Database/OCI8/Utils.php index 070c0d5d7348..9ed063e5022a 100644 --- a/system/Database/OCI8/Utils.php +++ b/system/Database/OCI8/Utils.php @@ -52,14 +52,7 @@ class Utils extends BaseUtils * * @var string */ - protected $listDatabases = 'SHOW DATABASES'; - - /** - * OPTIMIZE TABLE statement - * - * @var string - */ - protected $optimizeTable = 'OPTIMIZE TABLE %s'; + protected $listDatabases = 'SELECT TABLESPACE_NAME FROM USER_TABLESPACES'; //-------------------------------------------------------------------- From 82a22dc311453aa2f28415dcfad804df66918698 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 18:02:03 +0900 Subject: [PATCH 055/184] test: add test for oci listDatabase method. --- tests/system/Database/Live/DbUtilsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index 2e8beda5f82f..2eaea6500000 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -76,7 +76,7 @@ public function testUtilsListDatabases() { $util = (new Database())->loadUtils($this->db); - if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre', 'SQLSRV'], true)) { + if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre', 'SQLSRV', 'OCI8'], true)) { $databases = $util->listDatabases(); $this->assertContains($this->db->getDatabase(), $databases); From e07cee0b46697c3733de8e4e00392056ebc2ab55 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 18:03:35 +0900 Subject: [PATCH 056/184] test: add databaseExists test for oci8. --- tests/system/Database/Live/DbUtilsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index 2eaea6500000..cf2753e590fc 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -92,7 +92,7 @@ public function testUtilsDatabaseExist() { $util = (new Database())->loadUtils($this->db); - if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre', 'SQLSRV'], true)) { + if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre', 'SQLSRV', 'OCI8'], true)) { $exist = $util->databaseExists($this->db->getDatabase()); $this->assertTrue($exist); From b26946ec554e79d4e9d80fe826430422d46c075d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 31 Dec 2019 18:04:58 +0900 Subject: [PATCH 057/184] fix: skip not support oci8 test. --- tests/system/Database/Live/DbUtilsTest.php | 14 ++++++++++++++ tests/system/Database/Live/ForgeTest.php | 17 +++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index cf2753e590fc..e997981b55ec 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -108,6 +108,13 @@ public function testUtilsOptimizeDatabase() { $util = (new Database())->loadUtils($this->db); + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped( + 'Unsupported feature of the oracle database platform.' + ); + } + $d = $util->optimizeDatabase(); $this->assertTrue((bool) $d); @@ -147,6 +154,13 @@ public function testUtilsOptimizeTable() { $util = (new Database())->loadUtils($this->db); + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped( + 'Unsupported feature of the oracle database platform.' + ); + } + $d = $util->optimizeTable('db_job'); $this->assertTrue((bool) $d); diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 08ca65e03b95..07a0aea4b9b2 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -44,6 +44,9 @@ protected function setUp(): void public function testCreateDatabase() { + if ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped('OCI8 does not support create database.'); + } $databaseCreated = $this->forge->createDatabase('test_forge_database'); $this->assertTrue($databaseCreated); @@ -76,7 +79,11 @@ public function testCreateDatabaseIfNotExistsWithDb() public function testDropDatabase() { - if ($this->db->DBDriver === 'SQLite3') { + if ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped('OCI8 does not support drop database.'); + } + if ($this->db->DBDriver === 'SQLite3') + { $this->markTestSkipped('SQLite3 requires file path to drop database'); } @@ -169,6 +176,8 @@ public function testCreateTableApplyBigInt() $this->assertSame(strtolower($fieldsData[0]->type), 'bigint'); } elseif ($this->db->DBDriver === 'SQLite3') { $this->assertSame(strtolower($fieldsData[0]->type), 'integer'); + } elseif ($this->db->DBDriver === 'OCI8') { + $this->assertSame(strtolower($fieldsData[0]->type), 'number'); } elseif ($this->db->DBDriver === 'SQLSRV') { $this->assertSame(strtolower($fieldsData[0]->type), 'bigint'); } @@ -178,7 +187,11 @@ public function testCreateTableApplyBigInt() public function testCreateTableWithAttributes() { - if ($this->db->DBDriver === 'SQLite3') { + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped('OCI8 does not support comments on tables or columns.'); + } + if ($this->db->DBDriver === 'SQLite3') $this->markTestSkipped('SQLite3 does not support comments on tables or columns.'); } From 0a684f45687ad920faf858bbfaf5a5d8ac578eb5 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 3 Jan 2020 18:30:08 +0900 Subject: [PATCH 058/184] style: fix style. --- tests/system/Database/Live/ForgeTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 07a0aea4b9b2..1cc9c1153dae 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -44,7 +44,8 @@ protected function setUp(): void public function testCreateDatabase() { - if ($this->db->DBDriver === 'OCI8') { + if ($this->db->DBDriver === 'OCI8') + { $this->markTestSkipped('OCI8 does not support create database.'); } $databaseCreated = $this->forge->createDatabase('test_forge_database'); @@ -79,7 +80,8 @@ public function testCreateDatabaseIfNotExistsWithDb() public function testDropDatabase() { - if ($this->db->DBDriver === 'OCI8') { + if ($this->db->DBDriver === 'OCI8') + { $this->markTestSkipped('OCI8 does not support drop database.'); } if ($this->db->DBDriver === 'SQLite3') From 1e33e52fb85add1afebb3c8349259509dfeba7de Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 3 Jan 2020 18:30:49 +0900 Subject: [PATCH 059/184] test: add test for get foreign key method. --- tests/system/Database/Live/ForgeTest.php | 32 ++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 1cc9c1153dae..959d5f618e5d 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -424,13 +424,23 @@ public function testForeignKey() $this->forge->addKey('id', true); $this->forge->addForeignKey('users_id', 'forge_test_users', 'id', 'CASCADE', 'CASCADE'); - $this->forge->createTable('forge_test_invoices', true, $attributes); + $tableName = 'forge_test_invoices'; + if ($this->db->DBDriver === 'OCI8') + { + $tableName = 'forge_test_inv'; + } - $foreignKeyData = $this->db->getForeignKeyData('forge_test_invoices'); + $this->forge->createTable($tableName, true, $attributes); + + $foreignKeyData = $this->db->getForeignKeyData($tableName); if ($this->db->DBDriver === 'SQLite3') { $this->assertSame($foreignKeyData[0]->constraint_name, 'users_id to db_forge_test_users.id'); $this->assertSame($foreignKeyData[0]->sequence, 0); + } elseif ($this->db->DBDriver === 'OCI8') { + $this->assertEquals($foreignKeyData[0]->constraint_name, $this->db->DBPrefix . 'forge_test_inv_users_id_fk'); + $this->assertEquals($foreignKeyData[0]->column_name, 'users_id'); + $this->assertEquals($foreignKeyData[0]->foreign_column_name, 'id'); } else { $this->assertSame($foreignKeyData[0]->constraint_name, $this->db->DBPrefix . 'forge_test_invoices_users_id_foreign'); $this->assertSame($foreignKeyData[0]->column_name, 'users_id'); @@ -439,7 +449,7 @@ public function testForeignKey() $this->assertSame($foreignKeyData[0]->table_name, $this->db->DBPrefix . 'forge_test_invoices'); $this->assertSame($foreignKeyData[0]->foreign_table_name, $this->db->DBPrefix . 'forge_test_users'); - $this->forge->dropTable('forge_test_invoices', true); + $this->forge->dropTable($tableName, true); $this->forge->dropTable('forge_test_users', true); } @@ -693,15 +703,23 @@ public function testDropForeignKey() $this->forge->addKey('id', true); $this->forge->addForeignKey('users_id', 'forge_test_users', 'id', 'CASCADE', 'CASCADE'); - $this->forge->createTable('forge_test_invoices', true, $attributes); + $tableName = 'forge_test_invoices'; + $foreignKeyName = 'forge_test_invoices_users_id_foreign'; + if ($this->db->DBDriver === 'OCI8') + { + $tableName = 'forge_test_inv'; + $foreignKeyName = 'forge_test_inv_users_id_fk'; + } - $this->forge->dropForeignKey('forge_test_invoices', 'forge_test_invoices_users_id_foreign'); + $this->forge->createTable($tableName, true, $attributes); - $foreignKeyData = $this->db->getForeignKeyData('forge_test_invoices'); + $this->forge->dropForeignKey($tableName, $foreignKeyName); + + $foreignKeyData = $this->db->getForeignKeyData($tableName); $this->assertEmpty($foreignKeyData); - $this->forge->dropTable('forge_test_invoices', true); + $this->forge->dropTable($tableName, true); $this->forge->dropTable('forge_test_users', true); } From 65ff0573c804f8c7b9b611bb43a3d1980a5f855f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 3 Jan 2020 18:32:20 +0900 Subject: [PATCH 060/184] test: add test for add field method. --- tests/system/Database/Live/ForgeTest.php | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 959d5f618e5d..8f6962960276 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -760,7 +760,12 @@ public function testAddColumn() public function testAddFields() { - $this->forge->dropTable('forge_test_fields', true); + $tableName = 'forge_test_fields'; + if ($this->db->DBDriver === 'OCI8') + { + $tableName = 'getestfield'; + } + $this->forge->dropTable($tableName, true); $this->forge->addField([ 'id' => [ @@ -787,18 +792,14 @@ public function testAddFields() $this->forge->addKey('id', true); $this->forge->addUniqueKey(['username', 'active']); - $this->forge->createTable('forge_test_fields', true); + $create = $this->forge->createTable($tableName, true); - $fieldsNames = $this->db->getFieldNames('forge_test_fields'); - $fieldsData = $this->db->getFieldData('forge_test_fields'); + $fieldsNames = $this->db->getFieldNames($tableName); + $fieldsData = $this->db->getFieldData($tableName); - $this->forge->dropTable('forge_test_fields', true); + $this->forge->dropTable($tableName, true); $this->assertIsArray($fieldsNames); - $this->assertContains('id', $fieldsNames); - $this->assertContains('username', $fieldsNames); - $this->assertContains('name', $fieldsNames); - $this->assertContains('active', $fieldsNames); $fields = ['id', 'name', 'username', 'active']; $this->assertContains($fieldsData[0]->name, $fields); @@ -836,6 +837,12 @@ public function testAddFields() $this->assertSame('varchar', $fieldsData[1]->type); $this->assertNull($fieldsData[1]->default); $this->assertSame(255, (int) $fieldsData[1]->max_length); + } elseif ($this->db->DBDriver === 'OCI8') { + //Check types + $this->assertSame('NUMBER', $fieldsData[0]->type); + $this->assertSame('VARCHAR2', $fieldsData[1]->type); + $this->assertSame(32, $fieldsData[0]->max_length); + $this->assertSame(255, $fieldsData[1]->max_length); } else { $this->fail(sprintf('DB driver "%s" is not supported.', $this->db->DBDriver)); } From 14fd82c6d446de9e2bfa947a98b1a2d96c6ab028 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:39:41 +0900 Subject: [PATCH 061/184] fix: remove comment. --- system/Database/OCI8/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index be206e330b70..2e2076b3008a 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -58,7 +58,7 @@ class Builder extends BaseBuilder * @var array */ protected $randomKeyword = [ - 'DBMS_RANDOM.RANDOM' // @todo: 未検証 + 'DBMS_RANDOM.RANDOM' ]; /** From 01d92b864801835980bca45275ce2d66f59bb28f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:40:07 +0900 Subject: [PATCH 062/184] fix: remove as keyword. --- system/Database/OCI8/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 2e2076b3008a..b89575af9899 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -69,7 +69,7 @@ class Builder extends BaseBuilder * * @var string */ - protected $countString = 'SELECT COUNT(1) AS '; + protected $countString = 'SELECT COUNT(1) '; /** * Limit used flag From 6f19c1bf1c5a28e50234ea2d218fa6df797ac691 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:40:37 +0900 Subject: [PATCH 063/184] feat: insertBatch for auto increment. --- system/Database/OCI8/Builder.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index b89575af9899..4c602fe3e693 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -94,8 +94,24 @@ class Builder extends BaseBuilder */ protected function _insertBatch(string $table, array $keys, array $values): string { - $keys = implode(', ', $keys); - $sql = "INSERT ALL\n"; + $keys = implode(', ', $keys); + $has_primary_key = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); + + // ORA-00001 measures + if ($has_primary_key) + { + $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; + $select_query_values = []; + + for ($i = 0, $c = count($values); $i < $c; $i++) + { + $select_query_values[] = 'SELECT ' . substr(substr($values[$i], 1), 0, -1) . ' FROM DUAL'; + } + + return $sql . implode("\n UNION ALL \n", $select_query_values) . "\n)"; + } + + $sql = "INSERT ALL\n"; for ($i = 0, $c = count($values); $i < $c; $i++) { From 4a26bd583ab6a118b7eaf4789bf0fe1325706e99 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:41:16 +0900 Subject: [PATCH 064/184] feat: add replace command. --- system/Database/OCI8/Builder.php | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 4c602fe3e693..7199f031f104 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -123,6 +123,74 @@ protected function _insertBatch(string $table, array $keys, array $values): stri //-------------------------------------------------------------------- + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $values The insert values + * + * @return string + */ + protected function _replace(string $table, array $keys, array $values): string + { + $field_names = array_map(function ($column_name) { + return trim($column_name, '"'); + }, $keys); + + $unique_indexes = array_filter($this->db->getIndexData($table), function ($index) use ($field_names) { + $has_all_fields = count(array_intersect($index->fields, $field_names)) === count($index->fields); + + return (($index->type === 'PRIMARY') && $has_all_fields); + }); + $replaceable_fields = array_filter($keys, function ($column_name) use ($unique_indexes) { + foreach ($unique_indexes as $index) + { + if (in_array(trim($column_name, '"'), $index->fields, true)) + { + return false; + } + } + + return true; + }); + + $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; + + $sql .= implode(', ', array_map(function ($column_name, $value) { + return $value . ' ' . $column_name; + }, $keys, $values)); + + $sql .= ' FROM DUAL) "_replace" ON ( '; + + $on_list = []; + $on_list[] = '1 != 1'; + + foreach ($unique_indexes as $index) + { + $on_list[] = '(' . implode(' AND ', array_map(function ($column_name) use ($table) { + return $table . '."' . $column_name . '" = "_replace"."' . $column_name . '"'; + }, $index->fields)) . ')'; + } + + $sql .= implode(' OR ', $on_list) . ') WHEN MATCHED THEN UPDATE SET '; + + $sql .= implode(', ', array_map(function ($column_name) { + return $column_name . ' = "_replace".' . $column_name; + }, $replaceable_fields)); + + $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceable_fields) . ') VALUES '; + $sql .= ' (' . implode(', ', array_map(function ($column_name) { + return '"_replace".' . $column_name; + }, $replaceable_fields)) . ')'; + + return $sql; + } + + //-------------------------------------------------------------------- + /** * Truncate statement * From 5b9167f2821b1f17bd73d72842f47dd85b2d607f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:41:49 +0900 Subject: [PATCH 065/184] fix: update method for limit. --- system/Database/OCI8/Builder.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 7199f031f104..ed96cd55f1be 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -256,6 +256,37 @@ protected function _delete(string $table): string //-------------------------------------------------------------------- + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table the Table name + * @param array $values the Update data + * + * @return string + */ + protected function _update(string $table, array $values): string + { + $valStr = []; + + foreach ($values as $key => $val) + { + $valStr[] = $key . ' = ' . $val; + } + + if ($this->QBLimit) + { + $this->where('rownum <= ', $this->QBLimit, false); + } + + return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) + . $this->compileWhereHaving('QBWhere') + . $this->compileOrderBy(); + } + + //-------------------------------------------------------------------- + /** * LIMIT string * From 5798e8e8e96078685527272a58475c3de1131073 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:44:28 +0900 Subject: [PATCH 066/184] feat: add insertId method, @todo: has a strange design. --- system/Database/OCI8/Builder.php | 22 ++++++++++ system/Database/OCI8/Connection.php | 56 +++++++++++++++++++++++++- system/Database/OCI8/PreparedQuery.php | 38 +++++++++++++++-- 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index ed96cd55f1be..17fac1198ec1 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -319,4 +319,26 @@ protected function resetSelect() $this->limitUsed = false; parent::resetSelect(); } + + //-------------------------------------------------------------------- + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + * + * @return string + */ + protected function _insert(string $table, array $keys, array $unescapedKeys): string + { + // Has a strange design. + // Processing to get the table where the last insert was performed for insertId method. + $this->db->latestInsertedTableName = $table; + + return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ') RETURNING ROWID INTO :CI_OCI8_ROWID'; + } } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index e201502776a4..bc8ef8e1baee 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -112,6 +112,20 @@ class Connection extends BaseConnection implements ConnectionInterface */ public $cursorId; + /** + * RowID + * + * @var integer|null + */ + public $rowId; + + /** + * Latest inserted table name. + * + * @var string|null + */ + public $latestInsertedTableName; + /** * confirm DNS format. * @@ -228,13 +242,18 @@ public function execute(string $sql) if ($this->resetStmtId === true) { $sql = rtrim($sql, ';'); - if (strpos('BEGIN', ltrim($sql)) === 0) + if (strpos(ltrim($sql), 'BEGIN') === 0) { $sql .= ';'; } $this->stmtId = oci_parse($this->connID, $sql); } + if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) + { + oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); + } + oci_set_prefetch($this->stmtId, 1000); return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; } @@ -655,7 +674,40 @@ public function error(): array */ public function insertID(): int { - throw new DatabaseException(lang('Database.featureUnavailable')); + if (empty($this->rowId) || empty($this->latestInsertedTableName)) { + return 0; + } + + $indexs = $this->getIndexData($this->latestInsertedTableName); + $field_datas = $this->getFieldData($this->latestInsertedTableName); + + if (!$indexs || !$field_datas) { + return 0; + } + + $column_type_list = array_column($field_datas, 'type', 'name'); + $primary_column_name = ''; + foreach ((is_array($indexs) ? $indexs : [] ) as $index ) { + if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) { + continue; + } + + $primary_column_name = $this->protectIdentifiers($index->fields[0], false, false); + $primary_column_type = $column_type_list[$primary_column_name]; + + if ($primary_column_type !== 'NUMBER') { + continue; + } + } + + if (!$primary_column_name) { + return 0; + } + + $table = $this->protectIdentifiers($this->latestInsertedTableName, true); + $query = $this->query('SELECT '.$this->protectIdentifiers($primary_column_name, false).' SEQ FROM '.$table . ' WHERE ROWID = ?', $this->rowId)->getRow(); + + return (int)($query->SEQ ?? 0); } //-------------------------------------------------------------------- diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index ff5808846d96..e543d7301ac7 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -46,6 +46,31 @@ */ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface { + private $isCollectRowId; + + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param string $sql + * @param array $options Passed to the connection's prepare statement. + * @param string $queryClass + * + * @return mixed + */ + public function prepare(string $sql, array $options = [], string $queryClass = 'CodeIgniter\\Database\\Query') + { + $this->isCollectRowId = false; + + if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') { + $this->isCollectRowId = true; + } + + return parent::prepare($sql, $options, $queryClass); + } /** * Prepares the query against the database, and saves the connection @@ -95,9 +120,15 @@ public function _execute(array $data): bool throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } - foreach ($data as $key => $val) + $last_key = 0; + foreach (array_keys($data) as $key) { - oci_bind_by_name($this->statement, ':' . $key, $val); + oci_bind_by_name($this->statement, ':' . $key, $data[$key]); + $last_key = $key; + } + + if ($this->isCollectRowId) { + oci_bind_by_name($this->statement, ':' . (++$last_key), $this->db->rowId, 255); } return oci_execute($this->statement, $this->db->commitMode); @@ -131,8 +162,7 @@ public function parameterize(string $sql): string $count = 0; $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { - $count ++; - return ":{$count}"; + return ":".($count++); }, $sql); return $sql; From ea52a9e025060473c53ba2575d6bc3ac6b664016 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:45:12 +0900 Subject: [PATCH 067/184] feat: add nullable flag. --- system/Database/OCI8/Connection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index bc8ef8e1baee..15d1c50eade6 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -370,7 +370,8 @@ public function _fieldData(string $table): array { $default = ''; } - $retval[$i]->default = $default; + $retval[$i]->default = $default; + $retval[$i]->nullable = $query[$i]->NULLABLE === 'Y'; } return $retval; From 3a83365fab7efb5a4ff61d0dbcad00e78b10131f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:45:52 +0900 Subject: [PATCH 068/184] fix: remove dbPrefix. --- system/Database/OCI8/Connection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 15d1c50eade6..5fae1beb22ff 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -398,10 +398,10 @@ public function _indexData(string $table): array } $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME ' - . ' FROM ALL_IND_COLUMNS uic ' + . ' FROM ALL_IND_COLUMNS AIC ' . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME ' - . 'WHERE TABLE_NAME = ' . $this->escape(strtolower($this->DBPrefix . $table)) . ' ' - . 'AND TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' ' + . 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' ' + . 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' ' . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION'; if (($query = $this->query($sql)) === false) From cd5db61f58a4d74f065567610a72d5ca831ba7bb Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:46:40 +0900 Subject: [PATCH 069/184] style: fix code style. --- system/Database/OCI8/Connection.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 5fae1beb22ff..fac040854da9 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -675,38 +675,44 @@ public function error(): array */ public function insertID(): int { - if (empty($this->rowId) || empty($this->latestInsertedTableName)) { + if (empty($this->rowId) || empty($this->latestInsertedTableName)) + { return 0; } - $indexs = $this->getIndexData($this->latestInsertedTableName); + $indexs = $this->getIndexData($this->latestInsertedTableName); $field_datas = $this->getFieldData($this->latestInsertedTableName); - if (!$indexs || !$field_datas) { + if (! $indexs || ! $field_datas) + { return 0; } - $column_type_list = array_column($field_datas, 'type', 'name'); + $column_type_list = array_column($field_datas, 'type', 'name'); $primary_column_name = ''; - foreach ((is_array($indexs) ? $indexs : [] ) as $index ) { - if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) { + foreach ((is_array($indexs) ? $indexs : [] ) as $index) + { + if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) + { continue; } $primary_column_name = $this->protectIdentifiers($index->fields[0], false, false); $primary_column_type = $column_type_list[$primary_column_name]; - if ($primary_column_type !== 'NUMBER') { + if ($primary_column_type !== 'NUMBER') + { continue; } } - if (!$primary_column_name) { + if (! $primary_column_name) + { return 0; } $table = $this->protectIdentifiers($this->latestInsertedTableName, true); - $query = $this->query('SELECT '.$this->protectIdentifiers($primary_column_name, false).' SEQ FROM '.$table . ' WHERE ROWID = ?', $this->rowId)->getRow(); + $query = $this->query('SELECT ' . $this->protectIdentifiers($primary_column_name, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); return (int)($query->SEQ ?? 0); } From 5315adab1154b54a94e77a27996e40197b548cb9 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:47:30 +0900 Subject: [PATCH 070/184] fix: ORA-01451 --- system/Database/OCI8/Forge.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 85a5bdc3d3e2..4fe2826e0e67 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -122,10 +122,25 @@ protected function _alterTable(string $alter_type, string $table, $field) $alter_type = 'MODIFY'; } - $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); - $sqls = []; + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + $nullable_map = array_column($this->db->getFieldData($table), 'nullable', 'name'); + $sqls = []; for ($i = 0, $c = count($field); $i < $c; $i++) { + if ($alter_type === 'MODIFY') + { + // If a null constraint is added to a column with a null constraint, + // ORA-01451 will occur, + // so add null constraint is used only when it is different from the current null constraint. + $is_want_to_add_null = (strpos($field[$i]['null'], ' NOT') === false); + $current_null_addable = $nullable_map[$field[$i]['name']]; + + if ($is_want_to_add_null === $current_null_addable) + { + $field[$i]['null'] = ''; + } + } + if ($field[$i]['_literal'] !== false) { $field[$i] = "\n\t" . $field[$i]['_literal']; From 74db15b57c617d274055b6033b709a68dadf20a8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:48:05 +0900 Subject: [PATCH 071/184] fix: Fix to be able to update identity. --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 4fe2826e0e67..253aac5e754a 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -193,7 +193,7 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) && version_compare($this->db->getVersion(), '12.1', '>=') ) { - $field['auto_increment'] = ' GENERATED ALWAYS AS IDENTITY'; + $field['auto_increment'] = ' GENERATED BY DEFAULT AS IDENTITY'; } } From b6a96fa7c838e57a170ef4791f35ce528c81a933 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:49:09 +0900 Subject: [PATCH 072/184] feat: Specified default length. --- system/Database/OCI8/Forge.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 253aac5e754a..6b0906a935ed 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -235,11 +235,16 @@ protected function _attributeType(array &$attributes) switch (strtoupper($attributes['TYPE'])) { case 'TINYINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 3; case 'SMALLINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 5; case 'MEDIUMINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 7; case 'INT': case 'INTEGER': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 11; case 'BIGINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 19; case 'NUMERIC': $attributes['TYPE'] = 'NUMBER'; return; @@ -248,7 +253,8 @@ protected function _attributeType(array &$attributes) return; case 'TEXT': case 'VARCHAR': - $attributes['TYPE'] = 'VARCHAR2'; + $attributes['TYPE'] = 'VARCHAR2'; + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 255; return; default: return; } From d9ebce1520f0e9dc2a0ff2dd8dbd4766423f95f6 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:49:53 +0900 Subject: [PATCH 073/184] style: fix code style. --- system/Database/OCI8/PreparedQuery.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index e543d7301ac7..7fd98611c937 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -65,7 +65,8 @@ public function prepare(string $sql, array $options = [], string $queryClass = ' { $this->isCollectRowId = false; - if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') { + if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') + { $this->isCollectRowId = true; } @@ -127,7 +128,8 @@ public function _execute(array $data): bool $last_key = $key; } - if ($this->isCollectRowId) { + if ($this->isCollectRowId) + { oci_bind_by_name($this->statement, ':' . (++$last_key), $this->db->rowId, 255); } @@ -162,7 +164,7 @@ public function parameterize(string $sql): string $count = 0; $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { - return ":".($count++); + return ':' . ($count++); }, $sql); return $sql; From cad4a7e796ae61bbd6ea6c80a57f4d6e29cde431 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:50:18 +0900 Subject: [PATCH 074/184] fix: Fixed typing errors. --- system/Database/OCI8/Result.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 85faae1db6a2..2df16ab7273a 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -101,10 +101,10 @@ public function getFieldData(): array */ public function freeResult() { - if (is_object($this->resultID)) + if (is_resource($this->resultID)) { oci_free_statement($this->resultID); - $this->resultID = null; + $this->resultID = false; } } From 17b2e1f33d62f232b776f7c558554a8882e9ed8a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:51:02 +0900 Subject: [PATCH 075/184] fix: dbname to upper. --- tests/system/Database/Live/DbUtilsTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index e997981b55ec..61e44628822f 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -108,12 +108,12 @@ public function testUtilsOptimizeDatabase() { $util = (new Database())->loadUtils($this->db); - if ($this->db->DBDriver === 'OCI8') - { - $this->markTestSkipped( - 'Unsupported feature of the oracle database platform.' - ); - } + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped( + 'Unsupported feature of the oracle database platform.' + ); + } $d = $util->optimizeDatabase(); From 2576896be75cfa91eb6c4f807930f142ea5a8abd Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:51:29 +0900 Subject: [PATCH 076/184] test: skip to not support oci. --- tests/system/Database/Live/ForgeTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 8f6962960276..8b3be710da64 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -55,6 +55,10 @@ public function testCreateDatabase() public function testCreateDatabaseIfNotExists() { + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped('OCI8 does not support create database.'); + } $dbName = 'test_forge_database_exist'; $databaseCreateIfNotExists = $this->forge->createDatabase($dbName, true); @@ -67,6 +71,10 @@ public function testCreateDatabaseIfNotExists() public function testCreateDatabaseIfNotExistsWithDb() { + if ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped('OCI8 does not support create database.'); + } $dbName = 'test_forge_database_exist'; $this->forge->createDatabase($dbName); From 60b55e42b21033869ae005401d2ddcd88e2e5f61 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:52:16 +0900 Subject: [PATCH 077/184] test: fix column type. --- tests/system/Database/Live/ForgeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 8b3be710da64..d45bb29b31a7 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -850,6 +850,7 @@ public function testAddFields() $this->assertSame('NUMBER', $fieldsData[0]->type); $this->assertSame('VARCHAR2', $fieldsData[1]->type); $this->assertSame(32, $fieldsData[0]->max_length); + $this->assertSame('', $fieldsData[1]->default); $this->assertSame(255, $fieldsData[1]->max_length); } else { $this->fail(sprintf('DB driver "%s" is not supported.', $this->db->DBDriver)); From 850b598169290a352cc5b73e63e2e55669021e9b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:52:53 +0900 Subject: [PATCH 078/184] test: add getIndexData. --- tests/system/Database/Live/ForgeTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index d45bb29b31a7..7337d24e62f7 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -928,6 +928,18 @@ public function testCompositeKey() $this->assertSame($keys['db_forge_test_1_code_active']->fields, ['code', 'active']); $this->assertSame($keys['db_forge_test_1_code_active']->type, 'UNIQUE'); } + elseif ($this->db->DBDriver === 'OCI8') + { + $this->assertEquals($keys['pk_db_forge_test_1']->name, 'pk_db_forge_test_1'); + $this->assertEquals($keys['pk_db_forge_test_1']->fields, ['id']); + $this->assertEquals($keys['pk_db_forge_test_1']->type, 'PRIMARY'); + $this->assertEquals($keys['db_forge_test_1_code_company']->name, 'db_forge_test_1_code_company'); + $this->assertEquals($keys['db_forge_test_1_code_company']->fields, ['code', 'company']); + $this->assertEquals($keys['db_forge_test_1_code_company']->type, 'INDEX'); + $this->assertEquals($keys['db_forge_test_1_code_active']->name, 'db_forge_test_1_code_active'); + $this->assertEquals($keys['db_forge_test_1_code_active']->fields, ['code', 'active']); + $this->assertEquals($keys['db_forge_test_1_code_active']->type, 'UNIQUE'); + } $this->forge->dropTable('forge_test_1', true); } From 96e2fcb2d15557df4b3cc582eeb7b3768e108486 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:53:56 +0900 Subject: [PATCH 079/184] test: add escape. --- tests/system/Database/Live/GroupTest.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/system/Database/Live/GroupTest.php b/tests/system/Database/Live/GroupTest.php index 705172ac97f0..32ccbce6765e 100644 --- a/tests/system/Database/Live/GroupTest.php +++ b/tests/system/Database/Live/GroupTest.php @@ -42,7 +42,7 @@ public function testHavingBy() $result = $this->db->table('job') ->select('name') ->groupBy('name') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->get() ->getResultArray(); @@ -55,7 +55,7 @@ public function testOrHavingBy() ->select('id') ->groupBy('id') ->having('id >', 3) - ->orHaving('SUM(id) > 2') + ->orHaving('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->get() ->getResult(); @@ -114,7 +114,7 @@ public function testOrHavingNotIn() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orHavingNotIn('name', ['Developer', 'Politician']) ->get() ->getResult(); @@ -174,7 +174,7 @@ public function testOrNotHavingLike() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orNotHavingLike('name', 'ian') ->get() ->getResult(); @@ -191,9 +191,9 @@ public function testAndHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->havingGroupStart() - ->having('SUM(id) <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -209,9 +209,9 @@ public function testOrHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orHavingGroupStart() - ->having('SUM(id) <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -228,9 +228,9 @@ public function testNotHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->notHavingGroupStart() - ->having('SUM(id) <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -246,9 +246,9 @@ public function testOrNotHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(id) > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orNotHavingGroupStart() - ->having('SUM(id) < 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') < 2') ->havingLike('name', 'o') ->havingGroupEnd() ->get() From 8a41297d9cfb69b508ec992527dbbe6747520451 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:54:48 +0900 Subject: [PATCH 080/184] test: add last insert id. --- tests/system/Database/Live/PreparedQueryTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index acfc17277982..fcb4769a8801 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -69,6 +69,11 @@ public function testPrepareReturnsPreparedQuery() $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES ({$placeholders})"; } + if ($this->db->DBDriver === 'OCI8') + { + $expected .= ' RETURNING ROWID INTO ?'; + } + $this->assertSame($expected, $this->query->getQueryString()); } From cc75e37156f4699105142276f400fd8364c490ea Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:55:44 +0900 Subject: [PATCH 081/184] test: add test case for not escape. --- tests/system/Database/Live/UpdateTest.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index f7e2fb5e4b32..1e1e989ff5d5 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -201,6 +201,28 @@ public function testUpdatePeriods() */ public function testSetWithoutEscape() { + if ($this->db->DBDriver === 'OCI8') + { + $forge = \Config\Database::forge($this->DBGroup); + $forge->modifyColumn('job', [ + 'description' => [ + 'name' => 'DESCRIPTION', + ], + 'name' => [ + 'name' => 'NAME', + ], + ]); + $this->db->table('job') + ->set('description', 'name', false) + ->update(); + + $this->seeInDatabase('job', [ + 'NAME' => 'Developer', + 'DESCRIPTION' => 'Developer', + ]); + return; + } + $this->db->table('job') ->set('description', 'name', false) ->update(); From 940222f5706f347daa3b1bd82df94cc1f84bb280 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:56:37 +0900 Subject: [PATCH 082/184] test: add test case for sub query. --- tests/system/Database/Live/WhereTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/system/Database/Live/WhereTest.php b/tests/system/Database/Live/WhereTest.php index 76756e19a219..88364218a6ee 100644 --- a/tests/system/Database/Live/WhereTest.php +++ b/tests/system/Database/Live/WhereTest.php @@ -127,10 +127,21 @@ public function testSubQuery() ->where('name', 'Developer') ->getCompiledSelect(); + if ($this->db->DBDriver === 'OCI8') + { + $jobs = $this->db->table('job') + ->where('"id" not in (' . $subQuery . ')', null, false) + ->orderBy('id') + ->get() + ->getResult(); + } + else + { $jobs = $this->db->table('job') ->where('id not in (' . $subQuery . ')', null, false) ->get() ->getResult(); + } $this->assertCount(3, $jobs); $this->assertSame('Politician', $jobs[0]->name); @@ -145,10 +156,20 @@ public function testSubQueryAnotherType() ->where('name', 'Developer') ->getCompiledSelect(); + if ($this->db->DBDriver === 'OCI8') + { + $jobs = $this->db->table('job') + ->where('"id" = (' . $subQuery . ')', null, false) + ->get() + ->getResult(); + } + else + { $jobs = $this->db->table('job') ->where('id = (' . $subQuery . ')', null, false) ->get() ->getResult(); + } $this->assertCount(1, $jobs); $this->assertSame('Developer', $jobs[0]->name); From fc3f80fb745786e8da7b70898f9b88cc1d616909 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 14 Jan 2020 14:58:44 +0900 Subject: [PATCH 083/184] feat: add test case for dataSeek. --- tests/system/Database/Live/GetTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/system/Database/Live/GetTest.php b/tests/system/Database/Live/GetTest.php index 63f5ee9afd0c..a923435942f4 100644 --- a/tests/system/Database/Live/GetTest.php +++ b/tests/system/Database/Live/GetTest.php @@ -176,6 +176,10 @@ public function testGetDataSeek() $this->expectException(DatabaseException::class); $this->expectExceptionMessage('SQLite3 doesn\'t support seeking to other offset.'); } + elseif ($this->db->DBDriver === 'OCI8') + { + $this->markTestSkipped('OCI8 does not support data seek.'); + } $data->dataSeek(3); From 2e7df6357c94d1bb301ae7f2f03b0e6b4cff0bc1 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 17 Jan 2020 01:22:00 +0900 Subject: [PATCH 084/184] docs: add note. --- user_guide_src/source/database/helpers.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/database/helpers.rst b/user_guide_src/source/database/helpers.rst index a9a160a26713..0a6695d0bcdf 100644 --- a/user_guide_src/source/database/helpers.rst +++ b/user_guide_src/source/database/helpers.rst @@ -17,6 +17,8 @@ The insert ID number when performing database inserts. driver, this function requires a $name parameter, which specifies the appropriate sequence to check for the insert id. +.. note:: If using the OCI8 driver, the insert ID can be get when using insert() of :doc:`QueryBuilder<./querybuilder>`. + **$db->affectedRows()** Displays the number of affected rows, when doing "write" type queries From 3c27a6f6cf5d6034405adc7ce325392d53aab61a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 29 Jan 2020 23:40:51 +0900 Subject: [PATCH 085/184] fix: too many connection. --- system/Database/OCI8/Connection.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index fac040854da9..28d08bf5ecbd 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -185,6 +185,14 @@ public function reconnect() */ protected function _close() { + if (is_resource($this->cursorId)) + { + oci_free_statement($this->cursorId); + } + if (is_resource($this->stmtId)) + { + oci_free_statement($this->stmtId); + } oci_close($this->connID); } From e70071b1e261b56b5dc155064026c28cb7fb2cab Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:19:08 +0900 Subject: [PATCH 086/184] fix: the random value retrieval is now not affected by DB prefix. --- system/Database/OCI8/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 17fac1198ec1..d353e1ca60c5 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -58,7 +58,7 @@ class Builder extends BaseBuilder * @var array */ protected $randomKeyword = [ - 'DBMS_RANDOM.RANDOM' + '"DBMS_RANDOM"."RANDOM"' ]; /** From 623bf34ed854ec1e2a86b6052205a2bbc90c91b7 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:20:37 +0900 Subject: [PATCH 087/184] fix: due to supported ignoreOffset. --- system/Database/OCI8/Builder.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index d353e1ca60c5..0d5e3d8a4117 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -296,19 +296,20 @@ protected function _update(string $table, array $values): string * * @return string */ - protected function _limit(string $sql): string + protected function _limit(string $sql, bool $offsetIgnore = false): string { + $offset = (int)($offsetIgnore === false) ? $this->QBOffset : 0 ; if (version_compare($this->db->getVersion(), '12.1', '>=')) { // OFFSET-FETCH can be used only with the ORDER BY clause empty($this->QBOrderBy) && $sql .= ' ORDER BY 1'; - return $sql . ' OFFSET ' . (int) $this->QBOffset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; + return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; } $this->limitUsed = true; - return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($this->QBOffset + $this->QBLimit + 1) . ')' - . ($this->QBOffset ? ' WHERE rnum >= ' . ($this->QBOffset + 1) : ''); + return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($offset + $this->QBLimit + 1) . ')' + . ($offset ? ' WHERE rnum >= ' . ($offset + 1) : ''); } /** From 029a8fb13a0ffc155ecb63e82809d23d18682cd0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:23:09 +0900 Subject: [PATCH 088/184] fix: Not throw execute error when DBDebug is false. --- system/Database/OCI8/Connection.php | 45 ++++++++++++++++++----------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 28d08bf5ecbd..311a315574cb 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -41,6 +41,7 @@ use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; use stdClass; +use ErrorException; /** * Connection for Postgre @@ -247,23 +248,33 @@ public function getVersion(): string */ public function execute(string $sql) { - if ($this->resetStmtId === true) - { - $sql = rtrim($sql, ';'); - if (strpos(ltrim($sql), 'BEGIN') === 0) - { - $sql .= ';'; - } - $this->stmtId = oci_parse($this->connID, $sql); - } - - if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) - { - oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); - } - - oci_set_prefetch($this->stmtId, 1000); - return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; + try { + if ($this->resetStmtId === true) + { + $sql = rtrim($sql, ';'); + if (strpos(ltrim($sql), 'BEGIN') === 0) + { + $sql .= ';'; + } + $this->stmtId = oci_parse($this->connID, $sql); + } + + if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) + { + oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); + } + + oci_set_prefetch($this->stmtId, 1000); + return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; + } catch (ErrorException $e) { + log_message('error', $e->getMessage()); + + if ($this->DBDebug) { + throw $e; + } + } + + return false; } //-------------------------------------------------------------------- From a547c2923d2716640c1f6de37a59aaf4716e126f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:23:52 +0900 Subject: [PATCH 089/184] fix: fixed happen error when multiple column in bulk drop. --- system/Database/OCI8/Forge.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 6b0906a935ed..2d09ad307809 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -113,16 +113,21 @@ class Forge extends \CodeIgniter\Database\Forge */ protected function _alterTable(string $alter_type, string $table, $field) { + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + if ($alter_type === 'DROP') { - return parent::_alterTable($alter_type, $table, $field); + $fields = array_map(function ($field) { + return $this->db->escapeIdentifiers(trim($field)); + }, (is_string($field)) ? explode(',', $field) : $field); + + return $sql . ' DROP ('.implode(',', $fields).') CASCADE CONSTRAINT INVALIDATE'; } elseif ($alter_type === 'CHANGE') { $alter_type = 'MODIFY'; } - $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); $nullable_map = array_column($this->db->getFieldData($table), 'nullable', 'name'); $sqls = []; for ($i = 0, $c = count($field); $i < $c; $i++) From ef9ffc03f91d99e6f39a8df873025839c6175758 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:26:10 +0900 Subject: [PATCH 090/184] feat: Fixed to store missing types in oracle. --- system/Database/OCI8/Forge.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 2d09ad307809..2218798e016b 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -253,13 +253,22 @@ protected function _attributeType(array &$attributes) case 'NUMERIC': $attributes['TYPE'] = 'NUMBER'; return; + case 'DOUBLE': + $attributes['TYPE'] = 'FLOAT'; + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 126; + return; case 'DATETIME': + case 'TIME': $attributes['TYPE'] = 'DATE'; return; - case 'TEXT': + case 'SET': + case 'ENUM': case 'VARCHAR': - $attributes['TYPE'] = 'VARCHAR2'; $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 255; + case 'TEXT': + case 'MEDIUMTEXT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; + $attributes['TYPE'] = 'VARCHAR2'; return; default: return; } From 429cc0be30709258d828a01a075b219b3b145d75 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:27:09 +0900 Subject: [PATCH 091/184] feat: Use check constraint to reproduce the enum. --- system/Database/OCI8/Forge.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 2218798e016b..a1cf71b330ed 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -213,6 +213,17 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) */ protected function _processColumn(array $field): string { + $constraint = ''; + // @fixme: can’t cover multi pattern when set type. + if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) { + $constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name']) + . ' IN ' . $field['length'] . ')'; + + $field['length'] = '('.max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))).')'.$constraint; + } else if (count($this->primaryKeys) === 1 && $field['name'] === $this->primaryKeys[0]) { + $field['unique'] = ''; + } + return $this->db->escapeIdentifiers($field['name']) . ' ' . $field['type'] . $field['length'] . $field['unsigned'] From 555a9b2c53703b4d96d798b20ce39f4ebd7630ab Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:28:58 +0900 Subject: [PATCH 092/184] fix: Aligned the API with the changed interface. --- system/Database/OCI8/Forge.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index a1cf71b330ed..45f35f5db77a 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -296,17 +296,17 @@ protected function _attributeType(array &$attributes) * @param boolean $if_exists Whether to add an IF EXISTS condition * @param boolean $cascade * - * @return string + * @return string|boolean */ - protected function _dropTable(string $table, bool $if_exists, bool $cascade): string + protected function _dropTable(string $table, bool $if_exists, bool $cascade) { $sql = parent::_dropTable($table, $if_exists, $cascade); - if ($sql !== '' && $cascade === true) + if ($sql !== true && $cascade === true) { $sql .= ' CASCADE CONSTRAINTS PURGE'; } - elseif ($sql !== '') + elseif ($sql !== true) { $sql .= ' PURGE'; } From 79495055f8774753038b35153db6f89143f68a52 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:30:09 +0900 Subject: [PATCH 093/184] fix: Date types are now added according to the Oracle format. --- tests/_support/Database/Seeds/CITestSeeder.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index 372697aaa6ba..0126a0a6695d 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -166,6 +166,16 @@ public function run() ]; } + if ($this->db->DBDriver === 'OCI8') { + $this->db->query('alter session set NLS_DATE_FORMAT=?', ['YYYY/MM/DD HH24:MI:SS']); + $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; + $data['type_test'][0]['type_date'] = '2020-01-11 22:11:00'; + $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; + $data['type_test'][0]['type_datetime'] = '2020-06-18 05:12:24'; + $data['type_test'][0]['type_timestamp'] = '2020-06-18 21:53:21'; + unset($data['type_test'][0]['type_blob']); + } + foreach ($data as $table => $dummy_data) { $this->db->table($table)->truncate(); From 986574326b2ddea043c7f912b445f4d653063535 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:31:02 +0900 Subject: [PATCH 094/184] fix: syntax error. --- tests/system/Database/Live/ForgeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 7337d24e62f7..f1c3b5acc2cc 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -201,7 +201,7 @@ public function testCreateTableWithAttributes() { $this->markTestSkipped('OCI8 does not support comments on tables or columns.'); } - if ($this->db->DBDriver === 'SQLite3') + if ($this->db->DBDriver === 'SQLite3'){ $this->markTestSkipped('SQLite3 does not support comments on tables or columns.'); } From d6f0cd200a89f8578a80922b76ccd2bf61752fbe Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:31:36 +0900 Subject: [PATCH 095/184] fix: Due to table name to long. --- tests/system/Database/Live/ForgeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index f1c3b5acc2cc..879b1496db3e 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -454,7 +454,7 @@ public function testForeignKey() $this->assertSame($foreignKeyData[0]->column_name, 'users_id'); $this->assertSame($foreignKeyData[0]->foreign_column_name, 'id'); } - $this->assertSame($foreignKeyData[0]->table_name, $this->db->DBPrefix . 'forge_test_invoices'); + $this->assertSame($foreignKeyData[0]->table_name, $this->db->DBPrefix . $tableName); $this->assertSame($foreignKeyData[0]->foreign_table_name, $this->db->DBPrefix . 'forge_test_users'); $this->forge->dropTable($tableName, true); From a6c53c74a0c19d6b767bfddb04b8d6ffcdab6514 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:32:32 +0900 Subject: [PATCH 096/184] fix: Fixed to fit the problem that the structure of seeder has changed. --- tests/system/Database/Live/ForgeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 879b1496db3e..88653af4d36e 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -849,9 +849,9 @@ public function testAddFields() //Check types $this->assertSame('NUMBER', $fieldsData[0]->type); $this->assertSame('VARCHAR2', $fieldsData[1]->type); - $this->assertSame(32, $fieldsData[0]->max_length); + $this->assertSame('11', $fieldsData[0]->max_length); $this->assertSame('', $fieldsData[1]->default); - $this->assertSame(255, $fieldsData[1]->max_length); + $this->assertSame('255', $fieldsData[1]->max_length); } else { $this->fail(sprintf('DB driver "%s" is not supported.', $this->db->DBDriver)); } From bcde669eb2f11a9fc63e51bb8f7a7531f58e77bc Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:33:48 +0900 Subject: [PATCH 097/184] test: add OCI8 case for RANDOM. --- tests/system/Database/Live/OrderTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/system/Database/Live/OrderTest.php b/tests/system/Database/Live/OrderTest.php index 76f7e8374f32..eaab910edc5e 100644 --- a/tests/system/Database/Live/OrderTest.php +++ b/tests/system/Database/Live/OrderTest.php @@ -83,6 +83,8 @@ public function testOrderRandom() } elseif ($this->db->DBDriver === 'SQLSRV') { $key = 'NEWID()'; $table = '"' . $this->db->getDatabase() . '"."' . $this->db->schema . '".' . $table; + } elseif ($this->db->DBDriver === 'OCI8') { + $key = '"DBMS_RANDOM"."RANDOM"'; } $expected = 'SELECT * FROM ' . $table . ' ORDER BY ' . $key; From 90988911720c96d1c93c22039002753587f7005d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:34:23 +0900 Subject: [PATCH 098/184] fix: Since it is treated as uppercase unless explicitly specified. --- tests/system/Database/Live/PreparedQueryTest.php | 10 +++++++--- tests/system/Database/Live/WhereTest.php | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index fcb4769a8801..11cb741e267b 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -117,7 +117,11 @@ public function testExecuteRunsQueryAndReturnsResultObject() public function testExecuteRunsQueryAndReturnsManualResultObject() { $this->query = $this->db->prepare(static function ($db) { - $sql = "INSERT INTO {$db->DBPrefix}user (name, email, country) VALUES (?, ?, ?)"; + $sql = "INSERT INTO {$db->protectIdentifiers($db->DBPrefix . 'user')} (" + . $db->protectIdentifiers('name') . ', ' + . $db->protectIdentifiers('email') . ', ' + . $db->protectIdentifiers('country') + . ") VALUES (?, ?, ?)"; if ($db->DBDriver === 'SQLSRV') { $sql = "INSERT INTO {$db->schema}.{$db->DBPrefix}user (name, email, country) VALUES (?, ?, ?)"; @@ -126,8 +130,8 @@ public function testExecuteRunsQueryAndReturnsManualResultObject() return (new Query($db))->setQuery($sql); }); - $this->query->execute('foo', 'foo@example.com', ''); - $this->query->execute('bar', 'bar@example.com', ''); + $this->query->execute('foo', 'foo@example.com', 'US'); + $this->query->execute('bar', 'bar@example.com', 'GB'); $this->seeInDatabase($this->db->DBPrefix . 'user', ['name' => 'foo', 'email' => 'foo@example.com']); $this->seeInDatabase($this->db->DBPrefix . 'user', ['name' => 'bar', 'email' => 'bar@example.com']); diff --git a/tests/system/Database/Live/WhereTest.php b/tests/system/Database/Live/WhereTest.php index 88364218a6ee..d65b2d7296a7 100644 --- a/tests/system/Database/Live/WhereTest.php +++ b/tests/system/Database/Live/WhereTest.php @@ -237,7 +237,7 @@ public function testWhereWithLower() ]); $job = $builder - ->where(sprintf('LOWER(%s.name)', $this->db->prefixTable('job')), 'brewmaster') + ->where(sprintf("LOWER({$this->db->protectIdentifiers('%s.name')})", 'job'), 'brewmaster') ->get() ->getResult(); $this->assertCount(1, $job); From b9ce6eaf2e78fde13b6b873d1da26523a2b4fe8e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:36:11 +0900 Subject: [PATCH 099/184] feat: Due to name of the tablespace is not defined in the property when connecting by instance name. --- system/Database/OCI8/Connection.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 311a315574cb..9529bb805631 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -847,4 +847,16 @@ protected function _transRollback(): bool } // -------------------------------------------------------------------- + + /** + * Returns the name of the current database being used. + */ + public function getDatabase(): string + { + if (empty($this->database)) { + $this->database = $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? ''; + } + + return empty($this->database) ? '' : $this->database; + } } From 6e97504a5bad865e99c0dd4c597568820e65f55e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 02:51:20 +0900 Subject: [PATCH 100/184] style: php -d memory_limit=-1 ./vendor/bin/php-cs-fixer fix system/Database/ --- system/Database/OCI8/Builder.php | 591 +++++----- system/Database/OCI8/Connection.php | 1441 +++++++++++------------- system/Database/OCI8/Forge.php | 601 +++++----- system/Database/OCI8/PreparedQuery.php | 261 ++--- system/Database/OCI8/Result.php | 259 ++--- system/Database/OCI8/Utils.php | 74 +- 6 files changed, 1412 insertions(+), 1815 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 0d5e3d8a4117..3f2c997a1589 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -1,39 +1,12 @@ * - * This content is released under the MIT License (MIT) - * - * Copyright (c) 2014-2019 British Columbia Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; @@ -45,301 +18,263 @@ */ class Builder extends BaseBuilder { - /** - * Identifier escape character - * - * @var string - */ - protected $escapeChar = '"'; - - /** - * ORDER BY random keyword - * - * @var array - */ - protected $randomKeyword = [ - '"DBMS_RANDOM"."RANDOM"' - ]; - - /** - * COUNT string - * - * @used-by CI_DB_driver::count_all() - * @used-by BaseBuilder::count_all_results() - * - * @var string - */ - protected $countString = 'SELECT COUNT(1) '; - - /** - * Limit used flag - * - * If we use LIMIT, we'll add a field that will - * throw off num_fields later. - * - * @var boolean - */ - protected $limitUsed = false; - - /** - * Insert batch statement - * - * Generates a platform-specific insert string from the supplied data. - * - * @param string $table Table name - * @param array $keys INSERT keys - * @param array $values INSERT values - * - * @return string - */ - protected function _insertBatch(string $table, array $keys, array $values): string - { - $keys = implode(', ', $keys); - $has_primary_key = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); - - // ORA-00001 measures - if ($has_primary_key) - { - $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; - $select_query_values = []; - - for ($i = 0, $c = count($values); $i < $c; $i++) - { - $select_query_values[] = 'SELECT ' . substr(substr($values[$i], 1), 0, -1) . ' FROM DUAL'; - } - - return $sql . implode("\n UNION ALL \n", $select_query_values) . "\n)"; - } - - $sql = "INSERT ALL\n"; - - for ($i = 0, $c = count($values); $i < $c; $i++) - { - $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; - } - - return $sql . 'SELECT * FROM dual'; - } - - //-------------------------------------------------------------------- - - /** - * Replace statement - * - * Generates a platform-specific replace string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $values The insert values - * - * @return string - */ - protected function _replace(string $table, array $keys, array $values): string - { - $field_names = array_map(function ($column_name) { - return trim($column_name, '"'); - }, $keys); - - $unique_indexes = array_filter($this->db->getIndexData($table), function ($index) use ($field_names) { - $has_all_fields = count(array_intersect($index->fields, $field_names)) === count($index->fields); - - return (($index->type === 'PRIMARY') && $has_all_fields); - }); - $replaceable_fields = array_filter($keys, function ($column_name) use ($unique_indexes) { - foreach ($unique_indexes as $index) - { - if (in_array(trim($column_name, '"'), $index->fields, true)) - { - return false; - } - } - - return true; - }); - - $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; - - $sql .= implode(', ', array_map(function ($column_name, $value) { - return $value . ' ' . $column_name; - }, $keys, $values)); - - $sql .= ' FROM DUAL) "_replace" ON ( '; - - $on_list = []; - $on_list[] = '1 != 1'; - - foreach ($unique_indexes as $index) - { - $on_list[] = '(' . implode(' AND ', array_map(function ($column_name) use ($table) { - return $table . '."' . $column_name . '" = "_replace"."' . $column_name . '"'; - }, $index->fields)) . ')'; - } - - $sql .= implode(' OR ', $on_list) . ') WHEN MATCHED THEN UPDATE SET '; - - $sql .= implode(', ', array_map(function ($column_name) { - return $column_name . ' = "_replace".' . $column_name; - }, $replaceable_fields)); - - $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceable_fields) . ') VALUES '; - $sql .= ' (' . implode(', ', array_map(function ($column_name) { - return '"_replace".' . $column_name; - }, $replaceable_fields)) . ')'; - - return $sql; - } - - //-------------------------------------------------------------------- - - /** - * Truncate statement - * - * Generates a platform-specific truncate string from the supplied data - * - * If the database does not support the truncate() command, - * then this method maps to 'DELETE FROM table' - * - * @param string $table The table name - * - * @return string - */ - protected function _truncate(string $table): string - { - return 'TRUNCATE TABLE ' . $table; - } - - //-------------------------------------------------------------------- - - /** - * Delete - * - * Compiles a delete string and runs the query - * - * @param mixed $where The where clause - * @param integer $limit The limit clause - * @param boolean $reset_data - * - * @return mixed - * @throws \CodeIgniter\Database\Exceptions\DatabaseException - */ - public function delete($where = '', int $limit = null, bool $reset_data = true) - { - if (! empty($limit)) - { - $this->QBLimit = $limit; - } - - return parent::delete($where, null, $reset_data); - } - - //-------------------------------------------------------------------- - - /** - * Delete statement - * - * Generates a platform-specific delete string from the supplied data - * - * @param string $table The table name - * - * @return string - */ - protected function _delete(string $table): string - { - if ($this->QBLimit) - { - $this->where('rownum <= ', $this->QBLimit, false); - $this->QBLimit = false; - } - - return parent::_delete($table); - } - - //-------------------------------------------------------------------- - - /** - * Update statement - * - * Generates a platform-specific update string from the supplied data - * - * @param string $table the Table name - * @param array $values the Update data - * - * @return string - */ - protected function _update(string $table, array $values): string - { - $valStr = []; - - foreach ($values as $key => $val) - { - $valStr[] = $key . ' = ' . $val; - } - - if ($this->QBLimit) - { - $this->where('rownum <= ', $this->QBLimit, false); - } - - return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) - . $this->compileWhereHaving('QBWhere') - . $this->compileOrderBy(); - } - - //-------------------------------------------------------------------- - - /** - * LIMIT string - * - * Generates a platform-specific LIMIT clause. - * - * @param string $sql SQL Query - * - * @return string - */ - protected function _limit(string $sql, bool $offsetIgnore = false): string - { - $offset = (int)($offsetIgnore === false) ? $this->QBOffset : 0 ; - if (version_compare($this->db->getVersion(), '12.1', '>=')) - { - // OFFSET-FETCH can be used only with the ORDER BY clause - empty($this->QBOrderBy) && $sql .= ' ORDER BY 1'; - - return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; - } - - $this->limitUsed = true; - return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($offset + $this->QBLimit + 1) . ')' - . ($offset ? ' WHERE rnum >= ' . ($offset + 1) : ''); - } - - /** - * Resets the query builder values. Called by the get() function - */ - protected function resetSelect() - { - $this->limitUsed = false; - parent::resetSelect(); - } - - //-------------------------------------------------------------------- - - /** - * Insert statement - * - * Generates a platform-specific insert string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $unescapedKeys The insert values - * - * @return string - */ - protected function _insert(string $table, array $keys, array $unescapedKeys): string - { - // Has a strange design. - // Processing to get the table where the last insert was performed for insertId method. - $this->db->latestInsertedTableName = $table; - - return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ') RETURNING ROWID INTO :CI_OCI8_ROWID'; - } + /** + * Identifier escape character + * + * @var string + */ + protected $escapeChar = '"'; + + /** + * ORDER BY random keyword + * + * @var array + */ + protected $randomKeyword = [ + '"DBMS_RANDOM"."RANDOM"', + ]; + + /** + * COUNT string + * + * @used-by CI_DB_driver::count_all() + * @used-by BaseBuilder::count_all_results() + * + * @var string + */ + protected $countString = 'SELECT COUNT(1) '; + + /** + * Limit used flag + * + * If we use LIMIT, we'll add a field that will + * throw off num_fields later. + * + * @var bool + */ + protected $limitUsed = false; + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + */ + protected function _insertBatch(string $table, array $keys, array $values): string + { + $keys = implode(', ', $keys); + $has_primary_key = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); + + // ORA-00001 measures + if ($has_primary_key) { + $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; + $select_query_values = []; + + for ($i = 0, $c = count($values); $i < $c; $i++) { + $select_query_values[] = 'SELECT ' . substr(substr($values[$i], 1), 0, -1) . ' FROM DUAL'; + } + + return $sql . implode("\n UNION ALL \n", $select_query_values) . "\n)"; + } + + $sql = "INSERT ALL\n"; + + for ($i = 0, $c = count($values); $i < $c; $i++) { + $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; + } + + return $sql . 'SELECT * FROM dual'; + } + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $values The insert values + */ + protected function _replace(string $table, array $keys, array $values): string + { + $field_names = array_map(static function ($column_name) { + return trim($column_name, '"'); + }, $keys); + + $unique_indexes = array_filter($this->db->getIndexData($table), static function ($index) use ($field_names) { + $has_all_fields = count(array_intersect($index->fields, $field_names)) === count($index->fields); + + return ($index->type === 'PRIMARY') && $has_all_fields; + }); + $replaceable_fields = array_filter($keys, static function ($column_name) use ($unique_indexes) { + foreach ($unique_indexes as $index) { + if (in_array(trim($column_name, '"'), $index->fields, true)) { + return false; + } + } + + return true; + }); + + $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; + + $sql .= implode(', ', array_map(static function ($column_name, $value) { + return $value . ' ' . $column_name; + }, $keys, $values)); + + $sql .= ' FROM DUAL) "_replace" ON ( '; + + $on_list = []; + $on_list[] = '1 != 1'; + + foreach ($unique_indexes as $index) { + $on_list[] = '(' . implode(' AND ', array_map(static function ($column_name) use ($table) { + return $table . '."' . $column_name . '" = "_replace"."' . $column_name . '"'; + }, $index->fields)) . ')'; + } + + $sql .= implode(' OR ', $on_list) . ') WHEN MATCHED THEN UPDATE SET '; + + $sql .= implode(', ', array_map(static function ($column_name) { + return $column_name . ' = "_replace".' . $column_name; + }, $replaceable_fields)); + + $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceable_fields) . ') VALUES '; + $sql .= ' (' . implode(', ', array_map(static function ($column_name) { + return '"_replace".' . $column_name; + }, $replaceable_fields)) . ')'; + + return $sql; + } + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string $table The table name + */ + protected function _truncate(string $table): string + { + return 'TRUNCATE TABLE ' . $table; + } + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param mixed $where The where clause + * @param int $limit The limit clause + * + * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * + * @return mixed + */ + public function delete($where = '', ?int $limit = null, bool $reset_data = true) + { + if (! empty($limit)) { + $this->QBLimit = $limit; + } + + return parent::delete($where, null, $reset_data); + } + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table The table name + */ + protected function _delete(string $table): string + { + if ($this->QBLimit) { + $this->where('rownum <= ', $this->QBLimit, false); + $this->QBLimit = false; + } + + return parent::_delete($table); + } + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table the Table name + * @param array $values the Update data + */ + protected function _update(string $table, array $values): string + { + $valStr = []; + + foreach ($values as $key => $val) { + $valStr[] = $key . ' = ' . $val; + } + + if ($this->QBLimit) { + $this->where('rownum <= ', $this->QBLimit, false); + } + + return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) + . $this->compileWhereHaving('QBWhere') + . $this->compileOrderBy(); + } + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + */ + protected function _limit(string $sql, bool $offsetIgnore = false): string + { + $offset = (int) ($offsetIgnore === false) ? $this->QBOffset : 0; + if (version_compare($this->db->getVersion(), '12.1', '>=')) { + // OFFSET-FETCH can be used only with the ORDER BY clause + empty($this->QBOrderBy) && $sql .= ' ORDER BY 1'; + + return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; + } + + $this->limitUsed = true; + + return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($offset + $this->QBLimit + 1) . ')' + . ($offset ? ' WHERE rnum >= ' . ($offset + 1) : ''); + } + + /** + * Resets the query builder values. Called by the get() function + */ + protected function resetSelect() + { + $this->limitUsed = false; + parent::resetSelect(); + } + + /** + * Insert statement + * + * Generates a platform-specific insert string from the supplied data + * + * @param string $table The table name + * @param array $keys The insert keys + * @param array $unescapedKeys The insert values + */ + protected function _insert(string $table, array $keys, array $unescapedKeys): string + { + // Has a strange design. + // Processing to get the table where the last insert was performed for insertId method. + $this->db->latestInsertedTableName = $table; + + return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ') RETURNING ROWID INTO :CI_OCI8_ROWID'; + } } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 9529bb805631..598fed9c63c4 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -1,38 +1,12 @@ * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; @@ -40,232 +14,198 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; -use stdClass; use ErrorException; +use stdClass; /** * Connection for Postgre */ class Connection extends BaseConnection implements ConnectionInterface { + /** + * Database driver + * + * @var string + */ + public $DBDriver = 'OCI8'; + + /** + * Identifier escape character + * + * @var string + */ + public $escapeChar = '"'; + + /** + * List of reserved identifiers + * + * Identifiers that must NOT be escaped. + * + * @var array + */ + protected $reservedIdentifiers = [ + '*', + 'rownum', + ]; + + protected $validDSNs = [ + 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS + // Easy Connect string (Oracle 10g+) + 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', + 'in' => '/^[a-z0-9$_]+$/i', // Instance name (defined in tnsnames.ora) + ]; + + /** + * Reset $stmtId flag + * + * Used by storedProcedure() to prevent execute() from + * re-setting the statement ID. + */ + protected $resetStmtId = true; + + /** + * Statement ID + * + * @var resource + */ + public $stmtId; + + /** + * Commit mode flag + * + * @var int + */ + public $commitMode = OCI_COMMIT_ON_SUCCESS; + + /** + * Cursor ID + * + * @var resource + */ + public $cursorId; + + /** + * RowID + * + * @var int|null + */ + public $rowId; + + /** + * Latest inserted table name. + * + * @var string|null + */ + public $latestInsertedTableName; + + /** + * confirm DNS format. + */ + private function isValidDSN(): bool + { + foreach ($this->validDSNs as $regexp) { + if (preg_match($regexp, $this->DSN)) { + return true; + } + } + + return false; + } + + /** + * Connect to the database. + * + * @return mixed + */ + public function connect(bool $persistent = false) + { + if (empty($this->DSN) && ! $this->isValidDSN()) { + $this->buildDSN(); + } - /** - * Database driver - * - * @var string - */ - public $DBDriver = 'OCI8'; - - /** - * Identifier escape character - * - * @var string - */ - public $escapeChar = '"'; - - /** - * List of reserved identifiers - * - * Identifiers that must NOT be escaped. - * - * @var array - */ - protected $reservedIdentifiers = [ - '*', - 'rownum', - ]; - - protected $validDSNs = [ - 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS - // Easy Connect string (Oracle 10g+) - 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i', - 'in' => '/^[a-z0-9$_]+$/i',// Instance name (defined in tnsnames.ora) - ]; - - //-------------------------------------------------------------------- - - /** - * Reset $stmtId flag - * - * Used by storedProcedure() to prevent execute() from - * re-setting the statement ID. - */ - protected $resetStmtId = true; - - /** - * Statement ID - * - * @var resource - */ - public $stmtId; - - /** - * Commit mode flag - * - * @var integer - */ - public $commitMode = OCI_COMMIT_ON_SUCCESS; - - /** - * Cursor ID - * - * @var resource - */ - public $cursorId; - - /** - * RowID - * - * @var integer|null - */ - public $rowId; - - /** - * Latest inserted table name. - * - * @var string|null - */ - public $latestInsertedTableName; - - /** - * confirm DNS format. - * - * @return boolean - */ - private function isValidDSN() : bool - { - foreach ($this->validDSNs as $regexp) - { - if (preg_match($regexp, $this->DSN)) - { - return true; - } - } - - return false; - } - - /** - * Connect to the database. - * - * @param boolean $persistent - * @return mixed - */ - public function connect(bool $persistent = false) - { - if (empty($this->DSN) && ! $this->isValidDSN()) - { - $this->buildDSN(); - } - - $func = ($persistent === true) ? 'oci_pconnect' : 'oci_connect'; - - return empty($this->charset) - ? $func($this->username, $this->password, $this->DSN) - : $func($this->username, $this->password, $this->DSN, $this->charset); - } - - //-------------------------------------------------------------------- - - /** - * Keep or establish the connection if no queries have been sent for - * a length of time exceeding the server's idle timeout. - * - * @return void - */ - public function reconnect() - { - } - - //-------------------------------------------------------------------- - - /** - * Close the database connection. - * - * @return void - */ - protected function _close() - { - if (is_resource($this->cursorId)) - { - oci_free_statement($this->cursorId); - } - if (is_resource($this->stmtId)) - { - oci_free_statement($this->stmtId); - } - oci_close($this->connID); - } - - //-------------------------------------------------------------------- - - /** - * Select a specific database table to use. - * - * @param string $databaseName - * - * @return boolean - */ - public function setDatabase(string $databaseName): bool - { - return false; - } - - //-------------------------------------------------------------------- - - /** - * Returns a string containing the version of the database being used. - * - * @return string - */ - public function getVersion(): string - { - if (isset($this->dataCache['version'])) - { - return $this->dataCache['version']; - } - - if (! $this->connID || ($version_string = oci_server_version($this->connID)) === false) - { - return false; - } - elseif (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) - { - return $this->dataCache['version'] = $match[1]; - } - - return false; - } - - //-------------------------------------------------------------------- - - /** - * Executes the query against the database. - * - * @param string $sql - * - * @return resource - */ - public function execute(string $sql) - { + $func = ($persistent === true) ? 'oci_pconnect' : 'oci_connect'; + + return empty($this->charset) + ? $func($this->username, $this->password, $this->DSN) + : $func($this->username, $this->password, $this->DSN, $this->charset); + } + + /** + * Keep or establish the connection if no queries have been sent for + * a length of time exceeding the server's idle timeout. + * + * @return void + */ + public function reconnect() + { + } + + /** + * Close the database connection. + * + * @return void + */ + protected function _close() + { + if (is_resource($this->cursorId)) { + oci_free_statement($this->cursorId); + } + if (is_resource($this->stmtId)) { + oci_free_statement($this->stmtId); + } + oci_close($this->connID); + } + + /** + * Select a specific database table to use. + */ + public function setDatabase(string $databaseName): bool + { + return false; + } + + /** + * Returns a string containing the version of the database being used. + */ + public function getVersion(): string + { + if (isset($this->dataCache['version'])) { + return $this->dataCache['version']; + } + + if (! $this->connID || ($version_string = oci_server_version($this->connID)) === false) { + return false; + } + if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) { + return $this->dataCache['version'] = $match[1]; + } + + return false; + } + + /** + * Executes the query against the database. + * + * @return resource + */ + public function execute(string $sql) + { try { - if ($this->resetStmtId === true) - { - $sql = rtrim($sql, ';'); - if (strpos(ltrim($sql), 'BEGIN') === 0) - { - $sql .= ';'; - } - $this->stmtId = oci_parse($this->connID, $sql); - } - - if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) - { - oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); - } - - oci_set_prefetch($this->stmtId, 1000); - return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; + if ($this->resetStmtId === true) { + $sql = rtrim($sql, ';'); + if (strpos(ltrim($sql), 'BEGIN') === 0) { + $sql .= ';'; + } + $this->stmtId = oci_parse($this->connID, $sql); + } + + if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) { + oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); + } + + oci_set_prefetch($this->stmtId, 1000); + + return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; } catch (ErrorException $e) { log_message('error', $e->getMessage()); @@ -275,194 +215,156 @@ public function execute(string $sql) } return false; - } - - //-------------------------------------------------------------------- - - /** - * Returns the total number of rows affected by this query. - * - * @return integer - */ - public function affectedRows(): int - { - return oci_num_rows($this->stmtId); - } - - //-------------------------------------------------------------------- - - //-------------------------------------------------------------------- - - /** - * Generates the SQL for listing tables in a platform-dependent manner. - * - * @param boolean $prefixLimit - * - * @return string - */ - protected function _listTables(bool $prefixLimit = false): string - { - $sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"'; - - if ($prefixLimit !== false && $this->DBPrefix !== '') - { - return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' " - . sprintf($this->likeEscapeStr, $this->likeEscapeChar); - } - - return $sql; - } - - //-------------------------------------------------------------------- - - /** - * 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 - { - if (strpos($table, '.') !== false) - { - sscanf($table, '%[^.].%s', $owner, $table); - } - else - { - $owner = $this->username; - } - - return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS + } + + /** + * Returns the total number of rows affected by this query. + */ + public function affectedRows(): int + { + return oci_num_rows($this->stmtId); + } + + /** + * Generates the SQL for listing tables in a platform-dependent manner. + */ + protected function _listTables(bool $prefixLimit = false): string + { + $sql = 'SELECT "TABLE_NAME" FROM "USER_TABLES"'; + + if ($prefixLimit !== false && $this->DBPrefix !== '') { + return $sql . ' WHERE "TABLE_NAME" LIKE \'' . $this->escapeLikeString($this->DBPrefix) . "%' " + . sprintf($this->likeEscapeStr, $this->likeEscapeChar); + } + + return $sql; + } + + /** + * Generates a platform-specific query string so that the column names can be fetched. + */ + protected function _listColumns(string $table = ''): string + { + if (strpos($table, '.') !== false) { + sscanf($table, '%[^.].%s', $owner, $table); + } else { + $owner = $this->username; + } + + 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)); - } - - //-------------------------------------------------------------------- - - /** - * Returns an array of objects with field data - * - * @param string $table - * @return \stdClass[] - * @throws DatabaseException - */ - public function _fieldData(string $table): array - { - if (strpos($table, '.') !== false) - { - sscanf($table, '%[^.].%s', $owner, $table); - } - else - { - $owner = $this->username; - } - - $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE + } + + /** + * Returns an array of objects with field data + * + * @throws DatabaseException + * + * @return \stdClass[] + */ + public function _fieldData(string $table): array + { + if (strpos($table, '.') !== false) { + sscanf($table, '%[^.].%s', $owner, $table); + } else { + $owner = $this->username; + } + + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHAR_LENGTH, DATA_PRECISION, DATA_LENGTH, DATA_DEFAULT, NULLABLE FROM ALL_TAB_COLUMNS WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . ' AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($table)); - if (($query = $this->query($sql)) === false) - { - throw new DatabaseException(lang('Database.failGetFieldData')); - } - $query = $query->getResultObject(); - - $retval = []; - for ($i = 0, $c = count($query); $i < $c; $i++) - { - $retval[$i] = new stdClass(); - $retval[$i]->name = $query[$i]->COLUMN_NAME; - $retval[$i]->type = $query[$i]->DATA_TYPE; - - $length = ($query[$i]->CHAR_LENGTH > 0) - ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; - if ($length === null) - { - $length = $query[$i]->DATA_LENGTH; - } - $retval[$i]->max_length = $length; - - $default = $query[$i]->DATA_DEFAULT; - if ($default === null && $query[$i]->NULLABLE === 'N') - { - $default = ''; - } - $retval[$i]->default = $default; - $retval[$i]->nullable = $query[$i]->NULLABLE === 'Y'; - } - - return $retval; - } - - //-------------------------------------------------------------------- - - /** - * Returns an array of objects with index data - * - * @param string $table - * @return \stdClass[] - * @throws DatabaseException - */ - public function _indexData(string $table): array - { - if (strpos($table, '.') !== false) - { - sscanf($table, '%[^.].%s', $owner, $table); - } - else - { - $owner = $this->username; - } - - $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME ' - . ' FROM ALL_IND_COLUMNS AIC ' - . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME ' - . 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' ' - . 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' ' - . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION'; - - if (($query = $this->query($sql)) === false) - { - throw new DatabaseException(lang('Database.failGetIndexData')); - } - $query = $query->getResultObject(); - - $retVal = []; - $constraintTypes = [ - 'P' => 'PRIMARY', - 'U' => 'UNIQUE', - ]; - foreach ($query as $row) - { - if (isset($retVal[$row->INDEX_NAME])) - { - $retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME; - continue; - } - - $retVal[$row->INDEX_NAME] = new \stdClass(); - $retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME; - $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME]; - $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX'; - } - - return $retVal; - } - - //-------------------------------------------------------------------- - - /** - * Returns an array of objects with Foreign key data - * - * @param string $table - * @return \stdClass[] - * @throws DatabaseException - */ - public function _foreignKeyData(string $table): array - { - $sql = 'SELECT + if (($query = $this->query($sql)) === false) { + throw new DatabaseException(lang('Database.failGetFieldData')); + } + $query = $query->getResultObject(); + + $retval = []; + + for ($i = 0, $c = count($query); $i < $c; $i++) { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + + $length = ($query[$i]->CHAR_LENGTH > 0) + ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; + if ($length === null) { + $length = $query[$i]->DATA_LENGTH; + } + $retval[$i]->max_length = $length; + + $default = $query[$i]->DATA_DEFAULT; + if ($default === null && $query[$i]->NULLABLE === 'N') { + $default = ''; + } + $retval[$i]->default = $default; + $retval[$i]->nullable = $query[$i]->NULLABLE === 'Y'; + } + + return $retval; + } + + /** + * Returns an array of objects with index data + * + * @throws DatabaseException + * + * @return \stdClass[] + */ + public function _indexData(string $table): array + { + if (strpos($table, '.') !== false) { + sscanf($table, '%[^.].%s', $owner, $table); + } else { + $owner = $this->username; + } + + $sql = 'SELECT AIC.INDEX_NAME, UC.CONSTRAINT_TYPE, AIC.COLUMN_NAME ' + . ' FROM ALL_IND_COLUMNS AIC ' + . ' LEFT JOIN USER_CONSTRAINTS UC ON AIC.INDEX_NAME = UC.CONSTRAINT_NAME AND AIC.TABLE_NAME = UC.TABLE_NAME ' + . 'WHERE AIC.TABLE_NAME = ' . $this->escape(strtolower($table)) . ' ' + . 'AND AIC.TABLE_OWNER = ' . $this->escape(strtoupper($owner)) . ' ' + . ' ORDER BY UC.CONSTRAINT_TYPE, AIC.COLUMN_POSITION'; + + if (($query = $this->query($sql)) === false) { + throw new DatabaseException(lang('Database.failGetIndexData')); + } + $query = $query->getResultObject(); + + $retVal = []; + $constraintTypes = [ + 'P' => 'PRIMARY', + 'U' => 'UNIQUE', + ]; + + foreach ($query as $row) { + if (isset($retVal[$row->INDEX_NAME])) { + $retVal[$row->INDEX_NAME]->fields[] = $row->COLUMN_NAME; + + continue; + } + + $retVal[$row->INDEX_NAME] = new \stdClass(); + $retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME; + $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME]; + $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX'; + } + + return $retVal; + } + + /** + * Returns an array of objects with Foreign key data + * + * @throws DatabaseException + * + * @return \stdClass[] + */ + public function _foreignKeyData(string $table): array + { + $sql = 'SELECT acc.constraint_name, acc.table_name, acc.column_name, @@ -481,372 +383,319 @@ public function _foreignKeyData(string $table): array WHERE ac.constraint_type = ' . $this->escape('R') . ' AND acc.table_name = ' . $this->escape($table); - if (($query = $this->query($sql)) === false) - { - throw new DatabaseException(lang('Database.failGetForeignKeyData')); - } - $query = $query->getResultObject(); - - $retVal = []; - foreach ($query as $row) - { - $obj = new \stdClass(); - $obj->constraint_name = $row->CONSTRAINT_NAME; - $obj->table_name = $row->TABLE_NAME; - $obj->column_name = $row->COLUMN_NAME; - $obj->foreign_table_name = $row->FOREIGN_TABLE_NAME; - $obj->foreign_column_name = $row->FOREIGN_COLUMN_NAME; - $retVal[] = $obj; - } - - return $retVal; - } - - //-------------------------------------------------------------------- - - /** - * Returns platform-specific SQL to disable foreign key checks. - * - * @return string - */ - protected function _disableForeignKeyChecks() - { - return <<cursorId = oci_new_cursor($this->connID); - } - - //-------------------------------------------------------------------- - - /** - * Stored Procedure. Executes a stored procedure - * - * @param string package name in which the stored procedure is in - * @param string stored procedure name to execute - * @param array parameters - * @return mixed - * - * params array keys - * - * KEY OPTIONAL NOTES - * name no the name of the parameter should be in : format - * value no the value of the parameter. If this is an OUT or IN OUT parameter, - * this should be a reference to a variable - * type yes the type of the parameter - * length yes the max size of the parameter - */ - public function storedProcedure(string $package, string $procedure, array $params) - { - if ($package === '' || $procedure === '') - { - throw new DatabaseException(lang('Database.invalidArgument', [$package . $procedure])); - } - - // Build the query string - $sql = 'BEGIN ' . $package . '.' . $procedure . '('; - - $have_cursor = false; - foreach ($params as $param) - { - $sql .= $param['name'] . ','; - - if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) - { - $have_cursor = true; - } - } - $sql = trim($sql, ',') . '); END;'; - - $this->resetStmtId = false; - $this->stmtId = oci_parse($this->connID, $sql); - $this->bindParams($params); - $result = $this->query($sql, false, $have_cursor); - $this->resetStmtId = true; - return $result; - } - - // -------------------------------------------------------------------- - - /** - * Bind parameters - * - * @param array $params - * @return void - */ - protected function bindParams($params) - { - if (! is_array($params) || ! is_resource($this->stmtId)) - { - return; - } - - foreach ($params as $param) - { - foreach (['name', 'value', 'type', 'length'] as $val) - { - if (! isset($param[$val])) - { - $param[$val] = ''; - } - } - - oci_bind_by_name($this->stmtId, $param['name'], $param['value'], $param['length'], $param['type']); - } - } - - // -------------------------------------------------------------------- - - /** - * 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 - { - // oci_error() returns an array that already contains - // 'code' and 'message' keys, but it can return false - // if there was no error .... - if (is_resource($this->cursorId)) - { - $error = oci_error($this->cursorId); - } - elseif (is_resource($this->stmtId)) - { - $error = oci_error($this->stmtId); - } - elseif (is_resource($this->connID)) - { - $error = oci_error($this->connID); - } - else - { - $error = oci_error(); - } - - return is_array($error) - ? $error - : [ - 'code' => '', - 'message' => '', - ]; - } - - //-------------------------------------------------------------------- - - /** - * Insert ID - * - * @return integer - */ - public function insertID(): int - { - if (empty($this->rowId) || empty($this->latestInsertedTableName)) - { - return 0; - } - - $indexs = $this->getIndexData($this->latestInsertedTableName); - $field_datas = $this->getFieldData($this->latestInsertedTableName); - - if (! $indexs || ! $field_datas) - { - return 0; - } - - $column_type_list = array_column($field_datas, 'type', 'name'); - $primary_column_name = ''; - foreach ((is_array($indexs) ? $indexs : [] ) as $index) - { - if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) - { - continue; - } - - $primary_column_name = $this->protectIdentifiers($index->fields[0], false, false); - $primary_column_type = $column_type_list[$primary_column_name]; - - if ($primary_column_type !== 'NUMBER') - { - continue; - } - } - - if (! $primary_column_name) - { - return 0; - } - - $table = $this->protectIdentifiers($this->latestInsertedTableName, true); - $query = $this->query('SELECT ' . $this->protectIdentifiers($primary_column_name, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); - - return (int)($query->SEQ ?? 0); - } - - //-------------------------------------------------------------------- - - /** - * Build a DSN from the provided parameters - * - * @return void - */ - protected function buildDSN() - { - $this->DSN === '' || $this->DSN = ''; - - // Legacy support for TNS in the hostname configuration field - $this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname); - - if (preg_match($this->validDSNs['tns'], $this->hostname)) - { - $this->DSN = $this->hostname; - return; - } - - $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === false && strpos($this->hostname, ':') === false; - $easyConnectablePort = (( ! empty($this->port) && ctype_digit($this->port)) ? ':' . $this->port : ''); - $easyConnectableDatabase = ($this->database !== '' ? '/' . ltrim($this->database, '/') : ''); - - if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) - { - /* If the hostname field isn't empty, doesn't contain - * ':' and/or '/' and if port and/or database aren't - * empty, then the hostname field is most likely indeed - * just a hostname. Therefore we'll try and build an - * Easy Connect string from these 3 settings, assuming - * that the database field is a service name. - */ - $this->DSN = $this->hostname - . $easyConnectablePort - . $easyConnectableDatabase; - - if (preg_match($this->validDSNs['ec'], $this->DSN)) - { - return; - } - } - - /* At this point, we can only try and validate the hostname and - * database fields separately as DSNs. - */ - if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) - { - $this->DSN = $this->hostname; - return; - } - - $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database); - foreach ($valid_dsns as $regexp) - { - if (preg_match($regexp, $this->database)) - { - return; - } - } - - /* Well - OK, an empty string should work as well. - * PHP will try to use environment variables to - * determine which Oracle instance to connect to. - */ - $this->DSN = ''; - } - - //-------------------------------------------------------------------- - - /** - * Begin Transaction - * - * @return boolean - */ - protected function _transBegin(): bool - { - $this->commitMode = OCI_NO_AUTO_COMMIT; - - return true; - } - - // -------------------------------------------------------------------- - - /** - * Commit Transaction - * - * @return boolean - */ - protected function _transCommit(): bool - { - $this->commitMode = OCI_COMMIT_ON_SUCCESS; - - return oci_commit($this->connID); - } - - // -------------------------------------------------------------------- - - /** - * Rollback Transaction - * - * @return boolean - */ - protected function _transRollback(): bool - { - $this->commitMode = OCI_COMMIT_ON_SUCCESS; - - return oci_rollback($this->connID); - } - - // -------------------------------------------------------------------- + if (($query = $this->query($sql)) === false) { + throw new DatabaseException(lang('Database.failGetForeignKeyData')); + } + $query = $query->getResultObject(); + + $retVal = []; + + foreach ($query as $row) { + $obj = new \stdClass(); + $obj->constraint_name = $row->CONSTRAINT_NAME; + $obj->table_name = $row->TABLE_NAME; + $obj->column_name = $row->COLUMN_NAME; + $obj->foreign_table_name = $row->FOREIGN_TABLE_NAME; + $obj->foreign_column_name = $row->FOREIGN_COLUMN_NAME; + $retVal[] = $obj; + } + + return $retVal; + } + + /** + * Returns platform-specific SQL to disable foreign key checks. + * + * @return string + */ + protected function _disableForeignKeyChecks() + { + return <<<'SQL' + BEGIN + FOR c IN + (SELECT c.owner, c.table_name, c.constraint_name + FROM user_constraints c, user_tables t + WHERE c.table_name = t.table_name + AND c.status = 'ENABLED' + AND c.constraint_type = 'R' + AND t.iot_type IS NULL + ORDER BY c.constraint_type DESC) + LOOP + dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" disable constraint "' || c.constraint_name || '"'); + END LOOP; + END; + SQL; + } + + /** + * Returns platform-specific SQL to enable foreign key checks. + * + * @return string + */ + protected function _enableForeignKeyChecks() + { + return <<<'SQL' + BEGIN + FOR c IN + (SELECT c.owner, c.table_name, c.constraint_name + FROM user_constraints c, user_tables t + WHERE c.table_name = t.table_name + AND c.status = 'DISABLED' + AND c.constraint_type = 'R' + AND t.iot_type IS NULL + ORDER BY c.constraint_type DESC) + LOOP + dbms_utility.exec_ddl_statement('alter table "' || c.owner || '"."' || c.table_name || '" enable constraint "' || c.constraint_name || '"'); + END LOOP; + END; + SQL; + } + + /** + * Get cursor. Returns a cursor from the database + * + * @return resource + */ + public function getCursor() + { + return $this->cursorId = oci_new_cursor($this->connID); + } + + /** + * Stored Procedure. Executes a stored procedure + * + * @param string package name in which the stored procedure is in + * @param string stored procedure name to execute + * @param array parameters + * + * @return mixed + * + * params array keys + * + * KEY OPTIONAL NOTES + * name no the name of the parameter should be in : format + * value no the value of the parameter. If this is an OUT or IN OUT parameter, + * this should be a reference to a variable + * type yes the type of the parameter + * length yes the max size of the parameter + */ + public function storedProcedure(string $package, string $procedure, array $params) + { + if ($package === '' || $procedure === '') { + throw new DatabaseException(lang('Database.invalidArgument', [$package . $procedure])); + } + + // Build the query string + $sql = 'BEGIN ' . $package . '.' . $procedure . '('; + + $have_cursor = false; + + foreach ($params as $param) { + $sql .= $param['name'] . ','; + + if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) { + $have_cursor = true; + } + } + $sql = trim($sql, ',') . '); END;'; + + $this->resetStmtId = false; + $this->stmtId = oci_parse($this->connID, $sql); + $this->bindParams($params); + $result = $this->query($sql, false, $have_cursor); + $this->resetStmtId = true; + + return $result; + } + + /** + * Bind parameters + * + * @param array $params + * + * @return void + */ + protected function bindParams($params) + { + if (! is_array($params) || ! is_resource($this->stmtId)) { + return; + } + + foreach ($params as $param) { + foreach (['name', 'value', 'type', 'length'] as $val) { + if (! isset($param[$val])) { + $param[$val] = ''; + } + } + + oci_bind_by_name($this->stmtId, $param['name'], $param['value'], $param['length'], $param['type']); + } + } + + /** + * Returns the last error code and message. + * + * Must return an array with keys 'code' and 'message': + * + * return ['code' => null, 'message' => null); + */ + public function error(): array + { + // oci_error() returns an array that already contains + // 'code' and 'message' keys, but it can return false + // if there was no error .... + if (is_resource($this->cursorId)) { + $error = oci_error($this->cursorId); + } elseif (is_resource($this->stmtId)) { + $error = oci_error($this->stmtId); + } elseif (is_resource($this->connID)) { + $error = oci_error($this->connID); + } else { + $error = oci_error(); + } + + return is_array($error) + ? $error + : [ + 'code' => '', + 'message' => '', + ]; + } + + /** + * Insert ID + */ + public function insertID(): int + { + if (empty($this->rowId) || empty($this->latestInsertedTableName)) { + return 0; + } + + $indexs = $this->getIndexData($this->latestInsertedTableName); + $field_datas = $this->getFieldData($this->latestInsertedTableName); + + if (! $indexs || ! $field_datas) { + return 0; + } + + $column_type_list = array_column($field_datas, 'type', 'name'); + $primary_column_name = ''; + + foreach ((is_array($indexs) ? $indexs : []) as $index) { + if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) { + continue; + } + + $primary_column_name = $this->protectIdentifiers($index->fields[0], false, false); + $primary_column_type = $column_type_list[$primary_column_name]; + + if ($primary_column_type !== 'NUMBER') { + continue; + } + } + + if (! $primary_column_name) { + return 0; + } + + $table = $this->protectIdentifiers($this->latestInsertedTableName, true); + $query = $this->query('SELECT ' . $this->protectIdentifiers($primary_column_name, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); + + return (int) ($query->SEQ ?? 0); + } + + /** + * Build a DSN from the provided parameters + * + * @return void + */ + protected function buildDSN() + { + $this->DSN === '' || $this->DSN = ''; + + // Legacy support for TNS in the hostname configuration field + $this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname); + + if (preg_match($this->validDSNs['tns'], $this->hostname)) { + $this->DSN = $this->hostname; + + return; + } + + $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === false && strpos($this->hostname, ':') === false; + $easyConnectablePort = ((! empty($this->port) && ctype_digit($this->port)) ? ':' . $this->port : ''); + $easyConnectableDatabase = ($this->database !== '' ? '/' . ltrim($this->database, '/') : ''); + + if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) { + /* If the hostname field isn't empty, doesn't contain + * ':' and/or '/' and if port and/or database aren't + * empty, then the hostname field is most likely indeed + * just a hostname. Therefore we'll try and build an + * Easy Connect string from these 3 settings, assuming + * that the database field is a service name. + */ + $this->DSN = $this->hostname + . $easyConnectablePort + . $easyConnectableDatabase; + + if (preg_match($this->validDSNs['ec'], $this->DSN)) { + return; + } + } + + /* At this point, we can only try and validate the hostname and + * database fields separately as DSNs. + */ + if (preg_match($this->validDSNs['ec'], $this->hostname) || preg_match($this->validDSNs['in'], $this->hostname)) { + $this->DSN = $this->hostname; + + return; + } + + $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database); + + foreach ($valid_dsns as $regexp) { + if (preg_match($regexp, $this->database)) { + return; + } + } + + /* Well - OK, an empty string should work as well. + * PHP will try to use environment variables to + * determine which Oracle instance to connect to. + */ + $this->DSN = ''; + } + + /** + * Begin Transaction + */ + protected function _transBegin(): bool + { + $this->commitMode = OCI_NO_AUTO_COMMIT; + + return true; + } + + /** + * Commit Transaction + */ + protected function _transCommit(): bool + { + $this->commitMode = OCI_COMMIT_ON_SUCCESS; + + return oci_commit($this->connID); + } + + /** + * Rollback Transaction + */ + protected function _transRollback(): bool + { + $this->commitMode = OCI_COMMIT_ON_SUCCESS; + + return oci_rollback($this->connID); + } /** * Returns the name of the current database being used. diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 45f35f5db77a..0658ec8c2fe1 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -1,39 +1,12 @@ * - * This content is released under the MIT License (MIT) - * - * Copyright (c) 2014-2019 British Columbia Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; @@ -43,312 +16,290 @@ */ class Forge extends \CodeIgniter\Database\Forge { - - /** - * CREATE DATABASE statement - * - * @var string - */ - protected $createDatabaseStr = false; - - /** - * CREATE TABLE IF statement - * - * @var string - */ - protected $createTableIfStr = false; - - /** - * DROP TABLE IF EXISTS statement - * - * @var string - */ - protected $dropTableIfStr = false; - - /** - * DROP DATABASE statement - * - * @var string - */ - protected $dropDatabaseStr = false; - - /** - * UNSIGNED support - * - * @var boolean|array - */ - protected $unsigned = false; - - /** - * NULL value representation in CREATE/ALTER TABLE statements - * - * @var string - */ - protected $null = 'NULL'; - - /** - * RENAME TABLE statement - * - * @var string - */ - protected $renameTableStr = 'ALTER TABLE %s RENAME TO %s'; - - /** - * DROP CONSTRAINT statement - * - * @var string - */ - protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s'; - - //-------------------------------------------------------------------- - - /** - * ALTER TABLE - * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition - * - * @return string|string[] - */ - protected function _alterTable(string $alter_type, string $table, $field) - { - $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); - - if ($alter_type === 'DROP') - { + /** + * CREATE DATABASE statement + * + * @var string + */ + protected $createDatabaseStr = false; + + /** + * CREATE TABLE IF statement + * + * @var string + */ + protected $createTableIfStr = false; + + /** + * DROP TABLE IF EXISTS statement + * + * @var string + */ + protected $dropTableIfStr = false; + + /** + * DROP DATABASE statement + * + * @var string + */ + protected $dropDatabaseStr = false; + + /** + * UNSIGNED support + * + * @var array|bool + */ + protected $unsigned = false; + + /** + * NULL value representation in CREATE/ALTER TABLE statements + * + * @var string + */ + protected $null = 'NULL'; + + /** + * RENAME TABLE statement + * + * @var string + */ + protected $renameTableStr = 'ALTER TABLE %s RENAME TO %s'; + + /** + * DROP CONSTRAINT statement + * + * @var string + */ + protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s'; + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * + * @return string|string[] + */ + protected function _alterTable(string $alter_type, string $table, $field) + { + $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); + + if ($alter_type === 'DROP') { $fields = array_map(function ($field) { return $this->db->escapeIdentifiers(trim($field)); }, (is_string($field)) ? explode(',', $field) : $field); - return $sql . ' DROP ('.implode(',', $fields).') CASCADE CONSTRAINT INVALIDATE'; - } - elseif ($alter_type === 'CHANGE') - { - $alter_type = 'MODIFY'; - } - - $nullable_map = array_column($this->db->getFieldData($table), 'nullable', 'name'); - $sqls = []; - for ($i = 0, $c = count($field); $i < $c; $i++) - { - if ($alter_type === 'MODIFY') - { - // If a null constraint is added to a column with a null constraint, - // ORA-01451 will occur, - // so add null constraint is used only when it is different from the current null constraint. - $is_want_to_add_null = (strpos($field[$i]['null'], ' NOT') === false); - $current_null_addable = $nullable_map[$field[$i]['name']]; - - if ($is_want_to_add_null === $current_null_addable) - { - $field[$i]['null'] = ''; - } - } - - if ($field[$i]['_literal'] !== false) - { - $field[$i] = "\n\t" . $field[$i]['_literal']; - } - else - { - $field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]); - - if (! empty($field[$i]['comment'])) - { - $sqls[] = 'COMMENT ON COLUMN ' - . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name']) - . ' IS ' . $field[$i]['comment']; - } - - if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) - { - $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) - . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); - } - - $field[$i] = "\n\t" . $field[$i]['_literal']; - } - } - - $sql .= ' ' . $alter_type . ' '; - $sql .= (count($field) === 1) - ? $field[0] - : '(' . implode(',', $field) . ')'; - - // RENAME COLUMN must be executed after MODIFY - array_unshift($sqls, $sql); - return $sqls; - } - - //-------------------------------------------------------------------- - - /** - * Field attribute AUTO_INCREMENT - * - * @param array &$attributes - * @param array &$field - * - * @return void - */ - protected function _attributeAutoIncrement(array &$attributes, array &$field) - { - if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true - && stripos($field['type'], 'NUMBER') !== false - && version_compare($this->db->getVersion(), '12.1', '>=') - ) - { - $field['auto_increment'] = ' GENERATED BY DEFAULT AS IDENTITY'; - } - } - - //-------------------------------------------------------------------- - - /** - * Process column - * - * @param array $field - * - * @return string - */ - protected function _processColumn(array $field): string - { + return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; + } + if ($alter_type === 'CHANGE') { + $alter_type = 'MODIFY'; + } + + $nullable_map = array_column($this->db->getFieldData($table), 'nullable', 'name'); + $sqls = []; + + for ($i = 0, $c = count($field); $i < $c; $i++) { + if ($alter_type === 'MODIFY') { + // If a null constraint is added to a column with a null constraint, + // ORA-01451 will occur, + // so add null constraint is used only when it is different from the current null constraint. + $is_want_to_add_null = (strpos($field[$i]['null'], ' NOT') === false); + $current_null_addable = $nullable_map[$field[$i]['name']]; + + if ($is_want_to_add_null === $current_null_addable) { + $field[$i]['null'] = ''; + } + } + + if ($field[$i]['_literal'] !== false) { + $field[$i] = "\n\t" . $field[$i]['_literal']; + } else { + $field[$i]['_literal'] = "\n\t" . $this->_processColumn($field[$i]); + + if (! empty($field[$i]['comment'])) { + $sqls[] = 'COMMENT ON COLUMN ' + . $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($field[$i]['name']) + . ' IS ' . $field[$i]['comment']; + } + + if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) { + $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) + . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); + } + + $field[$i] = "\n\t" . $field[$i]['_literal']; + } + } + + $sql .= ' ' . $alter_type . ' '; + $sql .= (count($field) === 1) + ? $field[0] + : '(' . implode(',', $field) . ')'; + + // RENAME COLUMN must be executed after MODIFY + array_unshift($sqls, $sql); + + return $sqls; + } + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * + * @return void + */ + protected function _attributeAutoIncrement(array &$attributes, array &$field) + { + if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true + && stripos($field['type'], 'NUMBER') !== false + && version_compare($this->db->getVersion(), '12.1', '>=') + ) { + $field['auto_increment'] = ' GENERATED BY DEFAULT AS IDENTITY'; + } + } + + /** + * Process column + */ + protected function _processColumn(array $field): string + { $constraint = ''; // @fixme: can’t cover multi pattern when set type. if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) { $constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name']) . ' IN ' . $field['length'] . ')'; - $field['length'] = '('.max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))).')'.$constraint; - } else if (count($this->primaryKeys) === 1 && $field['name'] === $this->primaryKeys[0]) { + $field['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($field['length'], 2, -2)))) . ')' . $constraint; + } elseif (count($this->primaryKeys) === 1 && $field['name'] === $this->primaryKeys[0]) { $field['unique'] = ''; } - return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['default'] - . $field['auto_increment'] - . $field['null'] - . $field['unique']; - } - - //-------------------------------------------------------------------- - - /** - * Field attribute TYPE - * - * Performs a data type mapping between different databases. - * - * @param array &$attributes - * - * @return void - */ - protected function _attributeType(array &$attributes) - { - // Reset field lengths for data types that don't support it - // Usually overridden by drivers - switch (strtoupper($attributes['TYPE'])) - { - case 'TINYINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 3; - case 'SMALLINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 5; - case 'MEDIUMINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 7; - case 'INT': - case 'INTEGER': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 11; - case 'BIGINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 19; - case 'NUMERIC': - $attributes['TYPE'] = 'NUMBER'; - return; + return $this->db->escapeIdentifiers($field['name']) + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['default'] + . $field['auto_increment'] + . $field['null'] + . $field['unique']; + } + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * + * @return void + */ + protected function _attributeType(array &$attributes) + { + // Reset field lengths for data types that don't support it + // Usually overridden by drivers + switch (strtoupper($attributes['TYPE'])) { + case 'TINYINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 3; + // no break + case 'SMALLINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 5; + // no break + case 'MEDIUMINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 7; + // no break + case 'INT': + case 'INTEGER': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 11; + // no break + case 'BIGINT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 19; + // no break + case 'NUMERIC': + $attributes['TYPE'] = 'NUMBER'; + + return; + case 'DOUBLE': - $attributes['TYPE'] = 'FLOAT'; - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 126; - return; - case 'DATETIME': - case 'TIME': - $attributes['TYPE'] = 'DATE'; - return; - case 'SET': - case 'ENUM': - case 'VARCHAR': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 255; - case 'TEXT': - case 'MEDIUMTEXT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; - $attributes['TYPE'] = 'VARCHAR2'; - return; - default: return; - } - } - - //-------------------------------------------------------------------- - - /** - * Drop Table - * - * Generates a platform-specific DROP TABLE string - * - * @param string $table Table name - * @param boolean $if_exists Whether to add an IF EXISTS condition - * @param boolean $cascade - * - * @return string|boolean - */ - protected function _dropTable(string $table, bool $if_exists, bool $cascade) - { - $sql = parent::_dropTable($table, $if_exists, $cascade); - - if ($sql !== true && $cascade === true) - { - $sql .= ' CASCADE CONSTRAINTS PURGE'; - } - elseif ($sql !== true) - { - $sql .= ' PURGE'; - } - - return $sql; - } - - //-------------------------------------------------------------------- - - /** - * Process foreign keys - * - * @param string $table Table name - * - * @return string - */ - protected function _processForeignKeys(string $table): string - { - $sql = ''; - - $allowActions = [ - 'CASCADE', - 'SET NULL', - 'NO ACTION', - ]; - - if (count($this->foreignKeys) > 0) - { - foreach ($this->foreignKeys as $field => $fkey) - { - $name_index = $table . '_' . $field . '_fk'; - - $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']; - } - } - } - - return $sql; - } + $attributes['TYPE'] = 'FLOAT'; + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 126; + + return; + + case 'DATETIME': + case 'TIME': + $attributes['TYPE'] = 'DATE'; + + return; + + case 'SET': + case 'ENUM': + case 'VARCHAR': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 255; + // no break + case 'TEXT': + case 'MEDIUMTEXT': + $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; + $attributes['TYPE'] = 'VARCHAR2'; + + return; + + default: return; + } + } + + /** + * Drop Table + * + * Generates a platform-specific DROP TABLE string + * + * @param string $table Table name + * @param bool $if_exists Whether to add an IF EXISTS condition + * + * @return bool|string + */ + protected function _dropTable(string $table, bool $if_exists, bool $cascade) + { + $sql = parent::_dropTable($table, $if_exists, $cascade); + + if ($sql !== true && $cascade === true) { + $sql .= ' CASCADE CONSTRAINTS PURGE'; + } elseif ($sql !== true) { + $sql .= ' PURGE'; + } + + return $sql; + } + + /** + * Process foreign keys + * + * @param string $table Table name + */ + protected function _processForeignKeys(string $table): string + { + $sql = ''; + + $allowActions = [ + 'CASCADE', + 'SET NULL', + 'NO ACTION', + ]; + + if (count($this->foreignKeys) > 0) { + foreach ($this->foreignKeys as $field => $fkey) { + $name_index = $table . '_' . $field . '_fk'; + + $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, true)) { + $sql .= ' ON DELETE ' . $fkey['onDelete']; + } + } + } + + return $sql; + } } diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 7fd98611c937..6951f79c6fbe 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -1,172 +1,123 @@ * - * This content is released under the MIT License (MIT) - * - * Copyright (c) 2014-2019 British Columbia Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; -use CodeIgniter\Database\PreparedQueryInterface; use CodeIgniter\Database\BasePreparedQuery; +use CodeIgniter\Database\PreparedQueryInterface; /** * Prepared query for MySQLi */ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface { - private $isCollectRowId; - - /** - * Prepares the query against the database, and saves the connection - * info necessary to execute the query later. - * - * NOTE: This version is based on SQL code. Child classes should - * override this method. - * - * @param string $sql - * @param array $options Passed to the connection's prepare statement. - * @param string $queryClass - * - * @return mixed - */ - public function prepare(string $sql, array $options = [], string $queryClass = 'CodeIgniter\\Database\\Query') - { - $this->isCollectRowId = false; - - if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') - { - $this->isCollectRowId = true; - } - - return parent::prepare($sql, $options, $queryClass); - } - - /** - * Prepares the query against the database, and saves the connection - * info necessary to execute the query later. - * - * NOTE: This version is based on SQL code. Child classes should - * override this method. - * - * @param string $sql - * @param array $options Passed to the connection's prepare statement. - * Unused in the MySQLi driver. - * - * @return mixed - */ - public function _prepare(string $sql, array $options = []) - { - $sql = rtrim($sql, ';'); - if (strpos('BEGIN', ltrim($sql)) === 0) - { - $sql .= ';'; - } - - if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) - { - $error = oci_error($this->db->connID); - $this->errorCode = $error['code'] ?? 0; - $this->errorString = $error['message'] ?? ''; - } - - return $this; - } - - //-------------------------------------------------------------------- - - /** - * Takes a new set of data and runs it against the currently - * prepared query. Upon success, will return a Results object. - * - * @param array $data - * - * @return boolean - */ - public function _execute(array $data): bool - { - if (is_null($this->statement)) - { - throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); - } - - $last_key = 0; - foreach (array_keys($data) as $key) - { - oci_bind_by_name($this->statement, ':' . $key, $data[$key]); - $last_key = $key; - } - - if ($this->isCollectRowId) - { - oci_bind_by_name($this->statement, ':' . (++$last_key), $this->db->rowId, 255); - } - - return oci_execute($this->statement, $this->db->commitMode); - } - - //-------------------------------------------------------------------- - - /** - * Returns the result object for the prepared query. - * - * @return mixed - */ - public function _getResult() - { - return $this->statement; - } - - //-------------------------------------------------------------------- - - /** - * Replaces the ? placeholders with :1, :2, etc parameters for use - * within the prepared query. - * - * @param string $sql - * - * @return string - */ - public function parameterize(string $sql): string - { - // Track our current value - $count = 0; - - $sql = preg_replace_callback('/\?/', function ($matches) use (&$count) { - return ':' . ($count++); - }, $sql); - - return $sql; - } + private $isCollectRowId; + + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param array $options Passed to the connection's prepare statement. + * + * @return mixed + */ + public function prepare(string $sql, array $options = [], string $queryClass = 'CodeIgniter\\Database\\Query') + { + $this->isCollectRowId = false; + + if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') { + $this->isCollectRowId = true; + } + + return parent::prepare($sql, $options, $queryClass); + } + + /** + * Prepares the query against the database, and saves the connection + * info necessary to execute the query later. + * + * NOTE: This version is based on SQL code. Child classes should + * override this method. + * + * @param array $options Passed to the connection's prepare statement. + * Unused in the MySQLi driver. + * + * @return mixed + */ + public function _prepare(string $sql, array $options = []) + { + $sql = rtrim($sql, ';'); + if (strpos('BEGIN', ltrim($sql)) === 0) { + $sql .= ';'; + } + + if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) { + $error = oci_error($this->db->connID); + $this->errorCode = $error['code'] ?? 0; + $this->errorString = $error['message'] ?? ''; + } + + return $this; + } + + /** + * Takes a new set of data and runs it against the currently + * prepared query. Upon success, will return a Results object. + */ + public function _execute(array $data): bool + { + if (null === $this->statement) { + throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + } + + $last_key = 0; + + foreach (array_keys($data) as $key) { + oci_bind_by_name($this->statement, ':' . $key, $data[$key]); + $last_key = $key; + } + + if ($this->isCollectRowId) { + oci_bind_by_name($this->statement, ':' . (++$last_key), $this->db->rowId, 255); + } + + return oci_execute($this->statement, $this->db->commitMode); + } + + /** + * Returns the result object for the prepared query. + * + * @return mixed + */ + public function _getResult() + { + return $this->statement; + } + + /** + * Replaces the ? placeholders with :1, :2, etc parameters for use + * within the prepared query. + */ + public function parameterize(string $sql): string + { + // Track our current value + $count = 0; + + $sql = preg_replace_callback('/\?/', static function ($matches) use (&$count) { + return ':' . ($count++); + }, $sql); + + return $sql; + } } diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 2df16ab7273a..3352f308a5c7 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -1,39 +1,12 @@ * - * This content is released under the MIT License (MIT) - * - * Copyright (c) 2014-2019 British Columbia Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; @@ -47,130 +20,102 @@ */ class Result extends BaseResult implements ResultInterface { - - /** - * Gets the number of fields in the result set. - * - * @return integer - */ - public function getFieldCount(): int - { - return oci_num_fields($this->resultID); - } - - //-------------------------------------------------------------------- - - /** - * Generates an array of column names in the result set. - * - * @return array - */ - public function getFieldNames(): array - { - return array_map(function ($field_index) { - return oci_field_name($this->resultID, $field_index); - }, range(1, $this->getFieldCount())); - } - - //-------------------------------------------------------------------- - - /** - * Generates an array of objects representing field meta-data. - * - * @return array - */ - public function getFieldData(): array - { - return array_map(function ($field_index) { - return (object) [ - 'name' => oci_field_name($this->resultID, $field_index), - 'type' => oci_field_type($this->resultID, $field_index), - 'max_length' => oci_field_size($this->resultID, $field_index), - // 'primary_key' = (int) ($data->flags & 2), - // 'default' = $data->def, - ]; - }, range(1, $this->getFieldCount())); - } - - //-------------------------------------------------------------------- - - /** - * Frees the current result. - * - * @return void - */ - public function freeResult() - { - if (is_resource($this->resultID)) - { - oci_free_statement($this->resultID); - $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 integer $n - * - * @return mixed - */ - public function dataSeek(int $n = 0) - { - // We can't support data seek by oci - return false; - } - - //-------------------------------------------------------------------- - - /** - * Returns the result set as an array. - * - * Overridden by driver classes. - * - * @return mixed - */ - protected function fetchAssoc() - { - return oci_fetch_assoc($this->resultID); - } - - //-------------------------------------------------------------------- - - /** - * Returns the result set as an object. - * - * Overridden by child classes. - * - * @param string $className - * - * @return object|boolean|Entity - */ - protected function fetchObject(string $className = \stdClass::class) - { - $row = oci_fetch_object($this->resultID); - - if ($className === 'stdClass' || ! $row) - { - return $row; - } - elseif (is_subclass_of($className, Entity::class)) - { - return (new $className())->setAttributes((array) $row); - } - - $instance = new $className(); - foreach ($row as $key => $value) - { - $instance->$key = $value; - } - - return $instance; - } - - //-------------------------------------------------------------------- + /** + * Gets the number of fields in the result set. + */ + public function getFieldCount(): int + { + return oci_num_fields($this->resultID); + } + + /** + * Generates an array of column names in the result set. + */ + public function getFieldNames(): array + { + return array_map(function ($field_index) { + return oci_field_name($this->resultID, $field_index); + }, range(1, $this->getFieldCount())); + } + + /** + * Generates an array of objects representing field meta-data. + */ + public function getFieldData(): array + { + return array_map(function ($field_index) { + return (object) [ + 'name' => oci_field_name($this->resultID, $field_index), + 'type' => oci_field_type($this->resultID, $field_index), + 'max_length' => oci_field_size($this->resultID, $field_index), + // 'primary_key' = (int) ($data->flags & 2), + // 'default' = $data->def, + ]; + }, range(1, $this->getFieldCount())); + } + + /** + * Frees the current result. + * + * @return void + */ + public function freeResult() + { + if (is_resource($this->resultID)) { + oci_free_statement($this->resultID); + $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. + * + * @return mixed + */ + public function dataSeek(int $n = 0) + { + // We can't support data seek by oci + return false; + } + + /** + * Returns the result set as an array. + * + * Overridden by driver classes. + * + * @return mixed + */ + protected function fetchAssoc() + { + return oci_fetch_assoc($this->resultID); + } + + /** + * Returns the result set as an object. + * + * Overridden by child classes. + * + * @return bool|Entity|object + */ + protected function fetchObject(string $className = \stdClass::class) + { + $row = oci_fetch_object($this->resultID); + + if ($className === 'stdClass' || ! $row) { + return $row; + } + if (is_subclass_of($className, Entity::class)) { + return (new $className())->setAttributes((array) $row); + } + + $instance = new $className(); + + foreach ($row as $key => $value) { + $instance->{$key} = $value; + } + + return $instance; + } } diff --git a/system/Database/OCI8/Utils.php b/system/Database/OCI8/Utils.php index 9ed063e5022a..d3fafca8e09a 100644 --- a/system/Database/OCI8/Utils.php +++ b/system/Database/OCI8/Utils.php @@ -1,39 +1,12 @@ * - * This content is released under the MIT License (MIT) - * - * Copyright (c) 2014-2019 British Columbia Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @package CodeIgniter - * @author CodeIgniter Dev Team - * @copyright 2014-2019 British Columbia Institute of Technology (https://bcit.ca/) - * @license https://opensource.org/licenses/MIT MIT License - * @link https://codeigniter.com - * @since Version 4.0.0 - * @filesource + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Database\OCI8; @@ -46,27 +19,20 @@ */ class Utils extends BaseUtils { - - /** - * List databases statement - * - * @var string - */ - protected $listDatabases = 'SELECT TABLESPACE_NAME FROM USER_TABLESPACES'; - - //-------------------------------------------------------------------- - - /** - * Platform dependent version of the backup function. - * - * @param array|null $prefs - * - * @return mixed - */ - public function _backup(array $prefs = null) - { - throw new DatabaseException('Unsupported feature of the database platform you are using.'); - } - - //-------------------------------------------------------------------- + /** + * List databases statement + * + * @var string + */ + protected $listDatabases = 'SELECT TABLESPACE_NAME FROM USER_TABLESPACES'; + + /** + * Platform dependent version of the backup function. + * + * @return mixed + */ + public function _backup(?array $prefs = null) + { + throw new DatabaseException('Unsupported feature of the database platform you are using.'); + } } From a77ad8d42e2756cde5c50db0a97a9ee0950d8737 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 9 Sep 2021 10:58:20 +0900 Subject: [PATCH 101/184] style: php -d memory_limit=-1 ./vendor/bin/phpstan analyse --- system/Database/OCI8/Builder.php | 11 +++++++- system/Database/OCI8/Connection.php | 36 ++++++++++++++------------ system/Database/OCI8/Forge.php | 14 +++++----- system/Database/OCI8/PreparedQuery.php | 12 +++++++++ 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 3f2c997a1589..8b5318a588bf 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -54,6 +54,13 @@ class Builder extends BaseBuilder */ protected $limitUsed = false; + /** + * A reference to the database connection. + * + * @var Connection + */ + protected $db; + /** * Insert batch statement * @@ -240,7 +247,9 @@ protected function _limit(string $sql, bool $offsetIgnore = false): string $offset = (int) ($offsetIgnore === false) ? $this->QBOffset : 0; if (version_compare($this->db->getVersion(), '12.1', '>=')) { // OFFSET-FETCH can be used only with the ORDER BY clause - empty($this->QBOrderBy) && $sql .= ' ORDER BY 1'; + if (empty($this->QBOrderBy)) { + $sql .= ' ORDER BY 1'; + } return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 598fed9c63c4..c99d9c5420d6 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -174,19 +174,19 @@ public function getVersion(): string } if (! $this->connID || ($version_string = oci_server_version($this->connID)) === false) { - return false; + return ''; } if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) { return $this->dataCache['version'] = $match[1]; } - return false; + return ''; } /** * Executes the query against the database. * - * @return resource + * @return resource|boolean */ public function execute(string $sql) { @@ -464,9 +464,9 @@ public function getCursor() /** * Stored Procedure. Executes a stored procedure * - * @param string package name in which the stored procedure is in - * @param string stored procedure name to execute - * @param array parameters + * @param string $package package name in which the stored procedure is in + * @param string $procedure stored procedure name to execute + * @param array $params parameters * * @return mixed * @@ -544,14 +544,14 @@ public function error(): array // oci_error() returns an array that already contains // 'code' and 'message' keys, but it can return false // if there was no error .... - if (is_resource($this->cursorId)) { - $error = oci_error($this->cursorId); - } elseif (is_resource($this->stmtId)) { - $error = oci_error($this->stmtId); - } elseif (is_resource($this->connID)) { - $error = oci_error($this->connID); - } else { - $error = oci_error(); + $error = oci_error(); + $resources = [$this->cursorId, $this->stmtId, $this->connID]; + + foreach ($resources as $resource) { + if (is_resource($resource)) { + $error = oci_error($resource); + break; + } } return is_array($error) @@ -581,7 +581,7 @@ public function insertID(): int $column_type_list = array_column($field_datas, 'type', 'name'); $primary_column_name = ''; - foreach ((is_array($indexs) ? $indexs : []) as $index) { + foreach ($indexs as $index) { if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) { continue; } @@ -611,7 +611,9 @@ public function insertID(): int */ protected function buildDSN() { - $this->DSN === '' || $this->DSN = ''; + if ($this->DSN !== '') { + $this->DSN = ''; + } // Legacy support for TNS in the hostname configuration field $this->hostname = str_replace(["\n", "\r", "\t", ' '], '', $this->hostname); @@ -654,7 +656,7 @@ protected function buildDSN() $this->database = str_replace(["\n", "\r", "\t", ' '], '', $this->database); - foreach ($valid_dsns as $regexp) { + foreach ($this->validDSNs as $regexp) { if (preg_match($regexp, $this->database)) { return; } diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 0658ec8c2fe1..4b9fb009fbcc 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -19,28 +19,28 @@ class Forge extends \CodeIgniter\Database\Forge /** * CREATE DATABASE statement * - * @var string + * @var false */ protected $createDatabaseStr = false; /** * CREATE TABLE IF statement * - * @var string + * @var false */ protected $createTableIfStr = false; /** * DROP TABLE IF EXISTS statement * - * @var string + * @var false */ protected $dropTableIfStr = false; /** * DROP DATABASE statement * - * @var string + * @var false */ protected $dropDatabaseStr = false; @@ -146,8 +146,8 @@ protected function _alterTable(string $alter_type, string $table, $field) /** * Field attribute AUTO_INCREMENT * - * @param array &$attributes - * @param array &$field + * @param array $attributes + * @param array $field * * @return void */ @@ -191,7 +191,7 @@ protected function _processColumn(array $field): string * * Performs a data type mapping between different databases. * - * @param array &$attributes + * @param array $attributes * * @return void */ diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 6951f79c6fbe..36b4325df485 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -19,6 +19,18 @@ */ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface { + /** + * A reference to the db connection to use. + * + * @var Connection + */ + protected $db; + + /** + * Is collect row id + * + * @var bool + */ private $isCollectRowId; /** From 5fc81d8b22b0e13d0b824f4971d7af885f8cd477 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 10 Sep 2021 00:05:29 +0900 Subject: [PATCH 102/184] fix: BOOLEAN is now cast to NUMBER. --- system/Database/OCI8/Forge.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 4b9fb009fbcc..ba4f4f14a1c3 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -221,6 +221,13 @@ protected function _attributeType(array &$attributes) return; + case 'BOOLEAN': + $attributes['TYPE'] = 'NUMBER'; + $attributes['CONSTRAINT'] = 1; + $attributes['UNSIGNED'] = true; + $attributes['NULL'] = false; + return; + case 'DOUBLE': $attributes['TYPE'] = 'FLOAT'; $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 126; From f2924f676137f73ea8b9ff9cb8519995b8ddeed7 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 10 Sep 2021 00:07:45 +0900 Subject: [PATCH 103/184] fix: support CompositeForeignKey. --- system/Database/OCI8/Forge.php | 12 ++++-- tests/system/Database/Live/ForgeTest.php | 53 +++++++++++++++++------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index ba4f4f14a1c3..5e50557579aa 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -296,10 +296,14 @@ protected function _processForeignKeys(string $table): string if (count($this->foreignKeys) > 0) { foreach ($this->foreignKeys as $field => $fkey) { - $name_index = $table . '_' . $field . '_fk'; - - $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']) . ')'; + $nameIndex = $table . '_' . implode('_', $fkey['field']) . '_fk'; + $nameIndexFilled = $this->db->escapeIdentifiers($nameIndex); + $foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field'])); + $referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']); + $referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField'])); + + $formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)"; + $sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled); if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) { $sql .= ' ON DELETE ' . $fkey['onDelete']; diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 88653af4d36e..07585680daff 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -526,38 +526,59 @@ public function testCompositeForeignKey() $this->forge->addPrimaryKey(['id', 'second_id']); $this->forge->createTable('forge_test_users', true, $attributes); - $this->forge->addField([ + $forgeTestInvoicesTableName = 'forge_test_invoices'; + $userIdColumnName = 'user_id'; + $userSecondIdColumnName = 'user_second_id'; + $fields = [ 'id' => [ 'type' => 'INTEGER', 'constraint' => 11, ], - 'users_id' => [ - 'type' => 'INTEGER', - 'constraint' => 11, - ], - 'users_second_id' => [ - 'type' => 'VARCHAR', - 'constraint' => 50, - ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => 255, ], - ]); + ]; + + if ($this->db->DBDriver === 'OCI8') { + $userIdColumnName = 'uid'; + $userSecondIdColumnName = 'usid'; + $forgeTestInvoicesTableName = 'forge_test_inv'; + } + + $fields[$userIdColumnName] = [ + 'type' => 'INTEGER', + 'constraint' => 11, + ]; + + $fields[$userSecondIdColumnName] = [ + 'type' => 'VARCHAR', + 'constraint' => 50, + ]; + + $this->forge->addField($fields); $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey(['users_id', 'users_second_id'], 'forge_test_users', ['id', 'second_id'], 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey([$userIdColumnName, $userSecondIdColumnName], 'forge_test_users', ['id', 'second_id'], 'CASCADE', 'CASCADE'); - $this->forge->createTable('forge_test_invoices', true, $attributes); + $this->forge->createTable($forgeTestInvoicesTableName, true, $attributes); - $foreignKeyData = $this->db->getForeignKeyData('forge_test_invoices'); + $foreignKeyData = $this->db->getForeignKeyData($forgeTestInvoicesTableName); if ($this->db->DBDriver === 'SQLite3') { $this->assertSame('users_id to db_forge_test_users.id', $foreignKeyData[0]->constraint_name); $this->assertSame(0, $foreignKeyData[0]->sequence); $this->assertSame('users_second_id to db_forge_test_users.second_id', $foreignKeyData[1]->constraint_name); $this->assertSame(1, $foreignKeyData[1]->sequence); + } else if ($this->db->DBDriver === 'OCI8') { + $haystack = [$userIdColumnName, $userSecondIdColumnName]; + $this->assertSame($this->db->DBPrefix . 'forge_test_inv_uid_usid_fk', $foreignKeyData[0]->constraint_name); + $this->assertContains($foreignKeyData[0]->column_name, $haystack); + + $secondIdKey = 1; + $this->assertSame($this->db->DBPrefix . 'forge_test_inv_uid_usid_fk', $foreignKeyData[$secondIdKey]->constraint_name); + $this->assertContains($foreignKeyData[$secondIdKey]->column_name, $haystack); } else { - $haystack = ['users_id', 'users_second_id']; + $haystack = [$userIdColumnName, $userSecondIdColumnName]; $this->assertSame($this->db->DBPrefix . 'forge_test_invoices_users_id_users_second_id_foreign', $foreignKeyData[0]->constraint_name); $this->assertContains($foreignKeyData[0]->column_name, $haystack); @@ -565,10 +586,10 @@ public function testCompositeForeignKey() $this->assertSame($this->db->DBPrefix . 'forge_test_invoices_users_id_users_second_id_foreign', $foreignKeyData[$secondIdKey]->constraint_name); $this->assertContains($foreignKeyData[$secondIdKey]->column_name, $haystack); } - $this->assertSame($this->db->DBPrefix . 'forge_test_invoices', $foreignKeyData[0]->table_name); + $this->assertSame($this->db->DBPrefix . $forgeTestInvoicesTableName, $foreignKeyData[0]->table_name); $this->assertSame($this->db->DBPrefix . 'forge_test_users', $foreignKeyData[0]->foreign_table_name); - $this->forge->dropTable('forge_test_invoices', true); + $this->forge->dropTable($forgeTestInvoicesTableName, true); $this->forge->dropTable('forge_test_users', true); } From 205d8698e0e03226cda33120b9a83c40a47cc646 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 10 Sep 2021 00:15:16 +0900 Subject: [PATCH 104/184] style: php -d memory_limit=-1 ./vendor/bin/php-cs-fixer fix --- system/Database/OCI8/Connection.php | 18 ++--- system/Database/OCI8/Forge.php | 12 +-- .../_support/Database/Seeds/CITestSeeder.php | 8 +- tests/system/Database/Live/DbUtilsTest.php | 16 ++-- tests/system/Database/Live/ForgeTest.php | 79 ++++++++----------- tests/system/Database/Live/GetTest.php | 6 +- tests/system/Database/Live/GroupTest.php | 24 +++--- .../Database/Live/PreparedQueryTest.php | 5 +- tests/system/Database/Live/UpdateTest.php | 42 +++++----- tests/system/Database/Live/WhereTest.php | 52 ++++++------ 10 files changed, 118 insertions(+), 144 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index c99d9c5420d6..674f614d13d5 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -186,7 +186,7 @@ public function getVersion(): string /** * Executes the query against the database. * - * @return resource|boolean + * @return bool|resource */ public function execute(string $sql) { @@ -464,9 +464,9 @@ public function getCursor() /** * Stored Procedure. Executes a stored procedure * - * @param string $package package name in which the stored procedure is in - * @param string $procedure stored procedure name to execute - * @param array $params parameters + * @param string $package package name in which the stored procedure is in + * @param string $procedure stored procedure name to execute + * @param array $params parameters * * @return mixed * @@ -544,14 +544,14 @@ public function error(): array // oci_error() returns an array that already contains // 'code' and 'message' keys, but it can return false // if there was no error .... - $error = oci_error(); + $error = oci_error(); $resources = [$this->cursorId, $this->stmtId, $this->connID]; foreach ($resources as $resource) { - if (is_resource($resource)) { - $error = oci_error($resource); - break; - } + if (is_resource($resource)) { + $error = oci_error($resource); + break; + } } return is_array($error) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 5e50557579aa..382162b65fd0 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -146,9 +146,6 @@ protected function _alterTable(string $alter_type, string $table, $field) /** * Field attribute AUTO_INCREMENT * - * @param array $attributes - * @param array $field - * * @return void */ protected function _attributeAutoIncrement(array &$attributes, array &$field) @@ -191,8 +188,6 @@ protected function _processColumn(array $field): string * * Performs a data type mapping between different databases. * - * @param array $attributes - * * @return void */ protected function _attributeType(array &$attributes) @@ -222,10 +217,11 @@ protected function _attributeType(array &$attributes) return; case 'BOOLEAN': - $attributes['TYPE'] = 'NUMBER'; + $attributes['TYPE'] = 'NUMBER'; $attributes['CONSTRAINT'] = 1; - $attributes['UNSIGNED'] = true; - $attributes['NULL'] = false; + $attributes['UNSIGNED'] = true; + $attributes['NULL'] = false; + return; case 'DOUBLE': diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index 0126a0a6695d..d9a0ec8f3bed 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -168,10 +168,10 @@ public function run() if ($this->db->DBDriver === 'OCI8') { $this->db->query('alter session set NLS_DATE_FORMAT=?', ['YYYY/MM/DD HH24:MI:SS']); - $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; - $data['type_test'][0]['type_date'] = '2020-01-11 22:11:00'; - $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; - $data['type_test'][0]['type_datetime'] = '2020-06-18 05:12:24'; + $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; + $data['type_test'][0]['type_date'] = '2020-01-11 22:11:00'; + $data['type_test'][0]['type_time'] = '2020-07-18 15:22:00'; + $data['type_test'][0]['type_datetime'] = '2020-06-18 05:12:24'; $data['type_test'][0]['type_timestamp'] = '2020-06-18 21:53:21'; unset($data['type_test'][0]['type_blob']); } diff --git a/tests/system/Database/Live/DbUtilsTest.php b/tests/system/Database/Live/DbUtilsTest.php index 61e44628822f..e767d0b6991d 100644 --- a/tests/system/Database/Live/DbUtilsTest.php +++ b/tests/system/Database/Live/DbUtilsTest.php @@ -108,10 +108,9 @@ public function testUtilsOptimizeDatabase() { $util = (new Database())->loadUtils($this->db); - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $this->markTestSkipped( - 'Unsupported feature of the oracle database platform.' + 'Unsupported feature of the oracle database platform.' ); } @@ -154,12 +153,11 @@ public function testUtilsOptimizeTable() { $util = (new Database())->loadUtils($this->db); - if ($this->db->DBDriver === 'OCI8') - { - $this->markTestSkipped( - 'Unsupported feature of the oracle database platform.' - ); - } + if ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped( + 'Unsupported feature of the oracle database platform.' + ); + } $d = $util->optimizeTable('db_job'); diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 07585680daff..24b5a25abffc 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -44,8 +44,7 @@ protected function setUp(): void public function testCreateDatabase() { - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $this->markTestSkipped('OCI8 does not support create database.'); } $databaseCreated = $this->forge->createDatabase('test_forge_database'); @@ -55,10 +54,9 @@ public function testCreateDatabase() public function testCreateDatabaseIfNotExists() { - if ($this->db->DBDriver === 'OCI8') - { - $this->markTestSkipped('OCI8 does not support create database.'); - } + if ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped('OCI8 does not support create database.'); + } $dbName = 'test_forge_database_exist'; $databaseCreateIfNotExists = $this->forge->createDatabase($dbName, true); @@ -71,10 +69,9 @@ public function testCreateDatabaseIfNotExists() public function testCreateDatabaseIfNotExistsWithDb() { - if ($this->db->DBDriver === 'OCI8') - { - $this->markTestSkipped('OCI8 does not support create database.'); - } + if ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped('OCI8 does not support create database.'); + } $dbName = 'test_forge_database_exist'; $this->forge->createDatabase($dbName); @@ -88,12 +85,10 @@ public function testCreateDatabaseIfNotExistsWithDb() public function testDropDatabase() { - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $this->markTestSkipped('OCI8 does not support drop database.'); } - if ($this->db->DBDriver === 'SQLite3') - { + if ($this->db->DBDriver === 'SQLite3') { $this->markTestSkipped('SQLite3 requires file path to drop database'); } @@ -197,11 +192,10 @@ public function testCreateTableApplyBigInt() public function testCreateTableWithAttributes() { - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $this->markTestSkipped('OCI8 does not support comments on tables or columns.'); } - if ($this->db->DBDriver === 'SQLite3'){ + if ($this->db->DBDriver === 'SQLite3') { $this->markTestSkipped('SQLite3 does not support comments on tables or columns.'); } @@ -433,8 +427,7 @@ public function testForeignKey() $this->forge->addForeignKey('users_id', 'forge_test_users', 'id', 'CASCADE', 'CASCADE'); $tableName = 'forge_test_invoices'; - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $tableName = 'forge_test_inv'; } @@ -446,9 +439,9 @@ public function testForeignKey() $this->assertSame($foreignKeyData[0]->constraint_name, 'users_id to db_forge_test_users.id'); $this->assertSame($foreignKeyData[0]->sequence, 0); } elseif ($this->db->DBDriver === 'OCI8') { - $this->assertEquals($foreignKeyData[0]->constraint_name, $this->db->DBPrefix . 'forge_test_inv_users_id_fk'); - $this->assertEquals($foreignKeyData[0]->column_name, 'users_id'); - $this->assertEquals($foreignKeyData[0]->foreign_column_name, 'id'); + $this->assertSame($foreignKeyData[0]->constraint_name, $this->db->DBPrefix . 'forge_test_inv_users_id_fk'); + $this->assertSame($foreignKeyData[0]->column_name, 'users_id'); + $this->assertSame($foreignKeyData[0]->foreign_column_name, 'id'); } else { $this->assertSame($foreignKeyData[0]->constraint_name, $this->db->DBPrefix . 'forge_test_invoices_users_id_foreign'); $this->assertSame($foreignKeyData[0]->column_name, 'users_id'); @@ -527,9 +520,9 @@ public function testCompositeForeignKey() $this->forge->createTable('forge_test_users', true, $attributes); $forgeTestInvoicesTableName = 'forge_test_invoices'; - $userIdColumnName = 'user_id'; - $userSecondIdColumnName = 'user_second_id'; - $fields = [ + $userIdColumnName = 'user_id'; + $userSecondIdColumnName = 'user_second_id'; + $fields = [ 'id' => [ 'type' => 'INTEGER', 'constraint' => 11, @@ -541,8 +534,8 @@ public function testCompositeForeignKey() ]; if ($this->db->DBDriver === 'OCI8') { - $userIdColumnName = 'uid'; - $userSecondIdColumnName = 'usid'; + $userIdColumnName = 'uid'; + $userSecondIdColumnName = 'usid'; $forgeTestInvoicesTableName = 'forge_test_inv'; } @@ -569,7 +562,7 @@ public function testCompositeForeignKey() $this->assertSame(0, $foreignKeyData[0]->sequence); $this->assertSame('users_second_id to db_forge_test_users.second_id', $foreignKeyData[1]->constraint_name); $this->assertSame(1, $foreignKeyData[1]->sequence); - } else if ($this->db->DBDriver === 'OCI8') { + } elseif ($this->db->DBDriver === 'OCI8') { $haystack = [$userIdColumnName, $userSecondIdColumnName]; $this->assertSame($this->db->DBPrefix . 'forge_test_inv_uid_usid_fk', $foreignKeyData[0]->constraint_name); $this->assertContains($foreignKeyData[0]->column_name, $haystack); @@ -734,8 +727,7 @@ public function testDropForeignKey() $tableName = 'forge_test_invoices'; $foreignKeyName = 'forge_test_invoices_users_id_foreign'; - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $tableName = 'forge_test_inv'; $foreignKeyName = 'forge_test_inv_users_id_fk'; } @@ -790,8 +782,7 @@ public function testAddColumn() public function testAddFields() { $tableName = 'forge_test_fields'; - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $tableName = 'getestfield'; } $this->forge->dropTable($tableName, true); @@ -867,7 +858,7 @@ public function testAddFields() $this->assertNull($fieldsData[1]->default); $this->assertSame(255, (int) $fieldsData[1]->max_length); } elseif ($this->db->DBDriver === 'OCI8') { - //Check types + // Check types $this->assertSame('NUMBER', $fieldsData[0]->type); $this->assertSame('VARCHAR2', $fieldsData[1]->type); $this->assertSame('11', $fieldsData[0]->max_length); @@ -945,22 +936,20 @@ public function testCompositeKey() $this->assertSame($keys['db_forge_test_1_code_company']->fields, ['code', 'company']); $this->assertSame($keys['db_forge_test_1_code_company']->type, 'INDEX'); + $this->assertSame($keys['db_forge_test_1_code_active']->name, 'db_forge_test_1_code_active'); + $this->assertSame($keys['db_forge_test_1_code_active']->fields, ['code', 'active']); + $this->assertSame($keys['db_forge_test_1_code_active']->type, 'UNIQUE'); + } elseif ($this->db->DBDriver === 'OCI8') { + $this->assertSame($keys['pk_db_forge_test_1']->name, 'pk_db_forge_test_1'); + $this->assertSame($keys['pk_db_forge_test_1']->fields, ['id']); + $this->assertSame($keys['pk_db_forge_test_1']->type, 'PRIMARY'); + $this->assertSame($keys['db_forge_test_1_code_company']->name, 'db_forge_test_1_code_company'); + $this->assertSame($keys['db_forge_test_1_code_company']->fields, ['code', 'company']); + $this->assertSame($keys['db_forge_test_1_code_company']->type, 'INDEX'); $this->assertSame($keys['db_forge_test_1_code_active']->name, 'db_forge_test_1_code_active'); $this->assertSame($keys['db_forge_test_1_code_active']->fields, ['code', 'active']); $this->assertSame($keys['db_forge_test_1_code_active']->type, 'UNIQUE'); } - elseif ($this->db->DBDriver === 'OCI8') - { - $this->assertEquals($keys['pk_db_forge_test_1']->name, 'pk_db_forge_test_1'); - $this->assertEquals($keys['pk_db_forge_test_1']->fields, ['id']); - $this->assertEquals($keys['pk_db_forge_test_1']->type, 'PRIMARY'); - $this->assertEquals($keys['db_forge_test_1_code_company']->name, 'db_forge_test_1_code_company'); - $this->assertEquals($keys['db_forge_test_1_code_company']->fields, ['code', 'company']); - $this->assertEquals($keys['db_forge_test_1_code_company']->type, 'INDEX'); - $this->assertEquals($keys['db_forge_test_1_code_active']->name, 'db_forge_test_1_code_active'); - $this->assertEquals($keys['db_forge_test_1_code_active']->fields, ['code', 'active']); - $this->assertEquals($keys['db_forge_test_1_code_active']->type, 'UNIQUE'); - } $this->forge->dropTable('forge_test_1', true); } diff --git a/tests/system/Database/Live/GetTest.php b/tests/system/Database/Live/GetTest.php index a923435942f4..20ae2e099d0d 100644 --- a/tests/system/Database/Live/GetTest.php +++ b/tests/system/Database/Live/GetTest.php @@ -175,11 +175,9 @@ public function testGetDataSeek() if ($this->db->DBDriver === 'SQLite3') { $this->expectException(DatabaseException::class); $this->expectExceptionMessage('SQLite3 doesn\'t support seeking to other offset.'); + } elseif ($this->db->DBDriver === 'OCI8') { + $this->markTestSkipped('OCI8 does not support data seek.'); } - elseif ($this->db->DBDriver === 'OCI8') - { - $this->markTestSkipped('OCI8 does not support data seek.'); - } $data->dataSeek(3); diff --git a/tests/system/Database/Live/GroupTest.php b/tests/system/Database/Live/GroupTest.php index 32ccbce6765e..bfc66bd714f2 100644 --- a/tests/system/Database/Live/GroupTest.php +++ b/tests/system/Database/Live/GroupTest.php @@ -42,7 +42,7 @@ public function testHavingBy() $result = $this->db->table('job') ->select('name') ->groupBy('name') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->get() ->getResultArray(); @@ -55,7 +55,7 @@ public function testOrHavingBy() ->select('id') ->groupBy('id') ->having('id >', 3) - ->orHaving('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->orHaving('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->get() ->getResult(); @@ -114,7 +114,7 @@ public function testOrHavingNotIn() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orHavingNotIn('name', ['Developer', 'Politician']) ->get() ->getResult(); @@ -174,7 +174,7 @@ public function testOrNotHavingLike() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orNotHavingLike('name', 'ian') ->get() ->getResult(); @@ -191,9 +191,9 @@ public function testAndHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->havingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -209,9 +209,9 @@ public function testOrHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -228,9 +228,9 @@ public function testNotHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->notHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -246,9 +246,9 @@ public function testOrNotHavingGroupStart() ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') ->orNotHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') < 2') + ->having('SUM(' . $this->db->protectIdentifiers('id') . ') < 2') ->havingLike('name', 'o') ->havingGroupEnd() ->get() diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index 11cb741e267b..9a42f8efd2f6 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -69,8 +69,7 @@ public function testPrepareReturnsPreparedQuery() $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES ({$placeholders})"; } - if ($this->db->DBDriver === 'OCI8') - { + if ($this->db->DBDriver === 'OCI8') { $expected .= ' RETURNING ROWID INTO ?'; } @@ -121,7 +120,7 @@ public function testExecuteRunsQueryAndReturnsManualResultObject() . $db->protectIdentifiers('name') . ', ' . $db->protectIdentifiers('email') . ', ' . $db->protectIdentifiers('country') - . ") VALUES (?, ?, ?)"; + . ') VALUES (?, ?, ?)'; if ($db->DBDriver === 'SQLSRV') { $sql = "INSERT INTO {$db->schema}.{$db->DBPrefix}user (name, email, country) VALUES (?, ?, ?)"; diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 1e1e989ff5d5..fdf643be6deb 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -201,27 +201,27 @@ public function testUpdatePeriods() */ public function testSetWithoutEscape() { - if ($this->db->DBDriver === 'OCI8') - { - $forge = \Config\Database::forge($this->DBGroup); - $forge->modifyColumn('job', [ - 'description' => [ - 'name' => 'DESCRIPTION', - ], - 'name' => [ - 'name' => 'NAME', - ], - ]); - $this->db->table('job') - ->set('description', 'name', false) - ->update(); - - $this->seeInDatabase('job', [ - 'NAME' => 'Developer', - 'DESCRIPTION' => 'Developer', - ]); - return; - } + if ($this->db->DBDriver === 'OCI8') { + $forge = \Config\Database::forge($this->DBGroup); + $forge->modifyColumn('job', [ + 'description' => [ + 'name' => 'DESCRIPTION', + ], + 'name' => [ + 'name' => 'NAME', + ], + ]); + $this->db->table('job') + ->set('description', 'name', false) + ->update(); + + $this->seeInDatabase('job', [ + 'NAME' => 'Developer', + 'DESCRIPTION' => 'Developer', + ]); + + return; + } $this->db->table('job') ->set('description', 'name', false) diff --git a/tests/system/Database/Live/WhereTest.php b/tests/system/Database/Live/WhereTest.php index d65b2d7296a7..d03b2b5000e3 100644 --- a/tests/system/Database/Live/WhereTest.php +++ b/tests/system/Database/Live/WhereTest.php @@ -127,21 +127,18 @@ public function testSubQuery() ->where('name', 'Developer') ->getCompiledSelect(); - if ($this->db->DBDriver === 'OCI8') - { - $jobs = $this->db->table('job') - ->where('"id" not in (' . $subQuery . ')', null, false) - ->orderBy('id') - ->get() - ->getResult(); - } - else - { - $jobs = $this->db->table('job') - ->where('id not in (' . $subQuery . ')', null, false) - ->get() - ->getResult(); - } + if ($this->db->DBDriver === 'OCI8') { + $jobs = $this->db->table('job') + ->where('"id" not in (' . $subQuery . ')', null, false) + ->orderBy('id') + ->get() + ->getResult(); + } else { + $jobs = $this->db->table('job') + ->where('id not in (' . $subQuery . ')', null, false) + ->get() + ->getResult(); + } $this->assertCount(3, $jobs); $this->assertSame('Politician', $jobs[0]->name); @@ -156,20 +153,17 @@ public function testSubQueryAnotherType() ->where('name', 'Developer') ->getCompiledSelect(); - if ($this->db->DBDriver === 'OCI8') - { - $jobs = $this->db->table('job') - ->where('"id" = (' . $subQuery . ')', null, false) - ->get() - ->getResult(); - } - else - { - $jobs = $this->db->table('job') - ->where('id = (' . $subQuery . ')', null, false) - ->get() - ->getResult(); - } + if ($this->db->DBDriver === 'OCI8') { + $jobs = $this->db->table('job') + ->where('"id" = (' . $subQuery . ')', null, false) + ->get() + ->getResult(); + } else { + $jobs = $this->db->table('job') + ->where('id = (' . $subQuery . ')', null, false) + ->get() + ->getResult(); + } $this->assertCount(1, $jobs); $this->assertSame('Developer', $jobs[0]->name); From 5a6cc7c01a99ad7c75db9a040236d8c1667582ba Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 10 Sep 2021 00:24:02 +0900 Subject: [PATCH 105/184] fix: Changed foreach for object to be performed on the return value of get_object_vars. --- system/Database/OCI8/Result.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 3352f308a5c7..0f7f3456d15f 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -112,7 +112,7 @@ protected function fetchObject(string $className = \stdClass::class) $instance = new $className(); - foreach ($row as $key => $value) { + foreach (get_object_vars($row) as $key => $value) { $instance->{$key} = $value; } From 9be4e2fa0e196783ec2e2693d009861f3fa828a0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 16 Sep 2021 08:23:05 +0900 Subject: [PATCH 106/184] refactor: vendor/bin/rector --- system/Database/OCI8/Builder.php | 69 +++++++++++----------- system/Database/OCI8/Connection.php | 45 ++++++++------- system/Database/OCI8/Forge.php | 70 +++++++++++------------ system/Database/OCI8/PreparedQuery.php | 13 ++--- system/Database/OCI8/Result.php | 15 ++--- tests/system/Database/Live/UpdateTest.php | 3 +- 6 files changed, 109 insertions(+), 106 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 8b5318a588bf..d5366c6a170a 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\OCI8; +use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\BaseBuilder; /** @@ -73,24 +74,24 @@ class Builder extends BaseBuilder protected function _insertBatch(string $table, array $keys, array $values): string { $keys = implode(', ', $keys); - $has_primary_key = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); + $hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); // ORA-00001 measures - if ($has_primary_key) { + if ($hasPrimaryKey) { $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; - $select_query_values = []; + $selectQueryValues = []; - for ($i = 0, $c = count($values); $i < $c; $i++) { - $select_query_values[] = 'SELECT ' . substr(substr($values[$i], 1), 0, -1) . ' FROM DUAL'; + foreach ($values as $value) { + $selectQueryValues[] = 'SELECT ' . substr(substr($value, 1), 0, -1) . ' FROM DUAL'; } - return $sql . implode("\n UNION ALL \n", $select_query_values) . "\n)"; + return $sql . implode("\n UNION ALL \n", $selectQueryValues) . "\n)"; } $sql = "INSERT ALL\n"; - for ($i = 0, $c = count($values); $i < $c; $i++) { - $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $values[$i] . "\n"; + foreach ($values as $value) { + $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $value . "\n"; } return $sql . 'SELECT * FROM dual'; @@ -107,18 +108,18 @@ protected function _insertBatch(string $table, array $keys, array $values): stri */ protected function _replace(string $table, array $keys, array $values): string { - $field_names = array_map(static function ($column_name) { - return trim($column_name, '"'); + $fieldNames = array_map(static function ($columnName) { + return trim($columnName, '"'); }, $keys); - $unique_indexes = array_filter($this->db->getIndexData($table), static function ($index) use ($field_names) { - $has_all_fields = count(array_intersect($index->fields, $field_names)) === count($index->fields); + $uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames) { + $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); - return ($index->type === 'PRIMARY') && $has_all_fields; + return ($index->type === 'PRIMARY') && $hasAllFields; }); - $replaceable_fields = array_filter($keys, static function ($column_name) use ($unique_indexes) { - foreach ($unique_indexes as $index) { - if (in_array(trim($column_name, '"'), $index->fields, true)) { + $replaceableFields = array_filter($keys, static function ($columnName) use ($uniqueIndexes) { + foreach ($uniqueIndexes as $index) { + if (in_array(trim($columnName, '"'), $index->fields, true)) { return false; } } @@ -128,31 +129,31 @@ protected function _replace(string $table, array $keys, array $values): string $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; - $sql .= implode(', ', array_map(static function ($column_name, $value) { - return $value . ' ' . $column_name; + $sql .= implode(', ', array_map(static function ($columnName, $value) { + return $value . ' ' . $columnName; }, $keys, $values)); $sql .= ' FROM DUAL) "_replace" ON ( '; - $on_list = []; - $on_list[] = '1 != 1'; + $onList = []; + $onList[] = '1 != 1'; - foreach ($unique_indexes as $index) { - $on_list[] = '(' . implode(' AND ', array_map(static function ($column_name) use ($table) { - return $table . '."' . $column_name . '" = "_replace"."' . $column_name . '"'; + foreach ($uniqueIndexes as $index) { + $onList[] = '(' . implode(' AND ', array_map(static function ($columnName) use ($table) { + return $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"'; }, $index->fields)) . ')'; } - $sql .= implode(' OR ', $on_list) . ') WHEN MATCHED THEN UPDATE SET '; + $sql .= implode(' OR ', $onList) . ') WHEN MATCHED THEN UPDATE SET '; - $sql .= implode(', ', array_map(static function ($column_name) { - return $column_name . ' = "_replace".' . $column_name; - }, $replaceable_fields)); + $sql .= implode(', ', array_map(static function ($columnName) { + return $columnName . ' = "_replace".' . $columnName; + }, $replaceableFields)); - $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceable_fields) . ') VALUES '; - $sql .= ' (' . implode(', ', array_map(static function ($column_name) { - return '"_replace".' . $column_name; - }, $replaceable_fields)) . ')'; + $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceableFields) . ') VALUES '; + $sql .= ' (' . implode(', ', array_map(static function ($columnName) { + return '"_replace".' . $columnName; + }, $replaceableFields)) . ')'; return $sql; } @@ -180,17 +181,17 @@ protected function _truncate(string $table): string * @param mixed $where The where clause * @param int $limit The limit clause * - * @throws \CodeIgniter\Database\Exceptions\DatabaseException + * @throws DatabaseException * * @return mixed */ - public function delete($where = '', ?int $limit = null, bool $reset_data = true) + public function delete($where = '', ?int $limit = null, bool $resetData = true) { if (! empty($limit)) { $this->QBLimit = $limit; } - return parent::delete($where, null, $reset_data); + return parent::delete($where, null, $resetData); } /** diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 674f614d13d5..c0d1beee4218 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -19,6 +19,9 @@ /** * Connection for Postgre + * + * @property string|null $latestInsertedTableName + * @property int|null $rowId */ class Connection extends BaseConnection implements ConnectionInterface { @@ -173,10 +176,10 @@ public function getVersion(): string return $this->dataCache['version']; } - if (! $this->connID || ($version_string = oci_server_version($this->connID)) === false) { + if (! $this->connID || ($versionString = oci_server_version($this->connID)) === false) { return ''; } - if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $version_string, $match)) { + if (preg_match('#Release\s(\d+(?:\.\d+)+)#', $versionString, $match)) { return $this->dataCache['version'] = $match[1]; } @@ -188,7 +191,7 @@ public function getVersion(): string * * @return bool|resource */ - public function execute(string $sql) + protected function execute(string $sql) { try { if ($this->resetStmtId === true) { @@ -261,9 +264,9 @@ protected function _listColumns(string $table = ''): string * * @throws DatabaseException * - * @return \stdClass[] + * @return stdClass[] */ - public function _fieldData(string $table): array + protected function _fieldData(string $table): array { if (strpos($table, '.') !== false) { sscanf($table, '%[^.].%s', $owner, $table); @@ -311,9 +314,9 @@ public function _fieldData(string $table): array * * @throws DatabaseException * - * @return \stdClass[] + * @return stdClass[] */ - public function _indexData(string $table): array + protected function _indexData(string $table): array { if (strpos($table, '.') !== false) { sscanf($table, '%[^.].%s', $owner, $table); @@ -360,9 +363,9 @@ public function _indexData(string $table): array * * @throws DatabaseException * - * @return \stdClass[] + * @return stdClass[] */ - public function _foreignKeyData(string $table): array + protected function _foreignKeyData(string $table): array { $sql = 'SELECT acc.constraint_name, @@ -488,13 +491,13 @@ public function storedProcedure(string $package, string $procedure, array $param // Build the query string $sql = 'BEGIN ' . $package . '.' . $procedure . '('; - $have_cursor = false; + $haveCursor = false; foreach ($params as $param) { $sql .= $param['name'] . ','; if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) { - $have_cursor = true; + $haveCursor = true; } } $sql = trim($sql, ',') . '); END;'; @@ -502,7 +505,7 @@ public function storedProcedure(string $package, string $procedure, array $param $this->resetStmtId = false; $this->stmtId = oci_parse($this->connID, $sql); $this->bindParams($params); - $result = $this->query($sql, false, $have_cursor); + $result = $this->query($sql, false, $haveCursor); $this->resetStmtId = true; return $result; @@ -572,34 +575,34 @@ public function insertID(): int } $indexs = $this->getIndexData($this->latestInsertedTableName); - $field_datas = $this->getFieldData($this->latestInsertedTableName); + $fieldDatas = $this->getFieldData($this->latestInsertedTableName); - if (! $indexs || ! $field_datas) { + if (! $indexs || ! $fieldDatas) { return 0; } - $column_type_list = array_column($field_datas, 'type', 'name'); - $primary_column_name = ''; + $columnTypeList = array_column($fieldDatas, 'type', 'name'); + $primaryColumnName = ''; foreach ($indexs as $index) { if ($index->type !== 'PRIMARY' || count($index->fields) !== 1) { continue; } - $primary_column_name = $this->protectIdentifiers($index->fields[0], false, false); - $primary_column_type = $column_type_list[$primary_column_name]; + $primaryColumnName = $this->protectIdentifiers($index->fields[0], false, false); + $primaryColumnType = $columnTypeList[$primaryColumnName]; - if ($primary_column_type !== 'NUMBER') { + if ($primaryColumnType !== 'NUMBER') { continue; } } - if (! $primary_column_name) { + if (! $primaryColumnName) { return 0; } $table = $this->protectIdentifiers($this->latestInsertedTableName, true); - $query = $this->query('SELECT ' . $this->protectIdentifiers($primary_column_name, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); + $query = $this->query('SELECT ' . $this->protectIdentifiers($primaryColumnName, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); return (int) ($query->SEQ ?? 0); } diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 382162b65fd0..c428b40f9825 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -74,40 +74,40 @@ class Forge extends \CodeIgniter\Database\Forge /** * ALTER TABLE - * - * @param string $alter_type ALTER type - * @param string $table Table name - * @param mixed $field Column definition - * + * + * @param string $alterType ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * * @return string|string[] */ - protected function _alterTable(string $alter_type, string $table, $field) + protected function _alterTable(string $alterType, string $table, $field) { $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); - if ($alter_type === 'DROP') { + if ($alterType === 'DROP') { $fields = array_map(function ($field) { return $this->db->escapeIdentifiers(trim($field)); }, (is_string($field)) ? explode(',', $field) : $field); return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } - if ($alter_type === 'CHANGE') { - $alter_type = 'MODIFY'; + if ($alterType === 'CHANGE') { + $alterType = 'MODIFY'; } - $nullable_map = array_column($this->db->getFieldData($table), 'nullable', 'name'); + $nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name'); $sqls = []; for ($i = 0, $c = count($field); $i < $c; $i++) { - if ($alter_type === 'MODIFY') { + if ($alterType === 'MODIFY') { // If a null constraint is added to a column with a null constraint, // ORA-01451 will occur, // so add null constraint is used only when it is different from the current null constraint. - $is_want_to_add_null = (strpos($field[$i]['null'], ' NOT') === false); - $current_null_addable = $nullable_map[$field[$i]['name']]; + $isWantToAddNull = (strpos($field[$i]['null'], ' NOT') === false); + $currentNullAddable = $nullableMap[$field[$i]['name']]; - if ($is_want_to_add_null === $current_null_addable) { + if ($isWantToAddNull === $currentNullAddable) { $field[$i]['null'] = ''; } } @@ -123,7 +123,7 @@ protected function _alterTable(string $alter_type, string $table, $field) . ' IS ' . $field[$i]['comment']; } - if ($alter_type === 'MODIFY' && ! empty($field[$i]['new_name'])) { + if ($alterType === 'MODIFY' && ! empty($field[$i]['new_name'])) { $sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($field[$i]['name']) . ' TO ' . $this->db->escapeIdentifiers($field[$i]['new_name']); } @@ -132,7 +132,7 @@ protected function _alterTable(string $alter_type, string $table, $field) } } - $sql .= ' ' . $alter_type . ' '; + $sql .= ' ' . $alterType . ' '; $sql .= (count($field) === 1) ? $field[0] : '(' . implode(',', $field) . ')'; @@ -248,23 +248,23 @@ protected function _attributeType(array &$attributes) return; - default: return; + default: } } /** * Drop Table - * + * * Generates a platform-specific DROP TABLE string - * - * @param string $table Table name - * @param bool $if_exists Whether to add an IF EXISTS condition - * + * + * @param string $table Table name + * @param bool $ifExists Whether to add an IF EXISTS condition + * * @return bool|string */ - protected function _dropTable(string $table, bool $if_exists, bool $cascade) + protected function _dropTable(string $table, bool $ifExists, bool $cascade) { - $sql = parent::_dropTable($table, $if_exists, $cascade); + $sql = parent::_dropTable($table, $ifExists, $cascade); if ($sql !== true && $cascade === true) { $sql .= ' CASCADE CONSTRAINTS PURGE'; @@ -290,20 +290,18 @@ protected function _processForeignKeys(string $table): string 'NO ACTION', ]; - if (count($this->foreignKeys) > 0) { - foreach ($this->foreignKeys as $field => $fkey) { - $nameIndex = $table . '_' . implode('_', $fkey['field']) . '_fk'; - $nameIndexFilled = $this->db->escapeIdentifiers($nameIndex); - $foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field'])); - $referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']); - $referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField'])); + foreach ($this->foreignKeys as $fkey) { + $nameIndex = $table . '_' . implode('_', $fkey['field']) . '_fk'; + $nameIndexFilled = $this->db->escapeIdentifiers($nameIndex); + $foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field'])); + $referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']); + $referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField'])); - $formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)"; - $sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled); + $formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)"; + $sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled); - if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) { - $sql .= ' ON DELETE ' . $fkey['onDelete']; - } + if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) { + $sql .= ' ON DELETE ' . $fkey['onDelete']; } } diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 36b4325df485..952593a2f167 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\OCI8; +use BadMethodCallException; use CodeIgniter\Database\BasePreparedQuery; use CodeIgniter\Database\PreparedQueryInterface; @@ -90,18 +91,18 @@ public function _prepare(string $sql, array $options = []) public function _execute(array $data): bool { if (null === $this->statement) { - throw new \BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); + throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.'); } - $last_key = 0; + $lastKey = 0; foreach (array_keys($data) as $key) { oci_bind_by_name($this->statement, ':' . $key, $data[$key]); - $last_key = $key; + $lastKey = $key; } if ($this->isCollectRowId) { - oci_bind_by_name($this->statement, ':' . (++$last_key), $this->db->rowId, 255); + oci_bind_by_name($this->statement, ':' . (++$lastKey), $this->db->rowId, 255); } return oci_execute($this->statement, $this->db->commitMode); @@ -126,10 +127,8 @@ public function parameterize(string $sql): string // Track our current value $count = 0; - $sql = preg_replace_callback('/\?/', static function ($matches) use (&$count) { + return preg_replace_callback('/\?/', static function ($matches) use (&$count) { return ':' . ($count++); }, $sql); - - return $sql; } } diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 0f7f3456d15f..a798b9b12edb 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\OCI8; +use stdClass; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; @@ -33,8 +34,8 @@ public function getFieldCount(): int */ public function getFieldNames(): array { - return array_map(function ($field_index) { - return oci_field_name($this->resultID, $field_index); + return array_map(function ($fieldIndex) { + return oci_field_name($this->resultID, $fieldIndex); }, range(1, $this->getFieldCount())); } @@ -43,11 +44,11 @@ public function getFieldNames(): array */ public function getFieldData(): array { - return array_map(function ($field_index) { + return array_map(function ($fieldIndex) { return (object) [ - 'name' => oci_field_name($this->resultID, $field_index), - 'type' => oci_field_type($this->resultID, $field_index), - 'max_length' => oci_field_size($this->resultID, $field_index), + 'name' => oci_field_name($this->resultID, $fieldIndex), + 'type' => oci_field_type($this->resultID, $fieldIndex), + 'max_length' => oci_field_size($this->resultID, $fieldIndex), // 'primary_key' = (int) ($data->flags & 2), // 'default' = $data->def, ]; @@ -99,7 +100,7 @@ protected function fetchAssoc() * * @return bool|Entity|object */ - protected function fetchObject(string $className = \stdClass::class) + protected function fetchObject(string $className = stdClass::class) { $row = oci_fetch_object($this->resultID); diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index fdf643be6deb..7ec2c6ebdcbb 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\Live; +use Config\Database; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; @@ -202,7 +203,7 @@ public function testUpdatePeriods() public function testSetWithoutEscape() { if ($this->db->DBDriver === 'OCI8') { - $forge = \Config\Database::forge($this->DBGroup); + $forge = Database::forge($this->DBGroup); $forge->modifyColumn('job', [ 'description' => [ 'name' => 'DESCRIPTION', From 2e5ffef9066959a0d6715f86731c573408a36ed0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 17 Sep 2021 00:17:35 +0900 Subject: [PATCH 107/184] fix: user_id -> users_id --- tests/system/Database/Live/ForgeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 24b5a25abffc..e20f26556f1d 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -520,8 +520,8 @@ public function testCompositeForeignKey() $this->forge->createTable('forge_test_users', true, $attributes); $forgeTestInvoicesTableName = 'forge_test_invoices'; - $userIdColumnName = 'user_id'; - $userSecondIdColumnName = 'user_second_id'; + $userIdColumnName = 'users_id'; + $userSecondIdColumnName = 'users_second_id'; $fields = [ 'id' => [ 'type' => 'INTEGER', From a69f2817bbf60c53b4fe6d20ea44e5d80b5db91c Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 17 Sep 2021 00:28:27 +0900 Subject: [PATCH 108/184] style: vendor/bin/php-cs-fixer fix --- system/Database/OCI8/Builder.php | 6 +++--- system/Database/OCI8/Connection.php | 2 +- system/Database/OCI8/Forge.php | 22 +++++++++++----------- system/Database/OCI8/Result.php | 2 +- tests/system/Database/Live/UpdateTest.php | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index d5366c6a170a..5bdc1aaff39a 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -11,8 +11,8 @@ namespace CodeIgniter\Database\OCI8; -use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\BaseBuilder; +use CodeIgniter\Database\Exceptions\DatabaseException; /** * Builder for MySQLi @@ -73,12 +73,12 @@ class Builder extends BaseBuilder */ protected function _insertBatch(string $table, array $keys, array $values): string { - $keys = implode(', ', $keys); + $keys = implode(', ', $keys); $hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); // ORA-00001 measures if ($hasPrimaryKey) { - $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; + $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; $selectQueryValues = []; foreach ($values as $value) { diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index c0d1beee4218..ee1201216914 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -574,7 +574,7 @@ public function insertID(): int return 0; } - $indexs = $this->getIndexData($this->latestInsertedTableName); + $indexs = $this->getIndexData($this->latestInsertedTableName); $fieldDatas = $this->getFieldData($this->latestInsertedTableName); if (! $indexs || ! $fieldDatas) { diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index c428b40f9825..d2e4bb1ee9cb 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -74,11 +74,11 @@ class Forge extends \CodeIgniter\Database\Forge /** * ALTER TABLE - * + * * @param string $alterType ALTER type - * @param string $table Table name - * @param mixed $field Column definition - * + * @param string $table Table name + * @param mixed $field Column definition + * * @return string|string[] */ protected function _alterTable(string $alterType, string $table, $field) @@ -97,14 +97,14 @@ protected function _alterTable(string $alterType, string $table, $field) } $nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name'); - $sqls = []; + $sqls = []; for ($i = 0, $c = count($field); $i < $c; $i++) { if ($alterType === 'MODIFY') { // If a null constraint is added to a column with a null constraint, // ORA-01451 will occur, // so add null constraint is used only when it is different from the current null constraint. - $isWantToAddNull = (strpos($field[$i]['null'], ' NOT') === false); + $isWantToAddNull = (strpos($field[$i]['null'], ' NOT') === false); $currentNullAddable = $nullableMap[$field[$i]['name']]; if ($isWantToAddNull === $currentNullAddable) { @@ -254,12 +254,12 @@ protected function _attributeType(array &$attributes) /** * Drop Table - * + * * Generates a platform-specific DROP TABLE string - * - * @param string $table Table name - * @param bool $ifExists Whether to add an IF EXISTS condition - * + * + * @param string $table Table name + * @param bool $ifExists Whether to add an IF EXISTS condition + * * @return bool|string */ protected function _dropTable(string $table, bool $ifExists, bool $cascade) diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index a798b9b12edb..0d5c692560d4 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -11,10 +11,10 @@ namespace CodeIgniter\Database\OCI8; -use stdClass; use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\ResultInterface; use CodeIgniter\Entity; +use stdClass; /** * Result for OCI diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 7ec2c6ebdcbb..ac3c4c8d2744 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -11,10 +11,10 @@ namespace CodeIgniter\Database\Live; -use Config\Database; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; +use Config\Database; /** * @group DatabaseLive From 4a919fcc6001cf2fff1febc2b2eed5631da4c7c3 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 17 Sep 2021 00:46:54 +0900 Subject: [PATCH 109/184] docs: fix link path. --- user_guide_src/source/database/helpers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/database/helpers.rst b/user_guide_src/source/database/helpers.rst index 0a6695d0bcdf..d56c78c5799f 100644 --- a/user_guide_src/source/database/helpers.rst +++ b/user_guide_src/source/database/helpers.rst @@ -17,7 +17,7 @@ The insert ID number when performing database inserts. driver, this function requires a $name parameter, which specifies the appropriate sequence to check for the insert id. -.. note:: If using the OCI8 driver, the insert ID can be get when using insert() of :doc:`QueryBuilder<./querybuilder>`. +.. note:: If using the OCI8 driver, the insert ID can be get when using insert() of :doc:`QueryBuilder<./query_builder>`. **$db->affectedRows()** From 202f3eccc64472c05cbf0ff79053b2c149abdd7b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 24 Sep 2021 07:11:50 +0900 Subject: [PATCH 110/184] =?UTF-8?q?fix:=20remove=20unnecessary=20escape.?= =?UTF-8?q?=20i=20can=E2=80=99t=20imagine=20set=20key=20is=20not=20column.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/system/Database/Live/UpdateTest.php | 24 +---------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index ac3c4c8d2744..c5502ce949a1 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -202,30 +202,8 @@ public function testUpdatePeriods() */ public function testSetWithoutEscape() { - if ($this->db->DBDriver === 'OCI8') { - $forge = Database::forge($this->DBGroup); - $forge->modifyColumn('job', [ - 'description' => [ - 'name' => 'DESCRIPTION', - ], - 'name' => [ - 'name' => 'NAME', - ], - ]); - $this->db->table('job') - ->set('description', 'name', false) - ->update(); - - $this->seeInDatabase('job', [ - 'NAME' => 'Developer', - 'DESCRIPTION' => 'Developer', - ]); - - return; - } - $this->db->table('job') - ->set('description', 'name', false) + ->set('description', $this->db->escapeIdentifiers('name'), false) ->update(); $this->seeInDatabase('job', [ From 61ad650499f0a62fe31f8b4584d32d49e8b81afb Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 24 Sep 2021 07:16:20 +0900 Subject: [PATCH 111/184] fix: fix fail bulk insert when table has auto increment column. --- system/Database/OCI8/Builder.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 5bdc1aaff39a..c2f67c904620 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -73,16 +73,19 @@ class Builder extends BaseBuilder */ protected function _insertBatch(string $table, array $keys, array $values): string { - $keys = implode(', ', $keys); + $insertKeys = implode(', ', $keys); $hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true); // ORA-00001 measures if ($hasPrimaryKey) { - $sql = 'INSERT INTO ' . $table . ' (' . $keys . ") \n SELECT * FROM (\n"; + $sql = 'INSERT INTO ' . $table . ' (' . $insertKeys . ") \n SELECT * FROM (\n"; $selectQueryValues = []; foreach ($values as $value) { - $selectQueryValues[] = 'SELECT ' . substr(substr($value, 1), 0, -1) . ' FROM DUAL'; + $selectValues = implode(',', array_map(static function ($value, $key) { + return $value .' as '. $key; + }, explode(',', substr(substr($value, 1), 0, -1)), $keys)); + $selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL'; } return $sql . implode("\n UNION ALL \n", $selectQueryValues) . "\n)"; @@ -91,7 +94,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri $sql = "INSERT ALL\n"; foreach ($values as $value) { - $sql .= ' INTO ' . $table . ' (' . $keys . ') VALUES ' . $value . "\n"; + $sql .= ' INTO ' . $table . ' (' . $insertKeys . ') VALUES ' . $value . "\n"; } return $sql . 'SELECT * FROM dual'; From 6a6472af1c072adf5ae1dd67483a73f9b14696e8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 24 Sep 2021 07:18:36 +0900 Subject: [PATCH 112/184] test: add escape for lower case column name. --- tests/system/Models/FindModelTest.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php index bf06422d8f5f..61d1e0052fd2 100644 --- a/tests/system/Models/FindModelTest.php +++ b/tests/system/Models/FindModelTest.php @@ -168,7 +168,9 @@ public function testFirstAggregate($groupBy, $total): void $this->model->groupBy('id'); } - $user = $this->model->select('SUM(id) as total')->where('id >', 2)->first(); + $user = $this->model->select('SUM('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('total')) + ->where('id >', 2) + ->first(); $this->assertSame($total, (int) $user->total); } @@ -195,10 +197,14 @@ public function testFirstRespectsSoftDeletes($aggregate, $groupBy): void $this->createModel(UserModel::class); if ($aggregate) { - $this->model->select('SUM(id) as id'); + $this->model->select('SUM('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('id')); } if ($groupBy) { + if (!$aggregate) { + $this->model->select('id'); + } + $this->model->groupBy('id'); } @@ -228,10 +234,11 @@ public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void $this->model->delete(1); if ($aggregate) { - $this->model->select('sum(id) as id'); + $this->model->select('sum('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('id')); } if ($groupBy) { + $this->model->groupBy('id'); } From 6109a724b2c147e455fd79e1a3165c4d6f7fed55 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 24 Sep 2021 07:20:53 +0900 Subject: [PATCH 113/184] fix: fixed groupBy column not matching select column. --- tests/system/Models/FindModelTest.php | 6 ++++-- tests/system/Models/PaginateModelTest.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php index 61d1e0052fd2..80194a983177 100644 --- a/tests/system/Models/FindModelTest.php +++ b/tests/system/Models/FindModelTest.php @@ -218,7 +218,7 @@ public function testFirstRespectsSoftDeletes($aggregate, $groupBy): void $this->assertSame(9, (int) $user->id); } - $user = $this->model->withDeleted()->first(); + $user = $this->model->withDeleted()->select('id')->first(); $this->assertSame(1, (int) $user->id); } @@ -235,6 +235,8 @@ public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void if ($aggregate) { $this->model->select('sum('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('id')); + } else { + $this->model->select('id'); } if ($groupBy) { @@ -245,7 +247,7 @@ public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void $user = $this->model->withDeleted()->first(); $this->assertSame(1, (int) $user->id); - $user2 = $this->model->first(); + $user2 = $this->model->select('id')->first(); $this->assertSame(2, (int) $user2->id); } diff --git a/tests/system/Models/PaginateModelTest.php b/tests/system/Models/PaginateModelTest.php index 35e58221ecee..ae93f2f6c62b 100644 --- a/tests/system/Models/PaginateModelTest.php +++ b/tests/system/Models/PaginateModelTest.php @@ -46,7 +46,7 @@ public function testPaginatePassPerPageParameter(): void public function testPaginateForQueryWithGroupBy(): void { $this->createModel(ValidModel::class); - $this->model->groupBy('id'); + $this->model->select('id')->groupBy('id'); $this->model->paginate(); $this->assertSame(4, $this->model->pager->getDetails()['total']); } From 015840ae765b42d37bfcab7db3006bb120283831 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 24 Sep 2021 07:38:18 +0900 Subject: [PATCH 114/184] test: Added skip test for create database. --- tests/system/Commands/CreateDatabaseTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/system/Commands/CreateDatabaseTest.php b/tests/system/Commands/CreateDatabaseTest.php index a00de5c437a1..835aac4d03a6 100644 --- a/tests/system/Commands/CreateDatabaseTest.php +++ b/tests/system/Commands/CreateDatabaseTest.php @@ -14,6 +14,7 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\Database as DatabaseFactory; use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection; +use CodeIgniter\Database\OCI8\Connection as OCI8Connection; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Filters\CITestStreamFilter; use Config\Database; @@ -68,6 +69,10 @@ protected function getBuffer() public function testCreateDatabase() { + if ($this->connection instanceof OCI8Connection) { + $this->markTestSkipped('Needs to run on non-OCI8 drivers.'); + } + command('db:create foobar'); $this->assertStringContainsString('successfully created.', $this->getBuffer()); } @@ -87,8 +92,8 @@ public function testSqliteDatabaseDuplicated() public function testOtherDriverDuplicatedDatabase() { - if ($this->connection instanceof SQLite3Connection) { - $this->markTestSkipped('Needs to run on non-SQLite3 drivers.'); + if ($this->connection instanceof SQLite3Connection || $this->connection instanceof OCI8Connection) { + $this->markTestSkipped('Needs to run on non-SQLite3 and non-OCI8 drivers.'); } command('db:create foobar'); From 4622e3cbf1045e606162699755531a52994d523b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 25 Sep 2021 09:35:49 +0900 Subject: [PATCH 115/184] fix: Changed the database property to be immutable. --- system/Database/OCI8/Connection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index ee1201216914..693d2a66a813 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -707,10 +707,10 @@ protected function _transRollback(): bool */ public function getDatabase(): string { - if (empty($this->database)) { - $this->database = $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? ''; + if (!empty($this->database)) { + return $this->database; } - return empty($this->database) ? '' : $this->database; + return $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? ''; } } From 99c20e4914b251a19429df0f243ef54e9c6c2bb1 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 25 Sep 2021 09:44:36 +0900 Subject: [PATCH 116/184] style: vendor/bin/php-cs-fixer fix --verbose --ansi --using-cache=no --diff --- system/Database/OCI8/Builder.php | 2 +- system/Database/OCI8/Connection.php | 2 +- tests/system/Commands/CreateDatabaseTest.php | 3 +-- tests/system/Models/FindModelTest.php | 9 ++++----- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index c2f67c904620..dd6e08cbd60c 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -83,7 +83,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri foreach ($values as $value) { $selectValues = implode(',', array_map(static function ($value, $key) { - return $value .' as '. $key; + return $value . ' as ' . $key; }, explode(',', substr(substr($value, 1), 0, -1)), $keys)); $selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL'; } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 693d2a66a813..28ba609435a6 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -707,7 +707,7 @@ protected function _transRollback(): bool */ public function getDatabase(): string { - if (!empty($this->database)) { + if (! empty($this->database)) { return $this->database; } diff --git a/tests/system/Commands/CreateDatabaseTest.php b/tests/system/Commands/CreateDatabaseTest.php index 835aac4d03a6..f0ce8a683857 100644 --- a/tests/system/Commands/CreateDatabaseTest.php +++ b/tests/system/Commands/CreateDatabaseTest.php @@ -12,9 +12,8 @@ namespace CodeIgniter\Commands; use CodeIgniter\Database\BaseConnection; -use CodeIgniter\Database\Database as DatabaseFactory; -use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection; use CodeIgniter\Database\OCI8\Connection as OCI8Connection; +use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Filters\CITestStreamFilter; use Config\Database; diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php index 80194a983177..4cddee6530fb 100644 --- a/tests/system/Models/FindModelTest.php +++ b/tests/system/Models/FindModelTest.php @@ -168,7 +168,7 @@ public function testFirstAggregate($groupBy, $total): void $this->model->groupBy('id'); } - $user = $this->model->select('SUM('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('total')) + $user = $this->model->select('SUM(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('total')) ->where('id >', 2) ->first(); $this->assertSame($total, (int) $user->total); @@ -197,11 +197,11 @@ public function testFirstRespectsSoftDeletes($aggregate, $groupBy): void $this->createModel(UserModel::class); if ($aggregate) { - $this->model->select('SUM('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('id')); + $this->model->select('SUM(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('id')); } if ($groupBy) { - if (!$aggregate) { + if (! $aggregate) { $this->model->select('id'); } @@ -234,13 +234,12 @@ public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void $this->model->delete(1); if ($aggregate) { - $this->model->select('sum('.$this->db->escapeIdentifiers('id').') as '.$this->db->escapeIdentifiers('id')); + $this->model->select('sum(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('id')); } else { $this->model->select('id'); } if ($groupBy) { - $this->model->groupBy('id'); } From a37b852cfa37465baf745a7eaf85e85a1b0cdd1b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 25 Sep 2021 10:23:33 +0900 Subject: [PATCH 117/184] chore: Add phpunit workflow for oci8. --- .github/workflows/test-phpunit.yml | 34 +++++++++++++++++++++++++++-- tests/_support/Config/Registrar.php | 18 +++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index 6908f2be88e2..5f2189f41e42 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -31,7 +31,7 @@ jobs: fail-fast: false matrix: php-versions: ['7.4', '8.0', '8.1'] - db-platforms: ['MySQLi', 'Postgre', 'SQLite3', 'SQLSRV'] + db-platforms: ['MySQLi', 'Postgre', 'SQLite3', 'SQLSRV', 'OCI8'] mysql-versions: ['5.7'] include: - php-versions: '7.4' @@ -68,6 +68,14 @@ jobs: - 1433:1433 options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 + oracle: + image: quillbuilduser/oracle-18-xe + env: + ORACLE_ALLOW_REMOTE: true + ports: + - 1521:1521 + options: --health-cmd="/opt/oracle/product/18c/dbhomeXE/bin/sqlplus -s sys/Oracle18@oracledbxe/XE as sysdba <<< 'SELECT 1 FROM DUAL'" --health-interval=10s --health-timeout=5s --health-retries=3 + redis: image: redis ports: @@ -84,6 +92,28 @@ jobs: if: matrix.db-platforms == 'SQLSRV' run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" + - name: Install Oracle InstantClient + if: matrix.db-platforms == 'OCI8' + run: | + sudo apt-get install wget libaio1 alien + sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm + sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm + sudo wget https://download.oracle.com/otn_software/linux/instantclient/185000/oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm + sudo alien oracle-instantclient18.5-basic-18.5.0.0.0-3.x86_64.rpm + sudo alien oracle-instantclient18.5-devel-18.5.0.0.0-3.x86_64.rpm + sudo alien oracle-instantclient18.5-sqlplus-18.5.0.0.0-3.x86_64.rpm + sudo dpkg -i oracle-instantclient18.5-basic_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-devel_18.5.0.0.0-4_amd64.deb oracle-instantclient18.5-sqlplus_18.5.0.0.0-4_amd64.deb + echo "LD_LIBRARY_PATH=/lib/oracle/18.5/client64/lib/" >> $GITHUB_ENV + echo "NLS_LANG=AMERICAN_AMERICA.UTF8" >> $GITHUB_ENV + echo "C_INCLUDE_PATH=/usr/include/oracle/18.5/client64" >> $GITHUB_ENV + echo 'NLS_DATE_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV + echo 'NLS_TIMESTAMP_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV + echo 'NLS_TIMESTAMP_TZ_FORMAT=YYYY-MM-DD HH24:MI:SS' >> $GITHUB_ENV + + - name: Create database for Oracle Database + if: matrix.db-platforms == 'OCI8' + run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba + - name: Checkout uses: actions/checkout@v2 @@ -92,7 +122,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} tools: composer, pecl - extensions: imagick, sqlsrv, gd, sqlite3, redis, memcached, pgsql + extensions: imagick, sqlsrv, gd, sqlite3, redis, memcached, oci8, pgsql coverage: xdebug env: update: true diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php index 989b406655b1..b568629ea19f 100644 --- a/tests/_support/Config/Registrar.php +++ b/tests/_support/Config/Registrar.php @@ -100,6 +100,24 @@ class Registrar 'failover' => [], 'port' => 1433, ], + 'OCI8' => [ + 'DSN' => 'localhost:1521/XEPDB1', + 'hostname' => '', + 'username' => 'ORACLE', + 'password' => 'ORACLE', + 'database' => '', + 'DBDriver' => 'OCI8', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => (ENVIRONMENT !== 'production'), + 'charset' => 'utf8', + 'DBCollat' => 'utf8_general_ci', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + ], ]; /** From 523333a59a593de93767792b30b29777d20182bd Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 25 Sep 2021 14:38:44 +0900 Subject: [PATCH 118/184] docs: MySQLi -> OCI8 --- system/Database/OCI8/Builder.php | 2 +- system/Database/OCI8/Forge.php | 2 +- system/Database/OCI8/PreparedQuery.php | 4 ++-- system/Database/OCI8/Result.php | 2 +- system/Database/OCI8/Utils.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index dd6e08cbd60c..f81bfbea6ef1 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -15,7 +15,7 @@ use CodeIgniter\Database\Exceptions\DatabaseException; /** - * Builder for MySQLi + * Builder for OCI8 */ class Builder extends BaseBuilder { diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index d2e4bb1ee9cb..58b550cc9b89 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -12,7 +12,7 @@ namespace CodeIgniter\Database\OCI8; /** - * Forge for MySQLi + * Forge for OCI8 */ class Forge extends \CodeIgniter\Database\Forge { diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 952593a2f167..8c4c493543ac 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -16,7 +16,7 @@ use CodeIgniter\Database\PreparedQueryInterface; /** - * Prepared query for MySQLi + * Prepared query for OCI8 */ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface { @@ -64,7 +64,7 @@ public function prepare(string $sql, array $options = [], string $queryClass = ' * override this method. * * @param array $options Passed to the connection's prepare statement. - * Unused in the MySQLi driver. + * Unused in the OCI8 driver. * * @return mixed */ diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 0d5c692560d4..d7d52da45516 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -17,7 +17,7 @@ use stdClass; /** - * Result for OCI + * Result for OCI8 */ class Result extends BaseResult implements ResultInterface { diff --git a/system/Database/OCI8/Utils.php b/system/Database/OCI8/Utils.php index d3fafca8e09a..870306d8b8b1 100644 --- a/system/Database/OCI8/Utils.php +++ b/system/Database/OCI8/Utils.php @@ -15,7 +15,7 @@ use CodeIgniter\Database\Exceptions\DatabaseException; /** - * Utils for MySQLi + * Utils for OCI8 */ class Utils extends BaseUtils { From 751243b29a7dc6fe74e7d62ec23fa58887075d9d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 25 Sep 2021 15:24:51 +0900 Subject: [PATCH 119/184] docs: i writed that oci8 is supported. --- user_guide_src/source/intro/requirements.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_guide_src/source/intro/requirements.rst b/user_guide_src/source/intro/requirements.rst index 2a3d41ded92c..a55b8c46d0d9 100644 --- a/user_guide_src/source/intro/requirements.rst +++ b/user_guide_src/source/intro/requirements.rst @@ -19,12 +19,13 @@ Currently supported databases are: - PostgreSQL via the *Postgre* driver - SQLite3 via the *SQLite3* driver - MSSQL via the *SQLSRV* driver (version 2005 and above only) + - Oracle via the *OCI8* driver (version 12.1 and above only) Not all of the drivers have been converted/rewritten for CodeIgniter4. The list below shows the outstanding ones. - MySQL (5.1+) via the *pdo* driver - - Oracle via the *oci8* and *pdo* drivers + - Oracle via the *pdo* drivers - PostgreSQL via the *pdo* driver - MSSQL via the *pdo* driver - SQLite via the *sqlite* (version 2) and *pdo* drivers From e3a2a6959f0fe074766352ffc6d68bfa49bcac17 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 29 Sep 2021 00:39:34 +0900 Subject: [PATCH 120/184] test: Fixed to use escaped column names based on whether they are ANSI-SQL or not. --- tests/system/Database/Live/GroupTest.php | 48 ++++++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/system/Database/Live/GroupTest.php b/tests/system/Database/Live/GroupTest.php index bfc66bd714f2..da2a49884599 100644 --- a/tests/system/Database/Live/GroupTest.php +++ b/tests/system/Database/Live/GroupTest.php @@ -39,10 +39,13 @@ public function testGroupBy() public function testHavingBy() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->get() ->getResultArray(); @@ -51,11 +54,14 @@ public function testHavingBy() public function testOrHavingBy() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('user') ->select('id') ->groupBy('id') ->having('id >', 3) - ->orHaving('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->orHaving($sumIdColumn . ' >', 2) ->get() ->getResult(); @@ -110,11 +116,14 @@ public function testHavingNotIn() public function testOrHavingNotIn() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->orHavingNotIn('name', ['Developer', 'Politician']) ->get() ->getResult(); @@ -170,11 +179,14 @@ public function testOrHavingLike() public function testOrNotHavingLike() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->orNotHavingLike('name', 'ian') ->get() ->getResult(); @@ -187,13 +199,16 @@ public function testOrNotHavingLike() public function testAndHavingGroupStart() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->havingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having($sumIdColumn . ' <=', 4) ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -205,13 +220,16 @@ public function testAndHavingGroupStart() public function testOrHavingGroupStart() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->orHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having($sumIdColumn . ' <=', 4) ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -224,13 +242,16 @@ public function testOrHavingGroupStart() public function testNotHavingGroupStart() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->notHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') <= 4') + ->having($sumIdColumn . ' <=', 4) ->havingLike('name', 'ant', 'before') ->havingGroupEnd() ->get() @@ -242,13 +263,16 @@ public function testNotHavingGroupStart() public function testOrNotHavingGroupStart() { + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; + $result = $this->db->table('job') ->select('name') ->groupBy('name') ->orderBy('name', 'asc') - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') > 2') + ->having($sumIdColumn . ' >', 2) ->orNotHavingGroupStart() - ->having('SUM(' . $this->db->protectIdentifiers('id') . ') < 2') + ->having($sumIdColumn . ' <', 2) ->havingLike('name', 'o') ->havingGroupEnd() ->get() From 97cb6093b43aad36a00ff2dce46cceae7094d846 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 29 Sep 2021 23:12:07 +0900 Subject: [PATCH 121/184] test: fixed using variables for column names. --- tests/system/Database/Live/GroupTest.php | 283 +++++++++++++++-------- 1 file changed, 186 insertions(+), 97 deletions(-) diff --git a/tests/system/Database/Live/GroupTest.php b/tests/system/Database/Live/GroupTest.php index da2a49884599..c7514d0954dc 100644 --- a/tests/system/Database/Live/GroupTest.php +++ b/tests/system/Database/Live/GroupTest.php @@ -39,31 +39,48 @@ public function testGroupBy() public function testHavingBy() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->having($sumIdColumn . ' >', 2) - ->get() - ->getResultArray(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->having('SUM("id") >', 2) + ->get() + ->getResultArray(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->having('SUM(id) >', 2) + ->get() + ->getResultArray(); + } $this->assertCount(2, $result); } public function testOrHavingBy() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('user') - ->select('id') - ->groupBy('id') - ->having('id >', 3) - ->orHaving($sumIdColumn . ' >', 2) - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('user') + ->select('id') + ->groupBy('id') + ->having('id >', 3) + ->orHaving('SUM("id") >', 2) + ->get() + ->getResult(); + } else { + $result = $this->db->table('user') + ->select('id') + ->groupBy('id') + ->having('id >', 3) + ->orHaving('SUM(id) >', 2) + ->get() + ->getResult(); + } $this->assertCount(2, $result); } @@ -116,17 +133,27 @@ public function testHavingNotIn() public function testOrHavingNotIn() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->orHavingNotIn('name', ['Developer', 'Politician']) - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->orHavingNotIn('name', ['Developer', 'Politician']) + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->orHavingNotIn('name', ['Developer', 'Politician']) + ->get() + ->getResult(); + } $this->assertCount(2, $result); $this->assertSame('Accountant', $result[0]->name); @@ -179,17 +206,27 @@ public function testOrHavingLike() public function testOrNotHavingLike() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->orNotHavingLike('name', 'ian') - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->orNotHavingLike('name', 'ian') + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->orNotHavingLike('name', 'ian') + ->get() + ->getResult(); + } $this->assertCount(3, $result); $this->assertSame('Accountant', $result[0]->name); @@ -199,20 +236,33 @@ public function testOrNotHavingLike() public function testAndHavingGroupStart() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->havingGroupStart() - ->having($sumIdColumn . ' <=', 4) - ->havingLike('name', 'ant', 'before') - ->havingGroupEnd() - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->havingGroupStart() + ->having('SUM("id") <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->havingGroupStart() + ->having('SUM(id) <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } $this->assertCount(1, $result); $this->assertSame('Accountant', $result[0]->name); @@ -220,20 +270,33 @@ public function testAndHavingGroupStart() public function testOrHavingGroupStart() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->orHavingGroupStart() - ->having($sumIdColumn . ' <=', 4) - ->havingLike('name', 'ant', 'before') - ->havingGroupEnd() - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->orHavingGroupStart() + ->having('SUM("id") <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->orHavingGroupStart() + ->having('SUM(id) <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } $this->assertCount(2, $result); $this->assertSame('Accountant', $result[0]->name); @@ -242,20 +305,33 @@ public function testOrHavingGroupStart() public function testNotHavingGroupStart() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->notHavingGroupStart() - ->having($sumIdColumn . ' <=', 4) - ->havingLike('name', 'ant', 'before') - ->havingGroupEnd() - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->notHavingGroupStart() + ->having('SUM("id") <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->notHavingGroupStart() + ->having('SUM(id) <=', 4) + ->havingLike('name', 'ant', 'before') + ->havingGroupEnd() + ->get() + ->getResult(); + } $this->assertCount(1, $result); $this->assertSame('Musician', $result[0]->name); @@ -263,20 +339,33 @@ public function testNotHavingGroupStart() public function testOrNotHavingGroupStart() { - $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); - $sumIdColumn = $isANSISQL ? 'SUM("id")' : 'SUM(id)'; - - $result = $this->db->table('job') - ->select('name') - ->groupBy('name') - ->orderBy('name', 'asc') - ->having($sumIdColumn . ' >', 2) - ->orNotHavingGroupStart() - ->having($sumIdColumn . ' <', 2) - ->havingLike('name', 'o') - ->havingGroupEnd() - ->get() - ->getResult(); + $isANSISQL = in_array($this->db->DBDriver, ['OCI8'], true); + + if ($isANSISQL) { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM("id") >', 2) + ->orNotHavingGroupStart() + ->having('SUM("id") <', 2) + ->havingLike('name', 'o') + ->havingGroupEnd() + ->get() + ->getResult(); + } else { + $result = $this->db->table('job') + ->select('name') + ->groupBy('name') + ->orderBy('name', 'asc') + ->having('SUM(id) >', 2) + ->orNotHavingGroupStart() + ->having('SUM(id) <', 2) + ->havingLike('name', 'o') + ->havingGroupEnd() + ->get() + ->getResult(); + } $this->assertCount(3, $result); $this->assertSame('Accountant', $result[0]->name); From 5408d8bd6421bc6b4a5d5249f33eec6920c5675a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 3 Oct 2021 15:26:13 +0900 Subject: [PATCH 122/184] test: The process of generating select by character concatenation is now generated by branching. --- tests/system/Database/Live/WhereTest.php | 9 +++++++- tests/system/Models/FindModelTest.php | 26 +++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/tests/system/Database/Live/WhereTest.php b/tests/system/Database/Live/WhereTest.php index d03b2b5000e3..f349418eef0d 100644 --- a/tests/system/Database/Live/WhereTest.php +++ b/tests/system/Database/Live/WhereTest.php @@ -230,8 +230,15 @@ public function testWhereWithLower() 'description' => null, ]); + $ANSISQLDriverNames = ['OCI8']; + $lowerJobName = sprintf('LOWER(%s.name)', $this->db->prefixTable('job')); + + if (in_array($this->db->DBDriver, $ANSISQLDriverNames, true)) { + $lowerJobName = sprintf('LOWER("%s"."name")', $this->db->prefixTable('job')); + } + $job = $builder - ->where(sprintf("LOWER({$this->db->protectIdentifiers('%s.name')})", 'job'), 'brewmaster') + ->where($lowerJobName, 'brewmaster') ->get() ->getResult(); $this->assertCount(1, $job); diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php index 4cddee6530fb..e564f570ec8c 100644 --- a/tests/system/Models/FindModelTest.php +++ b/tests/system/Models/FindModelTest.php @@ -168,7 +168,15 @@ public function testFirstAggregate($groupBy, $total): void $this->model->groupBy('id'); } - $user = $this->model->select('SUM(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('total')) + $ANSISQLDriverNames = ['OCI8']; + + if (in_array($this->db->DBDriver, $ANSISQLDriverNames, true)) { + $this->model->select('SUM("id") as "total"'); + } else { + $this->model->select('SUM(id) as total'); + } + + $user = $this->model ->where('id >', 2) ->first(); $this->assertSame($total, (int) $user->total); @@ -197,7 +205,13 @@ public function testFirstRespectsSoftDeletes($aggregate, $groupBy): void $this->createModel(UserModel::class); if ($aggregate) { - $this->model->select('SUM(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('id')); + $ANSISQLDriverNames = ['OCI8']; + + if (in_array($this->db->DBDriver, $ANSISQLDriverNames, true)) { + $this->model->select('SUM("id") as "id"'); + } else { + $this->model->select('SUM(id) as id'); + } } if ($groupBy) { @@ -234,7 +248,13 @@ public function testFirstRecoverTempUseSoftDeletes($aggregate, $groupBy): void $this->model->delete(1); if ($aggregate) { - $this->model->select('sum(' . $this->db->escapeIdentifiers('id') . ') as ' . $this->db->escapeIdentifiers('id')); + $ANSISQLDriverNames = ['OCI8']; + + if (in_array($this->db->DBDriver, $ANSISQLDriverNames, true)) { + $this->model->select('SUM("id") as "id"'); + } else { + $this->model->select('SUM(id) as id'); + } } else { $this->model->select('id'); } From e9260fe2cfc1dcd90b476744822192a7bc88ff12 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 14 Oct 2021 23:31:33 +0900 Subject: [PATCH 123/184] feat: Fixed the problem that 1 is always returned when the type is not number. --- system/Database/OCI8/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 28ba609435a6..70bdd104b6e0 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -593,6 +593,7 @@ public function insertID(): int $primaryColumnType = $columnTypeList[$primaryColumnName]; if ($primaryColumnType !== 'NUMBER') { + $primaryColumnName = ''; continue; } } From 31a6540f30f380c921bc140a8066bb1e73e7dd66 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 14 Oct 2021 23:32:53 +0900 Subject: [PATCH 124/184] docs: fix typo Postgres -> OCI8 --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 70bdd104b6e0..4d4f68697122 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -18,7 +18,7 @@ use stdClass; /** - * Connection for Postgre + * Connection for OCI8 * * @property string|null $latestInsertedTableName * @property int|null $rowId From d7c269330bfadf80dd73c7bf2edb5198ab3e0fe2 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 14 Oct 2021 23:34:06 +0900 Subject: [PATCH 125/184] fix: access level for DBDriver property --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 4d4f68697122..24e5df59306e 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -30,7 +30,7 @@ class Connection extends BaseConnection implements ConnectionInterface * * @var string */ - public $DBDriver = 'OCI8'; + protected $DBDriver = 'OCI8'; /** * Identifier escape character From cb29df05000418f52a98032be45a9da2a29ebaca Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 14 Oct 2021 23:50:21 +0900 Subject: [PATCH 126/184] style: composer cs-fix --- system/Database/OCI8/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 24e5df59306e..8e72409352eb 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -594,6 +594,7 @@ public function insertID(): int if ($primaryColumnType !== 'NUMBER') { $primaryColumnName = ''; + continue; } } From 1a84ca04797f3237d46ffa9a9a4a2364667ce15a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 21:51:46 +0900 Subject: [PATCH 127/184] docs: add @used-by for OCI8 --- system/Database/OCI8/Connection.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 8e72409352eb..265e0a464847 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -90,6 +90,7 @@ class Connection extends BaseConnection implements ConnectionInterface /** * RowID * + * @used-by PreparedQuery::_execute() * @var int|null */ public $rowId; @@ -97,6 +98,7 @@ class Connection extends BaseConnection implements ConnectionInterface /** * Latest inserted table name. * + * @used-by Builder::_insert() * @var string|null */ public $latestInsertedTableName; From 8a0fca9444f020403411c87fa8f5e4fe26b0a82d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 21:52:17 +0900 Subject: [PATCH 128/184] docs: remove unnecessary @property annotation for OCI8 --- system/Database/OCI8/Connection.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 265e0a464847..527121517462 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -19,9 +19,6 @@ /** * Connection for OCI8 - * - * @property string|null $latestInsertedTableName - * @property int|null $rowId */ class Connection extends BaseConnection implements ConnectionInterface { From 275f729901d28c6ce1fed4bec1fea6dbdaa427ce Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 21:53:28 +0900 Subject: [PATCH 129/184] docs: The query method explicitly states that insertId cannot be obtained. --- user_guide_src/source/database/helpers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/database/helpers.rst b/user_guide_src/source/database/helpers.rst index d56c78c5799f..ef7bb4229d3e 100644 --- a/user_guide_src/source/database/helpers.rst +++ b/user_guide_src/source/database/helpers.rst @@ -17,7 +17,7 @@ The insert ID number when performing database inserts. driver, this function requires a $name parameter, which specifies the appropriate sequence to check for the insert id. -.. note:: If using the OCI8 driver, the insert ID can be get when using insert() of :doc:`QueryBuilder<./query_builder>`. +.. note:: If using the OCI8 driver, the ``$db->insertID()`` can be used only after using ``insert()`` of :doc:`QueryBuilder<./query_builder>`. You can't use it after ``$db->query()``. **$db->affectedRows()** From 0e9d0567e0d856aeb2ffbe8d5a9c14eb9df7c1a8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 21:56:43 +0900 Subject: [PATCH 130/184] fix: The scope of the access modifier has been changed to protected. --- system/Database/OCI8/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 527121517462..970992f23fef 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -68,7 +68,7 @@ class Connection extends BaseConnection implements ConnectionInterface * * @var resource */ - public $stmtId; + protected $stmtId; /** * Commit mode flag @@ -82,7 +82,7 @@ class Connection extends BaseConnection implements ConnectionInterface * * @var resource */ - public $cursorId; + protected $cursorId; /** * RowID From e7cb7c9492041eca55e79bcea18af35c84762a4d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 21:58:30 +0900 Subject: [PATCH 131/184] docs: add @used-by for commitMode --- system/Database/OCI8/Connection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 970992f23fef..286805cb3a2a 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -73,6 +73,7 @@ class Connection extends BaseConnection implements ConnectionInterface /** * Commit mode flag * + * @used-by PreparedQuery::_execute() * @var int */ public $commitMode = OCI_COMMIT_ON_SUCCESS; From 70a1fca5e4cefb866eb7288b8007514b8644af73 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:10:00 +0900 Subject: [PATCH 132/184] docs: remove unnecessary comment. --- system/Database/OCI8/Builder.php | 40 ----------------------------- system/Database/OCI8/Connection.php | 8 ++---- system/Database/OCI8/Forge.php | 14 ---------- system/Database/OCI8/Result.php | 4 +-- 4 files changed, 3 insertions(+), 63 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index f81bfbea6ef1..23b9825b4559 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -63,13 +63,7 @@ class Builder extends BaseBuilder protected $db; /** - * Insert batch statement - * * Generates a platform-specific insert string from the supplied data. - * - * @param string $table Table name - * @param array $keys INSERT keys - * @param array $values INSERT values */ protected function _insertBatch(string $table, array $keys, array $values): string { @@ -101,13 +95,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri } /** - * Replace statement - * * Generates a platform-specific replace string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $values The insert values */ protected function _replace(string $table, array $keys, array $values): string { @@ -162,14 +150,10 @@ protected function _replace(string $table, array $keys, array $values): string } /** - * Truncate statement - * * Generates a platform-specific truncate string from the supplied data * * If the database does not support the truncate() command, * then this method maps to 'DELETE FROM table' - * - * @param string $table The table name */ protected function _truncate(string $table): string { @@ -177,13 +161,8 @@ protected function _truncate(string $table): string } /** - * Delete - * * Compiles a delete string and runs the query * - * @param mixed $where The where clause - * @param int $limit The limit clause - * * @throws DatabaseException * * @return mixed @@ -198,11 +177,7 @@ public function delete($where = '', ?int $limit = null, bool $resetData = true) } /** - * Delete statement - * * Generates a platform-specific delete string from the supplied data - * - * @param string $table The table name */ protected function _delete(string $table): string { @@ -215,12 +190,7 @@ protected function _delete(string $table): string } /** - * Update statement - * * Generates a platform-specific update string from the supplied data - * - * @param string $table the Table name - * @param array $values the Update data */ protected function _update(string $table, array $values): string { @@ -240,11 +210,7 @@ protected function _update(string $table, array $values): string } /** - * LIMIT string - * * Generates a platform-specific LIMIT clause. - * - * @param string $sql SQL Query */ protected function _limit(string $sql, bool $offsetIgnore = false): string { @@ -274,13 +240,7 @@ protected function resetSelect() } /** - * Insert statement - * * Generates a platform-specific insert string from the supplied data - * - * @param string $table The table name - * @param array $keys The insert keys - * @param array $unescapedKeys The insert values */ protected function _insert(string $table, array $keys, array $unescapedKeys): string { diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 286805cb3a2a..1b8c315dd37f 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -189,7 +189,7 @@ public function getVersion(): string /** * Executes the query against the database. * - * @return bool|resource + * @return false|resource */ protected function execute(string $sql) { @@ -465,11 +465,10 @@ public function getCursor() } /** - * Stored Procedure. Executes a stored procedure + * Executes a stored procedure * * @param string $package package name in which the stored procedure is in * @param string $procedure stored procedure name to execute - * @param array $params parameters * * @return mixed * @@ -565,9 +564,6 @@ public function error(): array ]; } - /** - * Insert ID - */ public function insertID(): int { if (empty($this->rowId) || empty($this->latestInsertedTableName)) { diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 58b550cc9b89..2334f2b4b65d 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -184,8 +184,6 @@ protected function _processColumn(array $field): string } /** - * Field attribute TYPE - * * Performs a data type mapping between different databases. * * @return void @@ -247,19 +245,12 @@ protected function _attributeType(array &$attributes) $attributes['TYPE'] = 'VARCHAR2'; return; - - default: } } /** - * Drop Table - * * Generates a platform-specific DROP TABLE string * - * @param string $table Table name - * @param bool $ifExists Whether to add an IF EXISTS condition - * * @return bool|string */ protected function _dropTable(string $table, bool $ifExists, bool $cascade) @@ -275,11 +266,6 @@ protected function _dropTable(string $table, bool $ifExists, bool $cascade) return $sql; } - /** - * Process foreign keys - * - * @param string $table Table name - */ protected function _processForeignKeys(string $table): string { $sql = ''; diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index d7d52da45516..4d33dd0c9b64 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -49,8 +49,6 @@ public function getFieldData(): array 'name' => oci_field_name($this->resultID, $fieldIndex), 'type' => oci_field_type($this->resultID, $fieldIndex), 'max_length' => oci_field_size($this->resultID, $fieldIndex), - // 'primary_key' = (int) ($data->flags & 2), - // 'default' = $data->def, ]; }, range(1, $this->getFieldCount())); } @@ -73,7 +71,7 @@ public function freeResult() * internally before fetching results to make sure the result set * starts at zero. * - * @return mixed + * @return false */ public function dataSeek(int $n = 0) { From 51686fc7991a4e967de4add51f7312951c2bcd49 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:31:44 +0900 Subject: [PATCH 133/184] style: fix indent. --- system/Database/OCI8/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 23b9825b4559..699acc5f4438 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -205,8 +205,8 @@ protected function _update(string $table, array $values): string } return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . implode(', ', $valStr) - . $this->compileWhereHaving('QBWhere') - . $this->compileOrderBy(); + . $this->compileWhereHaving('QBWhere') + . $this->compileOrderBy(); } /** From 45c4ffb5f246f15c7386ded35c6ba7610763ee50 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:32:09 +0900 Subject: [PATCH 134/184] style: It seems that the reference operator needs to cover the result. --- system/Database/OCI8/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 699acc5f4438..27631faf64f1 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -214,7 +214,7 @@ protected function _update(string $table, array $values): string */ protected function _limit(string $sql, bool $offsetIgnore = false): string { - $offset = (int) ($offsetIgnore === false) ? $this->QBOffset : 0; + $offset = (int) ($offsetIgnore === false ? $this->QBOffset : 0); if (version_compare($this->db->getVersion(), '12.1', '>=')) { // OFFSET-FETCH can be used only with the ORDER BY clause if (empty($this->QBOrderBy)) { From 0295774512af908bd535563b2e69fd63efa6fb90 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:34:54 +0900 Subject: [PATCH 135/184] style: using sprintf for create query. --- system/Database/OCI8/Builder.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 27631faf64f1..4b4360db812c 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -225,9 +225,9 @@ protected function _limit(string $sql, bool $offsetIgnore = false): string } $this->limitUsed = true; + $limitTemplateQuery = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (%s) INNER_QUERY WHERE ROWNUM < %d)' . ($offset ? ' WHERE RNUM >= %d' : ''); - return 'SELECT * FROM (SELECT inner_query.*, rownum rnum FROM (' . $sql . ') inner_query WHERE rownum < ' . ($offset + $this->QBLimit + 1) . ')' - . ($offset ? ' WHERE rnum >= ' . ($offset + 1) : ''); + return sprintf($limitTemplateQuery, $sql, $offset + $this->QBLimit + 1, $offset); } /** @@ -244,10 +244,15 @@ protected function resetSelect() */ protected function _insert(string $table, array $keys, array $unescapedKeys): string { - // Has a strange design. - // Processing to get the table where the last insert was performed for insertId method. $this->db->latestInsertedTableName = $table; - - return 'INSERT ' . $this->compileIgnore('insert') . 'INTO ' . $table . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ') RETURNING ROWID INTO :CI_OCI8_ROWID'; + $sql = 'INSERT %sINTO %s (%s) VALUES (%s) RETURNING ROWID INTO :CI_OCI8_ROWID'; + + return sprintf( + $sql, + $this->compileIgnore('insert'), + $table, + implode(', ', $keys), + implode(', ', $unescapedKeys) + ); } } From c3ce98faa0f9ae4469cd299d6adad93d9238dadb Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:35:44 +0900 Subject: [PATCH 136/184] style: Fixed ternary operator style as noted. --- system/Database/OCI8/Connection.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 1b8c315dd37f..60f595b37761 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -126,7 +126,7 @@ public function connect(bool $persistent = false) $this->buildDSN(); } - $func = ($persistent === true) ? 'oci_pconnect' : 'oci_connect'; + $func = $persistent ? 'oci_pconnect' : 'oci_connect'; return empty($this->charset) ? $func($this->username, $this->password, $this->DSN) @@ -208,7 +208,7 @@ protected function execute(string $sql) oci_set_prefetch($this->stmtId, 1000); - return (oci_execute($this->stmtId, $this->commitMode)) ? $this->stmtId : false; + return oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false; } catch (ErrorException $e) { log_message('error', $e->getMessage()); @@ -291,11 +291,9 @@ protected function _fieldData(string $table): array $retval[$i]->name = $query[$i]->COLUMN_NAME; $retval[$i]->type = $query[$i]->DATA_TYPE; - $length = ($query[$i]->CHAR_LENGTH > 0) - ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; - if ($length === null) { - $length = $query[$i]->DATA_LENGTH; - } + $length = $query[$i]->CHAR_LENGTH > 0 ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; + $length = $length ?? $query[$i]->DATA_LENGTH; + $retval[$i]->max_length = $length; $default = $query[$i]->DATA_DEFAULT; From b8f0b5ec37f04e4ccef77a1c3942215a1a6f3707 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:36:58 +0900 Subject: [PATCH 137/184] style: importd stdClass. --- system/Database/OCI8/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 60f595b37761..4c5a814622ab 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -347,7 +347,7 @@ protected function _indexData(string $table): array continue; } - $retVal[$row->INDEX_NAME] = new \stdClass(); + $retVal[$row->INDEX_NAME] = new stdClass(); $retVal[$row->INDEX_NAME]->name = $row->INDEX_NAME; $retVal[$row->INDEX_NAME]->fields = [$row->COLUMN_NAME]; $retVal[$row->INDEX_NAME]->type = $constraintTypes[$row->CONSTRAINT_TYPE] ?? 'INDEX'; @@ -392,7 +392,7 @@ protected function _foreignKeyData(string $table): array $retVal = []; foreach ($query as $row) { - $obj = new \stdClass(); + $obj = new stdClass(); $obj->constraint_name = $row->CONSTRAINT_NAME; $obj->table_name = $row->TABLE_NAME; $obj->column_name = $row->COLUMN_NAME; From 7b2a9c039d1aa5f4d2d1f5d01d5e871a98cd1f29 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:37:26 +0900 Subject: [PATCH 138/184] style: Changed to start conditions from literals. --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 4c5a814622ab..76ae990f1c2b 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -384,7 +384,7 @@ protected function _foreignKeyData(string $table): array WHERE ac.constraint_type = ' . $this->escape('R') . ' AND acc.table_name = ' . $this->escape($table); - if (($query = $this->query($sql)) === false) { + if (false === $query = $this->query($sql)) { throw new DatabaseException(lang('Database.failGetForeignKeyData')); } $query = $query->getResultObject(); From c57778c4480be16bfa4bf61f22567f6a5e3f4dc4 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:46:44 +0900 Subject: [PATCH 139/184] style: Removed unnecessary parentheses. Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Connection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 76ae990f1c2b..7b41b9fc7a3d 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -624,8 +624,8 @@ protected function buildDSN() } $isEasyConnectableHostName = $this->hostname !== '' && strpos($this->hostname, '/') === false && strpos($this->hostname, ':') === false; - $easyConnectablePort = ((! empty($this->port) && ctype_digit($this->port)) ? ':' . $this->port : ''); - $easyConnectableDatabase = ($this->database !== '' ? '/' . ltrim($this->database, '/') : ''); + $easyConnectablePort = ! empty($this->port) && ctype_digit($this->port) ? ':' . $this->port : ''; + $easyConnectableDatabase = $this->database !== '' ? '/' . ltrim($this->database, '/') : ''; if ($isEasyConnectableHostName && ($easyConnectablePort !== '' || $easyConnectableDatabase !== '')) { /* If the hostname field isn't empty, doesn't contain From 9fb0685ff1f58e286d6d53bd31a83a22df631f82 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:47:58 +0900 Subject: [PATCH 140/184] style: remove empty line. Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Connection.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 7b41b9fc7a3d..54826e9895df 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -635,9 +635,7 @@ protected function buildDSN() * Easy Connect string from these 3 settings, assuming * that the database field is a service name. */ - $this->DSN = $this->hostname - . $easyConnectablePort - . $easyConnectableDatabase; + $this->DSN = $this->hostname . $easyConnectablePort . $easyConnectableDatabase; if (preg_match($this->validDSNs['ec'], $this->DSN)) { return; From 68cff0304ea0acd1d4ca9e5ebc8867df6ae460d5 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:49:34 +0900 Subject: [PATCH 141/184] style: fix indent Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Forge.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 2334f2b4b65d..14fbbb02affb 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -175,12 +175,12 @@ protected function _processColumn(array $field): string } return $this->db->escapeIdentifiers($field['name']) - . ' ' . $field['type'] . $field['length'] - . $field['unsigned'] - . $field['default'] - . $field['auto_increment'] - . $field['null'] - . $field['unique']; + . ' ' . $field['type'] . $field['length'] + . $field['unsigned'] + . $field['default'] + . $field['auto_increment'] + . $field['null'] + . $field['unique']; } /** From 21693600268167885fd6ce58d66818afda649be8 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:50:26 +0900 Subject: [PATCH 142/184] style: Removed unnecessary parentheses. Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 14fbbb02affb..c9d8bf0635d7 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -88,7 +88,7 @@ protected function _alterTable(string $alterType, string $table, $field) if ($alterType === 'DROP') { $fields = array_map(function ($field) { return $this->db->escapeIdentifiers(trim($field)); - }, (is_string($field)) ? explode(',', $field) : $field); + }, is_string($field)) ? explode(',', $field) : $field; return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } From a6a8c40e50b07bcf06935ae78e4ce7467fea4c50 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:51:30 +0900 Subject: [PATCH 143/184] style: Removed unnecessary parentheses. Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index c9d8bf0635d7..9676df2e4e09 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -104,7 +104,7 @@ protected function _alterTable(string $alterType, string $table, $field) // If a null constraint is added to a column with a null constraint, // ORA-01451 will occur, // so add null constraint is used only when it is different from the current null constraint. - $isWantToAddNull = (strpos($field[$i]['null'], ' NOT') === false); + $isWantToAddNull = strpos($field[$i]['null'], ' NOT') === false; $currentNullAddable = $nullableMap[$field[$i]['name']]; if ($isWantToAddNull === $currentNullAddable) { From 2b0b3ad943ddd00516fa85a195ca63a0ad236894 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Tue, 19 Oct 2021 22:52:19 +0900 Subject: [PATCH 144/184] style: Removed unnecessary parentheses. Co-authored-by: John Paul E. Balandan, CPA <51850998+paulbalandan@users.noreply.github.com> --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 9676df2e4e09..72cfb61c4482 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -133,7 +133,7 @@ protected function _alterTable(string $alterType, string $table, $field) } $sql .= ' ' . $alterType . ' '; - $sql .= (count($field) === 1) + $sql .= count($field) === 1 ? $field[0] : '(' . implode(',', $field) . ')'; From 5e610f90872028d9ad184fea52336de787d97b7a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:52:58 +0900 Subject: [PATCH 145/184] docs: fixme -> todo --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 72cfb61c4482..b13750d7f696 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -164,7 +164,7 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field) protected function _processColumn(array $field): string { $constraint = ''; - // @fixme: can’t cover multi pattern when set type. + // @todo: can’t cover multi pattern when set type. if ($field['type'] === 'VARCHAR2' && strpos($field['length'], "('") === 0) { $constraint = ' CHECK(' . $this->db->escapeIdentifiers($field['name']) . ' IN ' . $field['length'] . ')'; From 0f2dc4911aee3873733531b1eddd2609c91dddc7 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Tue, 19 Oct 2021 22:53:59 +0900 Subject: [PATCH 146/184] style: dual -> DUAL --- system/Database/OCI8/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 4b4360db812c..0fd72773245d 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -91,7 +91,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri $sql .= ' INTO ' . $table . ' (' . $insertKeys . ') VALUES ' . $value . "\n"; } - return $sql . 'SELECT * FROM dual'; + return $sql . 'SELECT * FROM DUAL'; } /** From 310d72427328abf0cfeac2e4407e8855658f606d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 20 Oct 2021 23:58:25 +0900 Subject: [PATCH 147/184] refactor: latestInsertedTableName -> lastInsertedTableName --- system/Database/OCI8/Builder.php | 2 +- system/Database/OCI8/Connection.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 0fd72773245d..519f162a27b5 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -244,7 +244,7 @@ protected function resetSelect() */ protected function _insert(string $table, array $keys, array $unescapedKeys): string { - $this->db->latestInsertedTableName = $table; + $this->db->lastInsertedTableName = $table; $sql = 'INSERT %sINTO %s (%s) VALUES (%s) RETURNING ROWID INTO :CI_OCI8_ROWID'; return sprintf( diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 54826e9895df..f6f1970fc9b5 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -99,7 +99,7 @@ class Connection extends BaseConnection implements ConnectionInterface * @used-by Builder::_insert() * @var string|null */ - public $latestInsertedTableName; + public $lastInsertedTableName; /** * confirm DNS format. @@ -564,12 +564,12 @@ public function error(): array public function insertID(): int { - if (empty($this->rowId) || empty($this->latestInsertedTableName)) { + if (empty($this->rowId) || empty($this->lastInsertedTableName)) { return 0; } - $indexs = $this->getIndexData($this->latestInsertedTableName); - $fieldDatas = $this->getFieldData($this->latestInsertedTableName); + $indexs = $this->getIndexData($this->lastInsertedTableName); + $fieldDatas = $this->getFieldData($this->lastInsertedTableName); if (! $indexs || ! $fieldDatas) { return 0; @@ -597,7 +597,7 @@ public function insertID(): int return 0; } - $table = $this->protectIdentifiers($this->latestInsertedTableName, true); + $table = $this->protectIdentifiers($this->lastInsertedTableName, true); $query = $this->query('SELECT ' . $this->protectIdentifiers($primaryColumnName, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); return (int) ($query->SEQ ?? 0); From 61fbf492fe69d354166e987ace1bb9414f124224 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 21 Oct 2021 00:01:17 +0900 Subject: [PATCH 148/184] fix: Fixed a code that should have been specified as an argument, but was not. --- system/Database/OCI8/Forge.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index b13750d7f696..5bffd8ad8b38 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -88,7 +88,7 @@ protected function _alterTable(string $alterType, string $table, $field) if ($alterType === 'DROP') { $fields = array_map(function ($field) { return $this->db->escapeIdentifiers(trim($field)); - }, is_string($field)) ? explode(',', $field) : $field; + }, is_string($field) ? explode(',', $field) : $field); return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } From b3a5ae7283ed659e0fa52cf564bff61b3ca37732 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 21 Oct 2021 07:43:39 +0900 Subject: [PATCH 149/184] style: cs-fixer error. --- system/Database/OCI8/Builder.php | 6 ++++-- system/Database/OCI8/Connection.php | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 519f162a27b5..836e0ae7f0ac 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -163,6 +163,8 @@ protected function _truncate(string $table): string /** * Compiles a delete string and runs the query * + * @param mixed $where + * * @throws DatabaseException * * @return mixed @@ -224,7 +226,7 @@ protected function _limit(string $sql, bool $offsetIgnore = false): string return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; } - $this->limitUsed = true; + $this->limitUsed = true; $limitTemplateQuery = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (%s) INNER_QUERY WHERE ROWNUM < %d)' . ($offset ? ' WHERE RNUM >= %d' : ''); return sprintf($limitTemplateQuery, $sql, $offset + $this->QBLimit + 1, $offset); @@ -245,7 +247,7 @@ protected function resetSelect() protected function _insert(string $table, array $keys, array $unescapedKeys): string { $this->db->lastInsertedTableName = $table; - $sql = 'INSERT %sINTO %s (%s) VALUES (%s) RETURNING ROWID INTO :CI_OCI8_ROWID'; + $sql = 'INSERT %sINTO %s (%s) VALUES (%s) RETURNING ROWID INTO :CI_OCI8_ROWID'; return sprintf( $sql, diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index f6f1970fc9b5..3f54e1c254d3 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -74,6 +74,7 @@ class Connection extends BaseConnection implements ConnectionInterface * Commit mode flag * * @used-by PreparedQuery::_execute() + * * @var int */ public $commitMode = OCI_COMMIT_ON_SUCCESS; @@ -89,6 +90,7 @@ class Connection extends BaseConnection implements ConnectionInterface * RowID * * @used-by PreparedQuery::_execute() + * * @var int|null */ public $rowId; @@ -97,6 +99,7 @@ class Connection extends BaseConnection implements ConnectionInterface * Latest inserted table name. * * @used-by Builder::_insert() + * * @var string|null */ public $lastInsertedTableName; From e653cdc2d5aaead2c969f56e9f37e7ce9bffcc2a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 21 Oct 2021 07:46:52 +0900 Subject: [PATCH 150/184] style: fix for rector. --- system/Database/OCI8/Builder.php | 2 +- system/Database/OCI8/Forge.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 836e0ae7f0ac..93063c52a482 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -223,7 +223,7 @@ protected function _limit(string $sql, bool $offsetIgnore = false): string $sql .= ' ORDER BY 1'; } - return $sql . ' OFFSET ' . (int) $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; + return $sql . ' OFFSET ' . $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY'; } $this->limitUsed = true; diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 5bffd8ad8b38..bce8a75492f9 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -243,8 +243,7 @@ protected function _attributeType(array &$attributes) case 'MEDIUMTEXT': $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; $attributes['TYPE'] = 'VARCHAR2'; - - return; + // no break } } From 1d8f6ccfd9c731e2b07ba7220258a00b6c9ba643 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Thu, 21 Oct 2021 08:13:53 +0900 Subject: [PATCH 151/184] docs: I made sure to start with zero bindings. --- system/Database/OCI8/PreparedQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 8c4c493543ac..28ebb72db55a 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -119,7 +119,7 @@ public function _getResult() } /** - * Replaces the ? placeholders with :1, :2, etc parameters for use + * Replaces the ? placeholders with :0, :1, etc parameters for use * within the prepared query. */ public function parameterize(string $sql): string From c63db8910cd88ebda20eb840a0ceeba5cfeeb13a Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 22 Oct 2021 04:37:36 +0900 Subject: [PATCH 152/184] style: I stopped using yoda notation. https://github.com/codeigniter4/CodeIgniter4/issues/5226 --- system/Database/OCI8/Connection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 3f54e1c254d3..d4f135aadde8 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -386,8 +386,9 @@ protected function _foreignKeyData(string $table): array AND accu.table_name = ccu.table_name WHERE ac.constraint_type = ' . $this->escape('R') . ' AND acc.table_name = ' . $this->escape($table); + $query = $this->query($sql); - if (false === $query = $this->query($sql)) { + if ($query === false) { throw new DatabaseException(lang('Database.failGetForeignKeyData')); } $query = $query->getResultObject(); From a77647500c9b8d9a5b0e1ed17f9f2cf05059ef1b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Fri, 22 Oct 2021 04:58:41 +0900 Subject: [PATCH 153/184] style: remove comment. --- system/Database/OCI8/Forge.php | 1 - 1 file changed, 1 deletion(-) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index bce8a75492f9..3e9cf0a582da 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -243,7 +243,6 @@ protected function _attributeType(array &$attributes) case 'MEDIUMTEXT': $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; $attributes['TYPE'] = 'VARCHAR2'; - // no break } } From d0722691db9b4df2135cfcf5e2495358b8a4e9b9 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 24 Oct 2021 23:28:41 +0900 Subject: [PATCH 154/184] test: Add a test for lastInsertId. --- .../Database/Live/OCI8/LastInsertIDTest.php | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/system/Database/Live/OCI8/LastInsertIDTest.php diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php new file mode 100644 index 000000000000..e5acb990454c --- /dev/null +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Live\OCI8; + +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\DatabaseTestTrait; + +/** + * @group DatabaseLive + * + * @internal + */ +final class LastInsertIDTest extends CIUnitTestCase +{ + use DatabaseTestTrait; + + protected $refresh = true; + protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder'; + + public function testGetInsertIDWithInsert() + { + $jobData = [ + 'name' => 'Grocery Sales', + 'description' => 'Discount!', + ]; + + $this->db->table('job')->insert($jobData); + $actual = $this->db->insertID(); + + $this->assertSame($actual, 5); + } + + public function testGetInsertIDWithQuery() + { + $this->db->query('INSERT INTO "db_job" ("name", "description") VALUES (?, ?)', ['Grocery Sales', 'Discount!']); + $actual = $this->db->insertID(); + + $this->assertSame($actual, 5); + } + + public function testGetInsertIDWithHasCommentQuery() + { + $sql = <<db->query($sql, ['Discount!']); + $actual = $this->db->insertID(); + + $this->assertSame($actual, 5); + } +} From 6c074e6287bd67e5035696af175f94b7bca93c9d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 24 Oct 2021 23:50:02 +0900 Subject: [PATCH 155/184] feat: To know the last insertID for OCI8. Added support for keeping the inserted table name as a property when executing SQL. --- system/Database/OCI8/Builder.php | 17 ---------- system/Database/OCI8/Connection.php | 47 ++++++++++++++++++-------- system/Database/OCI8/PreparedQuery.php | 33 ------------------ 3 files changed, 32 insertions(+), 65 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 93063c52a482..2b48e1ce0f01 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -240,21 +240,4 @@ protected function resetSelect() $this->limitUsed = false; parent::resetSelect(); } - - /** - * Generates a platform-specific insert string from the supplied data - */ - protected function _insert(string $table, array $keys, array $unescapedKeys): string - { - $this->db->lastInsertedTableName = $table; - $sql = 'INSERT %sINTO %s (%s) VALUES (%s) RETURNING ROWID INTO :CI_OCI8_ROWID'; - - return sprintf( - $sql, - $this->compileIgnore('insert'), - $table, - implode(', ', $keys), - implode(', ', $unescapedKeys) - ); - } } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index d4f135aadde8..bb5c3d2c982d 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -86,19 +86,10 @@ class Connection extends BaseConnection implements ConnectionInterface */ protected $cursorId; - /** - * RowID - * - * @used-by PreparedQuery::_execute() - * - * @var int|null - */ - public $rowId; - /** * Latest inserted table name. * - * @used-by Builder::_insert() + * @used-by PreparedQuery::_execute() * * @var string|null */ @@ -205,13 +196,16 @@ protected function execute(string $sql) $this->stmtId = oci_parse($this->connID, $sql); } - if (strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID') !== false) { - oci_bind_by_name($this->stmtId, ':CI_OCI8_ROWID', $this->rowId, 255); - } - oci_set_prefetch($this->stmtId, 1000); - return oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false; + $result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false; + $insertTableName = $this->parseInsertTableName($sql); + + if ($result && $insertTableName !== '') { + $this->lastInsertedTableName = $insertTableName; + } + + return $result; } catch (ErrorException $e) { log_message('error', $e->getMessage()); @@ -223,6 +217,29 @@ protected function execute(string $sql) return false; } + /** + * Get the table name for the insert statement from sql. + * + * @param string $sql + * + * @return string + */ + public function parseInsertTableName(string $sql): string { + $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/^-- .*/'], '', $sql); + $isInsertQuery = strpos(strtoupper(ltrim($commentStrippedSql)), 'INSERT') === 0; + + if (!$isInsertQuery) { + return ''; + } + + preg_match('/(?is)\b(?:into)\s+("?\w+"?)/', $commentStrippedSql, $match); + $tableName = $match[1] ?? ''; + $tableName = strpos($tableName, '"') === 0 ? trim($tableName, '"') : strtoupper($tableName); + + return $tableName; + } + + /** * Returns the total number of rows affected by this query. */ diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 28ebb72db55a..9c798b5d48c9 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -27,35 +27,6 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface */ protected $db; - /** - * Is collect row id - * - * @var bool - */ - private $isCollectRowId; - - /** - * Prepares the query against the database, and saves the connection - * info necessary to execute the query later. - * - * NOTE: This version is based on SQL code. Child classes should - * override this method. - * - * @param array $options Passed to the connection's prepare statement. - * - * @return mixed - */ - public function prepare(string $sql, array $options = [], string $queryClass = 'CodeIgniter\\Database\\Query') - { - $this->isCollectRowId = false; - - if (substr($sql, strpos($sql, 'RETURNING ROWID INTO :CI_OCI8_ROWID')) === 'RETURNING ROWID INTO :CI_OCI8_ROWID') { - $this->isCollectRowId = true; - } - - return parent::prepare($sql, $options, $queryClass); - } - /** * Prepares the query against the database, and saves the connection * info necessary to execute the query later. @@ -101,10 +72,6 @@ public function _execute(array $data): bool $lastKey = $key; } - if ($this->isCollectRowId) { - oci_bind_by_name($this->statement, ':' . (++$lastKey), $this->db->rowId, 255); - } - return oci_execute($this->statement, $this->db->commitMode); } From 6cfb0bec82bfa72b5128fd7488679b124e42d3ab Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 24 Oct 2021 23:51:12 +0900 Subject: [PATCH 156/184] feat: Added getting the last insertID. --- system/Database/OCI8/Connection.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index bb5c3d2c982d..f44d7bd2e2eb 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -585,7 +585,7 @@ public function error(): array public function insertID(): int { - if (empty($this->rowId) || empty($this->lastInsertedTableName)) { + if (empty($this->lastInsertedTableName)) { return 0; } @@ -609,8 +609,6 @@ public function insertID(): int if ($primaryColumnType !== 'NUMBER') { $primaryColumnName = ''; - - continue; } } @@ -618,8 +616,9 @@ public function insertID(): int return 0; } - $table = $this->protectIdentifiers($this->lastInsertedTableName, true); - $query = $this->query('SELECT ' . $this->protectIdentifiers($primaryColumnName, false) . ' SEQ FROM ' . $table . ' WHERE ROWID = ?', $this->rowId)->getRow(); + $query = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow(); + $lastInsertValue = str_replace('nextval', 'currval', $query->DATA_DEFAULT ?? '0'); + $query = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow(); return (int) ($query->SEQ ?? 0); } From fd8057a4057ae6a4a3b8b137dff9d44c4872e350 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 24 Oct 2021 23:52:12 +0900 Subject: [PATCH 157/184] feat: Added the ability to get the last insertID even when executing SQL with PreparedQuery. --- system/Database/OCI8/PreparedQuery.php | 17 ++++++++++++++++- .../Database/Live/OCI8/LastInsertIDTest.php | 15 +++++++++++++++ .../system/Database/Live/PreparedQueryTest.php | 3 --- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 9c798b5d48c9..32fe4b46e179 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -27,6 +27,13 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface */ protected $db; + /** + * Latest inserted table name. + * + * @var string|null + */ + private $lastInsertTableName; + /** * Prepares the query against the database, and saves the connection * info necessary to execute the query later. @@ -52,6 +59,8 @@ public function _prepare(string $sql, array $options = []) $this->errorString = $error['message'] ?? ''; } + $this->lastInsertTableName = $this->db->parseInsertTableName($sql); + return $this; } @@ -72,7 +81,13 @@ public function _execute(array $data): bool $lastKey = $key; } - return oci_execute($this->statement, $this->db->commitMode); + $result = oci_execute($this->statement, $this->db->commitMode); + + if ($result && $this->lastInsertTableName !== '') { + $this->db->lastInsertedTableName = $this->lastInsertTableName; + } + + return $result; } /** diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php index e5acb990454c..a1e78e3a363d 100644 --- a/tests/system/Database/Live/OCI8/LastInsertIDTest.php +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\Live\OCI8; +use CodeIgniter\Database\Query; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; @@ -59,4 +60,18 @@ public function testGetInsertIDWithHasCommentQuery() $this->assertSame($actual, 5); } + + public function testGetInsertIDWithPreparedQuery() + { + $query = $this->db->prepare(static function ($db) { + $sql = 'INSERT INTO "db_job" ("name", "description") VALUES (?, ?)'; + + return (new Query($db))->setQuery($sql); + }); + + $query->execute('foo', 'bar'); + $actual = $this->db->insertID(); + + $this->assertSame($actual, 5); + } } diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index 9a42f8efd2f6..e8bdabea91fa 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -69,9 +69,6 @@ public function testPrepareReturnsPreparedQuery() $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES ({$placeholders})"; } - if ($this->db->DBDriver === 'OCI8') { - $expected .= ' RETURNING ROWID INTO ?'; - } $this->assertSame($expected, $this->query->getQueryString()); } From 2745e6a9a247b760fa4688ca5eedd4f37d782e4e Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 24 Oct 2021 23:57:32 +0900 Subject: [PATCH 158/184] docs: remove unneeded note. --- user_guide_src/source/database/helpers.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/user_guide_src/source/database/helpers.rst b/user_guide_src/source/database/helpers.rst index ef7bb4229d3e..a9a160a26713 100644 --- a/user_guide_src/source/database/helpers.rst +++ b/user_guide_src/source/database/helpers.rst @@ -17,8 +17,6 @@ The insert ID number when performing database inserts. driver, this function requires a $name parameter, which specifies the appropriate sequence to check for the insert id. -.. note:: If using the OCI8 driver, the ``$db->insertID()`` can be used only after using ``insert()`` of :doc:`QueryBuilder<./query_builder>`. You can't use it after ``$db->query()``. - **$db->affectedRows()** Displays the number of affected rows, when doing "write" type queries From 1cee89421dc093d5016a7b1ab4f5a1fab4aebb68 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Mon, 25 Oct 2021 08:50:25 +0900 Subject: [PATCH 159/184] test: add setUp for skipTest when using OCI8 driver --- tests/system/Database/Live/OCI8/LastInsertIDTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php index a1e78e3a363d..a2e354bf0d95 100644 --- a/tests/system/Database/Live/OCI8/LastInsertIDTest.php +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -27,6 +27,14 @@ final class LastInsertIDTest extends CIUnitTestCase protected $refresh = true; protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder'; + public function setUp(){ + parent::setUp(); + + if ($this->db->DBDriver !== 'OCI8') { + $this->markTestSkipped('Only OCI8 has its own implementation.'); + } + } + public function testGetInsertIDWithInsert() { $jobData = [ From 7644cdd3a3759a7a528c3087b8934b2c0722bcb8 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 27 Oct 2021 20:42:08 +0900 Subject: [PATCH 160/184] test: add return type for setUp method. --- tests/system/Database/Live/OCI8/LastInsertIDTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php index a2e354bf0d95..0ed8ed2fb774 100644 --- a/tests/system/Database/Live/OCI8/LastInsertIDTest.php +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -27,7 +27,7 @@ final class LastInsertIDTest extends CIUnitTestCase protected $refresh = true; protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder'; - public function setUp(){ + public function setUp(): void { parent::setUp(); if ($this->db->DBDriver !== 'OCI8') { From 2d18992db05ecd7fedfbce835bac2ece0f075e1d Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 28 Nov 2021 17:24:35 +0900 Subject: [PATCH 161/184] feat: add dropIndexStr for OCI8. --- system/Database/OCI8/Forge.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 3e9cf0a582da..890af27080c3 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -16,6 +16,13 @@ */ class Forge extends \CodeIgniter\Database\Forge { + /** + * DROP INDEX statement + * + * @var string + */ + protected $dropIndexStr = 'DROP INDEX %s'; + /** * CREATE DATABASE statement * From e78d8c37b5a65f4b58ad1bf9875eefb36ac6f410 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 28 Nov 2021 17:54:56 +0900 Subject: [PATCH 162/184] feat: add test case. --- system/Database/OCI8/Connection.php | 2 +- tests/system/Database/Live/OCI8/LastInsertIDTest.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index f44d7bd2e2eb..dd3791d16bf6 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -225,7 +225,7 @@ protected function execute(string $sql) * @return string */ public function parseInsertTableName(string $sql): string { - $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/^-- .*/'], '', $sql); + $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/--.+/'], '', $sql); $isInsertQuery = strpos(strtoupper(ltrim($commentStrippedSql)), 'INSERT') === 0; if (!$isInsertQuery) { diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php index 0ed8ed2fb774..a8310a40b1f9 100644 --- a/tests/system/Database/Live/OCI8/LastInsertIDTest.php +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -60,8 +60,11 @@ public function testGetInsertIDWithHasCommentQuery() { $sql = <<db->query($sql, ['Discount!']); $actual = $this->db->insertID(); From 40fce31cb59179738177c0ddb22bf386255ff3e2 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 28 Nov 2021 20:15:07 +0900 Subject: [PATCH 163/184] style: compover cs-fix --- system/Database/OCI8/Connection.php | 21 +++++++------------ .../Database/Live/OCI8/LastInsertIDTest.php | 19 +++++++++-------- .../Database/Live/PreparedQueryTest.php | 1 - 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index dd3791d16bf6..0c5861fe39a2 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -198,7 +198,7 @@ protected function execute(string $sql) oci_set_prefetch($this->stmtId, 1000); - $result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false; + $result = oci_execute($this->stmtId, $this->commitMode) ? $this->stmtId : false; $insertTableName = $this->parseInsertTableName($sql); if ($result && $insertTableName !== '') { @@ -219,27 +219,22 @@ protected function execute(string $sql) /** * Get the table name for the insert statement from sql. - * - * @param string $sql - * - * @return string */ - public function parseInsertTableName(string $sql): string { + public function parseInsertTableName(string $sql): string + { $commentStrippedSql = preg_replace(['/\/\*(.|\n)*?\*\//m', '/--.+/'], '', $sql); - $isInsertQuery = strpos(strtoupper(ltrim($commentStrippedSql)), 'INSERT') === 0; + $isInsertQuery = strpos(strtoupper(ltrim($commentStrippedSql)), 'INSERT') === 0; - if (!$isInsertQuery) { + if (! $isInsertQuery) { return ''; } preg_match('/(?is)\b(?:into)\s+("?\w+"?)/', $commentStrippedSql, $match); $tableName = $match[1] ?? ''; - $tableName = strpos($tableName, '"') === 0 ? trim($tableName, '"') : strtoupper($tableName); - return $tableName; + return strpos($tableName, '"') === 0 ? trim($tableName, '"') : strtoupper($tableName); } - /** * Returns the total number of rows affected by this query. */ @@ -616,9 +611,9 @@ public function insertID(): int return 0; } - $query = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow(); + $query = $this->query('SELECT DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?', [$this->lastInsertedTableName, $primaryColumnName])->getRow(); $lastInsertValue = str_replace('nextval', 'currval', $query->DATA_DEFAULT ?? '0'); - $query = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow(); + $query = $this->query(sprintf('SELECT %s SEQ FROM DUAL', $lastInsertValue))->getRow(); return (int) ($query->SEQ ?? 0); } diff --git a/tests/system/Database/Live/OCI8/LastInsertIDTest.php b/tests/system/Database/Live/OCI8/LastInsertIDTest.php index a8310a40b1f9..16e4d84e989c 100644 --- a/tests/system/Database/Live/OCI8/LastInsertIDTest.php +++ b/tests/system/Database/Live/OCI8/LastInsertIDTest.php @@ -27,7 +27,8 @@ final class LastInsertIDTest extends CIUnitTestCase protected $refresh = true; protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder'; - public function setUp(): void { + protected function setUp(): void + { parent::setUp(); if ($this->db->DBDriver !== 'OCI8') { @@ -58,14 +59,14 @@ public function testGetInsertIDWithQuery() public function testGetInsertIDWithHasCommentQuery() { - $sql = <<db->query($sql, ['Discount!']); $actual = $this->db->insertID(); diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php index e8bdabea91fa..ef71e1500065 100644 --- a/tests/system/Database/Live/PreparedQueryTest.php +++ b/tests/system/Database/Live/PreparedQueryTest.php @@ -69,7 +69,6 @@ public function testPrepareReturnsPreparedQuery() $expected = "INSERT INTO {$ec}{$pre}user{$ec} ({$ec}name{$ec}, {$ec}email{$ec}) VALUES ({$placeholders})"; } - $this->assertSame($expected, $this->query->getQueryString()); } From 7e2424f3d914edae72a4f8bd36ccd48a8ee61181 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 28 Nov 2021 20:57:50 +0900 Subject: [PATCH 164/184] test: import need module. --- tests/system/Commands/CreateDatabaseTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/system/Commands/CreateDatabaseTest.php b/tests/system/Commands/CreateDatabaseTest.php index f0ce8a683857..ccbadc51f0c3 100644 --- a/tests/system/Commands/CreateDatabaseTest.php +++ b/tests/system/Commands/CreateDatabaseTest.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Commands; use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\Database as DatabaseFactory; use CodeIgniter\Database\OCI8\Connection as OCI8Connection; use CodeIgniter\Database\SQLite3\Connection as SQLite3Connection; use CodeIgniter\Test\CIUnitTestCase; From 3f700c6d64f6315eddbf2c63287515bcf0cda989 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 18 Dec 2021 18:45:59 +0900 Subject: [PATCH 165/184] chore: add stored procedure in github actions for oracle database. --- .github/workflows/test-phpunit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index 5f2189f41e42..b5c5731fad04 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -114,6 +114,10 @@ jobs: if: matrix.db-platforms == 'OCI8' run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba + - name: Create stored procedure for Oracle Database + if: matrix.db-platforms == 'OCI8' + run: echo -e "CREATE OR REPLACE PACKAGE calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER); END;\n/\nCREATE OR REPLACE PACKAGE BODY calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER) IS BEGIN result := left + right; END plus; END calculator;\n/\nCREATE OR REPLACE PROCEDURE plus(left IN NUMBER, right IN NUMBER, output OUT NUMBER) IS BEGIN output := left + right; END;\n/\n\n" | /lib/oracle/18.5/client64/bin/sqlplus -s ORACLE/ORACLE@localhost:1521/XEPDB1 + - name: Checkout uses: actions/checkout@v2 From 9c170aac52c13286755727c5a20eff784012df52 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 18 Dec 2021 18:47:19 +0900 Subject: [PATCH 166/184] feat: add call storedProcedure test. --- system/Database/OCI8/Connection.php | 27 +++---- .../Live/OCI8/CallStoredProcedureTest.php | 81 +++++++++++++++++++ 2 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 tests/system/Database/Live/OCI8/CallStoredProcedureTest.php diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 0c5861fe39a2..aafad7fe4433 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -481,8 +481,7 @@ public function getCursor() /** * Executes a stored procedure * - * @param string $package package name in which the stored procedure is in - * @param string $procedure stored procedure name to execute + * @param string $procedureName procedure name to execute * * @return mixed * @@ -495,14 +494,14 @@ public function getCursor() * type yes the type of the parameter * length yes the max size of the parameter */ - public function storedProcedure(string $package, string $procedure, array $params) + public function storedProcedure(string $procedureName, array $params) { - if ($package === '' || $procedure === '') { - throw new DatabaseException(lang('Database.invalidArgument', [$package . $procedure])); + if ($procedureName === '') { + throw new DatabaseException(lang('Database.invalidArgument', [$procedureName])); } // Build the query string - $sql = 'BEGIN ' . $package . '.' . $procedure . '('; + $sql = 'BEGIN ' . $procedureName . '('; $haveCursor = false; @@ -518,7 +517,7 @@ public function storedProcedure(string $package, string $procedure, array $param $this->resetStmtId = false; $this->stmtId = oci_parse($this->connID, $sql); $this->bindParams($params); - $result = $this->query($sql, false, $haveCursor); + $result = $this->query($sql); $this->resetStmtId = true; return $result; @@ -538,13 +537,13 @@ protected function bindParams($params) } foreach ($params as $param) { - foreach (['name', 'value', 'type', 'length'] as $val) { - if (! isset($param[$val])) { - $param[$val] = ''; - } - } - - oci_bind_by_name($this->stmtId, $param['name'], $param['value'], $param['length'], $param['type']); + oci_bind_by_name( + $this->stmtId, + $param['name'], + $param['value'], + $param['length'] ?? -1, + $param['type'] ?? SQLT_CHR + ); } } diff --git a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php new file mode 100644 index 000000000000..6dc4b12a9c6e --- /dev/null +++ b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Live\OCI8; + +use CodeIgniter\Database\Query; +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\DatabaseTestTrait; + +/** + * @group DatabaseLive + * + * @internal + */ +final class CallStoredProcedureTest extends CIUnitTestCase +{ + use DatabaseTestTrait; + + protected function setUp(): void + { + parent::setUp(); + + if ($this->db->DBDriver !== 'OCI8') { + $this->markTestSkipped('Only OCI8 has its own implementation.'); + } + } + + public function testCallPackageProcedure() + { + $result = 0; + + $this->db->storedProcedure('calculator.plus', [ + [ + 'name' => ':left', + 'value' => 2, + ], + [ + 'name' => ':right', + 'value' => 5, + ], + [ + 'name' => ':output', + 'value' => &$result, + ] + + ]); + + $this->assertSame($result, '7'); + } + + public function testCallStoredProcedure() + { + $result = 0; + + $this->db->storedProcedure('plus', [ + [ + 'name' => ':left', + 'value' => 2, + ], + [ + 'name' => ':right', + 'value' => 5, + ], + [ + 'name' => ':output', + 'value' => &$result, + ] + + ]); + + $this->assertSame($result, '7'); + } +} From c4f2e69947e74f049f3f3fb5bcf33d9237589994 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 19 Dec 2021 17:50:06 +0900 Subject: [PATCH 167/184] style: composer cs-fix --- .../Live/OCI8/CallStoredProcedureTest.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php index 6dc4b12a9c6e..96352d4ae8db 100644 --- a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php +++ b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php @@ -11,7 +11,6 @@ namespace CodeIgniter\Database\Live\OCI8; -use CodeIgniter\Database\Query; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; @@ -39,17 +38,17 @@ public function testCallPackageProcedure() $this->db->storedProcedure('calculator.plus', [ [ - 'name' => ':left', + 'name' => ':left', 'value' => 2, ], [ - 'name' => ':right', + 'name' => ':right', 'value' => 5, ], [ - 'name' => ':output', + 'name' => ':output', 'value' => &$result, - ] + ], ]); @@ -62,17 +61,17 @@ public function testCallStoredProcedure() $this->db->storedProcedure('plus', [ [ - 'name' => ':left', + 'name' => ':left', 'value' => 2, ], [ - 'name' => ':right', + 'name' => ':right', 'value' => 5, ], [ - 'name' => ':output', + 'name' => ':output', 'value' => &$result, - ] + ], ]); From 190f76df8b79174c4cf146adce81346e86c989f6 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 29 Dec 2021 14:07:57 +0900 Subject: [PATCH 168/184] feat: Removed semicolon from ALTER TABLE to make it distinguish between sql and PLSQL in OCI8. --- .github/workflows/test-phpunit.yml | 4 ---- system/Database/OCI8/Connection.php | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/test-phpunit.yml b/.github/workflows/test-phpunit.yml index b5c5731fad04..5f2189f41e42 100644 --- a/.github/workflows/test-phpunit.yml +++ b/.github/workflows/test-phpunit.yml @@ -114,10 +114,6 @@ jobs: if: matrix.db-platforms == 'OCI8' run: echo -e "ALTER SESSION SET CONTAINER = XEPDB1;\nCREATE BIGFILE TABLESPACE \"TEST\" DATAFILE '/opt/oracle/product/18c/dbhomeXE/dbs/TEST' SIZE 10M AUTOEXTEND ON MAXSIZE UNLIMITED SEGMENT SPACE MANAGEMENT AUTO EXTENT MANAGEMENT LOCAL AUTOALLOCATE;\nCREATE USER \"ORACLE\" IDENTIFIED BY \"ORACLE\" DEFAULT TABLESPACE \"TEST\" TEMPORARY TABLESPACE TEMP QUOTA UNLIMITED ON \"TEST\";\nGRANT CONNECT,RESOURCE TO \"ORACLE\";\nexit;" | /lib/oracle/18.5/client64/bin/sqlplus -s sys/Oracle18@localhost:1521/XE as sysdba - - name: Create stored procedure for Oracle Database - if: matrix.db-platforms == 'OCI8' - run: echo -e "CREATE OR REPLACE PACKAGE calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER); END;\n/\nCREATE OR REPLACE PACKAGE BODY calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER) IS BEGIN result := left + right; END plus; END calculator;\n/\nCREATE OR REPLACE PROCEDURE plus(left IN NUMBER, right IN NUMBER, output OUT NUMBER) IS BEGIN output := left + right; END;\n/\n\n" | /lib/oracle/18.5/client64/bin/sqlplus -s ORACLE/ORACLE@localhost:1521/XEPDB1 - - name: Checkout uses: actions/checkout@v2 diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index aafad7fe4433..7af79d93663f 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -189,10 +189,6 @@ protected function execute(string $sql) { try { if ($this->resetStmtId === true) { - $sql = rtrim($sql, ';'); - if (strpos(ltrim($sql), 'BEGIN') === 0) { - $sql .= ';'; - } $this->stmtId = oci_parse($this->connID, $sql); } From fb62ab203fb84a80769686664826e4ed75392253 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Wed, 29 Dec 2021 14:11:04 +0900 Subject: [PATCH 169/184] chore: Defined the stored procedure needed for the test. --- .../Migrations/20160428212500_Create_test_tables.php | 11 +++++++++++ .../Database/Live/OCI8/CallStoredProcedureTest.php | 2 ++ 2 files changed, 13 insertions(+) diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index cf517e988ebc..26419f89adae 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -151,6 +151,12 @@ public function up() $this->forge->addKey('id', true); $this->forge->createTable('ci_sessions', true); } + + if ($this->db->DBDriver === 'OCI8') { + $this->db->query('CREATE OR REPLACE PACKAGE calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER); END;'); + $this->db->query('CREATE OR REPLACE PACKAGE BODY calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER) IS BEGIN result := left + right; END plus; END calculator;'); + $this->db->query('CREATE OR REPLACE PROCEDURE plus(left IN NUMBER, right IN NUMBER, output OUT NUMBER) IS BEGIN output := left + right; END;'); + } } public function down() @@ -168,5 +174,10 @@ public function down() if (in_array($this->db->DBDriver, ['MySQLi', 'Postgre'], true)) { $this->forge->dropTable('ci_sessions', true); } + + if ($this->db->DBDriver === 'OCI8') { + $this->db->query('DROP PROCEDURE plus'); + $this->db->query('DROP PACKAGE BODY calculator'); + } } } diff --git a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php index 96352d4ae8db..3f5089ff48a2 100644 --- a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php +++ b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php @@ -23,6 +23,8 @@ final class CallStoredProcedureTest extends CIUnitTestCase { use DatabaseTestTrait; + protected $refresh = true; + protected function setUp(): void { parent::setUp(); From 8638ac2e57b677f378e242465114e5ff4b44b546 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 15 Jan 2022 19:18:12 +0900 Subject: [PATCH 170/184] chore: composer update --ansi --no-interaction && vendor/bin/rector process --- system/Database/OCI8/Builder.php | 24 ++++++------------------ system/Database/OCI8/Connection.php | 2 +- system/Database/OCI8/Forge.php | 20 +++++++++----------- system/Database/OCI8/PreparedQuery.php | 4 +--- system/Database/OCI8/Result.php | 16 ++++++---------- 5 files changed, 23 insertions(+), 43 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 2b48e1ce0f01..e1d82be89fb5 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -76,9 +76,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri $selectQueryValues = []; foreach ($values as $value) { - $selectValues = implode(',', array_map(static function ($value, $key) { - return $value . ' as ' . $key; - }, explode(',', substr(substr($value, 1), 0, -1)), $keys)); + $selectValues = implode(',', array_map(static fn($value, $key) => $value . ' as ' . $key, explode(',', substr(substr($value, 1), 0, -1)), $keys)); $selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL'; } @@ -99,9 +97,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri */ protected function _replace(string $table, array $keys, array $values): string { - $fieldNames = array_map(static function ($columnName) { - return trim($columnName, '"'); - }, $keys); + $fieldNames = array_map(static fn($columnName) => trim($columnName, '"'), $keys); $uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames) { $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); @@ -120,9 +116,7 @@ protected function _replace(string $table, array $keys, array $values): string $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; - $sql .= implode(', ', array_map(static function ($columnName, $value) { - return $value . ' ' . $columnName; - }, $keys, $values)); + $sql .= implode(', ', array_map(static fn($columnName, $value) => $value . ' ' . $columnName, $keys, $values)); $sql .= ' FROM DUAL) "_replace" ON ( '; @@ -130,21 +124,15 @@ protected function _replace(string $table, array $keys, array $values): string $onList[] = '1 != 1'; foreach ($uniqueIndexes as $index) { - $onList[] = '(' . implode(' AND ', array_map(static function ($columnName) use ($table) { - return $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"'; - }, $index->fields)) . ')'; + $onList[] = '(' . implode(' AND ', array_map(static fn($columnName) => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')'; } $sql .= implode(' OR ', $onList) . ') WHEN MATCHED THEN UPDATE SET '; - $sql .= implode(', ', array_map(static function ($columnName) { - return $columnName . ' = "_replace".' . $columnName; - }, $replaceableFields)); + $sql .= implode(', ', array_map(static fn($columnName) => $columnName . ' = "_replace".' . $columnName, $replaceableFields)); $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceableFields) . ') VALUES '; - $sql .= ' (' . implode(', ', array_map(static function ($columnName) { - return '"_replace".' . $columnName; - }, $replaceableFields)) . ')'; + $sql .= ' (' . implode(', ', array_map(static fn($columnName) => '"_replace".' . $columnName, $replaceableFields)) . ')'; return $sql; } diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 7af79d93663f..88071404059f 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -303,7 +303,7 @@ protected function _fieldData(string $table): array $retval[$i]->type = $query[$i]->DATA_TYPE; $length = $query[$i]->CHAR_LENGTH > 0 ? $query[$i]->CHAR_LENGTH : $query[$i]->DATA_PRECISION; - $length = $length ?? $query[$i]->DATA_LENGTH; + $length ??= $query[$i]->DATA_LENGTH; $retval[$i]->max_length = $length; diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 890af27080c3..02f9f23e1060 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -93,9 +93,7 @@ protected function _alterTable(string $alterType, string $table, $field) $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); if ($alterType === 'DROP') { - $fields = array_map(function ($field) { - return $this->db->escapeIdentifiers(trim($field)); - }, is_string($field) ? explode(',', $field) : $field); + $fields = array_map(fn($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field); return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } @@ -201,20 +199,20 @@ protected function _attributeType(array &$attributes) // Usually overridden by drivers switch (strtoupper($attributes['TYPE'])) { case 'TINYINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 3; + $attributes['CONSTRAINT'] ??= 3; // no break case 'SMALLINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 5; + $attributes['CONSTRAINT'] ??= 5; // no break case 'MEDIUMINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 7; + $attributes['CONSTRAINT'] ??= 7; // no break case 'INT': case 'INTEGER': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 11; + $attributes['CONSTRAINT'] ??= 11; // no break case 'BIGINT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 19; + $attributes['CONSTRAINT'] ??= 19; // no break case 'NUMERIC': $attributes['TYPE'] = 'NUMBER'; @@ -231,7 +229,7 @@ protected function _attributeType(array &$attributes) case 'DOUBLE': $attributes['TYPE'] = 'FLOAT'; - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 126; + $attributes['CONSTRAINT'] ??= 126; return; @@ -244,11 +242,11 @@ protected function _attributeType(array &$attributes) case 'SET': case 'ENUM': case 'VARCHAR': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 255; + $attributes['CONSTRAINT'] ??= 255; // no break case 'TEXT': case 'MEDIUMTEXT': - $attributes['CONSTRAINT'] = $attributes['CONSTRAINT'] ?? 4000; + $attributes['CONSTRAINT'] ??= 4000; $attributes['TYPE'] = 'VARCHAR2'; } } diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index 32fe4b46e179..e83e6f8f3a1f 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -29,10 +29,8 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface /** * Latest inserted table name. - * - * @var string|null */ - private $lastInsertTableName; + private ?string $lastInsertTableName = null; /** * Prepares the query against the database, and saves the connection diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 4d33dd0c9b64..c2aecc78b0e2 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -34,9 +34,7 @@ public function getFieldCount(): int */ public function getFieldNames(): array { - return array_map(function ($fieldIndex) { - return oci_field_name($this->resultID, $fieldIndex); - }, range(1, $this->getFieldCount())); + return array_map(fn($fieldIndex) => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount())); } /** @@ -44,13 +42,11 @@ public function getFieldNames(): array */ public function getFieldData(): array { - return array_map(function ($fieldIndex) { - return (object) [ - 'name' => oci_field_name($this->resultID, $fieldIndex), - 'type' => oci_field_type($this->resultID, $fieldIndex), - 'max_length' => oci_field_size($this->resultID, $fieldIndex), - ]; - }, range(1, $this->getFieldCount())); + return array_map(fn($fieldIndex) => (object) [ + 'name' => oci_field_name($this->resultID, $fieldIndex), + 'type' => oci_field_type($this->resultID, $fieldIndex), + 'max_length' => oci_field_size($this->resultID, $fieldIndex), + ], range(1, $this->getFieldCount())); } /** From 0cb021ab7b86102b11879479ff15ca7906a4fd79 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 15 Jan 2022 19:36:51 +0900 Subject: [PATCH 171/184] docs: add oci8 description in changelog. --- user_guide_src/source/changelogs/v4.2.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index 98d14b6e9197..24b85b255402 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -26,6 +26,8 @@ Enhancements - New View Decorators allow modifying the generated HTML prior to caching. - Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`. - Added Validation Strict Rules. See :ref:`validation-traditional-and-strict-rules`. +- Added new OCI8 driver for database. + - You to access Oracle Database. They support SQL and PL/SQL statements. Changes ******* From 50547e8db79cf6d9e70f245ae7676b8e69239838 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 15 Jan 2022 19:49:52 +0900 Subject: [PATCH 172/184] docs: Added a document to the query method about the presence of semicolons for OCI8. --- user_guide_src/source/database/queries.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/user_guide_src/source/database/queries.rst b/user_guide_src/source/database/queries.rst index af15bacea038..50bb0325fa11 100644 --- a/user_guide_src/source/database/queries.rst +++ b/user_guide_src/source/database/queries.rst @@ -27,6 +27,9 @@ this:: $query = $db->query('YOUR QUERY HERE'); +.. note:: If you are using OCI8 Driver, SQL statements should not end with a semi-colon (`;`). + PL/SQL statements should end with a semi-colon (`;`). + Simplified Queries ================== From a46d0bbd7a6b2a29ceb442e6c3a54934e22f8049 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sat, 15 Jan 2022 19:56:52 +0900 Subject: [PATCH 173/184] chore: vendor/bin/php-cs-fixer fix --verbose --ansi --- system/Database/OCI8/Builder.php | 12 ++++++------ system/Database/OCI8/Forge.php | 6 +++--- system/Database/OCI8/Result.php | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index e1d82be89fb5..ac64b7ba5ca7 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -76,7 +76,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri $selectQueryValues = []; foreach ($values as $value) { - $selectValues = implode(',', array_map(static fn($value, $key) => $value . ' as ' . $key, explode(',', substr(substr($value, 1), 0, -1)), $keys)); + $selectValues = implode(',', array_map(static fn ($value, $key) => $value . ' as ' . $key, explode(',', substr(substr($value, 1), 0, -1)), $keys)); $selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL'; } @@ -97,7 +97,7 @@ protected function _insertBatch(string $table, array $keys, array $values): stri */ protected function _replace(string $table, array $keys, array $values): string { - $fieldNames = array_map(static fn($columnName) => trim($columnName, '"'), $keys); + $fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys); $uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames) { $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); @@ -116,7 +116,7 @@ protected function _replace(string $table, array $keys, array $values): string $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; - $sql .= implode(', ', array_map(static fn($columnName, $value) => $value . ' ' . $columnName, $keys, $values)); + $sql .= implode(', ', array_map(static fn ($columnName, $value) => $value . ' ' . $columnName, $keys, $values)); $sql .= ' FROM DUAL) "_replace" ON ( '; @@ -124,15 +124,15 @@ protected function _replace(string $table, array $keys, array $values): string $onList[] = '1 != 1'; foreach ($uniqueIndexes as $index) { - $onList[] = '(' . implode(' AND ', array_map(static fn($columnName) => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')'; + $onList[] = '(' . implode(' AND ', array_map(static fn ($columnName) => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')'; } $sql .= implode(' OR ', $onList) . ') WHEN MATCHED THEN UPDATE SET '; - $sql .= implode(', ', array_map(static fn($columnName) => $columnName . ' = "_replace".' . $columnName, $replaceableFields)); + $sql .= implode(', ', array_map(static fn ($columnName) => $columnName . ' = "_replace".' . $columnName, $replaceableFields)); $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceableFields) . ') VALUES '; - $sql .= ' (' . implode(', ', array_map(static fn($columnName) => '"_replace".' . $columnName, $replaceableFields)) . ')'; + $sql .= ' (' . implode(', ', array_map(static fn ($columnName) => '"_replace".' . $columnName, $replaceableFields)) . ')'; return $sql; } diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php index 02f9f23e1060..d5ca20a9e4d9 100644 --- a/system/Database/OCI8/Forge.php +++ b/system/Database/OCI8/Forge.php @@ -93,7 +93,7 @@ protected function _alterTable(string $alterType, string $table, $field) $sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table); if ($alterType === 'DROP') { - $fields = array_map(fn($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field); + $fields = array_map(fn ($field) => $this->db->escapeIdentifiers(trim($field)), is_string($field) ? explode(',', $field) : $field); return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE'; } @@ -228,7 +228,7 @@ protected function _attributeType(array &$attributes) return; case 'DOUBLE': - $attributes['TYPE'] = 'FLOAT'; + $attributes['TYPE'] = 'FLOAT'; $attributes['CONSTRAINT'] ??= 126; return; @@ -247,7 +247,7 @@ protected function _attributeType(array &$attributes) case 'TEXT': case 'MEDIUMTEXT': $attributes['CONSTRAINT'] ??= 4000; - $attributes['TYPE'] = 'VARCHAR2'; + $attributes['TYPE'] = 'VARCHAR2'; } } diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index c2aecc78b0e2..ce3a73def1c4 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -34,7 +34,7 @@ public function getFieldCount(): int */ public function getFieldNames(): array { - return array_map(fn($fieldIndex) => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount())); + return array_map(fn ($fieldIndex) => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount())); } /** @@ -42,7 +42,7 @@ public function getFieldNames(): array */ public function getFieldData(): array { - return array_map(fn($fieldIndex) => (object) [ + return array_map(fn ($fieldIndex) => (object) [ 'name' => oci_field_name($this->resultID, $fieldIndex), 'type' => oci_field_type($this->resultID, $fieldIndex), 'max_length' => oci_field_size($this->resultID, $fieldIndex), From 7f09031261bbae05d1bb1a9c7cc81ea4839bbdb7 Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Sat, 15 Jan 2022 20:00:54 +0900 Subject: [PATCH 174/184] docs: Fixed the incomprehensible changelog. Co-authored-by: kenjis --- user_guide_src/source/changelogs/v4.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide_src/source/changelogs/v4.2.0.rst b/user_guide_src/source/changelogs/v4.2.0.rst index 24b85b255402..94024f4192f8 100644 --- a/user_guide_src/source/changelogs/v4.2.0.rst +++ b/user_guide_src/source/changelogs/v4.2.0.rst @@ -27,7 +27,7 @@ Enhancements - Added Subqueries in the FROM section. See :ref:`query-builder-from-subquery`. - Added Validation Strict Rules. See :ref:`validation-traditional-and-strict-rules`. - Added new OCI8 driver for database. - - You to access Oracle Database. They support SQL and PL/SQL statements. + - It can access Oracle Database and supports SQL and PL/SQL statements. Changes ******* From 870bd271aae8782aa4effb359390fb71152fc65f Mon Sep 17 00:00:00 2001 From: ytetsuro Date: Sun, 16 Jan 2022 08:55:14 +0900 Subject: [PATCH 175/184] docs: add backquote. Co-authored-by: kenjis --- user_guide_src/source/database/queries.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/database/queries.rst b/user_guide_src/source/database/queries.rst index 50bb0325fa11..b78cc1907b4c 100644 --- a/user_guide_src/source/database/queries.rst +++ b/user_guide_src/source/database/queries.rst @@ -27,8 +27,8 @@ this:: $query = $db->query('YOUR QUERY HERE'); -.. note:: If you are using OCI8 Driver, SQL statements should not end with a semi-colon (`;`). - PL/SQL statements should end with a semi-colon (`;`). +.. note:: If you are using OCI8 Driver, SQL statements should not end with a semi-colon (``;``). + PL/SQL statements should end with a semi-colon (``;``). Simplified Queries ================== From b01f918d7f2517e80d7f93bb32cb60111dc2d072 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 16 Jan 2022 12:02:44 +0900 Subject: [PATCH 176/184] refactor: Removed unnecessary processes. --- system/Database/OCI8/Connection.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 88071404059f..5d2059bba3ea 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -497,18 +497,11 @@ public function storedProcedure(string $procedureName, array $params) } // Build the query string - $sql = 'BEGIN ' . $procedureName . '('; - - $haveCursor = false; - - foreach ($params as $param) { - $sql .= $param['name'] . ','; - - if (isset($param['type']) && $param['type'] === OCI_B_CURSOR) { - $haveCursor = true; - } - } - $sql = trim($sql, ',') . '); END;'; + $sql = sprintf( + 'BEGIN %s (' . substr(str_repeat(',%s', count($params)), 1) . '); END;', + $procedureName, + ...array_map(static fn ($row) => $row['name'], $params) + ); $this->resetStmtId = false; $this->stmtId = oci_parse($this->connID, $sql); From 5feae180497e568ddaf5b042e5dc66f5df179361 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 15:22:47 +0900 Subject: [PATCH 177/184] docs: fix phpdoc for storedProcedure --- system/Database/OCI8/Connection.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 5d2059bba3ea..c470b599433d 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -478,17 +478,16 @@ public function getCursor() * Executes a stored procedure * * @param string $procedureName procedure name to execute - * - * @return mixed - * - * params array keys - * + * @param array $params params array keys * KEY OPTIONAL NOTES * name no the name of the parameter should be in : format * value no the value of the parameter. If this is an OUT or IN OUT parameter, * this should be a reference to a variable * type yes the type of the parameter * length yes the max size of the parameter + * + * @return Result|bool|CodeIgniter\Database\Query + * */ public function storedProcedure(string $procedureName, array $params) { From 5c1cda1114326f62c8978175baf5cacc7d266c0b Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 16:01:28 +0900 Subject: [PATCH 178/184] test: add call stored procedure test for cursor. --- .../20160428212500_Create_test_tables.php | 2 ++ .../Live/OCI8/CallStoredProcedureTest.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 26419f89adae..e802dcedc9be 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -156,6 +156,7 @@ public function up() $this->db->query('CREATE OR REPLACE PACKAGE calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER); END;'); $this->db->query('CREATE OR REPLACE PACKAGE BODY calculator AS PROCEDURE plus(left IN NUMBER, right IN NUMBER, result OUT NUMBER) IS BEGIN result := left + right; END plus; END calculator;'); $this->db->query('CREATE OR REPLACE PROCEDURE plus(left IN NUMBER, right IN NUMBER, output OUT NUMBER) IS BEGIN output := left + right; END;'); + $this->db->query('CREATE OR REPLACE PROCEDURE one(cursor OUT SYS_REFCURSOR) IS BEGIN open cursor for select 1 AS ONE from DUAL; END;'); } } @@ -176,6 +177,7 @@ public function down() } if ($this->db->DBDriver === 'OCI8') { + $this->db->query('DROP PROCEDURE one'); $this->db->query('DROP PROCEDURE plus'); $this->db->query('DROP PACKAGE BODY calculator'); } diff --git a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php index 3f5089ff48a2..b142f5f4588d 100644 --- a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php +++ b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php @@ -79,4 +79,23 @@ public function testCallStoredProcedure() $this->assertSame($result, '7'); } + + public function testCallStoredProcedureForCursor() + { + $result = $this->db->getCursor(); + + $this->db->storedProcedure('one', [ + [ + 'name' => ':cursor', + 'type' => OCI_B_CURSOR, + 'value' => &$result, + ], + + ]); + + oci_execute($result); + $row = oci_fetch_array($result, OCI_ASSOC+OCI_RETURN_NULLS); + + $this->assertSame($row, ['ONE' => '1']); + } } From c1bbee8c77671583cc5ed349c04f90abeccb8d9f Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 16:02:46 +0900 Subject: [PATCH 179/184] feat: add getDriverFunctionPrefix for oci8 --- system/Database/OCI8/Connection.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index c470b599433d..8f312766fb91 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -709,4 +709,12 @@ public function getDatabase(): string return $this->query('SELECT DEFAULT_TABLESPACE FROM USER_USERS')->getRow()->DEFAULT_TABLESPACE ?? ''; } + + /** + * Get the prefix of the function to access the DB. + */ + protected function getDriverFunctionPrefix(): string + { + return 'oci_'; + } } From fb1fb0001954f3f20cc605be6a119f82e784f3c9 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 16:05:23 +0900 Subject: [PATCH 180/184] docs: remove unnecessary notes in Oracle for random sort. --- user_guide_src/source/database/query_builder.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index d21896e3c251..e4283cf52f67 100755 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -779,9 +779,6 @@ be ignored, unless you specify a numeric seed value. $builder->orderBy(42, 'RANDOM'); // Produces: ORDER BY RAND(42) -.. note:: Random ordering is not currently supported in Oracle and - will default to ASC instead. - **************************** Limiting or Counting Results **************************** From af2654bd6670a1656484ab3d479864ec4a10b8a0 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 17:33:08 +0900 Subject: [PATCH 181/184] docs: fix return type for storedProcedure method. --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index 8f312766fb91..ca7280c82d75 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -486,7 +486,7 @@ public function getCursor() * type yes the type of the parameter * length yes the max size of the parameter * - * @return Result|bool|CodeIgniter\Database\Query + * @return Result|bool|\CodeIgniter\Database\Query * */ public function storedProcedure(string $procedureName, array $params) From 76c1029414ea8d8e5601fa9ec895553fae976d40 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Sun, 23 Jan 2022 18:52:54 +0900 Subject: [PATCH 182/184] chore: composer cs-fix && ./vendor/bin/rector process system/Database/OCI8/Connection.php --- system/Database/OCI8/Connection.php | 20 +++++++++---------- .../Live/OCI8/CallStoredProcedureTest.php | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index ca7280c82d75..e1e4ede00779 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Database\OCI8; +use CodeIgniter\Database\Query; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; @@ -478,16 +479,15 @@ public function getCursor() * Executes a stored procedure * * @param string $procedureName procedure name to execute - * @param array $params params array keys - * KEY OPTIONAL NOTES - * name no the name of the parameter should be in : format - * value no the value of the parameter. If this is an OUT or IN OUT parameter, - * this should be a reference to a variable - * type yes the type of the parameter - * length yes the max size of the parameter - * - * @return Result|bool|\CodeIgniter\Database\Query - * + * @param array $params params array keys + * KEY OPTIONAL NOTES + * name no the name of the parameter should be in : format + * value no the value of the parameter. If this is an OUT or IN OUT parameter, + * this should be a reference to a variable + * type yes the type of the parameter + * length yes the max size of the parameter + * + * @return bool|Query|Result */ public function storedProcedure(string $procedureName, array $params) { diff --git a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php index b142f5f4588d..3b85fd068d36 100644 --- a/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php +++ b/tests/system/Database/Live/OCI8/CallStoredProcedureTest.php @@ -87,14 +87,14 @@ public function testCallStoredProcedureForCursor() $this->db->storedProcedure('one', [ [ 'name' => ':cursor', - 'type' => OCI_B_CURSOR, + 'type' => OCI_B_CURSOR, 'value' => &$result, ], ]); oci_execute($result); - $row = oci_fetch_array($result, OCI_ASSOC+OCI_RETURN_NULLS); + $row = oci_fetch_array($result, OCI_ASSOC + OCI_RETURN_NULLS); $this->assertSame($row, ['ONE' => '1']); } From 119b59a1c2aafa80f9b62c674114d9f3f2058757 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Mon, 24 Jan 2022 12:26:37 +0900 Subject: [PATCH 183/184] fix: Fixed to problem PLSQL will be treated as SQL when the first word is not `BEGIN`. --- system/Database/OCI8/PreparedQuery.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/system/Database/OCI8/PreparedQuery.php b/system/Database/OCI8/PreparedQuery.php index e83e6f8f3a1f..311dacc4045f 100644 --- a/system/Database/OCI8/PreparedQuery.php +++ b/system/Database/OCI8/PreparedQuery.php @@ -46,11 +46,6 @@ class PreparedQuery extends BasePreparedQuery implements PreparedQueryInterface */ public function _prepare(string $sql, array $options = []) { - $sql = rtrim($sql, ';'); - if (strpos('BEGIN', ltrim($sql)) === 0) { - $sql .= ';'; - } - if (! $this->statement = oci_parse($this->db->connID, $this->parameterize($sql))) { $error = oci_error($this->db->connID); $this->errorCode = $error['code'] ?? 0; From c7d1620ce8f53bae73e679ff7aec2674bd41c5c5 Mon Sep 17 00:00:00 2001 From: Tetsuro Yoshikawa Date: Mon, 24 Jan 2022 12:54:17 +0900 Subject: [PATCH 184/184] style: composer cs-fix --- system/Database/OCI8/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php index e1e4ede00779..dc0b26f57c0e 100644 --- a/system/Database/OCI8/Connection.php +++ b/system/Database/OCI8/Connection.php @@ -11,10 +11,10 @@ namespace CodeIgniter\Database\OCI8; -use CodeIgniter\Database\Query; use CodeIgniter\Database\BaseConnection; use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Database\Exceptions\DatabaseException; +use CodeIgniter\Database\Query; use ErrorException; use stdClass;