package SQL::Translator::Parser::MySQL;
-# -------------------------------------------------------------------
-# Copyright (C) 2002-2009 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::MySQL - parser for MySQL
=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 Storable qw(dclone);
use DBI qw(:sql_types);
-use base qw(Exporter);
+use SQL::Translator::Utils qw/parse_mysql_version ddl_parser_instance/;
-use SQL::Translator::Utils qw/parse_mysql_version/;
+use base qw(Exporter);
+our @EXPORT_OK = qw(parse);
our %type_mapping = ();
-@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 constant DEFAULT_PARSER_VERSION => 30000;
-$GRAMMAR = << 'END_OF_GRAMMAR';
+our $GRAMMAR = << 'END_OF_GRAMMAR';
{
my ( $database_name, %tables, $table_order, @table_comments, %views,
drop : /drop/i WORD(s) "$delimiter"
{ @table_comments = () }
+bit:
+ /(b'[01]{1,64}')/ |
+ /(b"[01]{1,64}")/
+
string :
# MySQL strings, unlike common SQL strings, can be double-quoted or
# single-quoted, and you can escape the delmiters by doubling (but only the
create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) "$delimiter"
{
my $table_name = $item{'table_name'};
+ die "There is more than one definition for $table_name"
+ if ($tables{$table_name});
+
$tables{ $table_name }{'order'} = ++$table_order;
$tables{ $table_name }{'table_name'} = $table_name;
{ $item[2] }
on_update :
- /on update/i 'CURRENT_TIMESTAMP'
+ /on update/i CURRENT_TIMESTAMP
{ $item[2] }
|
/on update/i reference_option
unsigned : /unsigned/i { $return = 0 }
default_val :
- /default/i 'CURRENT_TIMESTAMP'
+ /default/i CURRENT_TIMESTAMP
+ {
+ $return = $item[2];
+ }
+ |
+ /default/i string
{
- $return = \$item[2];
+ $item[2] =~ s/^\s*'|'\s*$//g or $item[2] =~ s/^\s*"|"\s*$//g;
+ $return = $item[2];
+ }
+ |
+ /default/i bit
+ {
+ $item[2] =~ s/b['"]([01]+)['"]/$1/g;
+ $return = $item[2];
}
|
- /default/i /'(?:.*?(?:\\'|''))*.*?'|(?:')?[\w\d:.-]*(?:')?/
+ /default/i /[\w\d:.-]+/
{
- $item[2] =~ s/^\s*'|'\s*$//g;
$return = $item[2];
}
/foreign key/i
{ $return = '' }
-primary_key_def : primary_key index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
+primary_key_def : primary_key index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
{
$return = {
supertype => 'constraint',
- name => $item[2][0],
type => 'primary_key',
- fields => $item[5],
- options => $item[3][0] || $item[7][0],
+ fields => $item[4],
+ options => $item[2][0] || $item[6][0],
+ };
+ }
+ # In theory, and according to the doc, names should not be allowed here, but
+ # MySQL accept (and ignores) them, so we are not going to be less :)
+ | primary_key index_name_not_using(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
+ {
+ $return = {
+ supertype => 'constraint',
+ type => 'primary_key',
+ fields => $item[4],
+ options => $item[6][0],
};
}
| /NULL/
{ 'NULL' }
-CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
- | /now\(\)/i
- { 'CURRENT_TIMESTAMP' }
+# always a scalar-ref, so that it is treated as a function and not quoted by consumers
+CURRENT_TIMESTAMP :
+ /current_timestamp(\(\))?/i { \'CURRENT_TIMESTAMP' }
+ | /now\(\)/i { \'CURRENT_TIMESTAMP' }
END_OF_GRAMMAR
sub parse {
my ( $translator, $data ) = @_;
- my $parser = Parse::RecDescent->new($GRAMMAR);
+
+ # 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.
+
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 $parser = ddl_parser_instance('MySQL');
# Preprocess for MySQL-specific and not-before-version comments
# from mysqldump
# Takes a field, and returns
sub normalize_field {
my ($field) = @_;
- my ($size, $type, $list, $changed) = @_;
+ my ($size, $type, $list, $unsigned, $changed);
$size = $field->size;
$type = $field->data_type;
$list = $field->extra->{list} || [];
+ $unsigned = defined($field->extra->{unsigned});
if ( !ref $size && $size eq 0 ) {
if ( lc $type eq 'tinyint' ) {
- $changed = $size != 4;
- $size = 4;
+ $changed = $size != 4 - $unsigned;
+ $size = 4 - $unsigned;
}
elsif ( lc $type eq 'smallint' ) {
- $changed = $size != 6;
- $size = 6;
+ $changed = $size != 6 - $unsigned;
+ $size = 6 - $unsigned;
}
elsif ( lc $type eq 'mediumint' ) {
- $changed = $size != 9;
- $size = 9;
+ $changed = $size != 9 - $unsigned;
+ $size = 9 - $unsigned;
}
elsif ( $type =~ /^int(eger)?$/i ) {
- $changed = $size != 11 || $type ne 'int';
+ $changed = $size != 11 - $unsigned || $type ne 'int';
$type = 'int';
- $size = 11;
+ $size = 11 - $unsigned;
}
elsif ( lc $type eq 'bigint' ) {
$changed = $size != 20;