Modified to call translator to get schema rather than passing.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
index 93cdcc3..6b5e631 100644 (file)
@@ -1,7 +1,7 @@
 package SQL::Translator::Parser::MySQL;
 
 # -------------------------------------------------------------------
-# $Id: MySQL.pm,v 1.9 2003-01-27 17:04:45 dlc Exp $
+# $Id: MySQL.pm,v 1.18 2003-05-09 19:51:04 kycl4rk Exp $
 # -------------------------------------------------------------------
 # Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
 #                    darren chamberlain <darren@cpan.org>,
@@ -38,12 +38,93 @@ 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):
+
+  CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
+  [table_options] [select_statement]
+  
+  or
+  
+  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]
+              [PRIMARY KEY] [reference_definition]
+    or    PRIMARY KEY (index_col_name,...)
+    or    KEY [index_name] (index_col_name,...)
+    or    INDEX [index_name] (index_col_name,...)
+    or    UNIQUE [INDEX] [index_name] (index_col_name,...)
+    or    FULLTEXT [INDEX] [index_name] (index_col_name,...)
+    or    [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
+              [reference_definition]
+    or    CHECK (expr)
+  
+  type:
+          TINYINT[(length)] [UNSIGNED] [ZEROFILL]
+    or    SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
+    or    MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
+    or    INT[(length)] [UNSIGNED] [ZEROFILL]
+    or    INTEGER[(length)] [UNSIGNED] [ZEROFILL]
+    or    BIGINT[(length)] [UNSIGNED] [ZEROFILL]
+    or    REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
+    or    DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
+    or    FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
+    or    DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
+    or    NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
+    or    CHAR(length) [BINARY]
+    or    VARCHAR(length) [BINARY]
+    or    DATE
+    or    TIME
+    or    TIMESTAMP
+    or    DATETIME
+    or    TINYBLOB
+    or    BLOB
+    or    MEDIUMBLOB
+    or    LONGBLOB
+    or    TINYTEXT
+    or    TEXT
+    or    MEDIUMTEXT
+    or    LONGTEXT
+    or    ENUM(value1,value2,value3,...)
+    or    SET(value1,value2,value3,...)
+  
+  index_col_name:
+          col_name [(length)]
+  
+  reference_definition:
+          REFERENCES tbl_name [(index_col_name,...)]
+                     [MATCH FULL | MATCH PARTIAL]
+                     [ON DELETE reference_option]
+                     [ON UPDATE reference_option]
+  
+  reference_option:
+          RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
+  
+  table_options:
+          TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
+  or      AUTO_INCREMENT = #
+  or      AVG_ROW_LENGTH = #
+  or      CHECKSUM = {0 | 1}
+  or      COMMENT = "string"
+  or      MAX_ROWS = #
+  or      MIN_ROWS = #
+  or      PACK_KEYS = {0 | 1 | DEFAULT}
+  or      PASSWORD = "string"
+  or      DELAY_KEY_WRITE = {0 | 1}
+  or      ROW_FORMAT= { default | dynamic | fixed | compressed }
+  or      RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=#  RAID_CHUNKSIZE=#
+  or      UNION = (table_name,[table_name...])
+  or      INSERT_METHOD= {NO | FIRST | LAST }
+  or      DATA DIRECTORY="absolute path to directory"
+  or      INDEX DIRECTORY="absolute path to directory"
+
 =cut
 
 use strict;
 use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.9 $ =~ /(\d+)\.(\d+)/;
-$DEBUG   = 1 unless defined $DEBUG;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.18 $ =~ /(\d+)\.(\d+)/;
+$DEBUG   = 0 unless defined $DEBUG;
 
 use Data::Dumper;
 use Parse::RecDescent;
@@ -62,9 +143,19 @@ my $parser; # should we do this?  There's no programmic way to
 
 $GRAMMAR = q!
 
-{ our ( %tables, $table_order ) }
+{ 
+    our ( %tables, $table_order );
+}
+
+#
+# The "eofile" rule makes the parser fail if any "statement" rule
+# fails.  Otherwise, the first successful match by a "statement" 
+# won't cause the failure needed to know that the parse, as a whole,
+# failed. -ky
+#
+startrule : statement(s) eofile { \%tables }
 
-startrule : statement(s) { \%tables }
+eofile : /^\Z/
 
 statement : comment
     | drop
@@ -102,11 +193,13 @@ create : create_table table_name '(' create_definition(s /,/) ')' table_option(s
             }
         }
 
-        for my $opt ( @{ $item{'table_option'} } ) {
+        for my $opt ( @{ $item{'table_option(s?)'} } ) {
             if ( my ( $key, $val ) = each %$opt ) {
                 $tables{ $table_name }{'table_options'}{ $key } = $val;
             }
         }
+
+        1;
     }
 
 create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
@@ -128,9 +221,9 @@ comment : /^\s*(?:#|-{2}).*\n/
 
 blank : /\s*/
 
-field : field_name data_type field_qualifier(s?)
+field : field_name data_type field_qualifier(s?) reference_definition(?)
     { 
-        my %qualifiers = map { %$_ } @{ $item{'field_qualifier'} || [] };
+        my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
         my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
         delete $qualifiers{'not_null'};
         if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
@@ -144,6 +237,7 @@ field : field_name data_type field_qualifier(s?)
             size           => $item{'data_type'}{'size'},
             list           => $item{'data_type'}{'list'},
             null           => $null,
+            constraints    => $item{'reference_definition(?)'},
             %qualifiers,
         } 
     }
