Applied Dave Howorth's MySQL parser patches
Mark Addison [Thu, 25 Nov 2004 22:32:48 +0000 (22:32 +0000)]
- ignore INSERT statements
- permit ALTER TABLE ADD FOREIGN KEY
- allow trailing comma on last field in CREATE statements
- collect the database name

Changes
lib/SQL/Translator/Parser/MySQL.pm
t/02mysql-parser.t

diff --git a/Changes b/Changes
index 51639e7..120ee4a 100644 (file)
--- a/Changes
+++ b/Changes
@@ -4,6 +4,12 @@
 *   Refactoring: Added SQL::Translator::Schema::Object - base 
     class for all Schema objects.
 
+*   Changes to MySQL Parser (Dave Howorth)
+    - ignore INSERT statements
+    - permit ALTER TABLE ADD FOREIGN KEY
+    - allow trailing comma on last field in CREATE statements
+    - collect the database name
+
 # -----------------------------------------------------------
 # 0.06    2004-05-13
 # -----------------------------------------------------------
index c0a2144..69ad29f 100644 (file)
@@ -1,7 +1,7 @@
 package SQL::Translator::Parser::MySQL;
 
 # -------------------------------------------------------------------
-# $Id: MySQL.pm,v 1.44 2004-03-01 17:39:22 kycl4rk Exp $
+# $Id: MySQL.pm,v 1.45 2004-11-25 22:32:48 grommit Exp $
 # -------------------------------------------------------------------
 # Copyright (C) 2002-4 SQLFairy Authors
 #
@@ -117,11 +117,24 @@ Here's the word from the MySQL site
   or      DATA DIRECTORY="absolute path to directory"
   or      INDEX DIRECTORY="absolute path to directory"
 
+A subset of the ALTER TABLE syntax that allows addition of foreign keys:
+
+  ALTER [IGNORE] TABLE tbl_name alter_specification [, alter_specification] ...
+
+  alter_specification:
+          ADD [CONSTRAINT [symbol]]
+          FOREIGN KEY [index_name] (index_col_name,...)
+             [reference_definition]
+
+A subset of INSERT that we ignore:
+
+  INSERT anything
+
 =cut
 
 use strict;
 use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.44 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.45 $ =~ /(\d+)\.(\d+)/;
 $DEBUG   = 0 unless defined $DEBUG;
 
 use Data::Dumper;
@@ -139,7 +152,7 @@ $::RD_HINT   = 1; # Give out hints to help fix problems.
 $GRAMMAR = q!
 
 { 
-    my ( %tables, $table_order, @table_comments );
+    my ( $database_name, %tables, $table_order, @table_comments );
 }
 
 #
@@ -148,7 +161,9 @@ $GRAMMAR = q!
 # won't cause the failure needed to know that the parse, as a whole,
 # failed. -ky
 #
-startrule : statement(s) eofile { \%tables }
+startrule : statement(s) eofile { 
+    { tables => \%tables, database_name => $database_name } 
+}
 
 eofile : /^\Z/
 
@@ -157,10 +172,15 @@ statement : comment
     | set
     | drop
     | create
+    | alter
+    | insert
     | <error>
 
 use : /use/i WORD ';'
-    { @table_comments = () }
+    {
+        $database_name = $item[2];
+        @table_comments = ();
+    }
 
 set : /set/i /[^;]+/ ';'
     { @table_comments = () }
@@ -170,10 +190,26 @@ drop : /drop/i TABLE /[^;]+/ ';'
 drop : /drop/i WORD(s) ';'
     { @table_comments = () }
 
+insert : /insert/i  /[^;]+/ ';'
+
+alter : ALTER TABLE table_name alter_specification(s /,/) ';'
+    {
+        my $table_name                       = $item{'table_name'};
+    die "Cannot ALTER table '$table_name'; it does not exist"
+        unless $tables{ $table_name };
+        for my $definition ( @{ $item[4] } ) { 
+        $definition->{'extra'}->{'alter'} = 1;
+        push @{ $tables{ $table_name }{'constraints'} }, $definition;
+    }
+    }
+
+alter_specification : ADD foreign_key_def
+    { $return = $item[2] }
+
 create : CREATE /database/i WORD ';'
     { @table_comments = () }
 
-create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
+create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) ';'
     { 
         my $table_name                       = $item{'table_name'};
         $tables{ $table_name }{'order'}      = ++$table_order;
@@ -533,6 +569,10 @@ table_option : WORD /\s*=\s*/ WORD
         $return = { $item[1] => $item[3] };
     }
 
+ADD : /add/i
+
+ALTER : /alter/i
+
 CREATE : /create/i
 
 TEMPORARY : /temporary/i
