Committing new Access parser (yeah, Bill, we're coming after you now).
Ken Youens-Clark [Mon, 19 Apr 2004 16:38:17 +0000 (16:38 +0000)]
lib/SQL/Translator/Parser/Access.pm [new file with mode: 0644]

diff --git a/lib/SQL/Translator/Parser/Access.pm b/lib/SQL/Translator/Parser/Access.pm
new file mode 100644 (file)
index 0000000..6196f3b
--- /dev/null
@@ -0,0 +1,520 @@
+package SQL::Translator::Parser::Access;
+
+# -------------------------------------------------------------------
+# $Id: Access.pm,v 1.1 2004-04-19 16:38:17 kycl4rk Exp $
+# -------------------------------------------------------------------
+# Copyright (C) 2002-4 SQLFairy Authors
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+# 02111-1307  USA
+# -------------------------------------------------------------------
+
+=head1 NAME
+
+SQL::Translator::Parser::Access - parser for Access as produced by mdbtools
+
+=head1 SYNOPSIS
+
+  use SQL::Translator;
+  use SQL::Translator::Parser::Access;
+
+  my $translator = SQL::Translator->new;
+  $translator->parser("SQL::Translator::Parser::Access");
+
+=head1 DESCRIPTION
+
+The grammar derived from the MySQL grammar.  The input is expected to be 
+something similar to the output of mdbtools (http://mdbtools.sourceforge.net/).
+
+=cut
+
+use strict;
+use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
+$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
+$DEBUG   = 0 unless defined $DEBUG;
+
+use Data::Dumper;
+use Parse::RecDescent;
+use Exporter;
+use base qw(Exporter);
+
+@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.
+
+$GRAMMAR = q!
+
+{ 
+    my ( %tables, $table_order, @table_comments );
+}
+
+#
+# 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
+    | use
+    | set
+    | drop
+    | create
+    | <error>
+
+use : /use/i WORD ';'
+    { @table_comments = () }
+
+set : /set/i /[^;]+/ ';'
+    { @table_comments = () }
+
+drop : /drop/i TABLE /[^;]+/ ';'
+
+drop : /drop/i WORD(s) ';'
+    { @table_comments = () }
+
+create : CREATE /database/i WORD ';'
+    { @table_comments = () }
+
+create : CREATE TABLE table_name '(' create_definition(s /,/) ')' ';'
+    { 
+        my $table_name                       = $item{'table_name'};
+        $tables{ $table_name }{'order'}      = ++$table_order;
+        $tables{ $table_name }{'table_name'} = $table_name;
+
+        if ( @table_comments ) {
+            $tables{ $table_name }{'comments'} = [ @table_comments ];
+            @table_comments = ();
+        }
+
+        my $i = 1;
+        for my $definition ( @{ $item[5] } ) {
+            if ( $definition->{'supertype'} eq 'field' ) {
+                my $field_name = $definition->{'name'};
+                $tables{ $table_name }{'fields'}{ $field_name } = 
+                    { %$definition, order => $i };
+                $i++;
+        
+                if ( $definition->{'is_primary_key'} ) {
+                    push @{ $tables{ $table_name }{'constraints'} },
+                        {
+                            type   => 'primary_key',
+                            fields => [ $field_name ],
+                        }
+                    ;
+                }
+            }
+            elsif ( $definition->{'supertype'} eq 'constraint' ) {
+                push @{ $tables{ $table_name }{'constraints'} }, $definition;
+            }
+            elsif ( $definition->{'supertype'} eq 'index' ) {
+                push @{ $tables{ $table_name }{'indices'} }, $definition;
+            }
+        }
+
+        1;
+    }
+
+create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
+    {
+        @table_comments = ();
+        push @{ $tables{ $item{'table_name'} }{'indices'} },
+            {
+                name   => $item[4],
+                type   => $item[2] ? 'unique' : 'normal',
+                fields => $item[8],
+            }
+        ;
+    }
+
+create_definition : constraint 
+    | index
+    | field
+    | comment
+    | <error>
+
+comment : /^\s*--(.*)\n/ 
+    { 
+        my $comment =  $1;
+        $return     = $comment;
+        push @table_comments, $comment;
+    }
+
+field : field_name data_type field_qualifier(s?) reference_definition(?)
+    { 
+#        my %qualifiers  = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
+#        if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
+#            $qualifiers{ $_ } = 1 for @type_quals;
+#        }
+#
+#        my $null = defined $qualifiers{'not_null'} 
+#                   ? $qualifiers{'not_null'} : 1;
+#        delete $qualifiers{'not_null'};
+
+        $return = { 
+            supertype   => 'field',
+            name        => $item{'field_name'}, 
+            data_type   => $item{'data_type'}{'type'},
+            size        => $item{'data_type'}{'size'},
+#            null        => $null,
+            constraints => $item{'reference_definition(?)'},
+#            %qualifiers,
+        } 
+    }
+    | <error>
+
+field_qualifier : not_null
+    { 
+        $return = { 
+             null => $item{'not_null'},
+        } 
+    }
+
+field_qualifier : default_val
+    { 
+        $return = { 
+             default => $item{'default_val'},
+        } 
+    }
+
+field_qualifier : auto_inc
+    { 
+        $return = { 
+             is_auto_inc => $item{'auto_inc'},
+        } 
+    }
+
+field_qualifier : primary_key
+    { 
+        $return = { 
+             is_primary_key => $item{'primary_key'},
+        } 
+    }
+
+field_qualifier : unsigned
+    { 
+        $return = { 
+             is_unsigned => $item{'unsigned'},
+        } 
+    }
+
+field_qualifier : /character set/i WORD
+    {
+        $return = {
+            character_set => $item[2],
+        }
+    }
+
+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 { 'full' }
+    |
+    /match partial/i { '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 : normal_index
+    | fulltext_index
+    | <error>
+
+table_name   : NAME
+
+field_name   : NAME
+
+index_name   : NAME
+
+data_type    : access_data_type parens_value_list(s?) type_qualifier(s?)
+    { 
+        $return        = { 
+            type       => $item[1],
+            size       => $item[2][0],
+            qualifiers => $item[3],
+        } 
+    }
+
+access_data_type : /long integer/i { $return = 'Long Integer' }
+    | /text/i { $return = 'Text' }
+    | /datetime (\(short\))?/i { $return = 'DateTime' }
+    | /boolean/i { $return = 'Boolean' }
+    | WORD
+
+parens_field_list : '(' field_name(s /,/) ')'
+    { $item[2] }
+
+parens_value_list : '(' VALUE(s /,/) ')'
+    { $item[2] }
+
+type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
+    { lc $item[1] }
+
+field_type   : WORD
+
+create_index : /create/i /index/i
+
+not_null     : /not/i /null/i { $return = 0 }
+
+unsigned     : /unsigned/i { $return = 0 }
+
+default_val : /default/i /'(?:.*?\\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
+    {
+        $item[2] =~ s/^\s*'|'\s*$//g;
+        $return  =  $item[2];
+    }
+
+auto_inc : /auto_increment/i { 1 }
+
+primary_key : /primary/i /key/i { 1 }
+
+constraint : primary_key_def
+    | unique_key_def
+    | foreign_key_def
+    | <error>
+
+foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
+    {
+        $return              =  {
+            supertype        => 'constraint',
+            type             => 'foreign_key',
+            name             => $item[1],
+            fields           => $item[2],
+            %{ $item{'reference_definition'} },
+        }
+    }
+
+foreign_key_def_begin : /constraint/i /foreign key/i 
+    { $return = '' }
+    |
+    /constraint/i WORD /foreign key/i
+    { $return = $item[2] }
+    |
+    /foreign key/i
+    { $return = '' }
+
+primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
+    { 
+        $return       = { 
+            supertype => 'constraint',
+            name      => $item{'index_name(?)'}[0],
+            type      => 'primary_key',
+            fields    => $item[4],
+        };
+    }
+
+unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
+    { 
+        $return       = { 
+            supertype => 'constraint',
+            name      => $item{'index_name(?)'}[0],
+            type      => 'unique',
+            fields    => $item[5],
+        } 
+    }
+
+normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
+    { 
+        $return       = { 
+            supertype => 'index',
+            type      => 'normal',
+            name      => $item{'index_name(?)'}[0],
+            fields    => $item[4],
+        } 
+    }
+
+fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
+    { 
+        $return       = { 
+            supertype => 'index',
+            type      => 'fulltext',
+            name      => $item{'index_name(?)'}[0],
+            fields    => $item[5],
+        } 
+    }
+
+name_with_opt_paren : NAME parens_value_list(s?)
+    { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
+
+UNIQUE : /unique/i { 1 }
+
+KEY : /key/i | /index/i
+
+table_option : WORD /\s*=\s*/ WORD
+    { 
+        $return = { $item[1] => $item[3] };
+    }
+
+CREATE : /create/i
+
+TEMPORARY : /temporary/i
+
+TABLE : /table/i
+
+WORD : /\w+/
+
+DIGITS : /\d+/
+
+COMMA : ','
+
+NAME    : "`" /\w+/ "`"
+    { $item[2] }
+    | /\w+/
+    { $item[1] }
+
+VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
+    { $item[1] }
+    | /'.*?'/   
+    { 
+        # remove leading/trailing quotes 
+        my $val = $item[1];
+        $val    =~ s/^['"]|['"]$//g;
+        $return = $val;
+    }
+    | /NULL/
+    { 'NULL' }
+
+!;
+
+# -------------------------------------------------------------------
+sub parse {
+    my ( $translator, $data ) = @_;
+    my $parser = Parse::RecDescent->new($GRAMMAR);
+
+    local $::RD_TRACE  = $translator->trace ? 1 : undef;
+    local $DEBUG       = $translator->debug;
+
+    unless (defined $parser) {
+        return $translator->error("Error instantiating Parse::RecDescent ".
+            "instance: Bad grammer");
+    }
+
+    my $result = $parser->startrule($data);
+    return $translator->error( "Parse failed." ) unless defined $result;
+    warn Dumper( $result ) if $DEBUG;
+
+    my $schema = $translator->schema;
+    my @tables = sort { 
+        $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
+    } keys %{ $result };
+
+    for my $table_name ( @tables ) {
+        my $tdata =  $result->{ $table_name };
+        my $table =  $schema->add_table( 
+            name  => $tdata->{'table_name'},
+        ) or die $schema->error;
+
+        $table->comments( $tdata->{'comments'} );
+
+        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'},
+                comments          => $fdata->{'comments'},
+            ) or die $table->error;
+
+            $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
+
+#            for my $qual ( qw[ binary unsigned zerofill list ] ) {
+#                if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
+#                    next if ref $val eq 'ARRAY' && !@$val;
+#                    $field->extra( $qual, $val );
+#                }
+#            }
+
+#            for my $cdata ( @{ $fdata->{'constraints'} } ) {
+#                next unless $cdata->{'type'} eq 'foreign_key';
+#                $cdata->{'fields'} ||= [ $field->name ];
+#                push @{ $tdata->{'constraints'} }, $cdata;
+#            }
+        }
+
+        for my $idata ( @{ $tdata->{'indices'} || [] } ) {
+            my $index  =  $table->add_index(
+                name   => $idata->{'name'},
+                type   => uc $idata->{'type'},
+                fields => $idata->{'fields'},
+            ) or die $table->error;
+        }
+
+#        for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
+#            my $constraint       =  $table->add_constraint(
+#                name             => $cdata->{'name'},
+#                type             => $cdata->{'type'},
+#                fields           => $cdata->{'fields'},
+#                reference_table  => $cdata->{'reference_table'},
+#                reference_fields => $cdata->{'reference_fields'},
+#                match_type       => $cdata->{'match_type'} || '',
+#                on_delete        => $cdata->{'on_delete_do'},
+#                on_update        => $cdata->{'on_update_do'},
+#            ) or die $table->error;
+#        }
+    }
+
+    return 1;
+}
+
+1;
+
+# -------------------------------------------------------------------
+# Where man is not nature is barren.
+# William Blake
+# -------------------------------------------------------------------
+
+=pod
+
+=head1 AUTHOR
+
+Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
+
+=head1 SEE ALSO
+
+perl(1), Parse::RecDescent, SQL::Translator::Schema.
+
+=cut