@@ -184,9 +278,40 @@ field_qualifier : unsigned
         } 
     }
 
+reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
+    {
+        $return              =  {
+            type             => 'foreign_key',
+            reference_table  => $item[2],
+            reference_fields => $item[3][0],
+            match_type       => $item[4][0],
+            on_delete_do     => $item[5][0],
+            on_update_do     => $item[6][0],
+        }
+    }
+
+match_type : /match full/i { 'match_full' }
+    |
+    /match partial/i { 'match_partial' }
+
+on_delete_do : /on delete/i reference_option
+    { $item[2] }
+
+on_update_do : /on update/i reference_option
+    { $item[2] }
+
+reference_option: /restrict/i | 
+    /cascade/i   | 
+    /set null/i  | 
+    /no action/i | 
+    /set default/i
+    { $item[1] }  
+
 index : primary_key_index
     | unique_index
+    | fulltext_index
     | normal_index
+    | <error>
 
 table_name   : WORD
 
@@ -217,6 +342,9 @@ data_type    : WORD parens_value_list(s?) type_qualifier(s?)
         } 
     }
 
+parens_field_list : '(' field_name(s /,/) ')'
+    { $item[2] }
+
 parens_value_list : '(' VALUE(s /,/) ')'
     { $item[2] }
 
@@ -240,7 +368,7 @@ not_null     : /not/i /null/i { $return = 0 }
 
 unsigned     : /unsigned/i { $return = 0 }
 
-default_val  : /default/i /(?:')?[\w\d.-]*(?:')?/ 
+default_val  : /default/i /(?:')?[\w\d:.-]*(?:')?/ 
     { 
         $item[2] =~ s/'//g; 
         $return  =  $item[2];
@@ -253,16 +381,16 @@ primary_key : /primary/i /key/i { 1 }
 primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
     { 
         $return    = { 
-            name   => $item{'index_name'}[0],
+            name   => $item{'index_name(?)'}[0],
             type   => 'primary_key',
             fields => $item[4],
-        } 
+        };
     }
 
 normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
     { 
         $return    = { 
-            name   => $item{'index_name'}[0],
+            name   => $item{'index_name(?)'}[0],
             type   => 'normal',
             fields => $item[4],
         } 
@@ -271,15 +399,26 @@ normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
 unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
     { 
         $return    = { 
-            name   => $item{'index_name'}[0],
+            name   => $item{'index_name(?)'}[0],
             type   => 'unique',
             fields => $item[5],
         } 
     }
 
+fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
+    { 
+        $return    = { 
+            name   => $item{'index_name(?)'}[0],
+            type   => 'fulltext',
+            fields => $item[5],
+        } 
+    }
+
 name_with_opt_paren : NAME parens_value_list(s?)
     { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
 
+fulltext : /fulltext/i { 1 }
+
 unique : /unique/i { 1 }
 
 key : /key/i | /index/i
@@ -306,6 +445,12 @@ VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
     { $item[1] }
     | /NULL/
     { 'NULL' }
+#    {
+#        {
+#            value     => $item[1],
+#            attribute => $item[2]
+#        }
+#    }
 
 !;
 
@@ -314,8 +459,8 @@ sub parse {
     my ( $translator, $data ) = @_;
     $parser ||= Parse::RecDescent->new($GRAMMAR);
 
-    $::RD_TRACE  = $translator->trace ? 1 : undef;
-    $DEBUG       = $translator->debug;
+    local $::RD_TRACE  = $translator->trace ? 1 : undef;
+    local $DEBUG       = $translator->debug;
 
     unless (defined $parser) {
         return $translator->error("Error instantiating Parse::RecDescent ".
@@ -324,26 +469,53 @@ sub parse {
 
     my $result = $parser->startrule($data);
     die "Parse failed.\n" unless defined $result;
-    warn Dumper($result) if $DEBUG;
+    warn Dumper( $result ) if $DEBUG;
+
+    my $schema = $translator->schema;
+    for my $table_name ( keys %{ $result } ) {
+        my $tdata =  $result->{ $table_name };
+        my $table =  $schema->add_table( 
+            name  => $tdata->{'table_name'},
+        );
+
+        my @fields = sort { 
+            $tdata->{'fields'}->{$a}->{'order'} 
+            <=>
+            $tdata->{'fields'}->{$b}->{'order'}
+        } keys %{ $tdata->{'fields'} };
+
+        for my $fname ( @fields ) {
+            my $fdata = $tdata->{'fields'}{ $fname };
+            my $field = $table->add_field(
+                name              => $fdata->{'name'},
+                data_type         => $fdata->{'data_type'},
+                size              => $fdata->{'size'},
+                default_value     => $fdata->{'default'},
+                is_auto_increment => $fdata->{'is_auto_inc'},
+                is_nullable       => $fdata->{'null'},
+            );
+        }
+    }
+
     return $result;
 }
 
 1;
 
-#-----------------------------------------------------
+# ----------------------------------------------------
 # Where man is not nature is barren.
 # William Blake
-#-----------------------------------------------------
+# ----------------------------------------------------
 
 =pod
 
 =head1 AUTHOR
 
 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
-Chris Mungall
+Chris Mungall E<lt>cjm@fruitfly.orgE<gt>.
 
 =head1 SEE ALSO
 
-perl(1), Parse::RecDescent.
+perl(1), Parse::RecDescent, SQL::Translator::Schema.
 
 =cut