primary_key_def : primary_key index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ $item[2][0] || $item[6][0], $item[4] );
+
$return = {
supertype => 'constraint',
type => 'primary_key',
- fields => $item[4],
- options => $item[2][0] || $item[6][0],
+ fields => $fields,
+ options => $options,
};
}
# 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(?)
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ $item[6][0], $item[4] );
+
$return = {
supertype => 'constraint',
type => 'primary_key',
- fields => $item[4],
- options => $item[6][0],
+ fields => $fields,
+ options => $options,
};
}
unique_key_def : UNIQUE KEY(?) index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ $item[4][0] || $item[8][0], $item[6] );
+
$return = {
supertype => 'constraint',
name => $item[3][0],
type => 'unique',
- fields => $item[6],
- options => $item[4][0] || $item[8][0],
+ fields => $fields,
+ options => $options,
}
}
normal_index : KEY index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ $item[3][0] || $item[7][0], $item[5] );
+
$return = {
supertype => 'index',
type => 'normal',
name => $item[2][0],
- fields => $item[5],
- options => $item[3][0] || $item[7][0],
+ fields => $fields,
+ options => $options,
}
}
fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ undef, $item[5] );
+
$return = {
supertype => 'index',
type => 'fulltext',
name => $item{'index_name(?)'}[0],
- fields => $item[5],
+ fields => $fields,
+ # note: according to the MySQL doc, it ignores column index prefixes in
+ # fulltext index definitions
}
}
spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
{
+ my ($fields, $options)
+ = SQL::Translator::Parser::MySQL::calculate_fields_and_options(
+ undef, $item[5] );
+
$return = {
supertype => 'index',
type => 'spatial',
name => $item{'index_name(?)'}[0],
- fields => $item[5],
+ fields => $fields,
+ # note: according to the MySQL doc, it ignores column index prefixes in
+ # spatial index definitions
}
}
name_with_opt_paren : NAME parens_value_list(s?)
- { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
+ {
+ $return = {
+ field => $item[1],
+ prefix_length => $item[2][0] ? $item[2][0][0] : undef,
+ }
+ }
UNIQUE : /unique/i
if ( $fdata->{'has_index'} ) {
$table->add_index(
- name => '',
- type => 'NORMAL',
- fields => $fdata->{'name'},
+ name => '',
+ type => 'NORMAL',
+ fields => $fdata->{'name'},
+ options => $fdata->{'options'},
) or die $table->error;
}
for my $idata ( @{ $tdata->{'indices'} || [] } ) {
my $index = $table->add_index(
- name => $idata->{'name'},
- type => uc $idata->{'type'},
- fields => $idata->{'fields'},
+ name => $idata->{'name'},
+ type => uc $idata->{'type'},
+ fields => $idata->{'fields'},
+ options => $idata->{'options'},
) or die $table->error;
}
}
}
+# takes the index type and the raw fields item (arrayref of hashrefs as
+# returned by name_with_opt_paren()) and returns the arrayref to be passed as
+# $index->options and the arrayref to be passed as $index->fields
+sub calculate_fields_and_options {
+ my ($index_type, $field_items) = @_;
+
+ my @fields;
+ my %prefix_lengths;
+ foreach my $field_item (@$field_items) {
+ my $field_name = $field_item->{field};
+
+ push @fields, $field_name;
+
+ $prefix_lengths{$field_name} = $field_item->{prefix_length}
+ if defined $field_item->{prefix_length};
+ }
+
+ my @options;
+ push @options, { prefix_length => \%prefix_lengths }
+ if keys %prefix_lengths;
+ push @options, $index_type if defined $index_type;
+
+ return (\@fields, \@options);
+}
+
1;
# -------------------------------------------------------------------
=back
+=head2 Index options
+
+=over 4
+
+=item prefix_length
+
+This option allows to use a prefix of certain character type (eg. char,
+varchar, text) fields in the index.
+
+The value of this option is a hashref, keys are the field names, values are the
+prefix lengths.
+
+Example:
+
+ $table->add_index(
+ name => 'idx1',
+ fields => [ 'id', 'name', 'address' ],
+ type => 'normal',
+ options => [
+ {
+ prefix_length => {
+ name => 10,
+ address => 20,
+ }
+ },
+ ],
+ );
+
+ # It will generate the following SQL snippet in the table definition:
+ INDEX `idx1` (`id`, `name`(10), `address`(20)),
+
+=back
+
=cut
use strict;
my $DEFAULT_MAX_ID_LENGTH = 64;
use Data::Dumper;
+use List::Util qw(first);
use SQL::Translator::Schema::Constants;
use SQL::Translator::Utils qw(debug header_comment
truncate_id_uniquely parse_mysql_version);
my $qf = $options->{quote_field_names} || '';
+ my ($prefix_length) = map { ( $_ || {} )->{prefix_length} || {} }
+ first { ref $_ eq 'HASH' && exists $_->{prefix_length} }
+ $index->options;
+
+ my @fields = map {
+ "$qf$_$qf" . (defined $prefix_length->{$_} ? "($prefix_length->{$_})" : "")
+ } $index->fields;
+
return join(
' ',
map { $_ || () }
$options->{max_id_length} || $DEFAULT_MAX_ID_LENGTH
) . $qf
: '',
- '(' . $qf . join( "$qf, $qf", $index->fields ) . $qf . ')'
+ '(' . join(", ", @fields) . ')'
);
}
use FindBin qw/$Bin/;
BEGIN {
- maybe_plan(346, "SQL::Translator::Parser::MySQL");
+ maybe_plan(347, "SQL::Translator::Parser::MySQL");
SQL::Translator::Parser::MySQL->import('parse');
}
my $i3 = shift @indices;
is( $i3->name, 'name_idx', 'Name is "name_idx"' );
is( $i3->type, NORMAL, 'Normal index' );
- is( join(',', $i3->fields ), 'name(10)', 'Index is on field "name(10)"' );
+ is( join(',', $i3->fields ), 'name', 'Index is on field "name"' );
+ is_deeply( [ $i3->options ], [ { prefix_length => { name => 10 } } ],
+ 'Index is on the first 10 chars' );
my @constraints = $table->get_constraints;
is( scalar @constraints, 2, 'Right number of constraints (2)' );