From 97fdb4d0aeff6e9fb58a317ea1bc359d369262e6 Mon Sep 17 00:00:00 2001 From: Wilmer Date: Sat, 12 Feb 2022 11:41:19 -0300 Subject: [PATCH] Update workflows, remove unsed class, update README.md. --- .github/workflows/build.yml | 13 +- .github/workflows/mutation.yml | 4 +- .github/workflows/static.yml | 3 +- README.md | 4 +- src/Command.php | 36 -- src/Connection.php | 84 ---- src/QueryBuilder.php | 448 ----------------- src/Schema.php | 848 --------------------------------- 8 files changed, 14 insertions(+), 1426 deletions(-) delete mode 100644 src/Command.php delete mode 100644 src/Connection.php delete mode 100644 src/QueryBuilder.php delete mode 100644 src/Schema.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a17272d..bb543f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: name: PHP ${{ matrix.php }}-${{ matrix.os }} env: - extensions: pdo, pdo_oci, oci8 + extensions: pdo, pdo_oci runs-on: ${{ matrix.os }} @@ -36,7 +36,6 @@ jobs: - ubuntu-latest php: - - 7.4 - 8.0 - 8.1 @@ -64,7 +63,7 @@ jobs: run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - name: Cache dependencies installed with composer - uses: actions/cache@v2.1.4 + uses: actions/cache@v2 with: path: ${{ env.COMPOSER_CACHE_DIR }} key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} @@ -78,4 +77,10 @@ jobs: run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Run tests with phpunit - run: vendor/bin/phpunit --colors=always + run: vendor/bin/phpunit --coverage-clover=coverage.xml --colors=always + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 0235502..9b2d14c 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -34,7 +34,7 @@ jobs: - ubuntu-latest php: - - 8.1 + - 8.0 services: oci: @@ -60,7 +60,7 @@ jobs: run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - name: Cache dependencies installed with composer - uses: actions/cache@v2.1.4 + uses: actions/cache@v2 with: path: ${{ env.COMPOSER_CACHE_DIR }} key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index c034abb..62415ea 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -33,7 +33,6 @@ jobs: - ubuntu-latest php: - - 7.4 - 8.0 - 8.1 @@ -52,7 +51,7 @@ jobs: run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - name: Cache dependencies installed with composer - uses: actions/cache@v2.1.4 + uses: actions/cache@v2 with: path: ${{ env.COMPOSER_CACHE_DIR }} key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/README.md b/README.md index 316519b..2754182 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ Documentation is at [docs/guide/README.md](docs/guide/README.md). [![Latest Stable Version](https://poser.pugx.org/yiisoft/db-oracle/v/stable.png)](https://packagist.org/packages/yiisoft/db-oracle) [![Total Downloads](https://poser.pugx.org/yiisoft/db-oracle/downloads.png)](https://packagist.org/packages/yiisoft/db-oracle) -[![Build status](https://github.com/yiisoft/db-oracle/workflows/build/badge.svg)](https://github.com/yiisoft/db-oracle/actions?query=workflow%3Abuild) +[![build](https://github.com/yiisoft/db-oracle/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/yiisoft/db-oracle/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/yiisoft/db-oracle/branch/master/graph/badge.svg?token=XGJAFXVHSH)](https://codecov.io/gh/yiisoft/db-oracle) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fdb-oracle%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/db-oracle/master) -[![static analysis](https://github.com/yiisoft/db-oracle/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/db-oracle/actions?query=workflow%3A%22static+analysis%22) +[![static analysis](https://github.com/yiisoft/db-oracle/actions/workflows/static.yml/badge.svg?branch=dev)](https://github.com/yiisoft/db-oracle/actions/workflows/static.yml) [![type-coverage](https://shepherd.dev/github/yiisoft/db-oracle/coverage.svg)](https://shepherd.dev/github/yiisoft/db-oracle) diff --git a/src/Command.php b/src/Command.php deleted file mode 100644 index ccd2f3a..0000000 --- a/src/Command.php +++ /dev/null @@ -1,36 +0,0 @@ -getPdoStatement(); - - foreach ($this->pendingParams as $name => $value) { - if (PDO::PARAM_STR === $value[1] && $pdoStatement !== null) { - $paramsPassedByReference[$name] = $value[0]; - $pdoStatement->bindParam( - $name, - $paramsPassedByReference[$name], - $value[1], - strlen($value[0]) - ); - } elseif ($pdoStatement !== null) { - $pdoStatement->bindValue($name, $value[0], $value[1]); - } - } - - $this->pendingParams = []; - } -} diff --git a/src/Connection.php b/src/Connection.php deleted file mode 100644 index 6babc60..0000000 --- a/src/Connection.php +++ /dev/null @@ -1,84 +0,0 @@ -queryCache = $queryCache; - $this->schemaCache = $schemaCache; - - parent::__construct($dsn, $queryCache); - } - - public function createCommand(?string $sql = null, array $params = []): Command - { - if ($sql !== null) { - $sql = $this->quoteSql($sql); - } - - $command = new Command($this, $this->queryCache, $sql); - - if ($this->logger !== null) { - $command->setLogger($this->logger); - } - - if ($this->profiler !== null) { - $command->setProfiler($this->profiler); - } - - return $command->bindValues($params); - } - - /** - * Returns the schema information for the database opened by this connection. - * - * @return Schema the schema information for the database opened by this connection. - */ - public function getSchema(): Schema - { - return new Schema($this, $this->schemaCache); - } - - protected function createPdoInstance(): PDO - { - return new PDO($this->getDsn(), $this->getUsername(), $this->getPassword(), $this->getAttributes()); - } - - protected function initConnection(): void - { - $pdo = $this->getPDO(); - - if ($pdo !== null) { - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - if ($this->getEmulatePrepare() !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->getEmulatePrepare()); - } - } - } - - /** - * Returns the name of the DB driver. - * - * @return string name of the DB driver - */ - public function getDriverName(): string - { - return 'oci'; - } -} diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php deleted file mode 100644 index e3e5f7f..0000000 --- a/src/QueryBuilder.php +++ /dev/null @@ -1,448 +0,0 @@ - 'NUMBER(10) NOT NULL PRIMARY KEY', - Schema::TYPE_UPK => 'NUMBER(10) UNSIGNED NOT NULL PRIMARY KEY', - Schema::TYPE_BIGPK => 'NUMBER(20) NOT NULL PRIMARY KEY', - Schema::TYPE_UBIGPK => 'NUMBER(20) UNSIGNED NOT NULL PRIMARY KEY', - Schema::TYPE_CHAR => 'CHAR(1)', - Schema::TYPE_STRING => 'VARCHAR2(255)', - Schema::TYPE_TEXT => 'CLOB', - Schema::TYPE_TINYINT => 'NUMBER(3)', - Schema::TYPE_SMALLINT => 'NUMBER(5)', - Schema::TYPE_INTEGER => 'NUMBER(10)', - Schema::TYPE_BIGINT => 'NUMBER(20)', - Schema::TYPE_FLOAT => 'NUMBER', - Schema::TYPE_DOUBLE => 'NUMBER', - Schema::TYPE_DECIMAL => 'NUMBER', - Schema::TYPE_DATETIME => 'TIMESTAMP', - Schema::TYPE_TIMESTAMP => 'TIMESTAMP', - Schema::TYPE_TIME => 'TIMESTAMP', - Schema::TYPE_DATE => 'DATE', - Schema::TYPE_BINARY => 'BLOB', - Schema::TYPE_BOOLEAN => 'NUMBER(1)', - Schema::TYPE_MONEY => 'NUMBER(19,4)', - ]; - - protected function defaultExpressionBuilders(): array - { - return array_merge(parent::defaultExpressionBuilders(), [ - InCondition::class => InConditionBuilder::class, - LikeCondition::class => LikeConditionBuilder::class, - ]); - } - - public function buildOrderByAndLimit(string $sql, array $orderBy, $limit, $offset, array &$params = []): string - { - $orderBy = $this->buildOrderBy($orderBy, $params); - - if ($orderBy !== '') { - $sql .= $this->separator . $orderBy; - } - - $filters = []; - if ($this->hasOffset($offset)) { - $filters[] = 'rowNumId > ' . $offset; - } - - if ($this->hasLimit($limit)) { - $filters[] = 'rownum <= ' . $limit; - } - - if (empty($filters)) { - return $sql; - } - - $filter = implode(' AND ', $filters); - return <<getDb()->quoteTableName($oldName) . ' RENAME TO ' . - $this->getDb()->quoteTableName($newName); - } - - /** - * Builds a SQL statement for changing the definition of a column. - * - * @param string $table the table whose column is to be changed. The table name will be properly quoted by the - * method. - * @param string $column the name of the column to be changed. The name will be properly quoted by the method. - * @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column - * type (if any) into the physical one. Anything that is not recognized as abstract type will be kept in the - * generated SQL. - * - * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become - * 'varchar(255) not null'. - * - * @return string the SQL statement for changing the definition of a column. - */ - public function alterColumn(string $table, string $column, string $type): string - { - $type = $this->getColumnType($type); - - return 'ALTER TABLE ' - . $this->getDb()->quoteTableName($table) - . ' MODIFY ' - . $this->getDb()->quoteColumnName($column) - . ' ' . $this->getColumnType($type); - } - - /** - * Builds a SQL statement for dropping an index. - * - * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. - * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. - * - * @return string the SQL statement for dropping an index. - */ - public function dropIndex(string $name, string $table): string - { - return 'DROP INDEX ' . $this->getDb()->quoteTableName($name); - } - - /** - * Creates a SQL statement for resetting the sequence value of a table's primary key. - * - * The sequence will be reset such that the primary key of the next new row inserted will have the specified value - * or 1. - * - * @param string $tableName the name of the table whose primary key sequence will be reset. - * @param array|string|null $value the value for the primary key of the next new row inserted. If this is not set, - * the next new row's primary key will have a value 1. - * - * @throws Exception|InvalidArgumentException|InvalidConfigException|Throwable - */ - public function executeResetSequence(string $tableName, $value = null): void - { - $tableSchema = $this->getDb()->getTableSchema($tableName); - - if ($tableSchema === null) { - throw new InvalidArgumentException("Unknown table: $tableName"); - } - - if ($tableSchema->getSequenceName() === null) { - throw new InvalidArgumentException("There is no sequence associated with table: $tableName"); - } - - if ($value !== null) { - $value = (int) $value; - } else { - if (count($tableSchema->getPrimaryKey()) > 1) { - throw new InvalidArgumentException( - "Can't reset sequence for composite primary key in table: $tableName" - ); - } - /** use master connection to get the biggest PK value */ - $value = $this->getDb()->useMaster(static function (Connection $db) use ($tableSchema) { - return $db->createCommand( - 'SELECT MAX("' . $tableSchema->getPrimaryKey()[0] . '") FROM "' . $tableSchema->getName() . '"' - )->queryScalar(); - }) + 1; - } - - /** - * Oracle needs at least two queries to reset sequence (see adding transactions and/or use alter method to - * avoid grants' issue?) - */ - $this->getDb()->createCommand('DROP SEQUENCE "' . $tableSchema->getSequenceName() . '"')->execute(); - $this->getDb()->createCommand( - 'CREATE SEQUENCE "' . - $tableSchema->getSequenceName() . - '" START WITH ' . - $value . - ' INCREMENT BY 1 NOMAXVALUE NOCACHE' - )->execute(); - } - - public function addForeignKey( - string $name, - string $table, - $columns, - string $refTable, - $refColumns, - ?string $delete = null, - ?string $update = null - ): string { - $sql = 'ALTER TABLE ' . $this->getDb()->quoteTableName($table) - . ' ADD CONSTRAINT ' . $this->getDb()->quoteColumnName($name) - . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' - . ' REFERENCES ' . $this->getDb()->quoteTableName($refTable) - . ' (' . $this->buildColumns($refColumns) . ')'; - - if ($delete !== null) { - $sql .= ' ON DELETE ' . $delete; - } - - if ($update !== null) { - throw new Exception('Oracle does not support ON UPDATE clause.'); - } - - return $sql; - } - - protected function prepareInsertValues(string $table, $columns, array $params = []): array - { - [$names, $placeholders, $values, $params] = parent::prepareInsertValues($table, $columns, $params); - - if (!$columns instanceof Query && empty($names)) { - $tableSchema = $this->getDb()->getSchema()->getTableSchema($table); - - if ($tableSchema !== null) { - $tableColumns = $tableSchema->getColumns(); - $columns = !empty($tableSchema->getPrimaryKey()) - ? $tableSchema->getPrimaryKey() : [reset($tableColumns)->getName()]; - foreach ($columns as $name) { - $names[] = $this->getDb()->quoteColumnName($name); - $placeholders[] = 'DEFAULT'; - } - } - } - - return [$names, $placeholders, $values, $params]; - } - - /** - * {@see https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm#SQLRF01606} - * - * @param string $table - * @param $insertColumns - * @param $updateColumns - * @param array $params - * - * @throws Exception|InvalidArgumentException|InvalidConfigException|JsonException|NotSupportedException - * - * @return string - */ - public function upsert(string $table, $insertColumns, $updateColumns, array &$params = []): string - { - $constraints = []; - - /** @var Constraint[] $constraints */ - [$uniqueNames, $insertNames, $updateNames] = $this->prepareUpsertColumns( - $table, - $insertColumns, - $updateColumns, - $constraints - ); - - if (empty($uniqueNames)) { - return $this->insert($table, $insertColumns, $params); - } - - if ($updateNames === []) { - /** there are no columns to update */ - $updateColumns = false; - } - - $onCondition = ['or']; - $quotedTableName = $this->getDb()->quoteTableName($table); - - foreach ($constraints as $constraint) { - $constraintCondition = ['and']; - foreach ($constraint->getColumnNames() as $name) { - $quotedName = $this->getDb()->quoteColumnName($name); - $constraintCondition[] = "$quotedTableName.$quotedName=\"EXCLUDED\".$quotedName"; - } - - $onCondition[] = $constraintCondition; - } - - $on = $this->buildCondition($onCondition, $params); - - [, $placeholders, $values, $params] = $this->prepareInsertValues($table, $insertColumns, $params); - - if (!empty($placeholders)) { - $usingSelectValues = []; - foreach ($insertNames as $index => $name) { - $usingSelectValues[$name] = new Expression($placeholders[$index]); - } - - $usingSubQuery = (new Query($this->getDb())) - ->select($usingSelectValues) - ->from('DUAL'); - - [$usingValues, $params] = $this->build($usingSubQuery, $params); - } - - $mergeSql = 'MERGE INTO ' . $this->getDb()->quoteTableName($table) . ' ' - . 'USING (' . ($usingValues ?? ltrim($values, ' ')) . ') "EXCLUDED" ' - . "ON ($on)"; - - $insertValues = []; - foreach ($insertNames as $name) { - $quotedName = $this->getDb()->quoteColumnName($name); - - if (strrpos($quotedName, '.') === false) { - $quotedName = '"EXCLUDED".' . $quotedName; - } - - $insertValues[] = $quotedName; - } - - $insertSql = 'INSERT (' . implode(', ', $insertNames) . ')' - . ' VALUES (' . implode(', ', $insertValues) . ')'; - - if ($updateColumns === false) { - return "$mergeSql WHEN NOT MATCHED THEN $insertSql"; - } - - if ($updateColumns === true) { - $updateColumns = []; - foreach ($updateNames as $name) { - $quotedName = $this->getDb()->quoteColumnName($name); - - if (strrpos($quotedName, '.') === false) { - $quotedName = '"EXCLUDED".' . $quotedName; - } - $updateColumns[$name] = new Expression($quotedName); - } - } - - [$updates, $params] = $this->prepareUpdateSets($table, $updateColumns, $params); - - $updateSql = 'UPDATE SET ' . implode(', ', $updates); - - return "$mergeSql WHEN MATCHED THEN $updateSql WHEN NOT MATCHED THEN $insertSql"; - } - - /** - * Generates a batch INSERT SQL statement. - * - * For example, - * - * ```php - * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [ - * ['Tom', 30], - * ['Jane', 20], - * ['Linda', 25], - * ]); - * ``` - * - * Note that the values in each row must match the corresponding column names. - * - * @param string $table the table that new rows will be inserted into. - * @param array $columns the column names. - * @param array|Generator $rows the rows to be batch inserted into the table. - * @param array $params - * - * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException - * - * @return string the batch INSERT SQL statement. - */ - public function batchInsert(string $table, array $columns, $rows, array &$params = []): string - { - if (empty($rows)) { - return ''; - } - - $schema = $this->getDb()->getSchema(); - - if (($tableSchema = $schema->getTableSchema($table)) !== null) { - $columnSchemas = $tableSchema->getColumns(); - } else { - $columnSchemas = []; - } - - $values = []; - - foreach ($rows as $row) { - $vs = []; - foreach ($row as $i => $value) { - if (isset($columns[$i], $columnSchemas[$columns[$i]])) { - $value = $columnSchemas[$columns[$i]]->dbTypecast($value); - } - - if (is_string($value)) { - $value = $schema->quoteValue($value); - } elseif (is_float($value)) { - /* ensure type cast always has . as decimal separator in all locales */ - $value = NumericHelper::normalize($value); - } elseif ($value === false) { - $value = 0; - } elseif ($value === null) { - $value = 'NULL'; - } elseif ($value instanceof ExpressionInterface) { - $value = $this->buildExpression($value, $params); - } - - $vs[] = $value; - } - - $values[] = '(' . implode(', ', $vs) . ')'; - } - - if (empty($values)) { - return ''; - } - - foreach ($columns as $i => $name) { - $columns[$i] = $schema->quoteColumnName($name); - } - - $tableAndColumns = ' INTO ' . $schema->quoteTableName($table) - . ' (' . implode(', ', $columns) . ') VALUES '; - - return 'INSERT ALL ' . $tableAndColumns . implode($tableAndColumns, $values) . ' SELECT 1 FROM SYS.DUAL'; - } - - public function selectExists(string $rawSql): string - { - return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END FROM DUAL'; - } - - public function dropCommentFromColumn(string $table, string $column): string - { - return 'COMMENT ON COLUMN ' . $this->getDb()->quoteTableName($table) . '.' . $this->getDb()->quoteColumnName($column) . " IS ''"; - } - - public function dropCommentFromTable(string $table): string - { - return 'COMMENT ON TABLE ' . $this->getDb()->quoteTableName($table) . " IS ''"; - } -} diff --git a/src/Schema.php b/src/Schema.php deleted file mode 100644 index 9c61dea..0000000 --- a/src/Schema.php +++ /dev/null @@ -1,848 +0,0 @@ - IntegrityException::class, - ]; - - protected $tableQuoteCharacter = '"'; - - public function __construct(Connection $db, SchemaCache $schemaCache) - { - $this->defaultSchema = strtoupper($db->getUsername()); - - parent::__construct($db, $schemaCache); - } - - protected function resolveTableName(string $name): TableSchema - { - $resolvedName = new TableSchema(); - - $parts = explode('.', str_replace('"', '', $name)); - - if (isset($parts[1])) { - $resolvedName->schemaName($parts[0]); - $resolvedName->name($parts[1]); - } else { - $resolvedName->schemaName($this->defaultSchema); - $resolvedName->name($name); - } - - $fullName = ($resolvedName->getSchemaName() !== $this->defaultSchema - ? $resolvedName->getSchemaName() . '.' : '') . $resolvedName->getName(); - - $resolvedName->fullName($fullName); - - return $resolvedName; - } - - /** - * @see https://docs.oracle.com/cd/B28359_01/server.111/b28337/tdpsg_user_accounts.htm - */ - protected function findSchemaNames(): array - { - static $sql = <<<'SQL' -SELECT "u"."USERNAME" -FROM "DBA_USERS" "u" -WHERE "u"."DEFAULT_TABLESPACE" NOT IN ('SYSTEM', 'SYSAUX') -ORDER BY "u"."USERNAME" ASC -SQL; - - return $this->getDb()->createCommand($sql)->queryColumn(); - } - - /** - * @param string $schema - * - * @throws Exception|InvalidConfigException|Throwable - * - * @return array - */ - protected function findTableNames(string $schema = ''): array - { - if ($schema === '') { - $sql = <<<'SQL' -SELECT - TABLE_NAME -FROM USER_TABLES -UNION ALL -SELECT - VIEW_NAME AS TABLE_NAME -FROM USER_VIEWS -UNION ALL -SELECT - MVIEW_NAME AS TABLE_NAME -FROM USER_MVIEWS -ORDER BY TABLE_NAME -SQL; - - $command = $this->getDb()->createCommand($sql); - } else { - $sql = <<<'SQL' -SELECT - OBJECT_NAME AS TABLE_NAME -FROM ALL_OBJECTS -WHERE - OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') - AND OWNER = :schema -ORDER BY OBJECT_NAME -SQL; - $command = $this->getDb()->createCommand($sql, [':schema' => $schema]); - } - - $rows = $command->queryAll(); - $names = []; - - foreach ($rows as $row) { - if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) { - $row = array_change_key_case($row, CASE_UPPER); - } - $names[] = $row['TABLE_NAME']; - } - - return $names; - } - - /** - * @param string $name - * - * @throws Exception|InvalidConfigException|Throwable - * - * @return TableSchema|null - */ - protected function loadTableSchema(string $name): ?TableSchema - { - $table = new TableSchema(); - - $this->resolveTableNames($table, $name); - - if ($this->findColumns($table)) { - $this->findConstraints($table); - return $table; - } - - return null; - } - - /** - * @param string $tableName - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return Constraint|null - */ - protected function loadTablePrimaryKey(string $tableName): ?Constraint - { - return $this->loadTableConstraints($tableName, 'primaryKey'); - } - - /** - * @param string $tableName - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return array - */ - protected function loadTableForeignKeys(string $tableName): array - { - return $this->loadTableConstraints($tableName, 'foreignKeys'); - } - - /** - * @param string $tableName - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return array - */ - protected function loadTableIndexes(string $tableName): array - { - static $sql = <<<'SQL' -SELECT - /*+ PUSH_PRED("ui") PUSH_PRED("uicol") PUSH_PRED("uc") */ - "ui"."INDEX_NAME" AS "name", - "uicol"."COLUMN_NAME" AS "column_name", - CASE "ui"."UNIQUENESS" WHEN 'UNIQUE' THEN 1 ELSE 0 END AS "index_is_unique", - CASE WHEN "uc"."CONSTRAINT_NAME" IS NOT NULL THEN 1 ELSE 0 END AS "index_is_primary" -FROM "SYS"."USER_INDEXES" "ui" -LEFT JOIN "SYS"."USER_IND_COLUMNS" "uicol" - ON "uicol"."INDEX_NAME" = "ui"."INDEX_NAME" -LEFT JOIN "SYS"."USER_CONSTRAINTS" "uc" - ON "uc"."OWNER" = "ui"."TABLE_OWNER" AND "uc"."CONSTRAINT_NAME" = "ui"."INDEX_NAME" AND "uc"."CONSTRAINT_TYPE" = 'P' -WHERE "ui"."TABLE_OWNER" = :schemaName AND "ui"."TABLE_NAME" = :tableName -ORDER BY "uicol"."COLUMN_POSITION" ASC -SQL; - - $resolvedName = $this->resolveTableName($tableName); - - $indexes = $this->getDb()->createCommand($sql, [ - ':schemaName' => $resolvedName->getSchemaName(), - ':tableName' => $resolvedName->getName(), - ])->queryAll(); - - $indexes = $this->normalizePdoRowKeyCase($indexes, true); - - $indexes = ArrayHelper::index($indexes, null, 'name'); - - $result = []; - foreach ($indexes as $name => $index) { - $columnNames = ArrayHelper::getColumn($index, 'column_name'); - - if ($columnNames[0] === null) { - $columnNames[0] = ''; - } - - $result[] = (new IndexConstraint()) - ->primary((bool) $index[0]['index_is_primary']) - ->unique((bool) $index[0]['index_is_unique']) - ->name($name) - ->columnNames($columnNames); - } - - return $result; - } - - /** - * @param string $tableName - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return array - */ - protected function loadTableUniques(string $tableName): array - { - return $this->loadTableConstraints($tableName, 'uniques'); - } - - /** - * @param string $tableName - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return array - */ - protected function loadTableChecks(string $tableName): array - { - return $this->loadTableConstraints($tableName, 'checks'); - } - - /** - * @param string $tableName - * - * @throws NotSupportedException if this method is called. - * - * @return array - */ - protected function loadTableDefaultValues(string $tableName): array - { - throw new NotSupportedException('Oracle does not support default value constraints.'); - } - - public function releaseSavepoint(string $name): void - { - /* does nothing as Oracle does not support this */ - } - - public function quoteSimpleTableName(string $name): string - { - return strpos($name, '"') !== false ? $name : '"' . $name . '"'; - } - - public function createQueryBuilder(): QueryBuilder - { - return new QueryBuilder($this->getDb()); - } - - /** - * Create a column schema builder instance giving the type and value precision. - * - * This method may be overridden by child classes to create a DBMS-specific column schema builder. - * - * @param string $type type of the column. See {@see ColumnSchemaBuilder::$type}. - * @param array|int|string $length length or precision of the column {@see ColumnSchemaBuilder::$length}. - * - * @return ColumnSchemaBuilder column schema builder instance - */ - public function createColumnSchemaBuilder(string $type, $length = null): ColumnSchemaBuilder - { - return new ColumnSchemaBuilder($type, $length); - } - - /** - * Resolves the table name and schema name (if any). - * - * @param TableSchema $table the table metadata object - * @param string $name the table name - */ - protected function resolveTableNames(TableSchema $table, string $name): void - { - $parts = explode('.', str_replace('"', '', $name)); - - if (isset($parts[1])) { - $table->schemaName($parts[0]); - $table->name($parts[1]); - } else { - $table->schemaName($this->defaultSchema); - $table->name($name); - } - - $table->fullName($table->getSchemaName() !== $this->defaultSchema - ? $table->getSchemaName() . '.' . $table->getName() : $table->getName()); - } - - /** - * Collects the table column metadata. - * - * @param TableSchema $table the table schema. - * - * @throws Exception|Throwable - * - * @return bool whether the table exists. - */ - protected function findColumns(TableSchema $table): bool - { - $sql = <<<'SQL' -SELECT - A.COLUMN_NAME, - A.DATA_TYPE, - A.DATA_PRECISION, - A.DATA_SCALE, - ( - CASE A.CHAR_USED WHEN 'C' THEN A.CHAR_LENGTH - ELSE A.DATA_LENGTH - END - ) AS DATA_LENGTH, - A.NULLABLE, - A.DATA_DEFAULT, - COM.COMMENTS AS COLUMN_COMMENT -FROM ALL_TAB_COLUMNS A - INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME) - LEFT JOIN ALL_COL_COMMENTS COM ON (A.OWNER = COM.OWNER AND A.TABLE_NAME = COM.TABLE_NAME AND A.COLUMN_NAME = COM.COLUMN_NAME) -WHERE - A.OWNER = :schemaName - AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') - AND B.OBJECT_NAME = :tableName -ORDER BY A.COLUMN_ID -SQL; - - try { - $columns = $this->getDb()->createCommand($sql, [ - ':tableName' => $table->getName(), - ':schemaName' => $table->getSchemaName(), - ])->queryAll(); - } catch (Exception $e) { - return false; - } - - if (empty($columns)) { - return false; - } - - foreach ($columns as $column) { - if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) { - $column = array_change_key_case($column, CASE_UPPER); - } - - $c = $this->createColumn($column); - - $table->columns($c->getName(), $c); - } - - return true; - } - - /** - * Sequence name of table. - * - * @param string $tableName - * - * @throws Exception|InvalidConfigException|Throwable - * - * @return string|null whether the sequence exists. - * - * @internal TableSchema `$table->getName()` the table schema. - */ - protected function getTableSequenceName(string $tableName): ?string - { - $sequenceNameSql = <<getDb()->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar(); - - return $sequenceName === false ? null : $sequenceName; - } - - /** - * @Overrides method in class 'Schema' - * - * {@see https://secure.php.net/manual/en/function.PDO-lastInsertId.php} -> Oracle does not support this. - * - * Returns the ID of the last inserted row or sequence value. - * - * @param string $sequenceName name of the sequence object (required by some DBMS) - * - * @throws Exception|InvalidCallException|InvalidConfigException|Throwable if the DB connection is not active. - * - * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object. - */ - public function getLastInsertID(string $sequenceName = ''): string - { - if ($this->getDb()->isActive()) { - /* get the last insert id from the master connection */ - $sequenceName = $this->quoteSimpleTableName($sequenceName); - - return $this->getDb()->useMaster(function (Connection $db) use ($sequenceName) { - return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar(); - }); - } - - throw new InvalidCallException('DB Connection is not active.'); - } - - /** - * Creates ColumnSchema instance. - * - * @param array|string $column - * - * @return ColumnSchema - */ - protected function createColumn($column): ColumnSchema - { - $c = $this->createColumnSchema(); - - $c->name($column['COLUMN_NAME']); - $c->allowNull($column['NULLABLE'] === 'Y'); - $c->comment($column['COLUMN_COMMENT'] ?? ''); - $c->primaryKey(false); - - $this->extractColumnType( - $c, - $column['DATA_TYPE'], - $column['DATA_PRECISION'], - $column['DATA_SCALE'], - $column['DATA_LENGTH'] - ); - - $this->extractColumnSize( - $c, - $column['DATA_TYPE'], - $column['DATA_PRECISION'], - $column['DATA_SCALE'], - $column['DATA_LENGTH'] - ); - - $c->phpType($this->getColumnPhpType($c)); - - if (!$c->isPrimaryKey()) { - if ($column['DATA_DEFAULT'] !== null && stripos($column['DATA_DEFAULT'], 'timestamp') !== false) { - $c->defaultValue(null); - } else { - $defaultValue = $column['DATA_DEFAULT']; - - if ($c->getType() === 'timestamp' && $defaultValue === 'CURRENT_TIMESTAMP') { - $c->defaultValue(new Expression('CURRENT_TIMESTAMP')); - } else { - if ($defaultValue !== null) { - if (($len = strlen($defaultValue)) > 2 && $defaultValue[0] === "'" - && $defaultValue[$len - 1] === "'" - ) { - $defaultValue = substr($column['DATA_DEFAULT'], 1, -1); - } else { - $defaultValue = trim($defaultValue); - } - } - $c->defaultValue($c->phpTypecast($defaultValue)); - } - } - } - - return $c; - } - - /** - * Finds constraints and fills them into TableSchema object passed. - * - * @param TableSchema $table - * - * @throws Exception|InvalidConfigException|Throwable - */ - protected function findConstraints(TableSchema $table): void - { - $sql = <<<'SQL' -SELECT - /*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */ - D.CONSTRAINT_NAME, - D.CONSTRAINT_TYPE, - C.COLUMN_NAME, - C.POSITION, - D.R_CONSTRAINT_NAME, - E.TABLE_NAME AS TABLE_REF, - F.COLUMN_NAME AS COLUMN_REF, - C.TABLE_NAME -FROM ALL_CONS_COLUMNS C - INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME - LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME - LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION -WHERE - C.OWNER = :schemaName - AND C.TABLE_NAME = :tableName -ORDER BY D.CONSTRAINT_NAME, C.POSITION -SQL; - - $command = $this->getDb()->createCommand($sql, [ - ':tableName' => $table->getName(), - ':schemaName' => $table->getSchemaName(), - ]); - - $constraints = []; - - foreach ($command->queryAll() as $row) { - if ($this->getDb()->getSlavePdo()->getAttribute(PDO::ATTR_CASE) === PDO::CASE_LOWER) { - $row = array_change_key_case($row, CASE_UPPER); - } - - if ($row['CONSTRAINT_TYPE'] === 'P') { - $table->getColumns()[$row['COLUMN_NAME']]->primaryKey(true); - $table->primaryKey($row['COLUMN_NAME']); - - if (empty($table->getSequenceName())) { - $table->sequenceName($this->getTableSequenceName($table->getName())); - } - } - - if ($row['CONSTRAINT_TYPE'] !== 'R') { - /** - * This condition is not checked in SQL WHERE because of an Oracle Bug: - * - * {@see https://github.com/yiisoft/yii2/pull/8844} - */ - continue; - } - - $name = $row['CONSTRAINT_NAME']; - - if (!isset($constraints[$name])) { - $constraints[$name] = [ - 'tableName' => $row['TABLE_REF'], - 'columns' => [], - ]; - } - - $constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF']; - } - - foreach ($constraints as $constraint) { - $name = current(array_keys($constraint)); - - $table->foreignKey(array_merge([$constraint['tableName']], $constraint['columns'])); - } - } - - /** - * Returns all unique indexes for the given table. - * - * Each array element is of the following structure:. - * - * ```php - * [ - * 'IndexName1' => ['col1' [, ...]], - * 'IndexName2' => ['col2' [, ...]], - * ] - * ``` - * - * @param TableSchema $table the table metadata. - * - * @throws Exception|InvalidConfigException|Throwable - * - * @return array all unique indexes for the given table. - */ - public function findUniqueIndexes(TableSchema $table): array - { - $query = <<<'SQL' -SELECT - DIC.INDEX_NAME, - DIC.COLUMN_NAME -FROM ALL_INDEXES DI - INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME -WHERE - DI.UNIQUENESS = 'UNIQUE' - AND DIC.TABLE_OWNER = :schemaName - AND DIC.TABLE_NAME = :tableName -ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION -SQL; - $result = []; - - $command = $this->getDb()->createCommand($query, [ - ':tableName' => $table->getName(), - ':schemaName' => $table->getschemaName(), - ]); - - foreach ($command->queryAll() as $row) { - $result[$row['INDEX_NAME']][] = $row['COLUMN_NAME']; - } - - return $result; - } - - /** - * Extracts the data types for the given column. - * - * @param ColumnSchema $column - * @param string $dbType DB type. - * @param string|null $precision total number of digits. - * @param string|null $scale number of digits on the right of the decimal separator. - * @param string $length length for character types. - */ - protected function extractColumnType( - ColumnSchema $column, - string $dbType, - ?string $precision, - ?string $scale, - string $length - ): void { - $column->dbType($dbType); - - if (strpos($dbType, 'FLOAT') !== false || strpos($dbType, 'DOUBLE') !== false) { - $column->type('double'); - } elseif (strpos($dbType, 'NUMBER') !== false) { - if ($scale === null || $scale > 0) { - $column->type('decimal'); - } else { - $column->type('integer'); - } - } elseif (strpos($dbType, 'INTEGER') !== false) { - $column->type('integer'); - } elseif (strpos($dbType, 'BLOB') !== false) { - $column->type('binary'); - } elseif (strpos($dbType, 'CLOB') !== false) { - $column->type('text'); - } elseif (strpos($dbType, 'TIMESTAMP') !== false) { - $column->type('timestamp'); - } else { - $column->type('string'); - } - } - - /** - * Extracts size, precision and scale information from column's DB type. - * - * @param ColumnSchema $column - * @param string $dbType the column's DB type. - * @param string|null $precision total number of digits. - * @param string|null $scale number of digits on the right of the decimal separator. - * @param string $length length for character types. - */ - protected function extractColumnSize( - ColumnSchema $column, - string $dbType, - ?string $precision, - ?string $scale, - string $length - ): void { - $column->size(trim($length) === '' ? null : (int) $length); - $column->precision(trim((string) $precision) === '' ? null : (int) $precision); - $column->scale($scale === '' || $scale === null ? null : (int) $scale); - } - - public function insert($table, $columns) - { - $params = []; - $returnParams = []; - $sql = $this->getDb()->getQueryBuilder()->insert($table, $columns, $params); - $tableSchema = $this->getTableSchema($table); - $returnColumns = $tableSchema->getPrimaryKey(); - - if (!empty($returnColumns)) { - $columnSchemas = $tableSchema->getColumns(); - - $returning = []; - foreach ($returnColumns as $name) { - $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams)); - - $returnParams[$phName] = [ - 'column' => $name, - 'value' => '', - ]; - - if (!isset($columnSchemas[$name]) || $columnSchemas[$name]->getPhpType() !== 'integer') { - $returnParams[$phName]['dataType'] = PDO::PARAM_STR; - } else { - $returnParams[$phName]['dataType'] = PDO::PARAM_INT; - } - - $returnParams[$phName]['size'] = $columnSchemas[$name]->getSize() ?? -1; - - $returning[] = $this->quoteColumnName($name); - } - - $sql .= ' RETURNING ' . implode(', ', $returning) . ' INTO ' . implode(', ', array_keys($returnParams)); - } - - $command = $this->getDb()->createCommand($sql, $params); - - $command->prepare(false); - - foreach ($returnParams as $name => &$value) { - $command->getPdoStatement()->bindParam($name, $value['value'], $value['dataType'], $value['size']); - } - - if (!$command->execute()) { - return false; - } - - $result = []; - foreach ($returnParams as $value) { - $result[$value['column']] = $value['value']; - } - - return $result; - } - - /** - * Loads multiple types of constraints and returns the specified ones. - * - * @param string $tableName table name. - * @param string $returnType return type: - * - primaryKey - * - foreignKeys - * - uniques - * - checks - * - * @throws Exception|InvalidConfigException|NotSupportedException|Throwable - * - * @return mixed constraints. - */ - private function loadTableConstraints(string $tableName, string $returnType) - { - $sql = <<<'SQL' -SELECT - /*+ PUSH_PRED("uc") PUSH_PRED("uccol") PUSH_PRED("fuc") */ - "uc"."CONSTRAINT_NAME" AS "name", - "uccol"."COLUMN_NAME" AS "column_name", - "uc"."CONSTRAINT_TYPE" AS "type", - "fuc"."OWNER" AS "foreign_table_schema", - "fuc"."TABLE_NAME" AS "foreign_table_name", - "fuccol"."COLUMN_NAME" AS "foreign_column_name", - "uc"."DELETE_RULE" AS "on_delete", - "uc"."SEARCH_CONDITION" AS "check_expr" -FROM "USER_CONSTRAINTS" "uc" -INNER JOIN "USER_CONS_COLUMNS" "uccol" - ON "uccol"."OWNER" = "uc"."OWNER" AND "uccol"."CONSTRAINT_NAME" = "uc"."CONSTRAINT_NAME" -LEFT JOIN "USER_CONSTRAINTS" "fuc" - ON "fuc"."OWNER" = "uc"."R_OWNER" AND "fuc"."CONSTRAINT_NAME" = "uc"."R_CONSTRAINT_NAME" -LEFT JOIN "USER_CONS_COLUMNS" "fuccol" - ON "fuccol"."OWNER" = "fuc"."OWNER" AND "fuccol"."CONSTRAINT_NAME" = "fuc"."CONSTRAINT_NAME" AND "fuccol"."POSITION" = "uccol"."POSITION" -WHERE "uc"."OWNER" = :schemaName AND "uc"."TABLE_NAME" = :tableName -ORDER BY "uccol"."POSITION" ASC -SQL; - - $resolvedName = $this->resolveTableName($tableName); - - $constraints = $this->getDb()->createCommand($sql, [ - ':schemaName' => $resolvedName->getSchemaName(), - ':tableName' => $resolvedName->getName(), - ])->queryAll(); - - $constraints = $this->normalizePdoRowKeyCase($constraints, true); - - $constraints = ArrayHelper::index($constraints, null, ['type', 'name']); - - $result = [ - 'primaryKey' => null, - 'foreignKeys' => [], - 'uniques' => [], - 'checks' => [], - ]; - - foreach ($constraints as $type => $names) { - foreach ($names as $name => $constraint) { - switch ($type) { - case 'P': - $result['primaryKey'] = (new Constraint()) - ->name($name) - ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')); - break; - case 'R': - $result['foreignKeys'][] = (new ForeignKeyConstraint()) - ->name($name) - ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')) - ->foreignSchemaName($constraint[0]['foreign_table_schema']) - ->foreignTableName($constraint[0]['foreign_table_name']) - ->foreignColumnNames(ArrayHelper::getColumn($constraint, 'foreign_column_name')) - ->onDelete($constraint[0]['on_delete']) - ->onUpdate(null); - break; - case 'U': - $result['uniques'][] = (new Constraint()) - ->name($name) - ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')); - break; - case 'C': - $result['checks'][] = (new CheckConstraint()) - ->name($name) - ->columnNames(ArrayHelper::getColumn($constraint, 'column_name')) - ->expression($constraint[0]['check_expr']); - break; - } - } - } - - foreach ($result as $type => $data) { - $this->setTableMetadata($tableName, $type, $data); - } - - return $result[$returnType]; - } - - /** - * Creates a column schema for the database. - * - * This method may be overridden by child classes to create a DBMS-specific column schema. - * - * @return ColumnSchema column schema instance. - */ - protected function createColumnSchema(): ColumnSchema - { - return new ColumnSchema(); - } -}