AC_services_website_design/system/Database/OCI8/Forge.php

313 lines
9.5 KiB
PHP

<?php
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* 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\Forge as BaseForge;
/**
* Forge for OCI8
*/
class Forge extends BaseForge
{
/**
* DROP INDEX statement
*
* @var string
*/
protected $dropIndexStr = 'DROP INDEX %s';
/**
* CREATE DATABASE statement
*
* @var false
*/
protected $createDatabaseStr = false;
/**
* CREATE TABLE IF statement
*
* @var false
*
* @deprecated This is no longer used.
*/
protected $createTableIfStr = false;
/**
* DROP TABLE IF EXISTS statement
*
* @var false
*/
protected $dropTableIfStr = false;
/**
* DROP DATABASE statement
*
* @var false
*/
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';
/**
* Foreign Key Allowed Actions
*
* @var array
*/
protected $fkAllowActions = ['CASCADE', 'SET NULL', 'NO ACTION'];
/**
* ALTER TABLE
*
* @param string $alterType ALTER type
* @param string $table Table name
* @param array|string $processedFields Processed column definitions
* or column names to DROP
*
* @return list<string>|string SQL string
* @phpstan-return ($alterType is 'DROP' ? string : list<string>)
*/
protected function _alterTable(string $alterType, string $table, $processedFields)
{
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
if ($alterType === 'DROP') {
$columnNamesToDrop = $processedFields;
$fields = array_map(
fn ($field) => $this->db->escapeIdentifiers(trim($field)),
is_string($columnNamesToDrop) ? explode(',', $columnNamesToDrop) : $columnNamesToDrop
);
return $sql . ' DROP (' . implode(',', $fields) . ') CASCADE CONSTRAINT INVALIDATE';
}
if ($alterType === 'CHANGE') {
$alterType = 'MODIFY';
}
$nullableMap = array_column($this->db->getFieldData($table), 'nullable', 'name');
$sqls = [];
for ($i = 0, $c = count($processedFields); $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.
// If a not null constraint is added to a column with a not null constraint,
// ORA-01442 will occur.
$wantToAddNull = strpos($processedFields[$i]['null'], ' NOT') === false;
$currentNullable = $nullableMap[$processedFields[$i]['name']];
if ($wantToAddNull === true && $currentNullable === true) {
$processedFields[$i]['null'] = '';
} elseif ($processedFields[$i]['null'] === '' && $currentNullable === false) {
// Nullable by default
$processedFields[$i]['null'] = ' NULL';
} elseif ($wantToAddNull === false && $currentNullable === false) {
$processedFields[$i]['null'] = '';
}
}
if ($processedFields[$i]['_literal'] !== false) {
$processedFields[$i] = "\n\t" . $processedFields[$i]['_literal'];
} else {
$processedFields[$i]['_literal'] = "\n\t" . $this->_processColumn($processedFields[$i]);
if (! empty($processedFields[$i]['comment'])) {
$sqls[] = 'COMMENT ON COLUMN '
. $this->db->escapeIdentifiers($table) . '.' . $this->db->escapeIdentifiers($processedFields[$i]['name'])
. ' IS ' . $processedFields[$i]['comment'];
}
if ($alterType === 'MODIFY' && ! empty($processedFields[$i]['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($processedFields[$i]['name'])
. ' TO ' . $this->db->escapeIdentifiers($processedFields[$i]['new_name']);
}
$processedFields[$i] = "\n\t" . $processedFields[$i]['_literal'];
}
}
$sql .= ' ' . $alterType . ' ';
$sql .= count($processedFields) === 1
? $processedFields[0]
: '(' . implode(',', $processedFields) . ')';
// RENAME COLUMN must be executed after MODIFY
array_unshift($sqls, $sql);
return $sqls;
}
/**
* Field attribute AUTO_INCREMENT
*
* @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 ON NULL AS IDENTITY';
}
}
/**
* Process column
*/
protected function _processColumn(array $processedField): string
{
$constraint = '';
// @todo: can't cover multi pattern when set type.
if ($processedField['type'] === 'VARCHAR2' && strpos($processedField['length'], "('") === 0) {
$constraint = ' CHECK(' . $this->db->escapeIdentifiers($processedField['name'])
. ' IN ' . $processedField['length'] . ')';
$processedField['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($processedField['length'], 2, -2)))) . ')' . $constraint;
} elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $processedField['name'] === $this->primaryKeys['fields'][0]) {
$processedField['unique'] = '';
}
return $this->db->escapeIdentifiers($processedField['name'])
. ' ' . $processedField['type'] . $processedField['length']
. $processedField['unsigned']
. $processedField['default']
. $processedField['auto_increment']
. $processedField['null']
. $processedField['unique'];
}
/**
* Performs a data type mapping between different databases.
*
* @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'] ??= 3;
// no break
case 'SMALLINT':
$attributes['CONSTRAINT'] ??= 5;
// no break
case 'MEDIUMINT':
$attributes['CONSTRAINT'] ??= 7;
// no break
case 'INT':
case 'INTEGER':
$attributes['CONSTRAINT'] ??= 11;
// no break
case 'BIGINT':
$attributes['CONSTRAINT'] ??= 19;
// no break
case 'NUMERIC':
$attributes['TYPE'] = 'NUMBER';
return;
case 'BOOLEAN':
$attributes['TYPE'] = 'NUMBER';
$attributes['CONSTRAINT'] = 1;
$attributes['UNSIGNED'] = true;
return;
case 'DOUBLE':
$attributes['TYPE'] = 'FLOAT';
$attributes['CONSTRAINT'] ??= 126;
return;
case 'DATETIME':
case 'TIME':
$attributes['TYPE'] = 'DATE';
return;
case 'SET':
case 'ENUM':
case 'VARCHAR':
$attributes['CONSTRAINT'] ??= 255;
// no break
case 'TEXT':
case 'MEDIUMTEXT':
$attributes['CONSTRAINT'] ??= 4000;
$attributes['TYPE'] = 'VARCHAR2';
}
}
/**
* Generates a platform-specific DROP TABLE string
*
* @return bool|string
*/
protected function _dropTable(string $table, bool $ifExists, bool $cascade)
{
$sql = parent::_dropTable($table, $ifExists, $cascade);
if ($sql !== true && $cascade === true) {
$sql .= ' CASCADE CONSTRAINTS PURGE';
} elseif ($sql !== true) {
$sql .= ' PURGE';
}
return $sql;
}
/**
* Constructs sql to check if key is a constraint.
*/
protected function _dropKeyAsConstraint(string $table, string $constraintName): string
{
return "SELECT constraint_name FROM all_constraints WHERE table_name = '"
. trim($table, '"') . "' AND index_name = '"
. trim($constraintName, '"') . "'";
}
}