# A work in progress -- not yet close to functional. There are lots of
# issues with our assumptions about how to handle errors versus who
# sqlite throws them, with autocommit stuff, with the "schema has
# changed" error the sqlite throws, and with frequent mid-script
# losses of connection to the db.

#   here's a block for the Configuration file
#

# sqlite => {
#              sql_syntax  =>  'SQLite',
#              dbi_connect_info => [
#                                   'DBI:SQLite:test.db', '', '',
#                                   { RaiseError => 1,
#                                     PrintError => 1,
#                                     ShowErrorStatement => 1,
#                                     AutoCommit => 1,
#   HandleError => sub {
#     my ( $string, $handle ) = @_;
#     # print "handling error ($handle)\n";
#     if ( $string =~ m|schema has changed| ) {
#       $handle->execute();
#       return 1;
#     }
#     return;
#   }
#                                   } ],
#             },

package XML::Comma::SQL::SQLite;

use XML::Comma::Util qw( dbg );
use MIME::Base64;

use base qw( XML::Comma::SQL::Base );

use strict;

# due to some SQLite futziness, we need to implement our own ping
# and call it before every prepare in order to ensure that we have
# a viable database handle.  By "implement our own ping", I mean
# we stuff one in DBH_User from over here.

# we have to wait until this package is completely parsed before
# we can force-feed DBH_User our ping else we get in a BEGIN
# block race, hence the INIT.
INIT {

  *XML::Comma::SQL::DBH_User::ping = \&ping;

}

sub sql_create_lock_table {
  my $lock = shift;
  $lock->ping();
  my $dbh  = $lock->get_dbh_writer();
  my $sth = $dbh->prepare_cached (
    qq[
        CREATE TABLE comma_lock
            ( doc_key         VARCHAR(255) UNIQUE,
              pid             INT,
              info            VARCHAR(255),
              time            INT )
    ]);
  $sth->execute();
  $sth->finish();
}

sub sql_create_hold_table {
  my $lock = shift;
  $lock->ping();
  my $dbh  = $lock->get_dbh_writer();
  my $sth = $dbh->prepare_cached(
    qq[
        CREATE TABLE comma_hold ( key VARCHAR(255) UNIQUE )
    ]);
  $sth->execute();
  $sth->finish();
}

# $lock, $key
sub sql_get_lock_record {
  my ($lock, $key) = @_;
  $lock->ping();
  my $dbh  = $lock->get_dbh_reader();
  my $sth = $dbh->prepare_cached (
    qq[
        SELECT doc_key, pid, info, time
            FROM comma_lock
            WHERE doc_key = ?
    ]);
  $sth->execute( $key );
  my $result = $sth->fetchrow_arrayref();
  $sth->finish();
  return $result ? { doc_key => $result->[0],
                     pid     => $result->[1],
                     info    => $result->[2],
                     time    => $result->[3] } : '';
}

# $lock
sub sql_delete_locks_held_by_this_pid {
  my $lock = shift;
  $lock->ping();
  my $dbh  = $lock->get_dbh_writer();
  my $sth = $dbh->prepare_cached ( 
    qq[ DELETE from comma_lock 
            WHERE pid = ? ]
  );
  $sth->execute( $$ );
  $sth->finish();
}

# $lock, $key - returns 1 if row-insert succeeds, 0 on duplicate
# key. throws error for any error other than duplicate key.
sub sql_doc_lock {
  my ($lock, $key) = @_;
  $lock->ping();
  my $dbh = $lock->get_dbh_writer();
  eval { 
    my $sth = $dbh->prepare_cached (
      qq[ 
          INSERT INTO comma_lock ( doc_key, pid, time, info )
              VALUES ( ?, ?, ?, ? ) 
      ]);
    $sth->execute( $key, $$, time(), Sys::Hostname::hostname() );
    $sth->finish();
  }; if ( $@ ) {
    # dbg 'sql lock insert error', $@; we actually want to get an
    # error on a failed lock. we catch the error and check whether it
    # signals an attempt to insert a "duplicate" key. If so, the lock
    # attempt failed, so we return 0.
    if ( $@ =~ /duplicate/i ) {
      # print "lock on $_[1] failed\n";
      return 0;
    }
    die "Err, $@\n";
  }
  return 1;
}

