/** * {@inheritdoc} */ public function alterColumn(AbstractTable $table, AbstractColumn $initial, AbstractColumn $column) { $query = "ALTER TABLE {table} CHANGE {column} {statement}"; $query = \Spiral\interpolate($query, ['table' => $table->getName(true), 'column' => $initial->getName(true), 'statement' => $column->sqlStatement()]); $this->run($query); return $this; }
/** * {@inheritdoc} */ public function alterColumn(AbstractTable $table, AbstractColumn $initial, AbstractColumn $column) { if (!$initial instanceof ColumnSchema || !$column instanceof ColumnSchema) { throw new SchemaException("Postgres commander can work only with Postgres columns."); } //Rename is separate operation if ($column->getName() != $initial->getName()) { $this->renameColumn($table, $initial, $column); //This call is required to correctly built set of alter operations $initial->setName($column->getName()); } //Postgres columns should be altered using set of operations if (!($operations = $column->alteringOperations($initial))) { return $this; } //Postgres columns should be altered using set of operations $query = \Spiral\interpolate('ALTER TABLE {table} {operations}', ['table' => $table->getName(true), 'operations' => trim(join(', ', $operations), ', ')]); $this->run($query); return $this; }
/** * {@inheritdoc} */ public function sqlStatement() { $statement = parent::sqlStatement(); if ($this->abstractType() != 'enum') { return $statement; } $enumValues = []; foreach ($this->enumValues as $value) { $enumValues[] = $this->table->driver()->getPDO()->quote($value); } return "{$statement} CHECK ({$this->getName(true)} IN (" . join(', ', $enumValues) . "))"; }
/** * Cast default value based on column type. Required to prevent conflicts when not nullable * column added to existed table with data in. * * @param AbstractTable $table * @param AbstractColumn $column * @return bool|float|int|mixed|string */ private function castDefault(AbstractTable $table, AbstractColumn $column) { if ($column->abstractType() == 'timestamp' || $column->abstractType() == 'datetime') { $driver = $table->driver(); return $driver::DEFAULT_DATETIME; } if ($column->abstractType() == 'enum') { //We can use first enum value as default return $column->getEnumValues()[0]; } switch ($column->phpType()) { case 'int': return 0; break; case 'float': return 0.0; break; case 'bool': return false; break; } return ''; }
/** * {@inheritdoc} */ protected function doColumnChange(AbstractColumn $column, AbstractColumn $dbColumn) { /** * @var ColumnSchema $column * @var ColumnSchema $dbColumn */ //Renaming is separate operation if ($column->getName() != $dbColumn->getName()) { $this->driver->statement("sp_rename ?, ?, 'COLUMN'", [$this->getName() . '.' . $dbColumn->getName(), $column->getName()]); $column->setName($dbColumn->getName()); } //In SQLServer we have to drop ALL related indexes and foreign keys while //applying type change... yeah... $indexesBackup = []; $foreignBackup = []; foreach ($this->indexes as $index) { if (in_array($column->getName(), $index->getColumns())) { $indexesBackup[] = $index; $this->doIndexDrop($index); } } foreach ($this->references as $foreign) { if ($foreign->getColumn() == $column->getName()) { $foreignBackup[] = $foreign; $this->doForeignDrop($foreign); } } //Column will recreate needed constraints foreach ($column->getConstraints() as $constraint) { $this->doConstraintDrop($constraint); } foreach ($column->alterOperations($dbColumn) as $operation) { $query = \Spiral\interpolate('ALTER TABLE {table} {operation}', ['table' => $this->getName(true), 'operation' => $operation]); $this->driver->statement($query); } //Recreating indexes foreach ($indexesBackup as $index) { $this->doIndexAdd($index); } foreach ($foreignBackup as $foreign) { $this->doForeignAdd($foreign); } }
/** * {@inheritdoc} */ protected function doColumnChange(AbstractColumn $column, AbstractColumn $dbColumn) { $query = \Spiral\interpolate("ALTER TABLE {table} CHANGE {column} {statement}", ['table' => $this->getName(true), 'column' => $dbColumn->getName(true), 'statement' => $column->sqlStatement()]); $this->driver->statement($query); }
/** * {@inheritdoc} */ protected function doColumnChange(AbstractColumn $column, AbstractColumn $dbColumn) { /** * @var ColumnSchema $column */ //Rename is separate operation if ($column->getName() != $dbColumn->getName()) { $this->driver->statement(\Spiral\interpolate('ALTER TABLE {table} RENAME COLUMN {original} TO {column}', ['table' => $this->getName(true), 'column' => $column->getName(true), 'original' => $dbColumn->getName(true)])); $column->setName($dbColumn->getName()); } //Postgres columns should be altered using set of operations if (!($operations = $column->alterOperations($dbColumn))) { return; } //Postgres columns should be altered using set of operations $query = \Spiral\interpolate('ALTER TABLE {table} {operations}', ['table' => $this->getName(true), 'operations' => trim(join(', ', $operations), ', ')]); $this->driver->statement($query); }
/** * {@inheritdoc} */ protected function prepareDefault() { $defaultValue = parent::prepareDefault(); if ($this->abstractType() == 'boolean') { $defaultValue = (int) $this->defaultValue; } return $defaultValue; }
/** * Must compare two instances of AbstractColumn. * * @param self $initial * @return bool */ public function compare(self $initial) { $normalized = clone $initial; $normalized->declared = $this->declared; if ($this == $normalized) { return true; } $columnVars = get_object_vars($this); $dbColumnVars = get_object_vars($normalized); $difference = []; foreach ($columnVars as $name => $value) { if ($name == 'defaultValue') { //Default values has to compared using type-casted value if ($this->getDefaultValue() != $initial->getDefaultValue()) { $difference[] = $name; } continue; } if ($value != $dbColumnVars[$name]) { $difference[] = $name; } } return empty($difference); }
/** * Generate set of altering operations should be applied to column to change it's type, size, * default value or null flag. * * @param AbstractColumn $original * @return array */ public function alteringOperations(AbstractColumn $original) { $operations = []; $typeDefinition = [$this->type, $this->size, $this->precision, $this->scale]; $originalType = [$original->type, $original->size, $original->precision, $original->scale]; if ($typeDefinition != $originalType) { if ($this->abstractType() == 'enum') { //Getting longest value $enumSize = $this->size; foreach ($this->enumValues as $value) { $enumSize = max($enumSize, strlen($value)); } $type = "ALTER COLUMN {$this->getName(true)} TYPE character({$enumSize})"; $operations[] = $type; } else { $type = "ALTER COLUMN {$this->getName(true)} TYPE {$this->type}"; if (!empty($this->size)) { $type .= "({$this->size})"; } elseif (!empty($this->precision)) { $type .= "({$this->precision}, {$this->scale})"; } //Required to perform cross conversion $operations[] = "{$type} USING {$this->getName(true)}::{$this->type}"; } } if ($original->abstractType() == 'enum' && !empty($this->enumConstraint)) { $operations[] = 'DROP CONSTRAINT ' . $this->enumConstraint(true); } if ($original->defaultValue != $this->defaultValue) { if (is_null($this->defaultValue)) { $operations[] = "ALTER COLUMN {$this->getName(true)} DROP DEFAULT"; } else { $operations[] = "ALTER COLUMN {$this->getName(true)} SET DEFAULT {$this->prepareDefault()}"; } } if ($original->nullable != $this->nullable) { $operations[] = "ALTER COLUMN {$this->getName(true)} " . (!$this->nullable ? 'SET' : 'DROP') . " NOT NULL"; } if ($this->abstractType() == 'enum') { $enumValues = []; foreach ($this->enumValues as $value) { $enumValues[] = $this->table->driver()->getPDO()->quote($value); } $operations[] = "ADD CONSTRAINT {$this->enumConstraint(true)} " . "CHECK ({$this->getName(true)} IN (" . join(', ', $enumValues) . "))"; } return $operations; }
/** * Driver specific column remove (drop) command. * * @param AbstractColumn $column */ protected function doColumnDrop(AbstractColumn $column) { //We have to erase all associated constraints foreach ($column->getConstraints() as $constraint) { $this->doConstraintDrop($constraint); } if ($this->hasForeign($column->getName())) { $this->doForeignDrop($this->foreign($column->getName())); } $this->driver->statement("ALTER TABLE {$this->getName(true)} DROP COLUMN {$column->getName(true)}"); }
/** * Driver specific column remove (drop) command. * * @param AbstractTable $table * @param AbstractColumn $column * @return self */ public function dropColumn(AbstractTable $table, AbstractColumn $column) { foreach ($column->getConstraints() as $constraint) { //We have to erase all associated constraints $this->dropConstrain($table, $constraint); } $this->run("ALTER TABLE {$table->getName(true)} DROP COLUMN {$column->getName(true)}"); return $this; }
/** * Resolve correct abstract type to represent inner or outer key. Primary types will be * converted to appropriate sized integers. * * @param AbstractColumn $column * @return string */ protected function resolveAbstract(AbstractColumn $column) { switch ($column->abstractType()) { case 'bigPrimary': return 'bigInteger'; case 'primary': return 'integer'; default: //Not primary key return $column->abstractType(); } }
/** * {@inheritdoc} */ protected function prepareDefault() { if ($this->abstractType() == 'timestamp' && is_scalar($this->defaultValue)) { if (is_numeric($this->defaultValue)) { //Nothing to do return (int) $this->defaultValue; } $datetime = new \DateTime($this->defaultValue, new \DateTimeZone(DatabaseManager::DEFAULT_TIMEZONE)); return $datetime->getTimestamp(); } return parent::prepareDefault(); }
/** * Register new column element. * * @param AbstractColumn $column * @return AbstractColumn */ protected function registerColumn(AbstractColumn $column) { $this->columns[$column->getName()] = $column; return $column; }