
package OOPS::mysql;

@ISA = qw(OOPS::DBO);

use strict;
use warnings;

my %version_cache;

sub tmode
{
	my ($dbo, $dbh, $readonly) = @_;
	$dbh = $dbo->{dbh}
		unless $dbh;
	$readonly = $dbo->{readonly}
		if $dbo && ! defined $readonly;

	# SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ is the default for InnoDB
	my $tmode;
	if ($readonly) {
		$tmode = $dbh->prepare('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED') || die $dbh->errstr;
	} else {
		$tmode = $dbh->prepare('SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE') || die $dbh->errstr;
	}
	$tmode->execute() || die $dbh->errstr;
}

sub initialize
{
	my $dbo = shift;

	# $dbo->tmode;

	unless ($version_cache{$dbo->{database}}) {
		my $dbh = $dbo->{dbh};
		my $q = $dbo->adhoc_query("SELECT version()") 
			or confess $dbo->{dbh}->errstr();
		$q->execute() or confess $dbo->{dbh}->errstr();
		my ($v) = $q->fetchrow_array();
		$version_cache{$dbo->{database}} = $v;
	}
	$dbo->{mysql_version} = $version_cache{$dbo->{database}};
	unless (defined $dbo->{mysql_for_update}) {
		#
		# 5.0.45-Debian_1ubuntu3.1-log
		# 
		# Somewhere between 5.0.22 and 5.0.45, SERIALIZABLE
		# started working properly.  Before then FOR UPDATE
		# needed to be added to every query.
		#
		my $v = $dbo->{mysql_version};
		$v =~ m/^((?:\d+\.)+\d+)/;
		my $ver = $1;
		die unless $ver;
		my (@ver) = split(/\./, $ver);
		my $cver = $ver[0] + $ver[1] / 1000 + $ver[2] / 1_000_000;
		if ($cver >= 5.000045) {
			$dbo->{mysql_for_update} = 0;
		} else {
			$dbo->{mysql_for_update} = 1;
		}
	}
	$dbo->{counterdbh} = $dbo->dbiconnect(%$dbo, readonly => 1);
	$dbo->{id_pool_start} = 0;
	$dbo->{id_pool_end} = 0;
}

sub disconnect
{
	my ($dbo) = @_;
	$dbo->SUPER::disconnect();
	return unless $dbo->{counterdbh};
	$dbo->{counterdbh}->disconnect();
	delete $dbo->{counterdbh};
}

sub do_forcesave { 1 };

sub tabledefs
{
	my $x = <<'END';

	CREATE TABLE TP_object (
		id		BIGINT NOT NULL,
		loadgroup	BIGINT, 
		class		VARCHAR(255) BINARY,
		otype		CHAR(1),
		virtual		CHAR(1),
		reftarg		CHAR(1),
		rfe		CHAR(1),
		alen		INT,
		refs		INT, 
		counter		SMALLINT,
		gcgeneration	INT DEFAULT 1,
		PRIMARY KEY	(id), 
		INDEX		TP_group_index (loadgroup)) 
				TYPE = InnoDB;

	CREATE TABLE TP_attribute (
		id		BIGINT NOT NULL, 
		pkey		VARCHAR(255) BINARY,
		pval		VARCHAR(255) BINARY, 
		ptype		CHAR(1),
		PRIMARY KEY	(id, pkey),
		INDEX		TP_value_index (pval(15))) 
				TYPE = InnoDB;

	CREATE TABLE TP_big (
		id		BIGINT NOT NULL, 
		pkey		VARCHAR(255) BINARY,
		pval		LONGBLOB,
		PRIMARY KEY	(id, pkey))
				TYPE = InnoDB;

	CREATE TABLE TP_counters (
		name		VARCHAR(128) BINARY,
		cval		BIGINT,
		PRIMARY KEY	(name));


END
	$x =~ s/#.*//mg;
	return $x;
}

sub table_list
{
	return (qw(TP_object TP_attribute TP_big TP_counters));
}

sub clean_query
{
	my ($dbo, $query) = @_;
	if ($query =~ /\bSELECT\b/i && ! $dbo->{readyonly}) {
		$query =~ s/;//;
		# $query .= " LOCK IN SHARE MODE";
		# $query .= " FOR UPDATE";
		if ($dbo->{mysql_for_update}) {
			# great
		} elsif (defined $dbo->{mysql_for_update}) {
			$query .= " FOR UPDATE";
		} else {
			warn "dbo->mysql_for_update not defined";
			$query .= " FOR UPDATE";
		}
	}
	1 while $query =~ s/  +/ /g;  # query log is easier to debug
	return $dbo->SUPER::clean_query($query);
}

sub initial_query_set
{
	return <<END;
		allocate_id:
			UPDATE TP_counters
			SET cval = cval + ?
			WHERE name = 'objectid'
		get_id:
			SELECT cval 
			FROM TP_counters
			WHERE name = 'objectid'
		bigload: 2
			SELECT pval FROM TP_big 
			WHERE id = ? AND pkey = ?
		savebig: 2 3
			INSERT INTO TP_big 
			VALUES (?, ?, ?)
		updatebig: 1 3
			UPDATE TP_big
			SET pval = ?
			WHERE id = ? AND pkey = ?
		lock_object:
			SELECT loadgroup 
			FROM TP_object
			WHERE id = ? FOR UPDATE 
		lock_attribute:
			SELECT ptype
			FROM TP_attribute
			WHERE id = ? AND pkey = ? FOR UPDATE
END
}

sub db_initial_values
{
	require OOPS::Setup;
	return <<END;
	INSERT INTO TP_counters values ('objectid', $OOPS::last_reserved_oid + 1);
END
}

sub allocate_id
{
	my $dbo = shift;
	my $id;
	if ($dbo->{id_pool_start} && $dbo->{id_pool_start} < $dbo->{id_pool_end}) {
		$id = $dbo->{id_pool_start}++;
		print "in allocate_id, allocating $id from pool\n" if $OOPS::debug_object_id;
	} else {
		my $allocate_idQ = $dbo->query('allocate_id', dbh => $dbo->{counterdbh}, execute => $OOPS::id_alloc_size);
		my $get_idQ = $dbo->query('get_id', dbh => $dbo->{counterdbh}, execute => []);
		(($id) = $get_idQ->fetchrow_array) || die $get_idQ->errstr;
		$get_idQ->finish;
		$dbo->{id_pool_start} = $id+1;
		$dbo->{id_pool_end} = $id+$OOPS::id_alloc_size;
		$dbo->{counterdbh}->commit || die $dbo->{counterdbh}->errstr;
		print "in allocate_id, new pool: $dbo->{id_pool_start} to $dbo->{id_pool_end}\n" if $OOPS::debug_object_id;
		print "in allocate_id, allocated $id from before pool\n" if $OOPS::debug_object_id;
	}
	return $id;
}

sub post_new_object
{
	my $dbo = shift;
	return $_[0];
}

sub lock_object
{
	my ($dbo, $id) = @_;
	my $q = $dbo->query('lock_object', execute => [ $id ]);
	(undef) = $q->fetchrow_array;
	$q->finish()
}

sub lock_attribute
{
	my ($dbo, $id, $pkey) = @_;
	my $q = $dbo->query('lock_attribute', execute => [ $id, $pkey ]);
	(undef) = $q->fetchrow_array;
	$q->finish()
}

sub byebye
{
	my $dbo = shift;
	$dbo->{counterdbh}->disconnect() if $dbo->{counterdbh};
}


1;