# $lock, $key
sub sql_doc_unlock {
  my ($lock, $key) = @_;
  my $dbh  = $lock->get_dbh_writer();
  my $sth = $dbh->prepare_cached ( 
    qq[ 
        DELETE FROM comma_lock 
            WHERE doc_key = ? 
    ]);
  $sth->execute( $key );
  $sth->finish();
}

sub sql_get_hold {
  my ( $lock, $key ) = @_;
  $lock->ping();
  my $dbh = $lock->get_dbh_writer();
  dbg 'getting hold', $key;
  my $sth = $dbh->prepare_cached ( 
    qq[
        INSERT INTO comma_hold (key) VALUES (?)
    ]);
  eval {
    $sth->execute( $key );
  };  
  XML::Comma::Log->err( $@ ) if $@;
  $sth->finish();
}

sub sql_release_hold {
  my ( $lock, $key ) = @_;
  $lock->ping();
  # $lock->reconnect();
  my $dbh = $lock->get_dbh_writer();
  dbg 'releasing hold', $key;
  my $sth = $dbh->prepare_cached ( 
    qq[
        DELETE FROM comma_hold WHERE key = ?
    ]);
  eval {
    $sth->execute( $key );
  };
  XML::Comma::Log->err( $@ ) if $@;
  $sth->finish();
}

sub sql_create_index_tables_table {
  my $index = shift();
  $index->ping();
  my $dbh   = $index->get_dbh_writer();
  my $sth   = $dbh->prepare_cached (
    qq[
        CREATE TABLE index_tables
          ( _comma_flag    INTEGER,
            _sq            INTEGER PRIMARY KEY UNIQUE,
            doctype        VARYING CHARACTER(255),
            index_name     VARYING CHARACTER(255),
            table_name     VARYING CHARACTER(255),
            table_type     INTEGER,
            last_modified  INTEGER,
            sort_spec      VARYING CHARACTER(255),
            textsearch     VARYING CHARACTER(255),
            collection     VARYING CHARACTER(255),
            index_def      CLOB )
    ]);
  $sth->execute();
  $sth->finish();
}


sub sql_sort_table_definition {
  my $doc_id = $_[0]->element('doc_id_sql_type')->get();
  return qq[ 
    CREATE TABLE $_[1] (
       _comma_flag  INT2,
       doc_id $doc_id PRIMARY KEY
    )
  ];
}


sub sql_bcollection_table_definition {
  my ( $index, $name, %arg ) = @_;
  my $extra_column = '';
  if ( @{$arg{bcoll_el}->elements('field')} ) {
    $extra_column = ", extra " .
      $arg{bcoll_el}->element('field')->element('sql_type')->get();
  }

  return
"CREATE TABLE $name (
  _comma_flag  INT2,
  doc_id ${ \( $index->element('doc_id_sql_type')->get() ) },
  value   ${ \( $arg{bcoll_el}->element('sql_type')->get() ) }
  $extra_column
 );
 CREATE INDEX bci_$name ON $name (value)";
}


sub sql_textsearch_index_table_definition {
  my $max_length = $XML::Comma::Pkg::Textsearch::Preprocessor::max_word_length;
  return
"CREATE TABLE $_[1] (
  word  CHAR($max_length)  PRIMARY KEY,
  seqs  TEXT )";
}

sub sql_textsearch_defers_table_definition {
  return
"CREATE TABLE $_[1] (
  doc_id        ${ \( $_[0]->element('doc_id_sql_type')->get() ) },
  action        INT2,
  text          TEXT,
  _sq           SERIAL )";
}

