diff --git a/Changes b/Changes index d020bfec0..5b7ce6ca2 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,7 @@ Changes for SQL::Translator + * Support INCLUDE on indices for Pg (producer + parser) + 1.62 - 2020-09-14 * Update Pg support to allow version 12 (still supporting back to 7.4) diff --git a/lib/SQL/Translator/Parser/PostgreSQL.pm b/lib/SQL/Translator/Parser/PostgreSQL.pm index 37f657996..16d99548b 100644 --- a/lib/SQL/Translator/Parser/PostgreSQL.pm +++ b/lib/SQL/Translator/Parser/PostgreSQL.pm @@ -15,10 +15,10 @@ SQL::Translator::Parser::PostgreSQL - parser for PostgreSQL =head1 DESCRIPTION The grammar was started from the MySQL parsers. Here is the description -from PostgreSQL: +from PostgreSQL, truncated to what's currently supported (patches welcome, of course) : Table: -(http://www.postgresql.org/docs/view.php?version=7.3&idoc=1&file=sql-createtable.html) +(http://www.postgresql.org/docs/current/sql-createtable.html) CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name ( { column_name data_type [ DEFAULT default_expr ] @@ -51,11 +51,12 @@ Table: [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] -Index: -(http://www.postgresql.org/docs/view.php?version=7.3&idoc=1&file=sql-createindex.html) +Index : +(http://www.postgresql.org/docs/current/sql-createindex.html) CREATE [ UNIQUE ] INDEX index_name ON table [ USING acc_method ] ( column [ ops_name ] [, ...] ) + [ INCLUDE ( column [, ...] ) ] [ WHERE predicate ] CREATE [ UNIQUE ] INDEX index_name ON table [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] ) @@ -80,7 +81,7 @@ Alter table: ALTER TABLE table OWNER TO new_owner -View table: +View : CREATE [ OR REPLACE ] VIEW view [ ( column name list ) ] AS SELECT query @@ -236,7 +237,7 @@ create : CREATE temporary(?) TABLE table_id '(' create_definition(s? /,/) ')' ta 1; } -create : CREATE unique(?) /(index|key)/i index_name /on/i table_id using_method(?) '(' field_name(s /,/) ')' where_predicate(?) ';' +create : CREATE unique(?) /(index|key)/i index_name /on/i table_id using_method(?) '(' field_name(s /,/) ')' include_covering(?) where_predicate(?) ';' { my $table_info = $item{'table_id'}; my $schema_name = $table_info->{'schema_name'}; @@ -249,6 +250,7 @@ create : CREATE unique(?) /(index|key)/i index_name /on/i table_id using_method( fields => $item[9], method => $item{'using_method(?)'}[0], where => $item{'where_predicate(?)'}[0], + include => $item{'include_covering(?)'}[0] } ; } @@ -302,6 +304,9 @@ using_method : /using/i WORD { $item[2] } where_predicate : /where/i /[^;]+/ +include_covering : /include/i '(' covering_field_name(s /,/) ')' + { $item{'covering_field_name(s)'} } + create_definition : field | table_constraint | @@ -502,6 +507,8 @@ schema_name : NAME field_name : NAME +covering_field_name : NAME + double_quote: /"/ index_name : NAME @@ -1088,6 +1095,7 @@ sub parse { my @options = (); push @options, { using => $idata->{'method'} } if $idata->{method}; push @options, { where => $idata->{'where'} } if $idata->{where}; + push @options, { include => $idata->{'include'} } if $idata->{include}; my $index = $table->add_index( name => $idata->{'name'}, type => uc $idata->{'type'}, diff --git a/lib/SQL/Translator/Producer/PostgreSQL.pm b/lib/SQL/Translator/Producer/PostgreSQL.pm index 7520563ff..22ba7317b 100644 --- a/lib/SQL/Translator/Producer/PostgreSQL.pm +++ b/lib/SQL/Translator/Producer/PostgreSQL.pm @@ -129,6 +129,7 @@ and table_constraint is: CREATE [ UNIQUE ] INDEX index_name ON table [ USING acc_method ] ( column [ ops_name ] [, ...] ) + [ INCLUDE ( column [, ...] ) ] [ WHERE predicate ] CREATE [ UNIQUE ] INDEX index_name ON table [ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] ) @@ -296,6 +297,7 @@ sub create_table for my $index ( $table->get_indices ) { my ($idef, $constraints) = create_index($index, { generator => $generator, + postgres_version => $postgres_version, }); $idef and push @index_defs, $idef; push @constraint_defs, @$constraints; @@ -497,6 +499,7 @@ sub create_geometry_constraints { my $generator = _generator($options); my $table_name = $index->table->name; + my $postgres_version = $options->{postgres_version} || 0; my ($index_def, @constraint_defs); @@ -508,18 +511,23 @@ sub create_geometry_constraints { my @fields = $index->fields; return unless @fields; - my $index_using; - my $index_where; + my %index_extras; for my $opt ( $index->options ) { if ( ref $opt eq 'HASH' ) { foreach my $key (keys %$opt) { my $value = $opt->{$key}; next unless defined $value; if ( uc($key) eq 'USING' ) { - $index_using = "USING $value"; + $index_extras{using} = "USING $value"; } elsif ( uc($key) eq 'WHERE' ) { - $index_where = "WHERE $value"; + $index_extras{where} = "WHERE $value"; + } + elsif ( uc($key) eq 'INCLUDE' ) { + next unless $postgres_version >= 11; + die 'Include list must be an arrayref' unless ref $value eq 'ARRAY'; + my $value_list = join ', ', @$value; + $index_extras{include} = "INCLUDE ($value_list)" } } } @@ -536,7 +544,7 @@ sub create_geometry_constraints { elsif ( $type eq NORMAL ) { $index_def = 'CREATE INDEX ' . $generator->quote($name) . ' on ' . $generator->quote($table_name) . ' ' . - join ' ', grep { defined } $index_using, $field_names, $index_where; + join ' ', grep { defined } $index_extras{using}, $field_names, @index_extras{'include', 'where'}; } else { warn "Unknown index type ($type) on table $table_name.\n" diff --git a/t/14postgres-parser.t b/t/14postgres-parser.t index 3c9dcd449..30a0d6833 100644 --- a/t/14postgres-parser.t +++ b/t/14postgres-parser.t @@ -78,6 +78,7 @@ baz $foo$, CREATE INDEX test_index1 ON t_test1 (f_varchar); CREATE INDEX test_index2 ON t_test1 USING hash (f_char, f_bool); CREATE INDEX test_index3 ON t_test1 USING hash (f_bigint, f_tz) WHERE f_bigint = '1' AND f_tz IS NULL; + CREATE INDEX test_index4 ON t_test1 USING hash (f_bigint, f_tz) include (f_bool) WHERE f_bigint = '1' AND f_tz IS NULL; alter table t_test1 add f_fk2 integer; @@ -406,7 +407,7 @@ is( $trigger->action, 'EXECUTE PROCEDURE foo()', "Correct action for trigger"); # test index my @indices = $t1->get_indices; -is(scalar @indices, 3, 'got three indexes'); +is(scalar @indices, 4, 'got three indexes'); my $t1_i1 = $indices[0]; is( $t1_i1->name, 'test_index1', 'First index is "test_index1"' ); @@ -427,4 +428,17 @@ is_deeply( 'Index is using hash method and has predicate right' ); +my $t1_i4 = $indices[3]; +is( $t1_i4->name, 'test_index4', 'Fourth index is "test_index4"' ); +is( join(',', $t1_i4->fields), 'f_bigint,f_tz', 'Index is on fields "f_bigint, f_tz"' ); +is_deeply( + [ $t1_i4->options ], + [ + { using => 'hash' }, + { where => "f_bigint = '1' AND f_tz IS NULL" }, + { include => [ 'f_bool' ] } + ], + 'Index is using hash method and has predicate right and include INCLUDE' +); + done_testing; diff --git a/t/47postgres-producer.t b/t/47postgres-producer.t index 9c50db736..ec6fbf234 100644 --- a/t/47postgres-producer.t +++ b/t/47postgres-producer.t @@ -655,6 +655,14 @@ is($view2_sql1, $view2_sql_replace, 'correct "CREATE OR REPLACE VIEW" SQL 2'); is($def, 'CREATE INDEX "myindex" on "foobar" ("bar", lower(foo))', 'index created w/ quotes'); } + { + my $index = $table->add_index(name => 'covering', fields => ['bar'], options => { include => [ 'lower(foo)', 'baz' ] }); + my ($def) = SQL::Translator::Producer::PostgreSQL::create_index($index); + is($def, "CREATE INDEX covering on foobar (bar)", 'skip if postgres is too old'); + ($def) = SQL::Translator::Producer::PostgreSQL::create_index($index, { postgres_version => 11 }); + is($def, "CREATE INDEX covering on foobar (bar) INCLUDE (lower(foo), baz)", 'index created'); + } + { my $constr = $table->add_constraint(name => 'constr', type => UNIQUE, fields => ['foo']); my ($def) = SQL::Translator::Producer::PostgreSQL::create_constraint($constr);