From 7babecdeb316053c38fc84ae393567084ff992fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigmar=20K=C3=BChlmann?= Date: Wed, 6 Jul 2022 16:17:52 +0200 Subject: [PATCH 1/5] Fix-xDbType --- src/lib/AttributeResolver.php | 1 + src/lib/items/Attribute.php | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index 1b2e6a9a..c63fce0a 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -203,6 +203,7 @@ protected function resolveProperty(PropertySchema $property, bool $isRequired):v ->setDescription($property->getAttr('description', '')) ->setReadOnly($property->isReadonly()) ->setDefault($property->guessDefault()) + ->setXDbType($property->getAttr('x-db-type', null)) ->setIsPrimary($property->isPrimaryKey()); if ($property->isReference()) { if ($property->isVirtual()) { diff --git a/src/lib/items/Attribute.php b/src/lib/items/Attribute.php index a71f02e6..b911aa51 100644 --- a/src/lib/items/Attribute.php +++ b/src/lib/items/Attribute.php @@ -46,6 +46,11 @@ class Attribute extends BaseObject */ public $dbType = 'string'; + /** + * Custom db type + */ + public $xDbType; + /** * @var string */ @@ -117,6 +122,17 @@ public function setDbType(string $dbType):Attribute return $this; } + public function setXDbType($xDbType):Attribute + { + $this->xDbType = $xDbType; + return $this; + } + + public function getXDbType($xDbType) + { + return $this->xDbType; + } + public function setDescription(string $description):Attribute { $this->description = $description; @@ -270,6 +286,13 @@ public function toColumnSchema():ColumnSchema private function dbTypeAbstract(string $type):string { + /** + * Custom db type + */ + if ($this->xDbType){ + return $this->xDbType; + } + if (stripos($type, 'int') === 0) { return 'integer'; } From a9348594e18c779e377ed7df346beaf53a7ac7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigmar=20K=C3=BChlmann?= Date: Thu, 7 Jul 2022 13:17:23 +0200 Subject: [PATCH 2/5] Fix-Nullable (#99) fixes #99 --- README.md | 46 +++++++++++++++++++++++++++++++++++ src/lib/AttributeResolver.php | 1 + src/lib/items/Attribute.php | 22 ++++++++++++++--- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 597fbc33..d6f5de58 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,52 @@ $this->update('{{%company}}', ['name' => 'No name']); $this->alterColumn('{{%company}}', 'name', $this->string(128)->notNull()); ``` +### Handling of `NOT NULL` constraints (#required, #nullable) + +e.g. attribute = 'alpha'. + +1) If you define attribute neither "required" nor via "nullable", then it is by default: + ALLOW 'NOT NULL' + ```yaml + test_table: + required: + properties: + alpha: + type: string +``` + +2) If you define attribute by "required", then it is: + FORBIDDEN 'NOT NULL' + ```yaml + test_table: + required: + - alpha + properties: + alpha: + type: string +``` + +3) If you define attribute via "nullable", then it has the highest priority. + ALLOW 'NOT NULL' + ```yaml + test_table: + required: + - alpha + properties: + alpha: + type: string + nullable: true +``` + +FORBIDDEN 'NOT NULL' + ```yaml + test_table: + required: + properties: + alpha: + type: string + nullable: false +``` ## Screenshots diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index c63fce0a..d74287c7 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -204,6 +204,7 @@ protected function resolveProperty(PropertySchema $property, bool $isRequired):v ->setReadOnly($property->isReadonly()) ->setDefault($property->guessDefault()) ->setXDbType($property->getAttr('x-db-type', null)) + ->setNullable($property->getProperty()->getSerializableData()->nullable ?? null) ->setIsPrimary($property->isPrimaryKey()); if ($property->isReference()) { if ($property->isVirtual()) { diff --git a/src/lib/items/Attribute.php b/src/lib/items/Attribute.php index b911aa51..325dbc8d 100644 --- a/src/lib/items/Attribute.php +++ b/src/lib/items/Attribute.php @@ -48,9 +48,16 @@ class Attribute extends BaseObject /** * Custom db type + * string | null */ public $xDbType; + /** + * nullable + * bool | null + */ + public $nullable; + /** * @var string */ @@ -128,9 +135,10 @@ public function setXDbType($xDbType):Attribute return $this; } - public function getXDbType($xDbType) + public function setNullable($nullable):Attribute { - return $this->xDbType; + $this->nullable = $nullable; + return $this; } public function setDescription(string $description):Attribute @@ -263,7 +271,7 @@ public function toColumnSchema():ColumnSchema 'phpType'=>$this->phpType, 'dbType' => strtolower($this->dbType), 'type' => $this->dbTypeAbstract($this->dbType), - 'allowNull' => !$this->isRequired(), + 'allowNull' => $this->allowNull(), 'size' => $this->size > 0 ? $this->size : null, ]); $column->isPrimaryKey = $this->primary; @@ -313,4 +321,12 @@ private function dbTypeAbstract(string $type):string } return $type; } + + private function allowNull() + { + if (is_bool($this->nullable)){ + return $this->nullable; + } + return !$this->isRequired(); + } } From 5870b242c10b57c71e552b172a89fea7a4107442 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 7 Jul 2022 13:37:53 +0200 Subject: [PATCH 3/5] Update README.md --- README.md | 45 ++++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d6f5de58..dad8546e 100644 --- a/README.md +++ b/README.md @@ -248,52 +248,43 @@ $this->update('{{%company}}', ['name' => 'No name']); $this->alterColumn('{{%company}}', 'name', $this->string(128)->notNull()); ``` -### Handling of `NOT NULL` constraints (#required, #nullable) +### Handling of `NOT NULL` constraints -e.g. attribute = 'alpha'. +`NOT NULL` in DB migrations is determined by `nullable` and `required` properties of the OpenAPI schema. -1) If you define attribute neither "required" nor via "nullable", then it is by default: - ALLOW 'NOT NULL' - ```yaml - test_table: - required: +- If you define attribute neither "required" nor via "nullable", then it is by default `NULL`: + + ```yaml + ExampleSchema: properties: alpha: type: string -``` + ``` + +- If you define attribute in "required", then it is `NOT NULL` -2) If you define attribute by "required", then it is: - FORBIDDEN 'NOT NULL' - ```yaml - test_table: + ```yaml + ExampleSchema: required: - alpha properties: alpha: type: string -``` + ``` + +- If you define attribute via "nullable", then it overrides "required", e.g. allow `NULL` in this case: -3) If you define attribute via "nullable", then it has the highest priority. - ALLOW 'NOT NULL' - ```yaml - test_table: + ```yaml + ExampleSchema: required: - alpha properties: alpha: type: string nullable: true -``` + ``` + -FORBIDDEN 'NOT NULL' - ```yaml - test_table: - required: - properties: - alpha: - type: string - nullable: false -``` ## Screenshots From 775487829a24714e225f6960899b08a32e31469a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigmar=20K=C3=BChlmann?= Date: Tue, 12 Jul 2022 19:07:04 +0200 Subject: [PATCH 4/5] Fix-Enum on MariaDb --- README.md | 27 ++++++++++---- src/lib/ColumnToCode.php | 39 +++++++++++++++----- src/lib/items/DbModel.php | 3 +- src/lib/migrations/BaseMigrationBuilder.php | 4 ++ src/lib/migrations/MysqlMigrationBuilder.php | 2 +- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d6f5de58..e899cddb 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ $this->alterColumn('{{%company}}', 'name', $this->string(128)->notNull()); ### Handling of `NOT NULL` constraints (#required, #nullable) -e.g. attribute = 'alpha'. +e.g. attribute = 'my_property'. 1) If you define attribute neither "required" nor via "nullable", then it is by default: ALLOW 'NOT NULL' @@ -258,7 +258,7 @@ e.g. attribute = 'alpha'. test_table: required: properties: - alpha: + my_property: type: string ``` @@ -267,9 +267,9 @@ e.g. attribute = 'alpha'. ```yaml test_table: required: - - alpha + - my_property properties: - alpha: + my_property: type: string ``` @@ -278,9 +278,9 @@ e.g. attribute = 'alpha'. ```yaml test_table: required: - - alpha + - my_property properties: - alpha: + my_property: type: string nullable: true ``` @@ -290,11 +290,24 @@ FORBIDDEN 'NOT NULL' test_table: required: properties: - alpha: + my_property: type: string nullable: false ``` +### Handling of `enum` (#enum, #MariaDb) +It work on MariaDb. + + ```yaml + test_table: + properties: + my_property: + enum: + - one + - two + - three +``` + ## Screenshots Gii Generator Form: diff --git a/src/lib/ColumnToCode.php b/src/lib/ColumnToCode.php index 9f3b3973..829b22f8 100644 --- a/src/lib/ColumnToCode.php +++ b/src/lib/ColumnToCode.php @@ -147,7 +147,7 @@ public function isJson():bool public function isEnum():bool { - return StringHelper::startsWith($this->column->dbType, 'enum'); + return !empty($this->column->enumValues); } public static function escapeQuotes(string $str):string @@ -208,11 +208,7 @@ private function resolve():void if ($dbType === 'varchar') { $type = $dbType = 'string'; } - if ($this->fromDb === true) { - $this->isBuiltinType = isset((new ColumnSchemaBuilder(''))->categoryMap[$type]); - } else { - $this->isBuiltinType = isset((new ColumnSchemaBuilder(''))->categoryMap[$dbType]); - } + $this->isBuiltinType = $this->getIsBuiltinType($type, $dbType); $fluentSize = $this->column->size ? '(' . $this->column->size . ')' : '()'; $rawSize = $this->column->size ? '(' . $this->column->size . ')' : ''; $this->rawParts['nullable'] = $this->column->allowNull ? 'NULL' : 'NOT NULL'; @@ -235,6 +231,23 @@ private function resolve():void $this->resolveDefaultValue(); } + /** + * @param $type + * @param $dbType + * @return bool + */ + private function getIsBuiltinType($type, $dbType) + { + if ($this->isEnum() && $this->isMariaDb()){ + return false; + } + if ($this->fromDb === true){ + return isset((new ColumnSchemaBuilder(''))->categoryMap[$type]); + } else { + return isset((new ColumnSchemaBuilder(''))->categoryMap[$dbType]); + } + } + private function resolveEnumType():void { if ($this->isPostgres()) { @@ -314,10 +327,18 @@ private function resolveDefaultValue():void private function isDefaultAllowed():bool { $type = strtolower($this->column->dbType); - if ($type === 'tsvector') { - return false; + switch ($type){ + case 'tsvector': + return false; + case 'blob': + case 'geometry': + case 'text': + case 'json': + return ($this->isMysql() && !$this->isMariaDb()) === false; + case 'enum': + default: + return true; } - return !($this->isMysql() && !$this->isMariaDb() && in_array($type, ['blob', 'geometry', 'text', 'json'])); } private function typeWithoutSize(string $type):string diff --git a/src/lib/items/DbModel.php b/src/lib/items/DbModel.php index c306b877..dbfb09cc 100644 --- a/src/lib/items/DbModel.php +++ b/src/lib/items/DbModel.php @@ -139,8 +139,7 @@ public function getEnumAttributes():array return array_filter( $this->attributes, static function (Attribute $attribute) { - return !$attribute->isVirtual && StringHelper::startsWith($attribute->dbType, 'enum') - && !empty($attribute->enumValues); + return !$attribute->isVirtual && !empty($attribute->enumValues); } ); } diff --git a/src/lib/migrations/BaseMigrationBuilder.php b/src/lib/migrations/BaseMigrationBuilder.php index 781e7732..740ef9a8 100644 --- a/src/lib/migrations/BaseMigrationBuilder.php +++ b/src/lib/migrations/BaseMigrationBuilder.php @@ -205,6 +205,10 @@ function (string $unknownColumn) { $current->type = 'enum'; $current->dbType = 'enum'; } + if (!empty($desired->enumValues)) { + $desired->type = 'enum'; + $desired->dbType = 'enum'; + } $changedAttributes = $this->compareColumns($current, $desired); if (empty($changedAttributes)) { continue; diff --git a/src/lib/migrations/MysqlMigrationBuilder.php b/src/lib/migrations/MysqlMigrationBuilder.php index 65029fcd..2781f8b7 100644 --- a/src/lib/migrations/MysqlMigrationBuilder.php +++ b/src/lib/migrations/MysqlMigrationBuilder.php @@ -66,7 +66,7 @@ protected function compareColumns(ColumnSchema $current, ColumnSchema $desired): protected function createEnumMigrations():void { - // Postgres only case + // execute via default } protected function isDbDefaultSize(ColumnSchema $current):bool From 6ae03fbc9265071f84ce48e9ebb0283a2f784c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigmar=20K=C3=BChlmann?= Date: Thu, 25 Aug 2022 08:54:38 +0200 Subject: [PATCH 5/5] Fix-numeric (mariaDb) --- README.md | 31 +++++++++++ src/lib/ColumnToCode.php | 57 +++++++++++++++++++- src/lib/migrations/MysqlMigrationBuilder.php | 14 +++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7fbde1c..b23c6e87 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,37 @@ It work on MariaDb. - three ``` +### Handling of `numeric` (#numeric, #MariaDb) +precision-default = 10 +scale-default = 2 + +- You can define attribute like "numeric(precision,scale)": + ```yaml + test_table: + properties: + my_property: + x-db-type: decimal(12,4) +``` +DB-Result = decimal(12,4) + +- You can define attribute like "numeric(precision)" with default scale-default = 2: + ```yaml + test_table: + properties: + my_property: + x-db-type: decimal(12) +``` +DB-Result = decimal(12,2) + +- You can define attribute like "numeric" with precision-default = 10 and scale-default = 2: + ```yaml + test_table: + properties: + my_property: + x-db-type: decimal +``` +DB-Result = decimal(10,2) + ## Screenshots Gii Generator Form: diff --git a/src/lib/ColumnToCode.php b/src/lib/ColumnToCode.php index 829b22f8..239a3709 100644 --- a/src/lib/ColumnToCode.php +++ b/src/lib/ColumnToCode.php @@ -150,6 +150,56 @@ public function isEnum():bool return !empty($this->column->enumValues); } + public function isDecimal() + { + return self::isDecimalByDbType($this->column->dbType); + } + + /** + * @param $dbType + * @return array|false + */ + public static function isDecimalByDbType($dbType) + { + $precision = null; + $scale = null; + + // https://runebook.dev/de/docs/mariadb/decimal/index + $precisionDefault = 10; + $scaleDefault = 2; + + preg_match_all('/(decimal\()+(\d)+(,)+(\d)+(\))/', $dbType, $matches); + if (!empty($matches[4][0])) { + $precision = $matches[2][0]; + $scale = $matches[4][0]; + } + + if (empty($precision)){ + preg_match_all('/(decimal\()+(\d)+(\))/', $dbType, $matches); + if (!empty($matches[2][0])) { + $precision = $matches[2][0]; + $scale = $scaleDefault; + } + } + + if (empty($precision)){ + if (strtolower($dbType) === 'decimal'){ + $precision = $precisionDefault; + $scale = $scaleDefault; + } + } + + if (empty($precision)){ + return false; + } + + return [ + 'precision' => (int)$precision, + 'scale' => (int)$scale, + 'dbType' => "decimal($precision,$scale)", + ]; + } + public static function escapeQuotes(string $str):string { return str_replace(["'", '"', '$'], ["\\'", "\\'", '\$'], $str); @@ -208,7 +258,6 @@ private function resolve():void if ($dbType === 'varchar') { $type = $dbType = 'string'; } - $this->isBuiltinType = $this->getIsBuiltinType($type, $dbType); $fluentSize = $this->column->size ? '(' . $this->column->size . ')' : '()'; $rawSize = $this->column->size ? '(' . $this->column->size . ')' : ''; $this->rawParts['nullable'] = $this->column->allowNull ? 'NULL' : 'NOT NULL'; @@ -223,11 +272,17 @@ private function resolve():void $this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize); } elseif ($this->isEnum()) { $this->resolveEnumType(); + } elseif ($this->isDecimal()) { + $this->fluentParts['type'] = $dbType; + $this->rawParts['type'] = $dbType; } else { $this->fluentParts['type'] = $type . $fluentSize; $this->rawParts['type'] = $this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize); } + + $this->isBuiltinType = $this->getIsBuiltinType($type, $dbType); + $this->resolveDefaultValue(); } diff --git a/src/lib/migrations/MysqlMigrationBuilder.php b/src/lib/migrations/MysqlMigrationBuilder.php index 2781f8b7..eadb8fbd 100644 --- a/src/lib/migrations/MysqlMigrationBuilder.php +++ b/src/lib/migrations/MysqlMigrationBuilder.php @@ -7,6 +7,7 @@ namespace cebe\yii2openapi\lib\migrations; +use cebe\yii2openapi\lib\ColumnToCode; use cebe\yii2openapi\lib\items\DbIndex; use yii\base\NotSupportedException; use yii\db\ColumnSchema; @@ -56,6 +57,19 @@ protected function compareColumns(ColumnSchema $current, ColumnSchema $desired): if ($current->type === $desired->type && !$desired->size && $this->isDbDefaultSize($current)) { $desired->size = $current->size; } + + if ($decimalAttributes = ColumnToCode::isDecimalByDbType($desired->dbType)){ + $desired->precision = $decimalAttributes['precision']; + $desired->scale = $decimalAttributes['scale']; + $desired->type = 'decimal'; + $desired->size = $decimalAttributes['precision']; + foreach (['precision', 'scale', 'dbType'] as $decimalAttr) { + if ($current->$decimalAttr !== $desired->$decimalAttr) { + $changedAttributes[] = $decimalAttr; + } + } + } + foreach (['type', 'size', 'allowNull', 'defaultValue', 'enumValues'] as $attr) { if ($current->$attr !== $desired->$attr) { $changedAttributes[] = $attr;