Skip to content

Commit

Permalink
Merge pull request #13 from SOHELAHMED7/121-implement-default-by-lite…
Browse files Browse the repository at this point in the history
…ral-expression-using-new-field-x-db-default-expression

Draft: Implement default by literal expression using new field 'x-db-default-expression'
  • Loading branch information
SOHELAHMED7 authored Jan 24, 2023
2 parents 8a89624 + 5a4a59a commit 3e44c3e
Show file tree
Hide file tree
Showing 36 changed files with 861 additions and 43 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ up:
echo "Waiting for mariadb to start up..."
docker-compose exec -T mysql timeout 60s sh -c "while ! (mysql -udbuser -pdbpass -h maria --execute 'SELECT 1;' > /dev/null 2>&1); do echo -n '.'; sleep 0.1 ; done; echo 'ok'" || (docker-compose ps; docker-compose logs; exit 1)

# Solution to problem https://stackoverflow.com/questions/50026939/php-mysqli-connect-authentication-method-unknown-to-the-client-caching-sha2-pa
# if updated to PHP 7.4 or more, this command is not needed (TODO)
docker-compose exec -T mysql timeout 60s sh -c "while ! (mysql --execute \"ALTER USER 'dbuser'@'%' IDENTIFIED WITH mysql_native_password BY 'dbpass';\" > /dev/null 2>&1); do echo -n '.'; sleep 0.1 ; done; echo 'ok'" || (docker-compose ps; docker-compose logs; exit 1)