sub sql_textsearch_word_lock {
#   my ( $index, $i_table_name, $word ) = @_;
#   my $dbh = $index->get_dbh();
#   $dbh->{AutoCommit}=0;
#   $dbh->commit();
#   my $sth = $dbh->prepare
#     ( "LOCK TABLE $i_table_name IN SHARE ROW EXCLUSIVE MODE" );
#   $sth->execute();
#   $sth->finish();
}

sub sql_textsearch_word_unlock {
#   my ( $index, $i_table_name, $word ) = @_;
#   my $dbh = $index->get_dbh();
#   #my $q_lock_name = $dbh->quote ( $i_table_name . $word );
#   $dbh->commit();
#   $dbh->{AutoCommit}=1;
#   #$dbh->do ( "COMMIT WORK" );
}


sub sql_index_only_doc_id_type {
  return 'BLOB';
}


# yech. should we be trying to use the non-standard array *= operators
# in postgres to do this textsearch stuff?
sub sql_textsearch_pack_seq_list {
  shift();
  #return MIME::Base64::encode_base64( pack("L*", @_), '' );
  return join ( '-', @_ ) . '-';
}

sub sql_textsearch_unpack_seq_list {
  #return unpack ( "L*", MIME::Base64::decode_base64($_[1]) );
  chop ( $_[1] );
  return split ( '-', $_[1] );
}


# SQLite needs to do these the hard way -- creating a temp table and
# stuff. there is no difference between drop and modify, in the
# mechanics, and the field_name and field_type variables are not used
# (the def fields are pulled instead).
sub sql_alter_data_table_drop_or_modify {
  my $index = shift;
  $index->sql_alter_by_recreating();
}

sub sql_alter_data_table_add            {
  my $index = shift;
  $index->sql_alter_by_recreating();
}

sub sql_alter_data_table_add_collection {
  my $index = shift;
  $index->sql_alter_by_recreating();
}

#sub sql_alter_data_table_add_index      {
#  die "unimplemented";
#}

#sub sql_alter_data_table_drop_index     {
#  die "unimplemented";
#}

sub sql_data_table_name {
  my $index = shift();
  $index->ping();
  my $dbh = $index->get_dbh();  
  my $sth = $dbh->prepare_cached (
    qq[
        SELECT table_name FROM index_tables 
          WHERE doctype  = ? 
          AND index_name = ? 
          AND table_type = ? 
    ]);
  dbg 'data_table_name', $index->doctype() .
      $index->element('name')->get() .
      XML::Comma::Indexing::Index->DATA_TABLE_TYPE();
  $sth->execute( $index->doctype(), $index->element('name')->get(), 
                 XML::Comma::Indexing::Index->DATA_TABLE_TYPE() );
  my $result = $sth->fetchrow_arrayref();
  $sth->finish();
  return $result ? $result->[0] : die "FIX: no data table name found\n";
}

sub sql_data_table_definition {
  my ($index, $name) = @_;
  $index->sql_begin_data_table_definition( $name ) .
         $index->sql_end_data_table_definition();
}

sub sql_new_data_table_definition {
  my ( $index, $name ) = @_;
  my $sql = $index->sql_begin_data_table_definition( $name ) .
            $index->sql_columns_from_index_data_table_definition() . 
            $index->sql_end_data_table_definition();
  return $sql;
}

