package Rowlib::Database;

use strict;
use Carp;
use Rowlib::Row;

# new: create a new Database object
# params: DBI dbh, database schema description (or filehandle to same)
sub new {
    my $class = shift;
    my ($dbh, $schema_text, @extra_params) = @_;
    if (ref $schema_text) {
	local $/;
	$schema_text = <$schema_text>;
    }
    my %schema = ();
    for my $line (split /\n/, $schema_text) {
	my ($table, $module, $pkey) = $line =~ /^(\S*)(?: (\S*))?: *(.*)$/;
	$schema{$table} = { module => $module, pkey => [split ' ', $pkey] };
	if (defined $module) {
	    # try to load the module for this table
	    eval "local \$SIG{__DIE__}; use $module";
	    if ($@ ne '') {
		# an error occurred in locating / compiling the module
		croak "*** Rowlib module $module would not load / compile\n$@\n";
	    }
	}
    }
    my $self = {
	dbh => $dbh,
	schema => \%schema,
	debug => 0,
	auto => 1,
	@extra_params # just for overriding debug, auto
    };
    return bless $self, $class;
}

sub dbh {
    my $self = shift;
    return $self->{dbh};
}

sub select {
    my $self = shift;
    return $self->new_row(@_, mode=>'select');
}

sub insert {
    my $self = shift;
    return $self->new_row(@_, mode=>'insert');
}

sub new_row {
    local $Carp::CarpLevel = 1; # so croak doesn't attribute errors to Database.pm
    my $self = shift;
    my $table = shift;
    my $schema = $self->{schema}{$table};
    croak "Table $table not mentioned in schema" unless defined $schema;
    my $row_module = $schema->{module} || 'Rowlib::Row';
    $row_module->new(@_, db=>$self, table=>$table, pkey=>$schema->{pkey});
}

sub sel {
    my $self = shift;
    my $table = shift;
    my $schema = $self->{schema}{$table};
    croak "Table $table not mentioned in schema" unless defined $schema;
    if (@_ == 1) { unshift @_, $schema->{pkey}[0]; } # implicit key : primary key
    my %data = @_;
    my @args = %data ? (data=>\%data) : ();
    return $self->select($table, @args);
}

# sel1 and select1 are identical to sel and select, except that they return undef straight away
# if there are no matching rows
sub sel1 {
    my $self = shift;
    my $row = $self->sel(@_);
    return $row->next ? $row : undef;
}

sub select1 {
    my $self = shift;
    my $row = $self->select(@_);
    return $row->next ? $row : undef;
}

sub ins {
    my $self = shift;
    my $table = shift;
    return $self->insert($table, data=>(@_ ? {@_} : undef));
}

# this one looks for an existing record, matching the data given,
# and creates one if it doesn't exist already.  Useful for logging, etc.
sub selins {
    my $self = shift;
    my $row = $self->sel1(@_) || $self->ins(@_);
    return $row;
}

sub selectall {
    local $Carp::CarpLevel = 1;
    my $self = shift;
    my $table = shift;
    my $schema = $self->{schema}{$table};
    croak "Table $table not mentioned in schema" unless defined $schema;
    my $row_module = $schema->{module} || 'Rowlib::Row';
    $row_module->selectall(@_, db=>$self, table=>$table, pkey=>$schema->{pkey});
}

sub do {
    my $self = shift;
    my $command = shift;
    $self->{dbh}->do($command, @_)
	or croak "Rowlib::Database: cannot do command '$command'";
    # error handling?
}

sub debug {
    my $self = shift;
    my $debug = shift;
    $self->{debug} = defined $debug ? $debug : 1;
}

sub auto {
    my $self = shift;
    $self->{auto} = shift;
}

1
