Added grammar for "REFERENCES" (foreign keys).
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
index ece96e5..8446af4 100644 (file)
@@ -1,10 +1,11 @@
 package SQL::Translator::Parser::MySQL;
 
 # -------------------------------------------------------------------
-# $Id: MySQL.pm,v 1.8 2002-11-28 04:21:06 kycl4rk Exp $
+# $Id: MySQL.pm,v 1.13 2003-04-02 01:46:36 kycl4rk Exp $
 # -------------------------------------------------------------------
-# Copyright (C) 2002 Ken Y. Clark <kclark@cpan.org>,
-#                    darren chamberlain <darren@cpan.org>
+# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
+#                    darren chamberlain <darren@cpan.org>,
+#                    Chris Mungall <cjm@fruitfly.org>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -37,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.8 $ =~ /(\d+)\.(\d+)/;
-$DEBUG   = 1 unless defined $DEBUG;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.13 $ =~ /(\d+)\.(\d+)/;
+$DEBUG   = 0 unless defined $DEBUG;
 
 use Data::Dumper;
 use Parse::RecDescent;
@@ -63,7 +145,15 @@ $GRAMMAR = q!
 
 { our ( %tables, $table_order ) }
 
-startrule : statement(s) { \%tables }
+#
+# 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 }
+
+eofile : /^\Z/
 
 statement : comment
     | drop
@@ -127,7 +217,7 @@ 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 $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
@@ -143,6 +233,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,
         } 
     }
@@ -183,8 +274,38 @@ 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
 
 table_name   : WORD
@@ -216,6 +337,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] }
 
@@ -276,9 +400,20 @@ unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
         } 
     }
 
+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
@@ -305,6 +440,12 @@ VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
     { $item[1] }
     | /NULL/
     { 'NULL' }
+#    {
+#        {
+#            value     => $item[1],
+#            attribute => $item[2]
+#        }
+#    }
 
 !;