Skip to content
Open
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
24 changes: 19 additions & 5 deletions lib/SQL/Translator/Parser/MySQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ SQL::Translator::Parser::MySQL - parser for MySQL
The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.

Here's the word from the MySQL site
(http://www.mysql.com/doc/en/CREATE_TABLE.html):
(https://dev.mysql.com/doc/refman/en/create-table.html):

CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
[table_options] [select_statement]
Expand All @@ -27,7 +27,7 @@ Here's the word from the MySQL site
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;

create_definition:
col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
col_name type [GENERATED ALWAYS] AS (expr) [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
[PRIMARY KEY] [reference_definition]
or PRIMARY KEY (index_col_name,...)
or KEY [index_name] (index_col_name,...)
Expand Down Expand Up @@ -478,7 +478,7 @@ field_comment : /^\s*(?:#|-{2}).*\n/

blank : /\s*/

field : field_comment(s?) field_name data_type field_qualifier(s?) reference_definition(?) on_update(?) field_comment(s?)
field : field_comment(s?) field_name data_type field_generated(s?) field_qualifier(s?) reference_definition(?) on_update(?) field_comment(s?)
{
my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
Expand All @@ -497,6 +497,7 @@ field : field_comment(s?) field_name data_type field_qualifier(s?) reference_def
data_type => $item{'data_type'}{'type'},
size => $item{'data_type'}{'size'},
list => $item{'data_type'}{'list'},
generated => $item{'field_generated(s?)'}[0],
null => $null,
constraints => $item{'reference_definition(?)'},
comments => [ @comments ],
Expand All @@ -505,6 +506,14 @@ field : field_comment(s?) field_name data_type field_qualifier(s?) reference_def
}
| <error>

field_generated : generated_always(s?) /as/i '(' expr ')' generated_type(s?)
{
$return = {
expr => $item{'expr'},
type => $item{'generated_type(s?)'}[0] || 'VIRTUAL',
}
}

field_qualifier : not_null
{
$return = {
Expand Down Expand Up @@ -708,7 +717,12 @@ constraint : primary_key_def
| check_def
| <error>

expr : /[^)]* \( [^)]+ \) [^)]*/x # parens, balanced one deep
generated_always : /generated/i /always/i

generated_type : /stored/i
| /virtual/i

expr : /( [^()]* \( (?: (?> [^()]+ ) | (?1) )* \) ) [^)]*/x # parens, recursive balanced
| /[^)]+/

check_def : check_def_begin '(' expr ')'
Expand Down Expand Up @@ -972,7 +986,7 @@ sub parse {

$table->primary_key($field->name) if $fdata->{'is_primary_key'};

for my $qual (qw[ binary unsigned zerofill list collate ], 'character set', 'on update') {
for my $qual (qw[ binary unsigned zerofill list collate generated ], 'character set', 'on update') {
if (my $val = $fdata->{$qual} || $fdata->{ uc $qual }) {
next if ref $val eq 'ARRAY' && !@$val;
$field->extra($qual, $val);
Expand Down
3 changes: 3 additions & 0 deletions lib/SQL/Translator/Producer/MySQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ sub create_field {
my $data_type = $field->data_type;
my @size = $field->size;
my %extra = $field->extra;
my $generated = $extra{'generated'} || {};
my $list = $extra{'list'} || [];
my $commalist = join(', ', map { __PACKAGE__->_quote_string($_) } @$list);
my $charset = $extra{'mysql_charset'};
Expand Down Expand Up @@ -624,6 +625,8 @@ sub create_field {
$field_def .= '(' . join(', ', @size) . ')';
}

$field_def .= " GENERATED ALWAYS AS (" . $generated->{expr} . ") " . $generated->{type} if %$generated;

# char sets
$field_def .= " CHARACTER SET $charset" if $charset;
$field_def .= " COLLATE $collate" if $collate;
Expand Down
38 changes: 37 additions & 1 deletion t/02mysql-parser.t
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ BEGIN {
foo_enabled bit(1) default b'0',
bar_enabled bit(1) default b"1",
long_foo_enabled bit(10) default b'1010101',
g1 int(11) AS (if((1 + 1 = 2),1,NULL)),
g2 int(11) AS (if((1 + 1 = 2),1,(1 + 1))) STORED,
g3 int(11) GENERATED ALWAYS AS (1 + 1) VIRTUAL,
KEY (i1),
UNIQUE (date, i1) USING BTREE,
KEY date_idx (date),
Expand All @@ -103,7 +106,7 @@ BEGIN {
is($table->name, 'check', 'Found "check" table');

my @fields = $table->get_fields;
is(scalar @fields, 13, 'Right number of fields (13)');
is(scalar @fields, 16, 'Right number of fields (16)');
my $f1 = shift @fields;
is($f1->name, 'check_id', 'First field name is "check_id"');
is($f1->data_type, 'int', 'Type is "int"');
Expand Down Expand Up @@ -218,6 +221,39 @@ BEGIN {
is($f13->default_value, '1010101', 'Default value is 1010101');
is($f13->is_primary_key, 0, 'Field is not PK');

my $f14 = shift @fields;
is($f14->name, 'g1', 'Fifth field name is "g1"');
is($f14->data_type, 'int', 'Type is "int"');
is($f14->size, 11, 'Size is "11"');
is($f14->is_nullable, 1, 'Field can be null');
is($f14->default_value, undef, 'Default value is undefined');
is($f14->is_primary_key, 0, 'Field is not PK');
my %f14extra = $f14->extra;
is($f14extra{'generated'}->{expr}, "if((1 + 1 = 2),1,NULL)", 'Generated expr is "if((1 + 1 = 2),1,NULL)"');
is($f14extra{'generated'}->{type}, 'VIRTUAL', 'Generated type is "VIRTUAL"');

my $f15 = shift @fields;
is($f15->name, 'g2', 'Fifth field name is "g2"');
is($f15->data_type, 'int', 'Type is "int"');
is($f15->size, 11, 'Size is "11"');
is($f15->is_nullable, 1, 'Field can be null');
is($f15->default_value, undef, 'Default value is undefined');
is($f15->is_primary_key, 0, 'Field is not PK');
my %f15extra = $f15->extra;
is($f15extra{'generated'}->{expr}, 'if((1 + 1 = 2),1,(1 + 1))', 'Generated expr is "if((1 + 1 = 2),1,(1 + 1))"');
is($f15extra{'generated'}->{type}, 'STORED', 'Generated type is "STORED"');

my $f16 = shift @fields;
is($f16->name, 'g3', 'Fifth field name is "g3"');
is($f16->data_type, 'int', 'Type is "int"');
is($f16->size, 11, 'Size is "11"');
is($f16->is_nullable, 1, 'Field can be null');
is($f16->default_value, undef, 'Default value is undefined');
is($f16->is_primary_key, 0, 'Field is not PK');
my %f16extra = $f16->extra;
is($f16extra{'generated'}->{expr}, '1 + 1', 'Generated expr is "1 + 1"');
is($f16extra{'generated'}->{type}, 'VIRTUAL', 'Generated type is "VIRTUAL"');

my @indices = $table->get_indices;
is(scalar @indices, 3, 'Right number of indices (3)');

Expand Down
53 changes: 52 additions & 1 deletion t/38-mysql-producer.t
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use FindBin qw/$Bin/;
#=============================================================================

BEGIN {
maybe_plan(79, 'YAML', 'SQL::Translator::Producer::MySQL', 'Test::Differences',);
maybe_plan(81, 'YAML', 'SQL::Translator::Producer::MySQL', 'Test::Differences',);
}
use Test::Differences;
use SQL::Translator;
Expand Down Expand Up @@ -168,6 +168,26 @@ schema:
- foo
- bar
- ba'z
g1:
name: g1
data_type: int
size: 11
order: 5
is_not_null: 0
extra:
generated:
expr: if((1 + 1 = 2),1,NULL)
type: STORED
g2:
name: g2
data_type: int
size: 11
order: 6
is_not_null: 0
extra:
generated:
expr: 1 + 1
type: VIRTUAL
indices:
- type: NORMAL
fields:
Expand Down Expand Up @@ -227,6 +247,8 @@ EOSCHEMA
`foo` integer NOT NULL,
`foo2` integer NULL,
`bar_set` set('foo', 'bar', 'ba''z') NULL,
`g1` integer(11) GENERATED ALWAYS AS (if((1 + 1 = 2),1,NULL)) STORED NULL,
`g2` integer(11) GENERATED ALWAYS AS (1 + 1) VIRTUAL NULL,
INDEX `index_1` (`id`),
INDEX `really_long_name_bigger_than_64_chars_aaaaaaaaaaaaaaaaa_aed44c47` (`id`),
INDEX (`foo`),
Expand Down Expand Up @@ -847,6 +869,35 @@ EOV
);
}

{
my $table = SQL::Translator::Schema::Table->new(name => 'table');
my $options = { quote_table_names => '`' };

for my $type (qw/STORED VIRTUAL/) {
my $field = SQL::Translator::Schema::Field->new(
name => 'myfield',
table => $table,
data_type => 'int',
size => 11,
extra => {
generated => {
expr => 'if((1 + 1 = 2),1,NULL)',
type => $type
}
},
is_nullable => 1,
is_foreign_key => 0,
is_unique => 0
);

is(
SQL::Translator::Producer::MySQL::create_field($field, $options),
"`myfield` integer(11) GENERATED ALWAYS AS (if((1 + 1 = 2),1,NULL)) $type NULL",
'valid generated field'
);
}
}

{
my $schema = SQL::Translator::Schema->new();
my $table = $schema->add_table(name => 'foo', fields => ['bar']);
Expand Down