sub sql_begin_data_table_definition {
  my ($index, $name) = @_;
  my $doc_id_type = $index->element('doc_id_sql_type')->get();
  return 
  qq[
      CREATE TABLE $name (
        _comma_flag             TINYINT,
        record_last_modified    INT,
        _sq                     INTEGER PRIMARY KEY,
        doc_id                  $doc_id_type
    ];
}

sub sql_columns_from_index_data_table_definition {
  my $index = shift;
  my $sql_chunk;
  
  # standard index fields
  foreach my $field ( $index->elements('field') ) {
    $sql_chunk .= ", " . $field->element('name')->get() . " " .
                  $field->element('sql_type')->get();
  }
  
  # multi-index stuff
  $sql_chunk .= ", store VARCHAR(255) NOT NULL" if
    $index->element('store')->get();
  $sql_chunk .= ", doctype VARCHAR(255) NOT NULL" if 
    $index->element('doctype')->get();
  
  # collections
  foreach my $coll ( $index->elements( 'collection' ), 
                   $index->elements( 'sort' ) )        {
    my $type = $coll->element('type')->get();
    next if $type eq 'many_tables';
    my $name = $coll->element('name')->get();    
    if ( $type eq 'stringified' ) {
      $sql_chunk .= " ,$name TEXT"; 
    } 
  }
  dbg 'data_table_from_index', $sql_chunk;
  return $sql_chunk;
}

sub sql_drop_bcollection_table {
  my ( $index, $collection_name ) = @_;
  $index->ping();
  my $dbh = $index->get_dbh();
  my $sth = $dbh->prepare ( "SELECT table_name FROM index_tables WHERE collection=${ \( $dbh->quote($collection_name) ) } AND doctype=${ \( $dbh->quote($index->doctype()) )}" );
  $sth->execute();
  while ( my $row = $sth->fetchrow_arrayref() ) {
    my $table_name = $row->[0];
    my $sth = $dbh->prepare ( "DROP TABLE $table_name" );
    $sth->execute();
    $sth->finish();
    
    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare ( "DELETE FROM index_tables WHERE table_name=${ \( $dbh->quote($table_name) ) }" );
    $sth->execute();
    $sth->finish();
  }
}

sub sql_end_data_table_definition {
  return qq[ ) ];
}

sub sql_create_data_table {
  my ( $index, $existing_table_name ) = @_;
  return $index->sql_create_a_table
    ( table_type          => XML::Comma::Indexing::Index->DATA_TABLE_TYPE(),
      index_def           => $index->to_string(),
      table_def_sub       => 'sql_data_table_definition',
      existing_table_name => $existing_table_name );
}

sub sql_alter_by_recreating {
  my $index  = shift;
  my $temp_table_name = "t$$" . '_' . int(rand(0xffffffff));
  my $data_table_name = $index->data_table_name();

  dbg "altering", $data_table_name;
  $index->ping();
  my $dbh = $index->get_dbh();
  my $sth = $dbh->prepare( 
    "CREATE TABLE $temp_table_name AS SELECT * FROM $data_table_name"
  );
  $sth->execute();
  $sth->finish();  
  
  $index->ping();
  $dbh = $index->get_dbh();
  $sth = $dbh->prepare( "DROP TABLE $data_table_name" );
  $sth->execute();
  $sth->finish();

  # SQLite seems to remember the table schema that we dropped unless we
  # explicitly disconnect and reconnect.  XXX re-read documentation.
  $index->reconnect();
  $index->create_new_data_table ( $data_table_name );
  
  # The SQLite driver doesn't support column_info, and unless I missed 
  # something in the DBI docs (which is likely), we have to do 
  # a successful fetchrow_hashref in order to get at the column names.
  # This is so sqlite won't complain about a column mis-match with the 
  # INSERT INTO ... SELECT FROM ... below
  $index->ping();
  $dbh = $index->get_dbh();
  $sth = $dbh->prepare( "SELECT * FROM $temp_table_name LIMIT 1" );
  $sth->execute();
  my $col_ref = $sth->fetchrow_hashref();

  # only if the previous select returned a row
  if ( $col_ref ) {
    my $col_string = join ", ", keys %{ $col_ref };

    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare (
    "INSERT INTO $data_table_name ( _comma_flag, doc_id, record_last_modified, _sq, ${ \( join(', ',$index->columns()) ) } ) SELECT $col_string FROM $temp_table_name"
           );
    $sth->execute();
    $sth->finish();
  }

  $index->ping();
  $dbh = $index->get_dbh();
  $sth = $dbh->prepare ( "DROP TABLE $temp_table_name" );
  $sth->execute();
  $sth->finish();
  return '';
}

