Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve: Implement default by literal expression using new field 'x-db-default-expression' #13

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));
}
}