=head1 NAME

DBIx::SQLEngine::Driver::Mysql - Support DBD::mysql

=head1 SYNOPSIS

B<DBI Wrapper>: Adds methods to a DBI database handle.

  my $sqldb = DBIx::SQLEngine->new( 'dbi:mysql:test' );
  
B<Portability Subclasses:> Uses driver's idioms or emulation.

  $hash_ary = $sqldb->fetch_select( 
    table => 'students' 
    limit => 5, offset => 10
  );

=head1 DESCRIPTION

This package provides a subclass of DBIx::SQLEngine which compensates for MySQL's idiosyncrasies.

=head2 About Driver Subclasses

You do not need to use this package directly; when you connect to a database, the SQLEngine object is automatically re-blessed in to the appropriate subclass.

For more information about the underlying driver class, see L<DBD::Mysql>.

=cut

########################################################################

package DBIx::SQLEngine::Driver::Mysql;

use strict;
use Carp;

########################################################################

########################################################################

=head1 DRIVER AND DATABASE FLAVORS

=head2 About DBMS Flavors

This driver uses the DatabaseFlavors trait in order to accomodate variations between different versions of MySQL. For more information, see L<DBIx::SQLEngine::Driver::Trait::DatabaseFlavors>.

=head2 Detecting DBMS Flavors

=over 4

=item default_dbms_flavor()

  $sqldb->default_dbms_flavor() : "V3_0"

By default, it is assumed that we're talking to an early version of MySQL 3.0, without transactions, unions, or stored procedures.

=item detect_dbms_flavor()

  $sqldb->detect_dbms_flavor() : $flavor_name

Attempts to determine which version of MySQL we're connected to based on the results of are_transactions_supported() and detect_union_supported().

=back

If you want to take advantage of any advanced features that may be available, first call select_detect_dbms_flavor().

=cut

use DBIx::SQLEngine::Driver::Trait::DatabaseFlavors qw( :all !default_dbms_flavor !detect_dbms_flavor );

sub default_dbms_flavor { 'V3_0' }

sub detect_dbms_flavor {
  my $self = shift;
  my $guess = 'V3_0';
  $guess = 'V3_23' if ( $self->are_transactions_supported() );
  $guess = 'V4_0' if ( $self->detect_union_supported() );
  return $guess;
}

########################################################################

=head2 Version Classes

The following subclasses provide support for particular versions of MySQL:

=over 4

=item V3_0

This is the earliest version we have a subclass for. Default. 
No transactions, union selects, or stored procedures.

=item V3_23

This is the first version with support for transactions.
No union selects, or stored procedures.

=item V4_0

This is the first version with support for unions in select statements.
No stored procedures.

=item V5_0

The version is still in development. It will be the first version to support stored procedures.

=back

=cut

FLAVOR_CLASSES: { 

  no strict;
  
  ############################################################

  package DBIx::SQLEngine::Driver::Mysql::V3_0;
  @ISA = qw( DBIx::SQLEngine::Driver::Mysql );

  use DBIx::SQLEngine::Driver::Trait::NoUnions ':all';
  use DBIx::SQLEngine::Driver::Trait::NoAdvancedFeatures qw( /transaction/ /storedproc/ );
  
  ############################################################
  
  package DBIx::SQLEngine::Driver::Mysql::V3_23;
  @ISA = qw( DBIx::SQLEngine::Driver::Mysql );

  use DBIx::SQLEngine::Driver::Trait::NoUnions ':all';
  use DBIx::SQLEngine::Driver::Trait::NoAdvancedFeatures qw( /storedproc/ );
  
  ############################################################
  
  package DBIx::SQLEngine::Driver::Mysql::V4_0;
  @ISA = qw( DBIx::SQLEngine::Driver::Mysql );

  use DBIx::SQLEngine::Driver::Trait::NoAdvancedFeatures qw( /storedproc/ );
  
  ############################################################
  
  package DBIx::SQLEngine::Driver::Mysql::V5_0;
  @ISA = qw( DBIx::SQLEngine::Driver::Mysql );
  
  ############################################################

}

########################################################################

########################################################################

=head1 FETCHING DATA (SQL DQL)

=head2 Methods Used By Complex Queries 

=over 4

=item sql_limit()

Adds support for SQL select limit clause.

=back

=cut

sub sql_limit {
  my $self = shift;
  my ( $limit, $offset, $sql, @params ) = @_;

  # You can't apply "limit" to non-table fetches like "select LAST_INSERT_ID"
  if ( $sql =~ /\bfrom\b/ and $limit or $offset) {
    $limit ||= 1_000_000; # MySQL select with offset requires a limit
    $sql .= " limit " . ( $offset ? "$offset," : '' ) . $limit;
  }

  return ($sql, @params);
}

########################################################################

########################################################################

=head1 EDITING DATA (SQL DML)

=head2 Insert to Add Data 

=over 4

=item do_insert_with_sequence()

  $sqldb->do_insert_with_sequence( $sequence_name, %sql_clauses ) : $row_count

Implemented using _seq_do_insert_postfetch and seq_fetch_current.

=item seq_fetch_current()

  $sqldb->seq_fetch_current( ) : $current_value

Implemented using MySQL's "select LAST_INSERT_ID()". Note that this
doesn't fetch the current sequence value for a given table, since
it doesn't respect the table and field arguments, but merely returns
the last sequencial value created during this session.

=back

=cut

# $rows = $self->do_insert_with_sequence( $sequence, %clauses );
sub do_insert_with_sequence {
  (shift)->_seq_do_insert_postfetch( @_ )
}

# $current_id = $sqldb->seq_fetch_current( );
sub seq_fetch_current {
  my ($self, $table, $field) = @_;
  $self->fetch_one_value( sql => 'select LAST_INSERT_ID()' );
}

########################################################################

########################################################################

=head1 DEFINING STRUCTURES (SQL DDL)

=head2 Detect Tables and Columns

=over 4

=item sql_detect_table()

  $sqldb->sql_detect_table ( $tablename )  : %sql_select_clauses

Implemented using MySQL's "select * from $tablename limit 1".

=back

=cut

sub sql_detect_table {
  my ($self, $tablename) = @_;
  return ( sql => "select * from $tablename limit 1" );
}

########################################################################

=head2 Column Type Methods

=over 4

=item dbms_create_column_types()

  $sqldb->dbms_create_column_types () : %column_type_codes

Implemented using MySQL's blob and auto_increment types.

=item dbms_create_column_text_long_type()

  $sqldb->dbms_create_column_text_long_type () : $col_type_str

Implemented using MySQL's blob type.

=back

=cut

sub dbms_create_column_types {
  'sequential' => 'int auto_increment primary key', 
  'binary' => 'blob',
}

sub dbms_create_column_text_long_type {
  'blob'
}

########################################################################

########################################################################

=head1 INTERNAL STATEMENT METHODS (DBI STH)

=head2 Statement Error Handling 

=over 4

=item recoverable_query_exceptions()

  $sqldb->recoverable_query_exceptions() : @common_error_messages

Provides a list of error messages which represent common
communication failures or other incidental errors.

=back

=cut

sub recoverable_query_exceptions {
  'Lost connection to MySQL server',
  'MySQL server has gone away',
  'no statement executing',
  'fetch without execute',
  "\Qfetch() without execute()",
}

########################################################################

=head1 SEE ALSO

See L<DBIx::SQLEngine> for the overall interface and developer documentation.

See L<DBIx::SQLEngine::Docs::ReadMe> for general information about
this distribution, including installation and license information.

=cut

########################################################################

1;