sub create_new_data_table {
  my ( $index, $table_name ) = @_;
  return $index->sql_create_a_table
    ( table_type          => XML::Comma::Indexing::Index->DATA_TABLE_TYPE(),
      index_def           => $index->to_string(),
      table_def_sub       => 'sql_new_data_table_definition',
      existing_table_name => $table_name );
}

#  table_type => const for the table_type column
#  table_def_sub => string of sub name to call to get table def
#  existing_table_name => pass this to *re_create* a table under old name
#  index_def => string for index_def column (if any)
#  sort_spec => string for sort_spec column (if any)
#  textsearch => string for text_search column (if any)
#  collection => name of collection being binary indexed (if any)
sub sql_create_a_table {
  my ($index, %arg) = @_;
  my $sort_spec  = $arg{sort_spec}  || '';
  my $textsearch = $arg{textsearch} || '';
  my $collection = $arg{collection} || '';
  my $index_def  = $arg{index_def}  || '';
  my $table_type = $arg{table_type} || '';
  my $name;
  my $table_def_sub = $arg{table_def_sub} || die "need table def sub";

  my $dbh;

  dbg 'create_a_table', "using $table_def_sub";
  if ( ! $arg{existing_table_name} ) {
    # add an appropriate line to the index table
    $index->ping();
    $dbh = $index->get_dbh();
    my $sth = $dbh->prepare_cached ( 
      qq[ 
          INSERT INTO index_tables ( doctype, index_name, last_modified, 
                                     _comma_flag, index_def, sort_spec, 
                                     textsearch, collection, table_type ) 
            VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )
      ]);
    $sth->execute ( $index->doctype(), $index->element('name')->get(),
                    time(), 0, $index_def, $sort_spec, $textsearch, 
                    $collection, $table_type );
    $sth->finish();

    # make a name for that table
    my $stub = substr ( $index->doctype(), 0, 8 ); 
    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare_cached ( 
      qq[
          SELECT _sq FROM index_tables 
            WHERE doctype = ? 
              AND index_name = ? 
              AND table_type = ?
              AND index_def  = ? 
              AND sort_spec  = ?
              AND textsearch = ?
              AND collection = ?
      ]);
    $sth->execute ( $index->doctype(), $index->element('name')->get(),
                    $table_type, $index_def, $sort_spec, 
                    $textsearch, $collection );
    my $s = $sth->fetchrow_arrayref()->[0];
    $sth->finish();
    $name = $stub . '_' . sprintf ( "%04s", $s );
    my $q_t_name = $dbh->quote ( $name );
    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare_cached (
      qq[
          UPDATE index_tables 
            SET table_name = $q_t_name 
            WHERE _sq = $s 
      ]);
    $sth->execute();
    $sth->finish();
  } else {
    $name = $arg{existing_table_name};
  }

  $index->ping();
  $dbh = $index->get_dbh();
  
  # XXX
  $index->reconnect();
  $dbh = $index->get_dbh();
  
  # now make the table
  eval {
    my $sth = $dbh->prepare_cached ( $index->$table_def_sub( $name, %arg ) );
    $sth->execute();
    $sth->finish();
  };
  if ( $@ ) {
    XML::Comma::Log->err( 
      "couldn't create database table ($table_def_sub). DB says: $@\n"
    );
  }
  return $name;
}

