From f6e43f2fe68e9456fec3932cba233f3bc3eff7c5 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 1 Sep 2019 18:26:20 +0200 Subject: [PATCH] Add PHP API to update database tables See #2847 --- .../files/acp/update-com.woltlab.wcf_5.2.php | 203 ++++++ .../database/editor/DatabaseEditor.class.php | 10 + .../editor/MySQLDatabaseEditor.class.php | 39 +- .../database/table/DatabaseTable.class.php | 222 ++++++ .../DatabaseTableChangeProcessor.class.php | 681 ++++++++++++++++++ .../TDroppableDatabaseComponent.class.php | 39 + .../AbstractDatabaseTableColumn.class.php | 203 ++++++ ...stractDecimalDatabaseTableColumn.class.php | 16 + .../AbstractIntDatabaseTableColumn.class.php | 24 + .../BigintDatabaseTableColumn.class.php | 32 + .../BinaryDatabaseTableColumn.class.php | 34 + .../column/BlobDatabaseTableColumn.class.php | 18 + .../column/CharDatabaseTableColumn.class.php | 34 + .../column/DateDatabaseTableColumn.class.php | 18 + .../DatetimeDatabaseTableColumn.class.php | 18 + .../DecimalDatabaseTableColumn.class.php | 39 + ...tFalseBooleanDatabaseTableColumn.class.php | 27 + ...ltTrueBooleanDatabaseTableColumn.class.php | 27 + .../DoubleDatabaseTableColumn.class.php | 32 + .../column/EnumDatabaseTableColumn.class.php | 20 + .../column/FloatDatabaseTableColumn.class.php | 32 + ...AutoIncrementDatabaseTableColumn.class.php | 28 + .../column/IDatabaseTableColumn.class.php | 103 +++ .../IDecimalsDatabaseTableColumn.class.php | 30 + .../column/IEnumDatabaseTableColumn.class.php | 30 + .../ILengthDatabaseTableColumn.class.php | 30 + .../IUnsignedDatabaseTableColumn.class.php | 28 + .../column/IntDatabaseTableColumn.class.php | 25 + .../LongblobDatabaseTableColumn.class.php | 18 + .../LongtextDatabaseTableColumn.class.php | 18 + .../MediumblobDatabaseTableColumn.class.php | 18 + .../MediumintDatabaseTableColumn.class.php | 32 + .../MediumtextDatabaseTableColumn.class.php | 18 + .../NotNullInt10DatabaseTableColumn.class.php | 22 + ...ullVarchar191DatabaseTableColumn.class.php | 22 + ...ullVarchar255DatabaseTableColumn.class.php | 22 + .../ObjectIdDatabaseTableColumn.class.php | 24 + .../column/SetDatabaseTableColumn.class.php | 20 + .../SmallintDatabaseTableColumn.class.php | 25 + ...AutoIncrementDatabaseTableColumn.class.php | 40 + .../TDecimalsDatabaseTableColumn.class.php | 58 ++ .../column/TEnumDatabaseTableColumn.class.php | 41 ++ .../TLengthDatabaseTableColumn.class.php | 94 +++ .../TUnsignedDatabaseTableColumn.class.php | 40 + .../column/TextDatabaseTableColumn.class.php | 18 + .../column/TimeDatabaseTableColumn.class.php | 18 + .../TinyblobDatabaseTableColumn.class.php | 18 + .../TinyintDatabaseTableColumn.class.php | 25 + .../TinytextDatabaseTableColumn.class.php | 18 + .../VarbinaryDatabaseTableColumn.class.php | 27 + .../VarcharDatabaseTableColumn.class.php | 34 + .../column/YearDatabaseTableColumn.class.php | 29 + .../index/DatabaseTableForeignKey.class.php | 271 +++++++ .../table/index/DatabaseTableIndex.class.php | 151 ++++ .../index/DatabaseTablePrimaryIndex.class.php | 26 + wcfsetup/install/lang/de.xml | 20 + wcfsetup/install/lang/en.xml | 20 + 57 files changed, 3227 insertions(+), 2 deletions(-) create mode 100644 wcfsetup/install/files/acp/update-com.woltlab.wcf_5.2.php create mode 100644 wcfsetup/install/files/lib/system/database/table/DatabaseTable.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/DatabaseTableChangeProcessor.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/TDroppableDatabaseComponent.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/AbstractDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/AbstractDecimalDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/AbstractIntDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/BigintDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/BinaryDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/BlobDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/CharDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DateDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DatetimeDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DecimalDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DefaultFalseBooleanDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DefaultTrueBooleanDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/DoubleDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/EnumDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/FloatDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IAutoIncrementDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IDecimalsDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IEnumDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/ILengthDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IUnsignedDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/IntDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/LongblobDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/LongtextDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/MediumblobDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/MediumintDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/MediumtextDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/NotNullInt10DatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar191DatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar255DatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/ObjectIdDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/SetDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/SmallintDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TAutoIncrementDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TDecimalsDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TEnumDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TLengthDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TUnsignedDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TextDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TimeDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TinyblobDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TinyintDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/TinytextDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/VarbinaryDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/VarcharDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/column/YearDatabaseTableColumn.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/index/DatabaseTableForeignKey.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/index/DatabaseTableIndex.class.php create mode 100644 wcfsetup/install/files/lib/system/database/table/index/DatabaseTablePrimaryIndex.class.php diff --git a/wcfsetup/install/files/acp/update-com.woltlab.wcf_5.2.php b/wcfsetup/install/files/acp/update-com.woltlab.wcf_5.2.php new file mode 100644 index 0000000000..6884f7afb9 --- /dev/null +++ b/wcfsetup/install/files/acp/update-com.woltlab.wcf_5.2.php @@ -0,0 +1,203 @@ + + */ + +$tables = [ + DatabaseTable::create('wcf1_bbcode_media_provider') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('isDisabled') + ]), + + DatabaseTable::create('wcf1_blacklist_status') + ->columns([ + DateDatabaseTableColumn::create('date') + ->notNull(), + DefaultFalseBooleanDatabaseTableColumn::create('delta1'), + DefaultFalseBooleanDatabaseTableColumn::create('delta2'), + DefaultFalseBooleanDatabaseTableColumn::create('delta3'), + DefaultFalseBooleanDatabaseTableColumn::create('delta4') + ]), + + DatabaseTable::create('wcf1_blacklist_entry') + ->columns([ + EnumDatabaseTableColumn::create('type') + ->enumValues(['email', 'ipv4', 'ipv6','username']), + BinaryDatabaseTableColumn::create('hash') + ->length(32), + DatetimeDatabaseTableColumn::create('lastSeen') + ->notNull(), + SmallintDatabaseTableColumn::create('occurrences') + ->length(5) + ->notNull() + ]) + ->indices([ + DatabaseTableIndex::create('entry') + ->type(DatabaseTableIndex::UNIQUE_TYPE) + ->columns(['type', 'hash']), + DatabaseTableIndex::create('numberOfReports') + ->columns(['type', 'occurrences']) + ]), + + DatabaseTable::create('wcf1_box') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('isDisabled') + ]), + + DatabaseTable::create('wcf1_category') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('descriptionUseHtml') + ]), + + DatabaseTable::create('wcf1_comment') + ->columns([ + MediumtextDatabaseTableColumn::create('message') + ]), + + DatabaseTable::create('wcf1_comment_response') + ->columns([ + MediumtextDatabaseTableColumn::create('message') + ]), + + DatabaseTable::create('wcf1_contact_attachment') + ->columns([ + NotNullInt10DatabaseTableColumn::create('attachmentID'), + CharDatabaseTableColumn::create('accessKey') + ->length(40) + ->notNull() + ]) + ->foreignKeys([ + DatabaseTableForeignKey::create() + ->columns(['attachmentID']) + ->referencedTable('wcf1_attachment') + ->referencedColumns(['attachmentID']) + ->onDelete('CASCADE') + ]), + + DatabaseTable::create('wcf1_language_item') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('isCustomLanguageItem') + ]), + + DatabaseTable::create('wcf1_like') + ->columns([ + NotNullInt10DatabaseTableColumn::create('reactionTypeID') + ]) + ->foreignKeys([ + DatabaseTableForeignKey::create() + ->columns(['reactionTypeID']) + ->referencedTable('wcf1_reaction_type') + ->referencedColumns(['reactionTypeID']) + ->onDelete('CASCADE') + ]), + + DatabaseTable::create('wcf1_like_object') + ->columns([ + TextDatabaseTableColumn::create('cachedReactions') + ]), + + DatabaseTable::create('wcf1_media') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('captionEnableHtml'), + NotNullInt10DatabaseTableColumn::create('downloads') + ->defaultValue(0), + NotNullInt10DatabaseTableColumn::create('lastDownload') + ->defaultValue(0) + ]), + + DatabaseTable::create('wcf1_page') + ->columns([ + IntDatabaseTableColumn::create('overrideApplicationPackageID') + ->length(10), + DefaultFalseBooleanDatabaseTableColumn::create('enableShareButtons') + ]) + ->foreignKeys([ + DatabaseTableForeignKey::create() + ->columns(['overrideApplicationPackageID']) + ->referencedTable('wcf1_package') + ->referencedColumns(['packageID']) + ->onDelete('SET NULL') + ]), + + DatabaseTable::create('wcf1_reaction_type') + ->columns([ + ObjectIdDatabaseTableColumn::create('reactionTypeID'), + // TODO: is currently not `not null` + NotNullVarchar255DatabaseTableColumn::create('title'), + NotNullInt10DatabaseTableColumn::create('showOrder') + ->defaultValue(0), + // TODO: should be varchar + MediumtextDatabaseTableColumn::create('iconFile'), + DefaultFalseBooleanDatabaseTableColumn::create('isDisabled') + ]) + ->indices([ + DatabaseTablePrimaryIndex::create() + ->columns(['reactionTypeID']) + ]), + + DatabaseTable::create('wcf1_style') + ->columns([ + EnumDatabaseTableColumn::create('apiVersion') + ->enumValues(['3.0', '3.1', '5.2']) + ]), + + DatabaseTable::create('wcf1_trophy') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('revokeAutomatically'), + DefaultFalseBooleanDatabaseTableColumn::create('trophyUseHtml'), + NotNullInt10DatabaseTableColumn::create('showOrder') + ->defaultValue(0) + ]), + + DatabaseTable::create('wcf1_user') + ->columns([ + NotNullInt10DatabaseTableColumn::create('articles') + ->defaultValue(0), + NotNullVarchar255DatabaseTableColumn::create('blacklistMatches') + ->defaultValue('') + ]), + + DatabaseTable::create('wcf1_user_group') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('allowMention') + ]), + + DatabaseTable::create('wcf1_user_trophy') + ->columns([ + DefaultFalseBooleanDatabaseTableColumn::create('trophyUseHtml') + ]), +]; + +(new DatabaseTableChangeProcessor( + /** @var ScriptPackageInstallationPlugin $this */ + $this->installation->getPackage(), + $tables, + WCF::getDB()->getEditor()) +)->process(); diff --git a/wcfsetup/install/files/lib/system/database/editor/DatabaseEditor.class.php b/wcfsetup/install/files/lib/system/database/editor/DatabaseEditor.class.php index 15eaf875c7..dad7e2b17b 100644 --- a/wcfsetup/install/files/lib/system/database/editor/DatabaseEditor.class.php +++ b/wcfsetup/install/files/lib/system/database/editor/DatabaseEditor.class.php @@ -104,6 +104,16 @@ abstract class DatabaseEditor { */ abstract public function alterColumn($tableName, $oldColumnName, $newColumnName, $newColumnData); + /** + * Adds, alters and drops multiple columns at once. + * + * @param string $tableName + * @param array $alterData + */ + public function alterColumns($tableName, $alterData) { + throw new NotImplementedException(); + } + /** * Drops an existing column. * diff --git a/wcfsetup/install/files/lib/system/database/editor/MySQLDatabaseEditor.class.php b/wcfsetup/install/files/lib/system/database/editor/MySQLDatabaseEditor.class.php index 3352ccceb7..406bbbeedb 100644 --- a/wcfsetup/install/files/lib/system/database/editor/MySQLDatabaseEditor.class.php +++ b/wcfsetup/install/files/lib/system/database/editor/MySQLDatabaseEditor.class.php @@ -31,7 +31,7 @@ class MySQLDatabaseEditor extends DatabaseEditor { */ public function getColumns($tableName) { $columns = []; - $regex = new Regex('([a-z]+)\((.+)\)', Regex::CASE_INSENSITIVE); + $regex = new Regex('([a-z]+)\((.+)\)( unsigned)?', Regex::CASE_INSENSITIVE); $sql = "SHOW COLUMNS FROM `".$tableName."`"; $statement = $this->dbObj->prepareStatement($sql); @@ -44,6 +44,7 @@ class MySQLDatabaseEditor extends DatabaseEditor { $length = ''; $decimals = ''; $enumValues = ''; + $unsigned = false; if (!empty($typeMatches)) { $type = $typeMatches[1]; @@ -75,6 +76,10 @@ class MySQLDatabaseEditor extends DatabaseEditor { } break; } + + if (isset($typeMatches[3])) { + $unsigned = true; + } } $columns[] = ['name' => $row['Field'], 'data' => [ @@ -85,7 +90,8 @@ class MySQLDatabaseEditor extends DatabaseEditor { 'default' => $row['Default'], 'autoIncrement' => $row['Extra'] == 'auto_increment' ? true : false, 'enumValues' => $enumValues, - 'decimals' => $decimals + 'decimals' => $decimals, + 'unsigned' => $unsigned ]]; } @@ -258,6 +264,30 @@ class MySQLDatabaseEditor extends DatabaseEditor { $statement->execute(); } + /** + * @inheritDoc + */ + public function alterColumns($tableName, $alterData) { + $queries = ""; + foreach ($alterData as $columnName => $data) { + switch ($data['action']) { + case 'add': + $queries .= "ADD COLUMN {$this->buildColumnDefinition($columnName, $data['data'])},"; + break; + + case 'alter': + $queries .= "CHANGE COLUMN `{$columnName}` {$this->buildColumnDefinition($data['oldColumnName'], $data['data'])},"; + break; + + case 'drop': + $queries .= "DROP COLUMN `{$columnName}`,"; + break; + } + } + + $this->dbObj->prepareStatement("ALTER TABLE `{$tableName}` " . rtrim($queries, ','))->execute(); + } + /** * @inheritDoc */ @@ -342,6 +372,11 @@ class MySQLDatabaseEditor extends DatabaseEditor { $definition = "`".$columnName."`"; // column type $definition .= " ".$columnData['type']; + + if (!empty($columnData['unsigned'])) { + $definition .= ' UNSIGNED'; + } + // column length and decimals if (!empty($columnData['length'])) { $definition .= "(".$columnData['length'].(!empty($columnData['decimals']) ? ",".$columnData['decimals'] : "").")"; diff --git a/wcfsetup/install/files/lib/system/database/table/DatabaseTable.class.php b/wcfsetup/install/files/lib/system/database/table/DatabaseTable.class.php new file mode 100644 index 0000000000..bc6c9c3f17 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/DatabaseTable.class.php @@ -0,0 +1,222 @@ + + * @package WoltLabSuite\Core\System\Database\Table + * @since 5.2 + */ +class DatabaseTable { + use TDroppableDatabaseComponent; + + /** + * intended database table's columns + * @var IDatabaseTableColumn[] + */ + protected $columns = []; + + /** + * intended database table's foreign keys + * @var DatabaseTableForeignKey[] + */ + protected $foreignKeys = []; + + /** + * intended database table's indices + * @var DatabaseTableIndex[] + */ + protected $indices = []; + + /** + * name of the database table + * @var string + */ + protected $name; + + /** + * Creates a new instance of `DatabaseTable`. + * + * @param string $name name of the database table + */ + protected function __construct($name) { + $this->name = ApplicationHandler::insertRealDatabaseTableNames($name); + } + + /** + * Sets the columns of the database table. + * + * @param IDatabaseTableColumn[] $columns added/dropped columns + * @return $this this database table + * @throws \InvalidArgumentException if any column is invalid or duplicate column names exist + */ + public function columns(array $columns) { + $this->columns = []; + foreach ($columns as $column) { + if (!($column instanceof IDatabaseTableColumn)) { + throw new \InvalidArgumentException("Added columns have to be instances of '" . IDatabaseTableColumn::class . "'."); + } + + if (isset($this->columns[$column->getName()])) { + throw new \InvalidArgumentException("Duplicate column with name '{$column->getName()}'."); + } + + $this->columns[$column->getName()] = $column; + } + + return $this; + } + + /** + * Sets the foreign keys of the database table. + * + * @param DatabaseTableForeignKey[] $foreignKeys added/dropped foreign keys + * @return $this this database table + * @throws \InvalidArgumentException if any foreign key is invalid or duplicate foreign key names exist + */ + public function foreignKeys(array $foreignKeys) { + $this->foreignKeys = []; + foreach ($foreignKeys as $foreignKey) { + if (!($foreignKey instanceof DatabaseTableForeignKey)) { + throw new \InvalidArgumentException("Added foreign keys have to be instances of '" . DatabaseTableForeignKey::class . "'."); + } + + if (empty($foreignKey->getColumns())) { + throw new \InvalidArgumentException("Missing columns for foreign key."); + } + + if ($foreignKey->getName() === '') { + $foreignKey->name(md5($this->getName() . '_' . $foreignKey->getColumns()[0]) . '_fk'); + } + + if (isset($this->foreignKeys[$foreignKey->getName()])) { + throw new \InvalidArgumentException("Duplicate foreign key with name '{$foreignKey->getName()}'."); + } + + $this->foreignKeys[$foreignKey->getName()] = $foreignKey; + } + + return $this; + } + + /** + * Returns the columns of the table. + * + * @return IDatabaseTableColumn[] + */ + public function getColumns() { + return $this->columns; + } + + /** + * Returns the foreign keys of the table. + * + * @return DatabaseTableForeignKey[] + */ + public function getForeignKeys() { + return $this->foreignKeys; + } + + /** + * Returns the indices of the table. + * + * @return DatabaseTableIndex[] + */ + public function getIndices() { + return $this->indices; + } + + /** + * Returns the name of the database table. + * + * @return string database table name + */ + public function getName() { + return $this->name; + } + + /** + * Returns a `DatabaseTable` object with the given name. + * + * @param string $tableName + * @return static + */ + public static function create($tableName) { + return new static($tableName); + } + + /** + * Returns a `DatabaseTable` object for an existing database table with the given name. + * + * @param DatabaseEditor $dbEditor + * @param string $tableName + * @return DatabaseTable + */ + public static function createFromExistingTable(DatabaseEditor $dbEditor, $tableName) { + $table = new static($tableName); + + $columns = []; + foreach ($dbEditor->getColumns($tableName) as $columnData) { + $className = 'wcf\system\database\table\column\\' . ucfirst(strtolower($columnData['data']['type'])) . 'DatabaseTableColumn'; + if (!class_exists($className)) { + throw new \InvalidArgumentException("Unknown database table column type '{$columnData['data']['type']}'."); + } + + $columns[$columnData['name']] = $className::createFromData($columnData['name'], $columnData['data']); + } + $table->columns($columns); + + $foreignKeys = []; + foreach ($dbEditor->getForeignKeys($tableName) as $foreignKeysName => $foreignKeyData) { + $foreignKeys[$foreignKeysName] = DatabaseTableForeignKey::createFromData($foreignKeysName, $foreignKeyData); + } + $table->foreignKeys($foreignKeys); + + $indices = []; + foreach ($dbEditor->getIndexInformation($tableName) as $indexName => $indexData) { + if (!isset($foreignKeys[$indexName])) { + $indices[$indexName] = DatabaseTableIndex::createFromData($indexName, $indexData); + } + } + $table->indices($indices); + + return $table; + } + + /** + * Sets the indices of the database table. + * + * @param DatabaseTableIndex[] $indices added/dropped indices + * @return $this this database table + * @throws \InvalidArgumentException if any index is invalid or duplicate index key names exist + */ + public function indices(array $indices) { + $this->indices = []; + foreach ($indices as $index) { + if (!($index instanceof DatabaseTableIndex)) { + throw new \InvalidArgumentException("Added indices have to be instances of '" . DatabaseTableIndex::class . "'."); + } + + if ($index->getName() === '') { + $index->name(md5($this->getName() . '_' . $index->getColumns()[0])); + } + + if (isset($this->foreignKeys[$index->getName()])) { + throw new \InvalidArgumentException("Duplicate index with name '{$index->getName()}'."); + } + + $this->indices[$index->getName()] = $index; + } + + return $this; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/DatabaseTableChangeProcessor.class.php b/wcfsetup/install/files/lib/system/database/table/DatabaseTableChangeProcessor.class.php new file mode 100644 index 0000000000..7fe862cb16 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/DatabaseTableChangeProcessor.class.php @@ -0,0 +1,681 @@ + + * @package WoltLabSuite\Core\System\Database\Table + * @since 5.2 + */ +class DatabaseTableChangeProcessor { + /** + * added columns grouped by the table they belong to + * @var IDatabaseTableColumn[][] + */ + protected $addedColumns = []; + + /** + * added indices grouped by the table they belong to + * @var DatabaseTableIndex[][] + */ + protected $addedIndices = []; + + /** + * added tables + * @var DatabaseTable[] + */ + protected $addedTables = []; + + /** + * maps the registered database table column names to the ids of the packages they belong to + * @var int[][] + */ + protected $columnPackageIDs = []; + + /** + * database editor to apply the relevant changes to the table layouts + * @var DatabaseEditor + */ + protected $dbEditor; + + /** + * dropped columns grouped by the table they belong to + * @var IDatabaseTableColumn[][] + */ + protected $droppedColumns = []; + + /** + * dropped indices grouped by the table they belong to + * @var DatabaseTableIndex[][]|DatabaseTableForeignKey[][] + */ + protected $droppedIndices = []; + + /** + * dropped tables + * @var DatabaseTable[] + */ + protected $droppedTables = []; + + /** + * list of all existing tables in the used database + * @var string[] + */ + protected $existingTableNames = []; + + /** + * maps the registered database table index names to the ids of the packages they belong to + * @var int[][] + */ + protected $indexPackageIDs = []; + + /** + * maps the registered database table foreign key names to the ids of the packages they belong to + * @var int[][] + */ + protected $foreignKeyPackageIDs = []; + + /** + * is `true` if only one change will be handled per request + * @var bool + */ + protected $oneChangePerRequest = true; + + /** + * package that wants to apply the changes + * @var Package + */ + protected $package; + + /** + * layouts/layout changes of the relevant database table + * @var DatabaseTable[] + */ + protected $tables; + + /** + * maps the registered database table names to the ids of the packages they belong to + * @var int[] + */ + protected $tablePackageIDs = []; + + /** + * Creates a new instance of `DatabaseTableChangeProcessor`. + * + * @param Package $package + * @param DatabaseTable[] $tables + * @param DatabaseEditor $dbEditor + * @param bool $oneChangePerRequest + */ + public function __construct(Package $package, array $tables, DatabaseEditor $dbEditor, $oneChangePerRequest = true) { + $this->package = $package; + + $tableNames = []; + foreach ($tables as $table) { + if (!($table instanceof DatabaseTable)) { + throw new \InvalidArgumentException("Tables must be instance of '" . DatabaseTable::class . "'"); + } + + $tableNames[] = $table->getName(); + } + + $this->tables = $tables; + $this->dbEditor = $dbEditor; + $this->oneChangePerRequest = $oneChangePerRequest; + + $this->existingTableNames = $dbEditor->getTableNames(); + + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('sqlTable IN (?)', [$tableNames]); + + $sql = "SELECT * + FROM wcf".WCF_N."_package_installation_sql_log + " . $conditionBuilder; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditionBuilder->getParameters()); + + while ($row = $statement->fetchArray()) { + if ($row['sqlIndex'] === '' && $row['sqlColumn'] === '') { + $this->tablePackageIDs[$row['sqlTable']] = $row['packageID']; + } + else if ($row['sqlIndex'] === '') { + $this->columnPackageIDs[$row['sqlTable']][$row['sqlColumn']] = $row['packageID']; + } + else if (substr($row['sqlIndex'], -3) === '_fk') { + $this->foreignKeyPackageIDs[$row['sqlTable']][$row['sqlIndex']] = $row['packageID']; + } + else { + $this->indexPackageIDs[$row['sqlTable']][$row['sqlIndex']] = $row['packageID']; + } + } + } + + /** + * Creates the given table. + * + * @param DatabaseTable $table + * @throws SplitNodeException + */ + protected function createTable(DatabaseTable $table) { + $columnData = array_map(function(IDatabaseTableColumn $column) { + return [ + 'data' => $column->getData(), + 'name' => $column->getName() + ]; + }, $table->getColumns()); + $indexData = array_map(function(DatabaseTableIndex $index) { + return [ + 'data' => $index->getData(), + 'name' => $index->getName() + ]; + }, $table->getIndices()); + + $this->dbEditor->createTable($table->getName(), $columnData, $indexData); + + foreach ($table->getForeignKeys() as $foreignKey) { + $this->dbEditor->addForeignKey($table->getName(), $foreignKey->getName(), $foreignKey->getData()); + } + + $this->addedTables[] = $table; + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Created table '{$table->getName()}'."); + } + } + + /** + * Drops the given table. + * + * @param DatabaseTable $table + * @throws SplitNodeException + */ + protected function dropTable(DatabaseTable $table) { + $this->dbEditor->dropTable($table->getName()); + + $this->droppedTables[] = $table; + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Dropped table '{$table->getName()}'."); + } + } + + /** + * Returns the id of the package to with the given column belongs to. If there is no specific + * log entry for the given column, the table log is checked and the relevant package id of + * the whole table is returned. If the package of the table is also unknown, `null` is returned. + * + * @param DatabaseTable $table + * @param IDatabaseTableColumn $column + * @return null|int + */ + protected function getColumnPackageID(DatabaseTable $table, IDatabaseTableColumn $column) { + if (isset($this->columnPackageIDs[$table->getName()][$column->getName()])) { + return $this->columnPackageIDs[$table->getName()][$column->getName()]; + } + else if (isset($this->tablePackageIDs[$table->getName()])) { + return $this->tablePackageIDs[$table->getName()]; + } + + return null; + } + + /** + * Returns the id of the package to with the given foreign key belongs to. If there is no specific + * log entry for the given foreign key, the table log is checked and the relevant package id of + * the whole table is returned. If the package of the table is also unknown, `null` is returned. + * + * @param DatabaseTable $table + * @param DatabaseTableForeignKey $foreignKey + * @return null|int + */ + protected function getForeignKeyPackageID(DatabaseTable $table, DatabaseTableForeignKey $foreignKey) { + if (isset($this->foreignKeyPackageIDs[$table->getName()][$foreignKey->getName()])) { + return $this->foreignKeyPackageIDs[$table->getName()][$foreignKey->getName()]; + } + else if (isset($this->tablePackageIDs[$table->getName()])) { + return $this->tablePackageIDs[$table->getName()]; + } + + return null; + } + + /** + * Returns the id of the package to with the given index belongs to. If there is no specific + * log entry for the given index, the table log is checked and the relevant package id of + * the whole table is returned. If the package of the table is also unknown, `null` is returned. + * + * @param DatabaseTable $table + * @param DatabaseTableIndex $index + * @return null|int + */ + protected function getIndexPackageID(DatabaseTable $table, DatabaseTableIndex $index) { + if (isset($this->indexPackageIDs[$table->getName()][$index->getName()])) { + return $this->indexPackageIDs[$table->getName()][$index->getName()]; + } + else if (isset($this->tablePackageIDs[$table->getName()])) { + return $this->tablePackageIDs[$table->getName()]; + } + + return null; + } + + /** + * Logs all of the executed changes. + */ + protected function logChanges() { + if (!empty($this->droppedTables)) { + $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log + WHERE sqlTable = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($this->droppedTables as $table) { + $statement->execute([$table->getName()]); + } + WCF::getDB()->commitTransaction(); + } + + if (!empty($this->droppedColumns)) { + $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log + WHERE sqlTable = ? + AND sqlColumn = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($this->droppedColumns as $tableName => $columns) { + foreach ($columns as $column) { + $statement->execute([$tableName, $column->getName()]); + } + } + WCF::getDB()->commitTransaction(); + } + + if (!empty($this->droppedIndices)) { + $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log + WHERE sqlTable = ? + AND sqlIndex = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($this->droppedIndices as $tableName => $indices) { + foreach ($indices as $index) { + $statement->execute([$tableName, $index->getName()]); + } + } + WCF::getDB()->commitTransaction(); + } + + $insertionData = []; + foreach ($this->addedTables as $table) { + $insertionData[] = ['sqlTable' => $table->getName()]; + } + + foreach ($this->addedColumns as $tableName => $columns) { + foreach ($columns as $column) { + $insertionData[] = ['sqlTable' => $tableName, 'sqlColumn' => $column->getName()]; + } + } + + foreach ($this->addedIndices as $tableName => $indices) { + foreach ($indices as $index) { + $insertionData[] = ['sqlTable' => $tableName, 'sqlIndex' => $index->getName()]; + } + } + + if (!empty($insertionData)) { + $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log + (packageID, sqlTable, sqlColumn, sqlIndex) + VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($insertionData as $data) { + $statement->execute([ + $this->package->packageID, + $data['sqlTable'], + $data['sqlColumn'] ?? '', + $data['sqlIndex'] ?? '' + ]); + } + WCF::getDB()->commitTransaction(); + } + } + + /** + * Processes all tables and updates the current table layouts to match the specified layouts. + * + * @throws \RuntimeException if validation of the required layout changes fails + */ + public function process() { + $errors = $this->validate(); + if (!empty($errors)) { + throw new \RuntimeException(WCF::getLanguage()->getDynamicVariable('wcf.acp.package.error.databaseChange', [ + 'errors' => $errors + ])); + } + + foreach ($this->tables as $table) { + if ($table->willBeDropped()) { + if (in_array($table->getName(), $this->existingTableNames)) { + $this->dropTable($table); + } + } + else if (!in_array($table->getName(), $this->existingTableNames)) { + $this->createTable($table); + } + else { + // calculate difference between tables + $existingTable = DatabaseTable::createFromExistingTable($this->dbEditor, $table->getName()); + $existingColumns = $existingTable->getColumns(); + $existingForeignKeys = $existingTable->getForeignKeys(); + $existingIndices = $existingTable->getIndices(); + + $addedColumns = $alteredColumns = $droppedColumns = []; + foreach ($table->getColumns() as $column) { + if (!isset($existingColumns[$column->getName()]) && !$column->willBeDropped()) { + $addedColumns[$column->getName()] = $column; + } + else if (isset($existingColumns[$column->getName()])) { + if ($column->willBeDropped()) { + $droppedColumns[$column->getName()] = $column; + } + else if (!empty(array_diff($column->getData(), $existingColumns[$column->getName()]->getData()))) { + $alteredColumns[$column->getName()] = $column; + } + } + } + + $this->processColumns($table, $addedColumns, $alteredColumns, $droppedColumns); + + $addedForeignKeys = $droppedForeignKeys = []; + foreach ($table->getForeignKeys() as $foreignKey) { + $matchingExistingForeignKey = null; + foreach ($existingForeignKeys as $existingForeignKey) { + if (empty(array_diff($foreignKey->getData(), $existingForeignKey->getData()))) { + $matchingExistingForeignKey = $existingForeignKey; + break; + } + } + + if ($foreignKey->willBeDropped()) { + if ($matchingExistingForeignKey !== null) { + $droppedForeignKeys[$foreignKey->getName()] = $foreignKey; + } + } + else if ($matchingExistingForeignKey === null) { + $addedForeignKeys[$foreignKey->getName()] = $foreignKey; + } + } + + $this->processForeignKeys($table, $addedForeignKeys, $droppedForeignKeys); + + $addedIndices = $droppedIndices = []; + foreach ($table->getIndices() as $index) { + $matchingExistingIndex = null; + foreach ($existingIndices as $existingIndex) { + if (empty(array_diff($index->getData(), $existingIndex->getData()))) { + $matchingExistingIndex = $existingIndex; + break; + } + } + + if ($index->willBeDropped()) { + if ($matchingExistingIndex !== null) { + $droppedIndices[$index->getName()] = $index; + } + } + else if ($matchingExistingIndex === null) { + $addedIndices[$index->getName()] = $index; + } + } + + $this->processIndices($table, $addedIndices, $droppedIndices); + } + } + + $this->logChanges(); + } + + /** + * Adds, alters and drops the given columns. + * + * @param DatabaseTable $table + * @param IDatabaseTableColumn[] $addedColumns + * @param IDatabaseTableColumn[] $alteredColumns + * @param IDatabaseTableColumn[] $droppedColumns + * @throws SplitNodeException + */ + protected function processColumns(DatabaseTable $table, array $addedColumns, array $alteredColumns, array $droppedColumns) { + $columnData = []; + foreach ($droppedColumns as $droppedColumn) { + $columnData[$droppedColumn->getName()] = [ + 'action' => 'drop' + ]; + + $this->droppedColumns[$table->getName()][$droppedColumn->getName()] = $droppedColumn; + } + foreach ($addedColumns as $addedColumn) { + $columnData[$addedColumn->getName()] = [ + 'action' => 'add', + 'data' => $addedColumn->getData() + ]; + + if ($this->tablePackageIDs[$table->getName()] !== $this->package->packageID) { + $this->addedColumns[$table->getName()][$addedColumn->getName()] = $addedColumn; + } + } + foreach ($alteredColumns as $alteredColumn) { + $columnData[$alteredColumn->getName()] = [ + 'action' => 'alter', + 'data' => $alteredColumn->getData(), + 'oldColumnName' => $alteredColumn->getName() + ]; + } + + if (!empty($columnData)) { + $this->dbEditor->alterColumns($table->getName(), $columnData); + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Altered columns of table '{$table->getName()}'."); + } + } + } + + /** + * Adds and drops the given foreign keys. + * + * @param DatabaseTable $table + * @param DatabaseTableForeignKey[] $addedForeignKeys + * @param DatabaseTableForeignKey[] $droppedForeignKeys + * @throws SplitNodeException + */ + protected function processForeignKeys(DatabaseTable $table, array $addedForeignKeys, array $droppedForeignKeys) { + if (empty($addedForeignKeys) && empty($droppedForeignKeys)) { + return; + } + + foreach ($addedForeignKeys as $addedForeignKey) { + if ($this->tablePackageIDs[$table->getName()] !== $this->package->packageID) { + $this->addedIndices[$table->getName()][$addedForeignKey->getName()] = $addedForeignKey; + } + + $this->dbEditor->addForeignKey($table->getName(), $addedForeignKey->getName(), $addedForeignKey->getData()); + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Added foreign key '{$table->getName()}." . implode(',', $addedForeignKey->getColumns()) . "'"); + } + } + + foreach ($droppedForeignKeys as $droppedForeignKey) { + $this->droppedIndices[$table->getName()][$droppedForeignKey->getName()] = $droppedForeignKey; + + $this->dbEditor->dropForeignKey($table->getName(), $droppedForeignKey->getName()); + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Dropped foreign key '{$table->getName()}." . implode(',', $droppedForeignKey->getColumns()) . "' ({$droppedForeignKey->getName()})"); + } + } + } + + /** + * Adds and drops the given indices. + * + * @param DatabaseTable $table + * @param DatabaseTableIndex[] $addedIndices + * @param DatabaseTableIndex[] $droppedIndices + * @throws SplitNodeException + */ + protected function processIndices(DatabaseTable $table, array $addedIndices, array $droppedIndices) { + if (empty($addedIndices) && empty($droppedIndices)) { + return; + } + + foreach ($addedIndices as $addedIndex) { + if ($this->tablePackageIDs[$table->getName()] !== $this->package->packageID) { + $this->addedIndices[$table->getName()][$addedIndex->getName()] = $addedIndex; + } + + $this->dbEditor->addIndex($table->getName(), $addedIndex->getName(), $addedIndex->getData()); + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Added index '{$table->getName()}." . implode(',', $addedIndex->getColumns()) . "'"); + } + } + + foreach ($droppedIndices as $droppedIndex) { + $this->droppedIndices[$table->getName()][$droppedIndex->getName()] = $droppedIndex; + + $this->dbEditor->dropIndex($table->getName(), $droppedIndex->getName()); + + if ($this->oneChangePerRequest) { + $this->logChanges(); + + throw new SplitNodeException("Dropped index '{$table->getName()}." . implode(',', $droppedIndex->getColumns()) . "'"); + } + } + } + + /** + * Checks if the relevant table layout changes can be executed and returns an array with information + * on any validation error. + * + * @return array + */ + public function validate() { + $errors = []; + foreach ($this->tables as $table) { + if ($table->willBeDropped()) { + if (in_array($table->getName(), $this->existingTableNames)) { + if (!isset($this->tablePackageIDs[$table->getName()])) { + $errors[] = [ + 'tableName' => $table->getName(), + 'type' => 'unregisteredTableDrop' + ]; + } + else if ($this->tablePackageIDs[$table->getName()] !== $this->package->packageID) { + $errors[] = [ + 'tableName' => $table->getName(), + 'type' => 'foreignTableDrop' + ]; + } + } + } + else if (in_array($table->getName(), $this->existingTableNames)) { + if (!isset($this->tablePackageIDs[$table->getName()])) { + $errors[] = [ + 'tableName' => $table->getName(), + 'type' => 'unregisteredTableChange' + ]; + } + else { + $existingTable = DatabaseTable::createFromExistingTable($this->dbEditor, $table->getName()); + $existingColumns = $existingTable->getColumns(); + $existingIndices = $existingTable->getIndices(); + $existingForeignKeys = $existingTable->getForeignKeys(); + + foreach ($table->getColumns() as $column) { + if (isset($existingColumns[$column->getName()])) { + $columnPackageID = $this->getColumnPackageID($table, $column); + if ($column->willBeDropped()) { + if ($columnPackageID !== $this->package->packageID) { + $errors[] = [ + 'columnName' => $column->getName(), + 'tableName' => $table->getName(), + 'type' => 'foreignColumnDrop' + ]; + } + } + else if ($columnPackageID !== $this->package->packageID) { + $errors[] = [ + 'columnName' => $column->getName(), + 'tableName' => $table->getName(), + 'type' => 'foreignColumnChange' + ]; + } + } + } + + foreach ($table->getIndices() as $index) { + foreach ($existingIndices as $existingIndex) { + if (empty(array_diff($index->getData(), $existingIndex->getData()))) { + if ($index->willBeDropped()) { + if ($this->getIndexPackageID($table, $index) !== $this->package->packageID) { + $errors[] = [ + 'columnNames' => implode(',', $existingIndex->getColumns()), + 'tableName' => $table->getName(), + 'type' => 'foreignIndexDrop' + ]; + } + } + + continue 2; + } + } + } + + foreach ($table->getForeignKeys() as $foreignKey) { + foreach ($existingForeignKeys as $existingForeignKey) { + if (empty(array_diff($foreignKey->getData(), $existingForeignKey->getData()))) { + if ($foreignKey->willBeDropped()) { + if ($this->getForeignKeyPackageID($table, $foreignKey) !== $this->package->packageID) { + $errors[] = [ + 'columnNames' => implode(',', $existingForeignKey->getColumns()), + 'tableName' => $table->getName(), + 'type' => 'foreignForeignKeyDrop' + ]; + } + } + + continue 2; + } + } + } + } + } + } + + return $errors; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/TDroppableDatabaseComponent.class.php b/wcfsetup/install/files/lib/system/database/table/TDroppableDatabaseComponent.class.php new file mode 100644 index 0000000000..927414910f --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/TDroppableDatabaseComponent.class.php @@ -0,0 +1,39 @@ + + * @package WoltLabSuite\Core\System\Database\Table + * @since 5.2 + */ +trait TDroppableDatabaseComponent { + /** + * is `true` if the component will be dropped + * @var bool + */ + protected $drop = false; + + /** + * Marks the component to be dropped. + * + * @return $this + */ + public function drop() { + $this->drop = true; + + return $this; + } + + /** + * Returns `true` if the component will be dropped. + * + * @return bool + */ + public function willBeDropped() { + return $this->drop; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/AbstractDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/AbstractDatabaseTableColumn.class.php new file mode 100644 index 0000000000..3952e495af --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/AbstractDatabaseTableColumn.class.php @@ -0,0 +1,203 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +abstract class AbstractDatabaseTableColumn implements IDatabaseTableColumn { + use TDroppableDatabaseComponent; + + /** + * default value of the database table column + * @var mixed + */ + protected $defaultValue; + + /** + * name of the database table column + * @var string + */ + protected $name; + + /** + * is `true` if the values of the column may not be `null` + * @var bool + */ + protected $notNull = false; + + /** + * type of the database table column + * @var string + */ + protected $type; + + /** + * @inheritDoc + */ + public function defaultValue($defaultValue) { + $this->validateDefaultValue($defaultValue); + + $this->defaultValue = $defaultValue; + + return $this; + } + + /** + * @inheritDoc + */ + public function getData() { + $data = [ + 'default' => $this->defaultValue, + 'notNull' => $this->isNotNull() ? 1 : 0, + 'type' => $this->getType() + ]; + + if ($this instanceof IAutoIncrementDatabaseTableColumn) { + $data['autoIncrement'] = $this->isAutoIncremented() ? 1 : 0; + } + + if ($this instanceof IDecimalsDatabaseTableColumn && $this->getDecimals() !== null) { + $data['decimals'] = $this->getDecimals(); + } + + if ($this instanceof IEnumDatabaseTableColumn) { + $values = array_map(function($value) { + return str_replace(["'", '\\'], ["''", '\\\\'], $value); + }, $this->getEnumValues()); + + $data['values'] = "'" . implode("','", $values) . "'"; + } + + if ($this instanceof ILengthDatabaseTableColumn) { + $data['length'] = $this->getLength(); + } + + if ($this instanceof IUnsignedDatabaseTableColumn) { + $data['unsigned'] = $this->isUnsigned(); + } + + return $data; + } + + /** + * @inheritDoc + */ + public function getDefaultValue() { + return $this->defaultValue; + } + + /** + * @inheritDoc + */ + public function getName() { + if ($this->name === null) { + throw new \BadMethodCallException("Name of the database table column has not been set yet"); + } + + return $this->name; + } + + /** + * @inheritDoc + */ + public function getType() { + if ($this->type === null) { + throw new \BadMethodCallException("Type of the database table column " . get_class($this) . " has not been set yet"); + } + + return $this->type; + } + + /** + * @inheritDoc + */ + public function isNotNull() { + return $this->notNull; + } + + /** + * @inheritDoc + */ + public function name($name) { + if ($this->name !== null) { + throw new \BadMethodCallException("Name of the database table column has already been set."); + } + + $this->name = $name; + + return $this; + } + + /** + * @inheritDoc + */ + public function notNull($notNull = true) { + $this->notNull = $notNull; + + return $this; + } + + /** + * Checks if the given default value is valid. + * + * @param mixed $defaultValue validated default value + * @throws \InvalidArgumentException if given default value is invalid + */ + protected function validateDefaultValue($defaultValue) { + // does nothing + } + + /** + * @inheritDoc + */ + public static function create($name) { + return (new static())->name($name); + } + + /** + * @inheritDoc + */ + public static function createFromData($name, array $data) { + $column = static::create($name) + ->defaultValue($data['default']) + ->notNull($data['notNull']); + + if ($column instanceof IAutoIncrementDatabaseTableColumn) { + $column->autoIncrement($data['autoIncrement'] ?: null); + } + + if ($column instanceof IDecimalsDatabaseTableColumn) { + $column->decimals($data['decimals'] ?: null); + } + + if ($column instanceof IEnumDatabaseTableColumn) { + $values = explode(',', $data['enumValues'] ?? []); + + $values = array_map(function($value) { + // trim one leading and one trailing `'` + $value = substr($value, 1, -1); + + return str_replace(['\\\\', "''"], ['\\', "'"], $value); + }, $values); + + $column->enumValues($values); + } + + if ($column instanceof ILengthDatabaseTableColumn) { + $column->length($data['length'] ?: null); + } + + if ($column instanceof IUnsignedDatabaseTableColumn) { + $column->unsigned($data['unsigned'] ?? false); + } + + return $column; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/AbstractDecimalDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/AbstractDecimalDatabaseTableColumn.class.php new file mode 100644 index 0000000000..e76adcef80 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/AbstractDecimalDatabaseTableColumn.class.php @@ -0,0 +1,16 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +abstract class AbstractDecimalDatabaseTableColumn extends AbstractDatabaseTableColumn implements IDecimalsDatabaseTableColumn, IUnsignedDatabaseTableColumn { + use TDecimalsDatabaseTableColumn; + use TUnsignedDatabaseTableColumn; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/AbstractIntDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/AbstractIntDatabaseTableColumn.class.php new file mode 100644 index 0000000000..9e01837392 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/AbstractIntDatabaseTableColumn.class.php @@ -0,0 +1,24 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +abstract class AbstractIntDatabaseTableColumn extends AbstractDatabaseTableColumn implements IAutoIncrementDatabaseTableColumn, ILengthDatabaseTableColumn, IUnsignedDatabaseTableColumn { + use TAutoIncrementDatabaseTableColumn; + use TLengthDatabaseTableColumn; + use TUnsignedDatabaseTableColumn; + + /** + * @inheritDoc + */ + public function getMinimumLength() { + return 1; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/BigintDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/BigintDatabaseTableColumn.class.php new file mode 100644 index 0000000000..0340420fb4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/BigintDatabaseTableColumn.class.php @@ -0,0 +1,32 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class BigintDatabaseTableColumn extends AbstractIntDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'bigint'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 20; + } + + /** + * @inheritDoc + */ + public function getMaximumUnsignedLength() { + return 19; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/BinaryDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/BinaryDatabaseTableColumn.class.php new file mode 100644 index 0000000000..4b1189a437 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/BinaryDatabaseTableColumn.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class BinaryDatabaseTableColumn extends AbstractDatabaseTableColumn implements ILengthDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'binary'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 255; + } + + /** + * @inheritDoc + */ + public function getMinimumLength() { + return 0; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/BlobDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/BlobDatabaseTableColumn.class.php new file mode 100644 index 0000000000..a6335b70b4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/BlobDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class BlobDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'blob'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/CharDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/CharDatabaseTableColumn.class.php new file mode 100644 index 0000000000..14b4ba7902 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/CharDatabaseTableColumn.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class CharDatabaseTableColumn extends AbstractDatabaseTableColumn implements ILengthDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'char'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 255; + } + + /** + * @inheritDoc + */ + public function getMinimumLength() { + return 0; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DateDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DateDatabaseTableColumn.class.php new file mode 100644 index 0000000000..6a5d54c65d --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DateDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DateDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'date'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DatetimeDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DatetimeDatabaseTableColumn.class.php new file mode 100644 index 0000000000..d4df746f8e --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DatetimeDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DatetimeDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'datetime'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DecimalDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DecimalDatabaseTableColumn.class.php new file mode 100644 index 0000000000..6a4f408426 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DecimalDatabaseTableColumn.class.php @@ -0,0 +1,39 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DecimalDatabaseTableColumn extends AbstractDecimalDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'decimal'; + + /** + * @inheritDoc + */ + public function getMaximumDecimals() { + return 30; + } + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 65; + } + + /** + * @inheritDoc + */ + public function getMinimumLength() { + return 1; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DefaultFalseBooleanDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DefaultFalseBooleanDatabaseTableColumn.class.php new file mode 100644 index 0000000000..b26332421c --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DefaultFalseBooleanDatabaseTableColumn.class.php @@ -0,0 +1,27 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DefaultFalseBooleanDatabaseTableColumn extends TinyintDatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($name) { + /** @var TinyintDatabaseTableColumn $column */ + $column = parent::create($name); + + return $column + ->length(1) + ->notNull() + ->defaultValue(0); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DefaultTrueBooleanDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DefaultTrueBooleanDatabaseTableColumn.class.php new file mode 100644 index 0000000000..f5a79bc2c0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DefaultTrueBooleanDatabaseTableColumn.class.php @@ -0,0 +1,27 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DefaultTrueBooleanDatabaseTableColumn extends TinyintDatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($name) { + /** @var TinyintDatabaseTableColumn $column */ + $column = parent::create($name); + + return $column + ->length(1) + ->notNull() + ->defaultValue(1); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/DoubleDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/DoubleDatabaseTableColumn.class.php new file mode 100644 index 0000000000..b9d47cc2ca --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/DoubleDatabaseTableColumn.class.php @@ -0,0 +1,32 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class DoubleDatabaseTableColumn extends AbstractDecimalDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'double'; + + /** + * @inheritDoc + */ + public function getMaximumDecimals() { + return 30; + } + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 255; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/EnumDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/EnumDatabaseTableColumn.class.php new file mode 100644 index 0000000000..44cf11c930 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/EnumDatabaseTableColumn.class.php @@ -0,0 +1,20 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class EnumDatabaseTableColumn extends AbstractDatabaseTableColumn implements IEnumDatabaseTableColumn { + use TEnumDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'enum'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/FloatDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/FloatDatabaseTableColumn.class.php new file mode 100644 index 0000000000..1783c714a9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/FloatDatabaseTableColumn.class.php @@ -0,0 +1,32 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class FloatDatabaseTableColumn extends AbstractDecimalDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'float'; + + /** + * @inheritDoc + */ + public function getMaximumDecimals() { + return 30; + } + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 255; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IAutoIncrementDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IAutoIncrementDatabaseTableColumn.class.php new file mode 100644 index 0000000000..e3dd0b1d21 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IAutoIncrementDatabaseTableColumn.class.php @@ -0,0 +1,28 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface IAutoIncrementDatabaseTableColumn { + /** + * Sets if the values of the database table column are auto-increment and returns this column. + * + * @param bool $autoIncrement + * @return $this + */ + public function autoIncrement($autoIncrement = true); + + /** + * Returns `true` if the values of the database table column are auto-increment. + * + * @return bool + */ + public function isAutoIncremented(); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IDatabaseTableColumn.class.php new file mode 100644 index 0000000000..d9f164d743 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IDatabaseTableColumn.class.php @@ -0,0 +1,103 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface IDatabaseTableColumn { + /** + * Sets the default value of the column and returns the column. + * + * @param mixed $defaultValue + * @return $this + */ + public function defaultValue($defaultValue); + + /** + * Marks the column to be dropped and returns the column. + * + * @return $this + */ + public function drop(); + + /** + * Returns the data used by `DatabaseEditor` to add the column to a table. + * + * @return array + */ + public function getData(); + + /** + * Returns the default value of the column. + * + * @return $this + */ + public function getDefaultValue(); + + /** + * Returns the name of the column. + * + * @return string + */ + public function getName(); + + /** + * Returns the type of the column. + * + * @return string + */ + public function getType(); + + /** + * Returns `true` if the values of the column cannot be `null`. + * + * @return bool + */ + public function isNotNull(); + + /** + * Sets the name of the column and returns the column. + * + * @param string $name + * @return $this + */ + public function name($name); + + /** + * Sets if the values of the column cannot be `null`. + * + * @param bool $notNull + * @return $this + */ + public function notNull($notNull = true); + + /** + * Returns `true` if the column will be dropped. + * + * @return bool + */ + public function willBeDropped(); + + /** + * Returns a `DatabaseTableColumn` object with the given name. + * + * @param string $name + * @return $this + */ + public static function create($name); + + /** + * Returns a `DatabaseTableColumn` object with the given name and data. + * + * @param string $name + * @param array $data data returned by `DatabaseEditor::getColumns()` + * @return $this + */ + public static function createFromData($name, array $data); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IDecimalsDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IDecimalsDatabaseTableColumn.class.php new file mode 100644 index 0000000000..3fa6c39cb4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IDecimalsDatabaseTableColumn.class.php @@ -0,0 +1,30 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface IDecimalsDatabaseTableColumn extends ILengthDatabaseTableColumn { + /** + * Sets the number of decimals the database table column supports or unsets the previously + * set value if `null` is passed and returns this column. + * + * @param null|int $decimals + * @return $this + */ + public function decimals($decimals); + + /** + * Returns the number of decimals the database table column supports or `null` if the number + * of decimals has not be specified. + * + * @return null|int + */ + public function getDecimals(); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IEnumDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IEnumDatabaseTableColumn.class.php new file mode 100644 index 0000000000..936008c50f --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IEnumDatabaseTableColumn.class.php @@ -0,0 +1,30 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface IEnumDatabaseTableColumn extends IDatabaseTableColumn { + /** + * Sets the predetermined set of valid values for the database table column and returns this + * column. + * + * @param array $values + * @return $this + */ + public function enumValues(array $values); + + /** + * Returns the predetermined set of valid values for the database table column. + * + * @return array + */ + public function getEnumValues(); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/ILengthDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/ILengthDatabaseTableColumn.class.php new file mode 100644 index 0000000000..63f4490d80 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/ILengthDatabaseTableColumn.class.php @@ -0,0 +1,30 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface ILengthDatabaseTableColumn extends IDatabaseTableColumn { + /** + * Returns the (maximum) length of the column's values or `null` if no length has been set. + * + * @return null|int + */ + public function getLength(); + + /** + * Sets the (maximum) length of the column's values. + * + * @param null|int $length (maximum) column value length or `null` to unset previously set value + * @return $this this column + * @throws \InvalidArgumentException if given length is invalid + */ + public function length($length); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IUnsignedDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IUnsignedDatabaseTableColumn.class.php new file mode 100644 index 0000000000..b41ae3e3a6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IUnsignedDatabaseTableColumn.class.php @@ -0,0 +1,28 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +interface IUnsignedDatabaseTableColumn extends IDatabaseTableColumn { + /** + * Returns `true` if the values of the database table column are unsigned. + * + * @return bool + */ + public function isUnsigned(); + + /** + * Sets if the values of the database table column are unsigned and returns this column. + * + * @param bool $unsigned + * @return $this + */ + public function unsigned($unsigned = true); +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/IntDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/IntDatabaseTableColumn.class.php new file mode 100644 index 0000000000..8a3ab91257 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/IntDatabaseTableColumn.class.php @@ -0,0 +1,25 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class IntDatabaseTableColumn extends AbstractIntDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'int'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 11; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/LongblobDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/LongblobDatabaseTableColumn.class.php new file mode 100644 index 0000000000..2d515fc48f --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/LongblobDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class LongblobDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'longblob'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/LongtextDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/LongtextDatabaseTableColumn.class.php new file mode 100644 index 0000000000..49a5cb7e4e --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/LongtextDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class LongtextDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'longtext'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/MediumblobDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/MediumblobDatabaseTableColumn.class.php new file mode 100644 index 0000000000..8c104a8988 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/MediumblobDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class MediumblobDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'mediumblob'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/MediumintDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/MediumintDatabaseTableColumn.class.php new file mode 100644 index 0000000000..42597780ec --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/MediumintDatabaseTableColumn.class.php @@ -0,0 +1,32 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class MediumintDatabaseTableColumn extends AbstractIntDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'mediumint'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 8; + } + + /** + * @inheritDoc + */ + public function getMaximumUnsignedLength() { + return 7; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/MediumtextDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/MediumtextDatabaseTableColumn.class.php new file mode 100644 index 0000000000..3dea632322 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/MediumtextDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class MediumtextDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'mediumtext'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/NotNullInt10DatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/NotNullInt10DatabaseTableColumn.class.php new file mode 100644 index 0000000000..c083be98ed --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/NotNullInt10DatabaseTableColumn.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class NotNullInt10DatabaseTableColumn extends IntDatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($name) { + return parent::create($name) + ->notNull() + ->length(10); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar191DatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar191DatabaseTableColumn.class.php new file mode 100644 index 0000000000..4cd20148f6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar191DatabaseTableColumn.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class NotNullVarchar191DatabaseTableColumn extends VarcharDatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($name) { + return parent::create($name) + ->notNull() + ->length(191); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar255DatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar255DatabaseTableColumn.class.php new file mode 100644 index 0000000000..91eef6c96a --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/NotNullVarchar255DatabaseTableColumn.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class NotNullVarchar255DatabaseTableColumn extends VarcharDatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($name) { + return parent::create($name) + ->notNull() + ->length(255); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/ObjectIdDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/ObjectIdDatabaseTableColumn.class.php new file mode 100644 index 0000000000..0224defab6 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/ObjectIdDatabaseTableColumn.class.php @@ -0,0 +1,24 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class ObjectIdDatabaseTableColumn extends NotNullInt10DatabaseTableColumn { + /** + * @inheritDoc + */ + public static function create($columnName) { + return parent::create($columnName) + ->autoIncrement(); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/SetDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/SetDatabaseTableColumn.class.php new file mode 100644 index 0000000000..f050ecaef7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/SetDatabaseTableColumn.class.php @@ -0,0 +1,20 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class SetDatabaseTableColumn extends AbstractDatabaseTableColumn implements IEnumDatabaseTableColumn { + use TEnumDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'set'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/SmallintDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/SmallintDatabaseTableColumn.class.php new file mode 100644 index 0000000000..b13876529b --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/SmallintDatabaseTableColumn.class.php @@ -0,0 +1,25 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class SmallintDatabaseTableColumn extends AbstractIntDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'smallint'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 5; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TAutoIncrementDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TAutoIncrementDatabaseTableColumn.class.php new file mode 100644 index 0000000000..7e5a548fd5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TAutoIncrementDatabaseTableColumn.class.php @@ -0,0 +1,40 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +trait TAutoIncrementDatabaseTableColumn { + /** + * is `true` if the values of the database table column are auto-increment + * @var bool + */ + protected $autoIncrement = false; + + /** + * Sets if the values of the database table column are auto-increment and returns this column. + * + * @param bool $autoIncrement + * @return $this + */ + public function autoIncrement($autoIncrement = true) { + $this->autoIncrement = $autoIncrement; + + return $this; + } + + /** + * Returns `true` if the values of the database table column are auto-increment. + * + * @return bool + */ + public function isAutoIncremented() { + return $this->autoIncrement; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TDecimalsDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TDecimalsDatabaseTableColumn.class.php new file mode 100644 index 0000000000..a1c6aceaac --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TDecimalsDatabaseTableColumn.class.php @@ -0,0 +1,58 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +trait TDecimalsDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * number of decimals the database table column supports + * @var null|int + */ + protected $decimals; + + /** + * Sets the number of decimals the database table column supports or unsets the previously + * set value if `null` is passed and returns this column. + * + * @param null|int $decimals + * @return $this + */ + public function decimals($decimals) { + if ($this->getMaximumDecimals() !== null && $decimals > $this->getMaximumDecimals()) { + throw new \InvalidArgumentException("Given number of decimals is greater than the maximum number '{$this->getMaximumDecimals()}'."); + } + + $this->decimals = $decimals; + + return $this; + } + + /** + * Returns the number of decimals the database table column supports or `null` if the number + * of decimals has not be specified. + * + * @return null|int + */ + public function getDecimals() { + return $this->decimals; + } + + /** + * Returns the maxium number of decimals supported by this column or `null` if there is no such + * maximum. + * + * @return null|int + */ + public function getMaximumDecimals() { + return null; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TEnumDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TEnumDatabaseTableColumn.class.php new file mode 100644 index 0000000000..69f15868a8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TEnumDatabaseTableColumn.class.php @@ -0,0 +1,41 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +trait TEnumDatabaseTableColumn { + /** + * predetermined set of valid values for the database table column + * @var array + */ + protected $enumValues = []; + + /** + * Sets the predetermined set of valid values for the database table column and returns this + * column. + * + * @param array $values + * @return $this + */ + public function enumValues(array $values) { + $this->enumValues = $values; + + return $this; + } + + /** + * Returns the predetermined set of valid values for the database table column. + * + * @return array + */ + public function getEnumValues() { + return $this->enumValues; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TLengthDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TLengthDatabaseTableColumn.class.php new file mode 100644 index 0000000000..582a97a5be --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TLengthDatabaseTableColumn.class.php @@ -0,0 +1,94 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +trait TLengthDatabaseTableColumn { + /** + * (maximum) length of the column's values + * @var null|int + */ + protected $length; + + /** + * Returns the maxium length value supported by this column or `null` if there is no such + * maximum. + * + * @return null|int + */ + public function getMaximumLength() { + if ($this instanceof IUnsignedDatabaseTableColumn && $this->getMaximumUnsignedLength() !== null) { + return $this->getMaximumUnsignedLength(); + } + + return null; + } + + /** + * Returns the maximum length value supported by the values of the column are unsigned values + * or `null` if there is no such maximum. + * + * @return null|int + */ + public function getMaximumUnsignedLength() { + return null; + } + + /** + * Returns the minimum length value supported by this column or `null` if there is no such + * minimum. + * + * @return null|int + */ + public function getMinimumLength() { + return 0; + } + + /** + * Returns the (maximum) length of the column's values or `null` if no length has been set. + * + * @return null|int + */ + public function getLength() { + return $this->length; + } + + /** + * Sets the (maximum) length of the column's values. + * + * @param null|int $length (maximum) column value length or `null` to unset previously set value + * @return $this this column + * @throws \InvalidArgumentException if given length is invalid + */ + public function length($length) { + if ($length !== null) { + $this->validateLength($length); + } + + $this->length = $length; + + return $this; + } + + /** + * Validates the given length. + * + * @param int $length + * @throws \InvalidArgumentException if given length is invalid + */ + protected function validateLength($length) { + if ($this->getMinimumLength() !== null && $length < $this->getMinimumLength()) { + throw new \InvalidArgumentException("Given length is smaller than the minimum length '{$this->getMinimumLength()}'."); + } + if ($this->getMaximumLength() !== null && $length > $this->getMaximumLength()) { + throw new \InvalidArgumentException("Given length is greater than the maximum length '{$this->getMaximumLength()}'."); + } + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TUnsignedDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TUnsignedDatabaseTableColumn.class.php new file mode 100644 index 0000000000..aa6ef31334 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TUnsignedDatabaseTableColumn.class.php @@ -0,0 +1,40 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +trait TUnsignedDatabaseTableColumn { + /** + * `true` if the values of the database table column are unsigned + * @var bool + */ + protected $unsigned = false; + + /** + * Returns `true` if the values of the database table column are unsigned. + * + * @return bool + */ + public function isUnsigned() { + return $this->unsigned; + } + + /** + * Sets if the values of the database table column are unsigned and returns this column. + * + * @param bool $unsigned + * @return $this + */ + public function unsigned($unsigned = true) { + $this->unsigned = $unsigned; + + return $this; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TextDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TextDatabaseTableColumn.class.php new file mode 100644 index 0000000000..029320111e --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TextDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class TextDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'text'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TimeDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TimeDatabaseTableColumn.class.php new file mode 100644 index 0000000000..82e7f9d681 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TimeDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class TimeDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'time'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TinyblobDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TinyblobDatabaseTableColumn.class.php new file mode 100644 index 0000000000..c8b7e5c6b7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TinyblobDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class TinyblobDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'tinyblob'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TinyintDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TinyintDatabaseTableColumn.class.php new file mode 100644 index 0000000000..a493e27e85 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TinyintDatabaseTableColumn.class.php @@ -0,0 +1,25 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class TinyintDatabaseTableColumn extends AbstractIntDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'tinyint'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 3; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/TinytextDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/TinytextDatabaseTableColumn.class.php new file mode 100644 index 0000000000..45d796b9cd --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/TinytextDatabaseTableColumn.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class TinytextDatabaseTableColumn extends AbstractDatabaseTableColumn { + /** + * @inheritDoc + */ + protected $type = 'tinytext'; +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/VarbinaryDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/VarbinaryDatabaseTableColumn.class.php new file mode 100644 index 0000000000..dc320870fa --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/VarbinaryDatabaseTableColumn.class.php @@ -0,0 +1,27 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class VarbinaryDatabaseTableColumn extends AbstractDatabaseTableColumn implements ILengthDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'varbinary'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 65535; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/VarcharDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/VarcharDatabaseTableColumn.class.php new file mode 100644 index 0000000000..58d6bf2ce7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/VarcharDatabaseTableColumn.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class VarcharDatabaseTableColumn extends AbstractDatabaseTableColumn implements ILengthDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'varchar'; + + /** + * @inheritDoc + */ + public function getMaximumLength() { + return 65535; + } + + /** + * @inheritDoc + */ + public function getMinimumLength() { + return 0; + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/column/YearDatabaseTableColumn.class.php b/wcfsetup/install/files/lib/system/database/table/column/YearDatabaseTableColumn.class.php new file mode 100644 index 0000000000..e3cb51d222 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/column/YearDatabaseTableColumn.class.php @@ -0,0 +1,29 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Column + * @since 5.2 + */ +class YearDatabaseTableColumn extends AbstractDatabaseTableColumn implements ILengthDatabaseTableColumn { + use TLengthDatabaseTableColumn; + + /** + * @inheritDoc + */ + protected $type = 'year'; + + /** + * @inheritDoc + */ + protected function validateLength($length) { + if ($length !== 2 && $length !== 4) { + throw new \InvalidArgumentException("Only '2' and '4' are valid lengths for year columns"); + } + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableForeignKey.class.php b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableForeignKey.class.php new file mode 100644 index 0000000000..65d332aff0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableForeignKey.class.php @@ -0,0 +1,271 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Index + * @since 5.2 + */ +class DatabaseTableForeignKey { + use TDroppableDatabaseComponent; + + /** + * columns affected by the foreign key + * @var string[] + */ + protected $columns; + + /** + * name of the foreign key + * @var string + */ + protected $name; + + /** + * action executed in referenced table if row is deleted + * @var null|string + */ + protected $onDelete; + + /** + * action executed in referenced table if row is updated + * @var null|string + */ + protected $onUpdate; + + /** + * relevant columns in referenced table + * @var string[] + */ + protected $referencedColumns; + + /** + * name of referenced table + * @var string + */ + protected $referencedTable; + + /** + * valid on delete/update actions + * @var string[] + */ + const VALID_ACTIONS = [ + 'CASCADE', + 'NO ACTION', + 'SET NULL' + ]; + + /** + * Creates a new `DatabaseTableForeignKey` object. + * + * @param string $name column name + */ + protected function __construct($name) { + $this->name = $name; + } + + /** + * Sets the columns affected by the foreign key and returns the foreign key. + * + * @param string[] $columns columns affected by foreign key + * @return $this this foreign key + */ + public function columns(array $columns) { + $this->columns = array_values($columns); + + return $this; + } + + /** + * Returns the name of the foreign key. + * + * If the key belongs to a database table layout not created from an existing database table, + * the name might be empty. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Returns the columns affected by the foreign key + * + * @return string[] columns affected by foreign key + * @throws \BadMethodCallException if not columns have been set + */ + public function getColumns() { + if ($this->columns === null) { + throw new \BadMethodCallException("Before getting the columns, they must be set for foreign key '{$this->getName()}'."); + } + + return $this->columns; + } + + /** + * Returns the data used by `DatabaseEditor` to add the foreign key to a table. + * + * @return array + */ + public function getData() { + return [ + 'columns' => implode(',', $this->getColumns()), + 'ON DELETE' => $this->getOnDelete(), + 'ON UPDATE' => $this->getOnUpdate(), + 'referencedColumns' => implode(',', $this->getReferencedColumns()), + 'referencedTable' => $this->getReferencedTable() + ]; + } + + /** + * Returns the action executed in referenced table if row is deleted or `null` if no such + * action has been set. + * + * @return null|string + */ + public function getOnDelete() { + return $this->onDelete; + } + + /** + * Returns the action executed in referenced table if row is updated or `null` if no such + * action has been set. + * + * @return null|string + */ + public function getOnUpdate() { + return $this->onUpdate; + } + + /** + * Returns the relevant columns in referenced table. + * + * @return string[] + * @throws \BadMethodCallException if referenced columns have not been set + */ + public function getReferencedColumns() { + if ($this->referencedColumns === null) { + throw new \BadMethodCallException("Before getting the referenced columns, they must be set for foreign key '{$this->getName()}'."); + } + + return $this->referencedColumns; + } + + /** + * Returns the name of the referenced table. + * + * @return string + * @throws \BadMethodCallException if referenced table has not been set + */ + public function getReferencedTable() { + if ($this->referencedTable === null) { + throw new \BadMethodCallException("Before getting the referenced table, it must be set for foreign key '{$this->getName()}'."); + } + + return $this->referencedTable; + } + + /** + * Sets the name of the foreign key. + * + * @param string $name index name + * @return $this this index + */ + public function name($name) { + $this->name = $name; + + return $this; + } + + /** + * Sets the action executed in referenced table if row is deleted and returns the foreign + * key. + * + * @param string $onDelete action executed in referenced table if row is deleted + * @return $this this foreign key + * @throws \InvalidArgumentException if given action is invalid + */ + public function onDelete($onDelete) { + if ($onDelete !== null && !in_array($onDelete, static::VALID_ACTIONS)) { + throw new \InvalidArgumentException("Unknown on delete action '{$onDelete}'."); + } + + $this->onDelete = $onDelete; + + return $this; + } + + /** + * Sets the action executed in referenced table if row is updated and returns the foreign + * key. + * + * @param string $onUpdate action executed in referenced table if row is updated + * @return $this this foreign key + * @throws \InvalidArgumentException if given action is invalid + */ + public function onUpdate($onUpdate) { + if ($onUpdate !== null && !in_array($onUpdate, static::VALID_ACTIONS)) { + throw new \InvalidArgumentException("Unknown on update action '{$onUpdate}'."); + } + + $this->onUpdate = $onUpdate; + + return $this; + } + + /** + * Sets the relevant columns of the referenced table and returns the foreign key. + * + * @param string[] $referencedColumns columns of referenced table + * @return $this this foreign key + */ + public function referencedColumns(array $referencedColumns) { + $this->referencedColumns = $referencedColumns; + + return $this; + } + + /** + * Sets the name of the referenced table and returns the foreign key. + * + * @param string $referencedTable name of referenced table + * @return $this this foreign key + */ + public function referencedTable($referencedTable) { + $this->referencedTable = ApplicationHandler::insertRealDatabaseTableNames($referencedTable); + + return $this; + } + + /** + * Returns a `DatabaseTableForeignKey` object with the given name. + * + * @param string $name + * @return static + */ + public static function create($name = '') { + return new static($name); + } + + /** + * Returns a `DatabaseTableForeignKey` object with the given name and data. + * + * @param string $name + * @param array $data data returned by `DatabaseEditor::getForeignKeys()` + * @return static + */ + public static function createFromData($name, $data) { + return static::create($name) + ->columns($data['columns']) + ->onDelete($data['ON DELETE']) + ->onUpdate($data['ON UPDATE']) + ->referencedColumns($data['referencedColumns']) + ->referencedTable($data['referencedTable']); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableIndex.class.php b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableIndex.class.php new file mode 100644 index 0000000000..747001eb5c --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTableIndex.class.php @@ -0,0 +1,151 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Index + * @since 5.2 + */ +class DatabaseTableIndex { + use TDroppableDatabaseComponent; + + /** + * indexed columns + * @var string[] + */ + protected $columns; + + /** + * name of index + * @var string + */ + protected $name; + + /** + * type of index (see `*_TYPE` constants) + * @var null|string + */ + protected $type; + + const DEFAULT_TYPE = null; + const PRIMARY_TYPE = 'PRIMARY'; + const UNIQUE_TYPE = 'UNIQUE'; + const FULLTEXT_TYPE = 'FULLTEXT'; + + /** + * Creates a new `DatabaseTableIndex` object. + * + * @param string $name column name + */ + protected function __construct($name) { + $this->name = $name; + } + + /** + * Sets the indexed columns and returns the index. + * + * @param string[] $columns indexed columns + * @return $this this index + */ + public function columns($columns) { + $this->columns = array_values($columns); + + return $this; + } + + /** + * Returns the index columns. + * + * @return string[] + */ + public function getColumns() { + return $this->columns; + } + + /** + * Returns the data used by `DatabaseEditor` to add the index to a table. + * + * @return array + */ + public function getData() { + return [ + 'columns' => implode(',', $this->columns), + 'type' => $this->type + ]; + } + + /** + * Returns the name of the index. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Returns the type of the index (see `*_TYPE` constants). + * + * @return null|string + */ + public function getType() { + return $this->type; + } + + /** + * Sets the name of the index. + * + * @param string $name index name + * @return $this this index + */ + public function name($name) { + $this->name = $name; + + return $this; + } + + /** + * Sets the type of the index and returns the index + * + * @param null|string $type index type + * @return $this this index + * @throws \InvalidArgumentException if given type is invalid + */ + public function type($type) { + if ($type !== static::DEFAULT_TYPE && $type !== static::PRIMARY_TYPE && $type !== static::UNIQUE_TYPE && $type !== static::FULLTEXT_TYPE) { + throw new \InvalidArgumentException("Unknown index type '{$type}'."); + } + + $this->type = $type; + + return $this; + } + + /** + * Returns a `DatabaseTableIndex` object with the given name. + * + * @param string $name + * @return static + */ + public static function create($name = '') { + return new static($name); + } + + /** + * Returns a `DatabaseTableIndex` object with the given name and data. + * + * @param string $name + * @param array $data data returned by `DatabaseEditor::getIndexInformation()` + * @return static + */ + public static function createFromData($name, array $data) { + return static::create($name) + ->type($data['type']) + ->columns($data['columns']); + } +} diff --git a/wcfsetup/install/files/lib/system/database/table/index/DatabaseTablePrimaryIndex.class.php b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTablePrimaryIndex.class.php new file mode 100644 index 0000000000..964c6fb0fe --- /dev/null +++ b/wcfsetup/install/files/lib/system/database/table/index/DatabaseTablePrimaryIndex.class.php @@ -0,0 +1,26 @@ + + * @package WoltLabSuite\Core\System\Database\Table\Index + * @since 5.2 + */ +class DatabaseTablePrimaryIndex extends DatabaseTableIndex { + /** + * Returns a `PrimaryDatabaseTableIndex` object with `PRIMARY` as name and primary as type. + * + * @return $this + */ + public static function create() { + return parent::create('PRIMARY') + ->type(static::PRIMARY_TYPE); + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index d280066aad..61f952f3eb 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -1937,6 +1937,26 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 2dac401c24..6ffc87b604 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1922,6 +1922,26 @@ If you have already bought the licenses for the listed apps, th + -- 2.20.1