@@ -579,15 +619,19 @@ sub parse {
 
     my $result = $parser->startrule($data);
     return $translator->error( "Parse failed." ) unless defined $result;
-    warn Dumper( $result ) if $DEBUG;
+    warn "Parse result:".Dumper( $result ) if $DEBUG;
 
     my $schema = $translator->schema;
+    $schema->name($result->{'database_name'}) if $result->{'database_name'};
+
     my @tables = sort { 
-        $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
-    } keys %{ $result };
+        $result->{'tables'}{ $a }{'order'} 
+        <=> 
+        $result->{'tables'}{ $b }{'order'}
+    } keys %{ $result->{'tables'} };
 
     for my $table_name ( @tables ) {
-        my $tdata =  $result->{ $table_name };
+        my $tdata =  $result->{tables}{ $table_name };
         my $table =  $schema->add_table( 
             name  => $tdata->{'table_name'},
         ) or die $schema->error;
index 1839795..64c3c1b 100644 (file)
@@ -10,7 +10,7 @@ use SQL::Translator::Schema::Constants;
 use Test::SQL::Translator qw(maybe_plan);
 
 BEGIN {
-    maybe_plan(180, "SQL::Translator::Parser::MySQL");
+    maybe_plan(199, "SQL::Translator::Parser::MySQL");
     SQL::Translator::Parser::MySQL->import('parse');
 }
 
@@ -389,3 +389,77 @@ BEGIN {
     my @t2_fields = $t2->get_fields;
     is( scalar @t2_fields, 8, 'Right number of fields (8)' );
 }
+
+# djh Tests for:
+#    USE database ;
+#    ALTER TABLE ADD FOREIGN KEY
+#    trailing comma on last create definition
+#    Ignoring INSERT statements
+#
+{
+    my $tr = SQL::Translator->new;
+    my $data = parse($tr, 
+        q[
+            USE database_name;
+
+            CREATE TABLE one (
+              id                     integer NOT NULL auto_increment,
+              two_id                 integer NOT NULL auto_increment,
+              some_data              text,
+              PRIMARY KEY (id),
+              INDEX (two_id),
+            ) TYPE=INNODB;
+
+            CREATE TABLE two (
+              id                     int NOT NULL auto_increment,
+              one_id                 int NOT NULL auto_increment,
+              some_data              text,
+              PRIMARY KEY (id),
+              INDEX (one_id),
+              FOREIGN KEY (one_id) REFERENCES one (id),
+            ) TYPE=INNODB;
+
+            ALTER TABLE one ADD FOREIGN KEY (two_id) REFERENCES two (id);
+
+            INSERT absolutely *#! any old $£ ? rubbish ;
+        ]
+    ) or die $tr->error;
+
+    my $schema = $tr->schema;
+    is( $schema->is_valid, 1, 'Schema is valid' );
+    my $db_name = $schema->name;
+    is( $db_name, 'database_name', 'Database name extracted from USE' );
+    my @tables = $schema->get_tables;
+    is( scalar @tables, 2, 'Right number of tables (2)' );
+    my $table1 = shift @tables;
+    is( $table1->name, 'one', 'Found "one" table' );
+    my $table2 = shift @tables;
+    is( $table2->name, 'two', 'Found "two" table' );
+
+    my @constraints = $table1->get_constraints;
+    is(scalar @constraints, 2, 'Right number of constraints (2) on table one');
+
+    my $t1c1 = shift @constraints;
+    is( $t1c1->type, PRIMARY_KEY, 'Constraint is a PK' );
+    is( join(',', $t1c1->fields), 'id', 'Constraint is on "id"' );
+
+    my $t1c2 = shift @constraints;
+    is( $t1c2->type, FOREIGN_KEY, 'Constraint is a FK' );
+    is( join(',', $t1c2->fields), 'two_id', 'Constraint is on "two_id"' );
+    is( $t1c2->reference_table, 'two', 'To table "two"' );
+    is( join(',', $t1c2->reference_fields), 'id', 'To field "id"' );
+
+    @constraints = $table2->get_constraints;
+    is(scalar @constraints, 2, 'Right number of constraints (2) on table two');
+
+    my $t2c1 = shift @constraints;
+    is( $t2c1->type, PRIMARY_KEY, 'Constraint is a PK' );
+    is( join(',', $t2c1->fields), 'id', 'Constraint is on "id"' );
+
+    my $t2c2 = shift @constraints;
+    is( $t2c2->type, FOREIGN_KEY, 'Constraint is a FK' );
+    is( join(',', $t2c2->fields), 'one_id', 'Constraint is on "one_id"' );
+    is( $t2c2->reference_table, 'one', 'To table "one"' );
+    is( join(',', $t2c2->reference_fields), 'id', 'To field "id"' );
+}
+