sub sql_insert_into_data {
  my ( $index, $doc, $comma_flag ) = @_;
  $comma_flag ||= 0;

  # the normal case is to treat the doc's doc_id as a nearly-normal
  # field, getting its value straight from the doc. but there is a
  # special case where we want the doc_id to get its value here,
  # during the write, from the _sq number.
  my ( $doc_make_id_flag, $doc_id ) = ( undef, $doc->doc_id() );
  dbg 'doc_id', $doc_id;
  if ( $doc->doc_id() eq 'COMMA_DB_SEQUENCE_SET' ) {
    $doc_id = "0";
    $doc_make_id_flag = 1;
  };

  $index->ping(); 
  my $dbh        = $index->get_dbh();
  my $data_table = $index->data_table_name();
 
  # With SQLite, we are responsible for maintaining the SERIAL 
  # equivalent for _sq.  If this is our first _sq for this table,
  # int-ify it so the later select max(_sq) +1 on insert will work
  
  # my $sth = $dbh->prepare_cached( "SELECT max(_sq) from $data_table" );
  # $sth->execute();
  
  # my $first_sq = 1 unless $sth->fetchrow_arrayref->[0];
  # $sth->finish();

  # the core logic -- insert the row into the data table with all
  # columns properly filled

  my @columns = $index->columns();
  my $columns_list = join ( ',', @columns );
  dbg 'columns_list', $columns_list;

  my @columns_values = map { $index->column_value($_, $doc,) } @columns;
  my $placeholders = join( ", ", ('?') x @columns_values );

  # my $sql_string;
  # if ( $first_sq ) {
  #   $sql_string  = 
  #    qq[
  #      INSERT INTO $data_table
  #          ( _sq, doc_id,  _comma_flag, record_last_modified, $columns_list ) 
  #        VALUES 
  #          ( 1, $doc_id, ?, ?, $placeholders )
  #      ];
  # } else {
  my $sql_string  =
      qq[
        INSERT INTO $data_table
            ( _sq, doc_id, _comma_flag, record_last_modified, $columns_list ) 
          VALUES 
            ( NULL, ?, ?, ?, $placeholders )
        ];
  dbg 'sql', $sql_string;
  $index->ping();
  $dbh = $index->get_dbh();
  my $sth = $dbh->prepare_cached ( $sql_string ); 
  dbg 'placeholders', $doc_id, $comma_flag, time(), @columns_values;
  $sth->execute($doc_id, $comma_flag, time(), @columns_values );
  $sth->finish();

  # and, finally set the id field correctly, both in the db and in the
  # doc, if we're responsible for generating the id. CAVEAT: we only
  # set the 'id' info in the doc -- some caller up the chain should
  # take responsibility for making all of the doc's storage_info stuff
  # right.
  if ( $doc_make_id_flag ) {
    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare_cached ( 
      qq[
          SELECT _sq FROM $data_table 
          WHERE doc_id = ? 
      ]);
    dbg 'doc_id', $doc_id;
    $sth->execute( $doc_id );
    $doc_id = $sth->fetchrow_arrayref->[0];
    dbg 'doc_id', $doc_id;
    $sth->finish();

    $index->ping();
    $dbh = $index->get_dbh();
    $sth = $dbh->prepare_cached ( 
      qq[
          UPDATE $data_table SET doc_id = ?
          WHERE _sq = ?
      ]);
    $sth->execute( $doc_id, $doc_id );
    $sth->finish();
    $doc->set_storage_info ( undef, undef, $doc_id );
  }
}

# this update w/ subselect ought to work, but Pg won't allow the order
# by in the subselect. maybe oracle?
#
#
#  # args: index, table_name, order_by, size_limit
#  sub sql_set_comma_flags {
#    my ( $index, $table_name, $order_by, $size_limit ) = @_;
#    my $data_table_name = $index->data_table_name();
#    my $dbh = $index->get_dbh();
#    my $sel = $dbh->do
#   ("UPDATE $table_name SET _comma_flag=1 WHERE doc_id IN
#     (SELECT S.doc_id FROM $table_name AS S, $data_table_name AS D
#      WHERE S.doc_id = D.doc_id ORDER BY D.$order_by LIMIT $size_limit)");
#  }


sub sql_create_bcollection_table {
  my ( $index, $collection_name, $bcoll_el ) = @_;
  return $index->sql_create_a_table
    ( table_type    => XML::Comma::Indexing::Index->BCOLLECTION_TABLE_TYPE(),
      table_def_sub => 'sql_bcollection_table_definition',
      collection    => $collection_name,
      bcoll_el      => $bcoll_el );
}