cli:
docker-compose exec php bash

Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,35 @@ Specify table indexes
default: '{}'
```

### `x-db-default-expression`

Ability to provide default value by database expression

```yaml
created_at:
readOnly: true
type: string
format: datetime
x-db-type: datetime
nullable: false
x-db-default-expression: current_timestamp()
```

Note: If both `default` and `x-db-default-expression` are present then `default` will be considered.

```yaml
created_at:
readOnly: true
type: string
format: datetime
x-db-type: datetime
nullable: false
x-db-default-expression: current_timestamp() # this will be ignored
default: "2011-11-11" # this will be considered
```

Also see: https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html

### Many-to-Many relation definition

There are two ways for define many-to-many relations:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ services:
- maria
tty: true
mysql:
image: mysql:5.7
image: mysql:8
ports:
- '13306:3306'
volumes:
Expand Down
4 changes: 2 additions & 2 deletions src/generator/default/migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ class <?= $migration->fileClassName ?> extends \yii\db\Migration
{
public function <?=$isTransactional? 'safeUp':'up'?>()
{
<?= str_replace(["'\$this", ")',"], ['$this', '),'], $migration->upCodeString) ?>
<?= $migration->upCodeString ?>

}

public function <?=$isTransactional? 'safeDown':'down'?>()
{
<?= str_replace(["'\$this", ")',"], ['$this', '),'], $migration->downCodeString) ?>
<?= $migration->downCodeString ?>

}
}
4 changes: 3 additions & 1 deletion src/lib/AttributeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace cebe\yii2openapi\lib;

use cebe\yii2openapi\lib\CustomSpecAttr;
use cebe\yii2openapi\lib\exceptions\InvalidDefinitionException;
use cebe\yii2openapi\lib\items\Attribute;
use cebe\yii2openapi\lib\items\AttributeRelation;
Expand Down Expand Up @@ -203,7 +204,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'))
->setXDbType($property->getAttr(CustomSpecAttr::DB_TYPE))
->setXDbDefaultExpression($property->getAttr(CustomSpecAttr::DB_DEFAULT_EXPRESSION))
->setNullable($property->getProperty()->getSerializableData()->nullable ?? null)
->setIsPrimary($property->isPrimaryKey());
if ($property->isReference()) {
Expand Down
33 changes: 18 additions & 15 deletions src/lib/ColumnToCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ public function getCode(bool $quoted = false):string
return '$this->' . $this->fluentParts['type'];
}
if ($this->isBuiltinType) {
$parts = [$this->fluentParts['type'], $this->fluentParts['nullable'], $this->fluentParts['default'], $this->fluentParts['position']];
$parts = [
$this->fluentParts['type'],
$this->fluentParts['nullable'],
$this->fluentParts['default'],
$this->fluentParts['position']
];
array_unshift($parts, '$this');
return implode('->', array_filter(array_map('trim', $parts), 'trim'));
}
Expand Down Expand Up @@ -443,8 +448,9 @@ private function resolveDefaultValue():void
$this->fluentParts['default'] = "defaultValue('" . Json::encode($value->getValue()) . "')";
$this->rawParts['default'] = $this->defaultValueArray($value->getValue());
} else {
$this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = self::escapeQuotes((string)$value);
// $value instanceof \yii\db\Expression
$this->fluentParts['default'] = 'defaultExpression("' . (string)$value . '")';
$this->rawParts['default'] = (string)$value;
}
break;
case 'array':
Expand All @@ -454,18 +460,10 @@ private function resolveDefaultValue():void
: $this->defaultValueArray($value);
break;
default:
$isExpression = StringHelper::startsWith($value, 'CURRENT')
|| StringHelper::startsWith($value, 'current')
|| StringHelper::startsWith($value, 'LOCAL')
|| substr($value, -1, 1) === ')';
if ($isExpression) {
$this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = $value;
} else {
$this->fluentParts['default'] = $expectInteger
? 'defaultValue(' . $value . ')' : 'defaultValue("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);
}
$this->fluentParts['default'] = $expectInteger
? 'defaultValue(' . $value . ')' : 'defaultValue("' . self::escapeQuotes((string)$value) . '")';
$this->rawParts['default'] = $expectInteger ? $value : self::wrapQuotes($value);

if ((ApiGenerator::isMysql() || ApiGenerator::isMariaDb()) && $this->isEnum()) {
$this->rawParts['default'] = self::escapeQuotes($this->rawParts['default']);
}
Expand All @@ -474,6 +472,11 @@ private function resolveDefaultValue():void

private function isDefaultAllowed():bool
{
// default expression with parenthases is allowed
if ($this->column->defaultValue instanceof \yii\db\Expression) {
return true;
}

$type = strtolower($this->column->dbType);
switch ($type) {
case 'tsvector':
Expand Down
7 changes: 7 additions & 0 deletions src/lib/CustomSpecAttr.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ class CustomSpecAttr
public const FAKER = 'x-faker';
// Custom db type (MUST CONTAINS ONLY DB TYPE! (json, jsonb, uuid, varchar etc))
public const DB_TYPE = 'x-db-type';
/**
* Provide default value by database expression
* @example `current_timestamp()`
* @see https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html
* @see https://github.com/cebe/yii2-openapi/blob/master/README.md#x-db-default-expression
*/
public const DB_DEFAULT_EXPRESSION = 'x-db-default-expression';
}
9 changes: 9 additions & 0 deletions src/lib/items/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ public function setXDbType($xDbType):Attribute
return $this;
}

public function setXDbDefaultExpression($xDbDefaultExpression): Attribute
{
// first priority is given to `default` and then to `x-db-default-expression`
if ($xDbDefaultExpression !== null && $this->defaultValue === null) {
$this->defaultValue = new \yii\db\Expression('('.$xDbDefaultExpression.')');
}
return $this;
}

public function setNullable($nullable):Attribute
{
$this->nullable = $nullable;
Expand Down
27 changes: 26 additions & 1 deletion src/lib/migrations/MigrationRecordBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public function createTable(string $tableAlias, array $columns):string
}
}

$codeColumns = str_replace([PHP_EOL, "\\\'"], [PHP_EOL . self::INDENT, "'"], VarDumper::export($codeColumns));
$codeColumns = static::makeString($codeColumns);

return sprintf(self::ADD_TABLE, $tableAlias, $codeColumns);
}

Expand Down Expand Up @@ -290,4 +291,28 @@ public static function quote(string $columnName): string
}
return $columnName;
}

/**
* Convert code columns array to comlpete syntactically correct PHP code string which will be written to migration file
*/
public static function makeString(array $codeColumns): string
{
$finalStr = ''.PHP_EOL;
foreach ($codeColumns as $key => $column) {
if (is_string($key)) {
if (substr($column, 0, 5) === '$this') {
$finalStr .= VarDumper::export($key).' => '.$column.','.PHP_EOL;
} else {
$finalStr .= VarDumper::export($key).' => '.VarDumper::export($column).','.PHP_EOL;
}
} else {
$finalStr .= VarDumper::export($key).' => '.VarDumper::export($column).','.PHP_EOL;
}
}

$codeColumns = str_replace([PHP_EOL, "\\\'"], [PHP_EOL . self::INDENT.' ', "'"], $finalStr);
$codeColumns = trim($codeColumns);
$codeColumns = '['.PHP_EOL.self::INDENT.' '.$codeColumns.PHP_EOL . self::INDENT.']';
return $codeColumns;
}
}
2 changes: 1 addition & 1 deletion tests/fixtures/blog.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
'flags' => (new Attribute('flags', ['phpType'=>'int', 'dbType'=>'integer']))->setDefault(0)->setFakerStub
('$faker->numberBetween(0, 1000000)'),
'created_at' => (new Attribute('created_at', ['phpType' => 'string', 'dbType' => 'datetime']))
->setDefault('CURRENT_TIMESTAMP')->setFakerStub('$faker->dateTimeThisYear(\'now\', \'UTC\')->format(DATE_ATOM)'),
->setDefault(new \yii\db\Expression('(CURRENT_TIMESTAMP)'))->setFakerStub('$faker->dateTimeThisYear(\'now\', \'UTC\')->format(DATE_ATOM)'),
],
'relations' => [],
'indexes' => [
Expand Down
2 changes: 1 addition & 1 deletion tests/specs/blog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ components:
created_at:
type: string
format: date-time
default: CURRENT_TIMESTAMP
x-db-default-expression: CURRENT_TIMESTAMP
Users:
type: array
items:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function up()
'password' => $this->string()->notNull(),
'role' => $this->string(20)->null()->defaultValue("reader"),
'flags' => $this->integer()->null()->defaultValue(0),
'created_at' => $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP"),
'created_at' => $this->timestamp()->null()->defaultExpression("(CURRENT_TIMESTAMP)"),
]);
$this->createIndex('users_username_key', '{{%users}}', 'username', true);
$this->createIndex('users_email_key', '{{%users}}', 'email', true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function up()
'password' => $this->string()->notNull(),
'role' => $this->string(20)->null()->defaultValue("reader"),
'flags' => $this->integer()->null()->defaultValue(0),
'created_at' => $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP"),
'created_at' => $this->timestamp()->null()->defaultExpression("(CURRENT_TIMESTAMP)"),
]);
$this->createIndex('users_username_key', '{{%users}}', 'username', true);
$this->createIndex('users_email_key', '{{%users}}', 'email', true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function up()
'password' => $this->string()->notNull(),
'role' => $this->string(20)->null()->defaultValue("reader"),
'flags' => $this->integer()->null()->defaultValue(0),
'created_at' => $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP"),
'created_at' => $this->timestamp()->null()->defaultExpression("(CURRENT_TIMESTAMP)"),
]);
$this->createIndex('users_username_key', '{{%users}}', 'username', true);
$this->createIndex('users_email_key', '{{%users}}', 'email', true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function safeUp()
'password' => $this->string()->notNull(),
'role' => $this->string(20)->null()->defaultValue("reader"),
'flags' => $this->integer()->null()->defaultValue(0),
'created_at' => $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP"),
'created_at' => $this->timestamp()->null()->defaultExpression("(CURRENT_TIMESTAMP)"),
]);
$this->createIndex('users_username_key', '{{%users}}', 'username', true);
$this->createIndex('users_email_key', '{{%users}}', 'email', true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public function up()
public function down()
{
$this->createIndex('v2_posts_slug_key', '{{%v2_posts}}', 'slug', true);
$this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer(11)->null()->defaultValue(null));
$this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer()->null()->defaultValue(null));
$this->alterColumn('{{%v2_posts}}', 'active', $this->tinyInteger(1)->notNull()->defaultValue(0));
$this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer(11)->notNull());
$this->addColumn('{{%v2_posts}}', 'uid', $this->bigInteger(20)->notNull()->first());
$this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer()->notNull());
$this->addColumn('{{%v2_posts}}', 'uid', $this->bigInteger()->notNull()->first());
$this->dropColumn('{{%v2_posts}}', 'lang');
$this->dropColumn('{{%v2_posts}}', 'id');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class m200000_000005_change_table_v2_comments extends \yii\db\Migration
{
public function up()
{
$this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}');
$this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}');
$this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}');
$this->addColumn('{{%v2_comments}}', 'user_id', $this->bigInteger()->null()->defaultValue(null)->after('post_id'));
$this->dropColumn('{{%v2_comments}}', 'author_id');
$this->alterColumn('{{%v2_comments}}', 'message', $this->text()->notNull());
Expand All @@ -22,12 +22,12 @@ public function down()
{
$this->dropForeignKey('fk_v2_comments_user_id_v2_users_id', '{{%v2_comments}}');
$this->dropForeignKey('fk_v2_comments_post_id_v2_posts_id', '{{%v2_comments}}');
$this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer(11)->notNull());
$this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer()->notNull());
$this->alterColumn('{{%v2_comments}}', 'meta_data', 'json NOT NULL');
$this->alterColumn('{{%v2_comments}}', 'message', 'json NOT NULL');
$this->addColumn('{{%v2_comments}}', 'author_id', $this->integer(11)->notNull());
$this->addColumn('{{%v2_comments}}', 'author_id', $this->integer()->notNull());
$this->dropColumn('{{%v2_comments}}', 'user_id');
$this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'id', 'v2_users', 'author_id');
$this->addForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}', 'uid', 'v2_posts', 'post_id');
$this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'id', 'v2_users', 'author_id');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

return [
'openApiPath' => '@specs/issue_fix/create_table_in_down_code/create_table_in_down_code.yaml',
'generateUrls' => false,
'generateModels' => false,
'excludeModels' => [
'Error',
],
'generateControllers' => false,
'generateMigrations' => true,
'generateModelFaker' => false,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Create table in down code
paths:
/:
get:
summary: List
operationId: list
responses:
'200':
description: The information

components:
schemas:
Fruit:
type: object
description: Create table in down code
properties:
id:
type: integer
colourName:
type: string
maxLength: 255
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* Table for Fruit
*/
class m200000_000000_change_table_fruits extends \yii\db\Migration
{
public function up()
{
$this->alterColumn('{{%fruits}}', 'ts', $this->datetime()->null()->defaultExpression("(CURRENT_TIMESTAMP)"));
$this->alterColumn('{{%fruits}}', 'ts2', $this->datetime()->null()->defaultValue("2011-11-11 00:00:00"));
$this->alterColumn('{{%fruits}}', 'ts3', $this->datetime()->null()->defaultValue("2022-11-11 00:00:00"));
$this->alterColumn('{{%fruits}}', 'ts4', $this->timestamp()->null()->defaultValue("2022-11-11 00:00:00"));
$this->alterColumn('{{%fruits}}', 'ts5', $this->timestamp()->null()->defaultExpression("(CURRENT_TIMESTAMP)"));
$this->alterColumn('{{%fruits}}', 'ts6', $this->timestamp()->null()->defaultValue("2000-11-11 00:00:00"));
$this->alterColumn('{{%fruits}}', 'd', $this->date()->null()->defaultExpression("(CURRENT_DATE + INTERVAL 1 YEAR)"));
$this->alterColumn('{{%fruits}}', 'd2', $this->text()->null()->defaultExpression("(CURRENT_DATE + INTERVAL 1 YEAR)"));
$this->alterColumn('{{%fruits}}', 'd3', $this->text()->null()->defaultValue("text default"));
$this->alterColumn('{{%fruits}}', 'ts7', $this->date()->null()->defaultExpression("(CURRENT_DATE + INTERVAL 1 YEAR)"));
}

public function down()
{
$this->alterColumn('{{%fruits}}', 'ts7', $this->date()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'd3', $this->text()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'd2', $this->text()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'd', $this->date()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'ts6', $this->timestamp()->notNull()->defaultValue("0000-00-00 00:00:00"));
$this->alterColumn('{{%fruits}}', 'ts5', $this->timestamp()->notNull()->defaultValue("0000-00-00 00:00:00"));
$this->alterColumn('{{%fruits}}', 'ts4', $this->timestamp()->notNull()->defaultExpression("current_timestamp()"));
$this->alterColumn('{{%fruits}}', 'ts3', $this->datetime()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'ts2', $this->datetime()->null()->defaultValue(null));
$this->alterColumn('{{%fruits}}', 'ts', $this->datetime()->null()->defaultValue(null));
}
}
Loading

0 comments on commit 3e44c3e

Please sign in to comment.