Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Fix-xDbType #103

Merged
merged 6 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,97 @@ $this->update('{{%company}}', ['name' => 'No name']);
$this->alterColumn('{{%company}}', 'name', $this->string(128)->notNull());
```

### Handling of `NOT NULL` constraints

`NOT NULL` in DB migrations is determined by `nullable` and `required` properties of the OpenAPI schema.
e.g. attribute = 'my_property'.

- If you define attribute neither "required" nor via "nullable", then it is by default `NULL`:

```yaml
ExampleSchema:
properties:
my_property:
type: string
```

- If you define attribute in "required", then it is `NOT NULL`

```yaml
ExampleSchema:
required:
- my_property
properties:
my_property:
type: string
```

- If you define attribute via "nullable", then it overrides "required", e.g. allow `NULL` in this case:

```yaml
ExampleSchema:
required:
- my_property
properties:
my_property:
type: string
nullable: true
```

- If you define attribute via "nullable", then it overrides "required", e.g. `NOT NULL` in this case:

```yaml
test_table:
required:
properties:
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
```

### 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

Expand Down
2 changes: 2 additions & 0 deletions src/lib/AttributeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ 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))
->setNullable($property->getProperty()->getSerializableData()->nullable ?? null)
->setIsPrimary($property->isPrimaryKey());
if ($property->isReference()) {
if ($property->isVirtual()) {
Expand Down
94 changes: 85 additions & 9 deletions src/lib/ColumnToCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,57 @@ public function isJson():bool

public function isEnum():bool
{
return StringHelper::startsWith($this->column->dbType, 'enum');
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
Expand Down Expand Up @@ -208,11 +258,6 @@ 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]);
}
$fluentSize = $this->column->size ? '(' . $this->column->size . ')' : '()';
$rawSize = $this->column->size ? '(' . $this->column->size . ')' : '';
$this->rawParts['nullable'] = $this->column->allowNull ? 'NULL' : 'NOT NULL';
Expand All @@ -227,14 +272,37 @@ 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();
}

/**
* @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()) {
Expand Down Expand Up @@ -314,10 +382,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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

condition looks hard to read, should be positive check instead of negative.

Copy link
Contributor

@SOHELAHMED7 SOHELAHMED7 Oct 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Condition:

  is mysql is mariadb type in array final global negation
mysql json T !F=T T T F
mysql nonjson T !F=T F F T
mariadb json F !T=F T F T
mariadb nonjson F !T=F F F T

Default are not allowed for BLOB, TEXT, GEOMETRY, and JSON in MySQL prior to MySQL 8.0.13

Curious to know why it is allowed for MariaDB?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mariadb allows default value for JSON column at least, have been using that a few times. e.g. data JSON NOT NULL DEFAULT '{}'

case 'enum':
default:
return true;
}
return !($this->isMysql() && !$this->isMariaDb() && in_array($type, ['blob', 'geometry', 'text', 'json']));
}

private function typeWithoutSize(string $type):string
Expand Down
41 changes: 40 additions & 1 deletion src/lib/items/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ class Attribute extends BaseObject
*/
public $dbType = 'string';

/**
* Custom db type
* string | null
*/
public $xDbType;

/**
* nullable
* bool | null
*/
public $nullable;

/**
* @var string
*/
Expand Down Expand Up @@ -117,6 +129,18 @@ public function setDbType(string $dbType):Attribute
return $this;
}

public function setXDbType($xDbType):Attribute
{
$this->xDbType = $xDbType;
return $this;
}

public function setNullable($nullable):Attribute
{
$this->nullable = $nullable;
return $this;
}

public function setDescription(string $description):Attribute
{
$this->description = $description;
Expand Down Expand Up @@ -247,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;
Expand All @@ -270,6 +294,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';
}
Expand All @@ -290,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();
}
}
3 changes: 1 addition & 2 deletions src/lib/items/DbModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/migrations/BaseMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 15 additions & 1 deletion src/lib/migrations/MysqlMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -66,7 +80,7 @@ protected function compareColumns(ColumnSchema $current, ColumnSchema $desired):

protected function createEnumMigrations():void
{
// Postgres only case
// execute via default
}

protected function isDbDefaultSize(ColumnSchema $current):bool
Expand Down