Properly tag our XML namespace URI - it is not a real link
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / PostgreSQL.pm
index 56dd4a9..ac59c4b 100644 (file)
@@ -87,25 +87,22 @@ View table:
 =cut
 
 use strict;
-use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
-$VERSION = '1.59';
+use warnings;
+
+our $VERSION = '1.59';
+
+our $DEBUG;
 $DEBUG   = 0 unless defined $DEBUG;
 
 use Data::Dumper;
-use Parse::RecDescent;
-use Exporter;
-use base qw(Exporter);
+use SQL::Translator::Utils qw/ddl_parser_instance/;
 
-@EXPORT_OK = qw(parse);
-
-# Enable warnings within the Parse::RecDescent module.
-$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
-$::RD_WARN   = 1; # Enable warnings. This will warn on unused rules &c.
-$::RD_HINT   = 1; # Give out hints to help fix problems.
+use base qw(Exporter);
+our @EXPORT_OK = qw(parse);
 
-$GRAMMAR = q!
+our $GRAMMAR = <<'END_OF_GRAMMAR';
 
-{ my ( %tables, @views, $table_order, $field_order, @table_comments) }
+{ my ( %tables, @views, @triggers, $table_order, $field_order, @table_comments) }
 
 #
 # The "eofile" rule makes the parser fail if any "statement" rule
@@ -113,11 +110,16 @@ $GRAMMAR = q!
 # won't cause the failure needed to know that the parse, as a whole,
 # failed. -ky
 #
-startrule : statement(s) eofile { { tables => \%tables, views => \@views } }
+startrule : statement(s) eofile {
+    {
+        tables => \%tables,
+        views => \@views,
+        triggers => \@triggers,
+    }
+}
 
 eofile : /^\Z/
 
-
 statement : create
   | comment_on_table
   | comment_on_column
@@ -139,7 +141,7 @@ statement : create
 
 commit : /commit/i ';'
 
-connect : /^\s*\\\connect.*\n/
+connect : /^\s*\\connect.*\n/
 
 set : /set/i /[^;]*/ ';'
 
@@ -176,7 +178,7 @@ grant : /grant/i WORD(s /,/) /on/i SCHEMA(?) schema_name /to/i name_with_opt_quo
 drop : /drop/i /[^;]*/ ';'
 
 string :
-   /'(\\.|''|[^\\\'])*'/
+   /'(\.|''|[^\\'])*'/
 
 nonstring : /[^;\'"]+/
 
@@ -261,6 +263,34 @@ create : CREATE or_replace(?) temporary(?) VIEW view_id view_fields(?) /AS/i vie
         }
     }
 
+trigger_name : name_with_opt_quotes
+
+trigger_scope : /FOR/i /EACH/i /(ROW|STATEMENT)/i { $return = lc $1 }
+
+before_or_after : /(before|after)/i { $return = lc $1 }
+
+trigger_action : /.+/
+
+database_event : /insert|update|delete/i
+database_events : database_event(s /OR/)
+
+create : CREATE /TRIGGER/i trigger_name before_or_after database_events /ON/i table_id trigger_scope(?) trigger_action
+    {
+        # Hack to pass roundtrip tests which have trigger statements terminated by double semicolon
+        # and expect the returned data to have the same
+        my $action = $item{trigger_action};
+        $action =~ s/;$//;
+
+        push @triggers, {
+            name => $item{trigger_name},
+            perform_action_when => $item{before_or_after},
+            database_events => $item{database_events},
+            on_table => $item{table_id}{table_name},
+            scope => $item{'trigger_scope(?)'}[0],
+            action => $action,
+        }
+    }
+
 #
 # Create anything else (e.g., domain, etc.)
 #
@@ -492,6 +522,7 @@ double_quote: /"/
 
 index_name : name_with_opt_quotes
 
+
 data_type : pg_data_type parens_value_list(?)
     {
         my $data_type = $item[1];
@@ -791,7 +822,7 @@ alter : alter_sequence NAME /owned/i /by/i column_name ';'
 
 storage_type : /(plain|external|extended|main)/i
 
-temporary : /temp(orary)?\\b/i
+temporary : /temp(orary)?\b/i
   {
     1;
   }
@@ -900,10 +931,11 @@ create_table : CREATE TABLE
 
 create_index : CREATE /index/i
 
-default_val  : DEFAULT /(\d+|'[^']*'|\w+\(.*\))|\w+/
+default_val  : DEFAULT /(\d+|'[^']*'|\w+\(.*\))|\w+|\(\d+\)/ ( '::' data_type )(?)
     {
         my $val =  defined $item[2] ? $item[2] : '';
         $val    =~ s/^'|'$//g;
+        $val =~ s/^\((\d+)\)\z/$1/; # for example (0)::smallint
         $return =  {
             supertype => 'constraint',
             type      => 'default',
@@ -988,19 +1020,20 @@ VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
     | /null/i
     { 'NULL' }
 
-!;
+END_OF_GRAMMAR
 
 sub parse {
     my ( $translator, $data ) = @_;
-    my $parser = Parse::RecDescent->new($GRAMMAR);
 
-    $::RD_TRACE  = $translator->trace ? 1 : undef;
-    $DEBUG       = $translator->debug;
+    # Enable warnings within the Parse::RecDescent module.
+    local $::RD_ERRORS = 1 unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
+    local $::RD_WARN   = 1 unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
+    local $::RD_HINT   = 1 unless defined $::RD_HINT; # Give out hints to help fix problems.
 
-    unless (defined $parser) {
-        return $translator->error("Error instantiating Parse::RecDescent ".
-            "instance: Bad grammer");
-    }
+    local $::RD_TRACE  = $translator->trace ? 1 : undef;
+    local $DEBUG       = $translator->debug;
+
+    my $parser = ddl_parser_instance('PostgreSQL');
 
     my $result = $parser->startrule($data);
     die "Parse failed.\n" unless defined $result;
@@ -1087,6 +1120,10 @@ sub parse {
       $view->extra ( temporary => 1 ) if $vinfo->{is_temporary};
     }
 
+    for my $trigger (@{ $result->{triggers} }) {
+        $schema->add_trigger( %$trigger );
+    }
+
     return 1;
 }