sub sql_clean_find_orphans {
  my ( $table_name, $data_table_name ) = @_;
  return "SELECT $table_name.doc_id FROM $table_name WHERE $table_name.doc_id NOT IN (SELECT $data_table_name.doc_id FROM $data_table_name)";
}

sub sql_limit_clause {
  my ( $index, $limit_number, $limit_offset ) = @_;
  if ( $limit_number ) {
    if ( $limit_offset ) {
      return " LIMIT $limit_number OFFSET $limit_offset";
    } else {
      return " LIMIT $limit_number";
    }
  } else {
    return '';
  }
}

#  sub sql_create_textsearch_temp_table {
#    my ( $index, $ts_index_table_name, $word ) = @_;
#    my $dbh = $index->get_dbh();
#    my $packed =
#      $index->sql_get_textsearch_index_packed ( $ts_index_table_name, $word );

#    my $temp_table_name;
#    my $count;

#    if ( $packed ) {
#      $temp_table_name = '_temp_' . $$ . '_' . int(rand(0xffffff));
#      $dbh->do ( "CREATE TEMPORARY TABLE $temp_table_name ( id VARCHAR(255) PRIMARY KEY )" );
#      my %seen;
#      foreach ( $index->sql_textsearch_unpack_seq_list($packed) ) {
#        $count++;
#        next if $seen{$_}++;
#        my $value = $dbh->quote ( $_ );
#        $dbh->do ( "INSERT INTO $temp_table_name (id) VALUES ($value)" );
#      }
#    } else {
#      return ('',0);
#    }
#    #dbg 'tt/s', $temp_table_name, $count;
#    return ( $temp_table_name, $count );
#  }

sub sql_create_textsearch_temp_table_stmt {
  my ( $index, $dbh ) = @_;
  my $temp_table_name = '_temp_' . $$ . '_' . int(rand(0xffffffff));
  $dbh->do ( "CREATE TEMPORARY TABLE $temp_table_name ( id VARCHAR(255) PRIMARY KEY )" );
  return $temp_table_name;
}

sub sql_load_data {
  my ( $index, $dbh, $temp_table_name, $temp_filename ) = @_;
  $dbh->do ( "COPY $temp_table_name FROM '$temp_filename'" );
}

sub ping {
  my $self = shift();

  dbg 'ping from sqlite', $self->get_dbh();
  my $dbh = $self->get_dbh_reader();
  my $sth;
  # assume comma_hold is already set up
  eval {
    $sth = $dbh->prepare_cached( "SELECT key FROM comma_hold LIMIT 1" );
    $sth->execute();
    $sth->finish();
  };
  if ( $@ ) {
    return $self->reconnect();
  }
  return 1;
}

# aped from DBH_User's _connect.  I don't like the code duplication, 
# but we don't want to run the connect_check method, and we don't
# want to sleep after the disconnect as this is called routinely.
sub reconnect {
  my $self = shift;
  # try to deal nicely with a currently-connected handle (which we may
  # have inherited from a fork, etc.
  eval { $self->{_DBH}->disconnect(); sleep 1; };
  my $db_struct = $self->db_struct();
  my @connect_array = @{ $db_struct->{dbi_connect_info} };

  # try to connect -- looping to try again if we fail
  my $max_attempts = 30;
  for my $attempt ( 1 .. $max_attempts ) {
    eval { $self->{_DBH} = DBI->connect( @connect_array ); };
    last  unless  $@;
    if ( $attempt < $max_attempts ) {
      XML::Comma::Log->warn ( 'Couldn\'t connect to database ' .
                              "(attempt $attempt) -- $@" );
      sleep 2;
    } else {
      XML::Comma::Log->err ( 'DB_CONNECTION_ERROR', "$@" );
    }
  }
  dbg 'reconnect', $self->{_DBH};
  $self->{_DBH_pid} = $$;
  return $self->{_DBH};
}

1;


