package MySQL::Partition; use 5.008001; use strict; use warnings; our $VERSION = "1.0000"; use MySQL::Partition::Handle; use Module::Load (); use Class::Accessor::Lite ( rw => [qw/dry_run verbose/], ro => [qw/type dbh table expression/], ); sub dbname { my $self = shift; exists $self->{dbname} ? $self->{dbname} : $self->{dbname} ||= _get_dbname($self->dbh->{Name}); } sub new { my $class = shift; die q[can't call new method directory in sub class] if $class ne __PACKAGE__; my %args = @_ == 1 ? %{$_[0]} : @_; $args{type} = uc $args{type}; my ($type) = split /\s+/, $args{type}; my $sub_class = __PACKAGE__ . '::Type::' . ucfirst( lc $type ); Module::Load::load($sub_class); bless \%args, $sub_class; } __PACKAGE__->_grow_methods(qw/create_partitions add_partitions drop_partitions truncate_partitions/); sub retrieve_partitions { my ($self, $table) = @_; my $parts = $self->{partitions} ||= do { my @parts; my $sth = $self->dbh->prepare(' SELECT partition_name FROM information_schema.PARTITIONS WHERE table_name = ? AND table_schema = ? AND partition_method = ? ORDER BY partition_name '); $sth->execute($self->table, $self->dbname, $self->type); while (my $row = $sth->fetchrow_arrayref) { push @parts, $row->[0] if defined $row->[0]; } \@parts; }; @$parts; } sub is_partitioned { my $self = shift; $self->retrieve_partitions ? 1 : (); } sub has_partition { my ($self, $partition_name) = @_; grep {$_ eq $partition_name} $self->retrieve_partitions; } sub _build_create_partitions_sql { my ($self, @args) = @_; if ($self->isa('MySQL::Partition::Type::Range') && $self->catch_all_partition_name) { push @args, $self->catch_all_partition_name, 'MAXVALUE'; } sprintf 'ALTER TABLE %s PARTITION BY %s (%s) (%s)', $self->table, $self->type, $self->expression, $self->_build_partition_parts(@args); } sub _build_add_partitions_sql { my ($self, @args) = @_; sprintf 'ALTER TABLE %s ADD PARTITION (%s)', $self->table, $self->_build_partition_parts(@args); } sub _build_partition_parts { my ($self, @args) = @_; my @parts; while (my ($partition_name, $partition_description) = splice @args, 0, 2) { push @parts, $self->_build_partition_part($partition_name, $partition_description); } join ', ', @parts; } sub _build_partition_part { die 'this is abstruct method'; } sub _build_drop_partitions_sql { my ($self, @partition_names) = @_; sprintf 'ALTER TABLE %s DROP PARTITION %s', $self->table, join(', ', @partition_names); } sub _build_truncate_partitions_sql { my ($self, @partition_names) = @_; sprintf 'ALTER TABLE %s TRUNCATE PARTITION %s', $self->table, join(', ', @partition_names); } sub _grow_methods { my ($class, @methods) = @_; for my $method (@methods) { my $prepare_method = "prepare_$method"; my $sql_builder_method = "_build_${method}_sql"; no strict 'refs'; *{$class . '::' . $prepare_method} = sub { use strict 'refs'; my ($self, @args) = @_; my $sql = $self->$sql_builder_method(@args); return MySQL::Partition::Handle->new( statement => $sql, mysql_partition => $self, ); }; *{$class . '::' . $method} = sub { use strict 'refs'; my ($self, @args) = @_; $self->$prepare_method(@args)->execute; }; } } sub _get_dbname { my $connected_db = shift; # XXX can't parse 'host=hoge;database=fuga' my ($dbname) = $connected_db =~ m!^(?:(?:database|dbname)=)?([^;]*)!i; $dbname; } 1; __END__ =encoding utf-8 =head1 NAME MySQL::Partition - Utility for MySQL partitioning =head1 SYNOPSIS use MySQL::Partition; my $dbh = DBI->connect(@connect_info); my $list_partition = MySQL::Partition->new( dbh => $dbh, type => 'list', table => 'test', expression => 'event_id', ); $list_partition->is_partitioned; $list_partition->create_partitions('p1' => 1); # ALTER TABLE test PARTITION BY LIST ... $list_partition->has_partition('p1'); # true $list_partition->add_partitions('p2_3' => '2, 3'); # handle interface my $handle = $list_partition->prepare_add_partitions('p4' => 4); print $handle->statement; $handle->execute; $list_partition->truncate_partitions('p1'); $handle = $list_partition->prepare_truncate_partitions('p2_3'); $handle->execute; $list_partition->drop_partitions('p1'); $handle = $list_partition->prepare_drop_partitions('p2_3'); $handle->execute; =head1 DESCRIPTION MySQL::Partition is utility module for MySQL partitions. This module creates a object for manipulating MySQL partitions. This is very useful that we no longer write complicated and MySQL specific SQL syntax any more. B =head1 INTERFACE =head2 Constructor =head3 C<< my $mysql_partition:MySQL::Partition = MySQL::Partition->new(%args) >> Create a new object which is subclass of L. (L or L. Following keys are required in C<%args>. =over =item C<< dbh => DBI::db >> =item C<< table => Str >> =item C<< type => Str >> partitioning method. C<< list(?: columns)? >> or C<< range(?: columns)? >>. If C is specified, C method returns C object. =item C<< expression => Str >> partitioning expression. e.g. C, C, C, etc. =back =head2 Methods =head3 C<< my @partition_names = $mysql_partition->retrieve_partitions >> Returns partition names in the table. =head3 C<< my $bool = $mysql_partition->is_partitioned >> Returns the table is partitioned or not. =head3 C<< my $bool = $mysql_partition->has_partitione($partition_name) >> Returns the table has a specified partition name or not. =head2 Methods for manipulating partition =head3 C<< $mysql_partition->create_partitions($partition_name => $partition_description, [$name => $description, ...]) >> =head3 C<< $mysql_partition->add_partitions($partition_name => $partition_description, [$name => $description], ...) >> =head3 C<< $mysql_partition->drop_partitions(@partition_names) >> =head3 C<< $mysql_partition->truncate_partitions(@partition_names) >> =head2 Methods for MySQL::Partition::Handle Each method for manipulating partition has C method which returns L object. =over =item C =item C =item C =item C =back Actually, C<< $mysql_partition->create_partitions(...); >> is a shortcut of following. my $handle = $mysql_partition->prepare_create_partitions(...); $handle->execute; =head1 LICENSE Copyright (C) Songmu. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Songmu Ey.songmu@gmail.comE =cut