#!/usr/bin/perl -w
#
# $Id: Interface.pm,v 1.2 2002/03/18 20:33:08 fpmip Exp $
#
# IPFC Database abstraction layer
#
# Author : Tycho Fruru <tycho@fruru.com>
#
# Copyright (C) 2001 Tycho Fruru <tycho@fruru.com>
# Copyright (C) 2001 Conostix S.A. Luxembourg - Tycho Fruru <tycho@conostix.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
## This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
package IPFC::Backend::DB::Interface;

#use Data::Dumper;

use IPFC::Backend::Interface;
use IPFC::Backend::DB::Attributes;
use IPFC::Log;
use IPFC::LogUnit;
use IPFC::Event;
use IPFC::EventGroup;

use strict;
use warnings;

our @ISA = ("IPFC::Backend::Interface");

my %text_to_id;
my %id_to_text;
my %text_to_id_not_committed;
my %id_to_text_not_committed;
my @text_not_committed;
my $current_sql_query = '';    #used to stall database inserts until pre-commit time
my @current_sql_bindings = (); #used to stall database inserts until pre-commit time

sub new($) {
# takes an open db handle
	my $proto = shift;
	my $class = ref($proto) || $proto;
	my $dbh = shift;
	
	my $self;
	
#	if ($dbh->isa("DBI")) {
		$self->{'database-handle'} = $dbh;
#	} else {
#		die "IPFC::DB::Interface takes only a DBI database handle as argument !";
#	}
	
	$dbh->do("set enable_seqscan to off;");
	
	bless($self,$class);
	IPFC::Backend::DB::Attributes->init($self);
	return $self;
}
	
# sub get_event from IPFC::Backend::Interface

sub get_events($) {
# takes a reference to an array of event_ids to get
# returns a IPFC::EventGroup
# $return->{<event_id>}->{<attribute name>}->[index] = value

  my $self = shift;
  my $event_ids = shift;

  my $dbh = $self->{"database-handle"};
  
  my $attribute_name_id = IPFC::Backend::DB::Attributes->get_attribute_name_id();
  my $attribute_id_name = IPFC::Backend::DB::Attributes->get_attribute_id_name();
  
  my $sql_query = 'select "event_id","agent_id",events."table_id","time","logfile_id","message",'.
  		  '"agent_type_name","entry_type","entry_subtype","entry_version","entry_transport" '.
  		  'from "events","table_names" where table_names.table_id = events.table_id and "event_id" in (\''.
		  join("','",@{$event_ids}).'\')';

  my $problem = 0;
  
  my $sth = $dbh->prepare($sql_query) or $problem = 1;
  $sth->execute or $problem = 1;
  
  my $result;
  
  while (my ($event_id,$agent_id,$table_id,$time,$logfile_id,$message,$agent_type_name,
             $event_type,$event_subtype,$event_version,$event_transport) = $sth->fetchrow_array) {
    $result->{$event_id}->{'event_id'}=$event_id;
    $result->{$event_id}->{'agent_id'}=$agent_id;
    $result->{$event_id}->{'table_id'}=$table_id;
    $result->{$event_id}->{'time'}=$time;
    $result->{$event_id}->{'logfile_id'}=$logfile_id;
    $result->{$event_id}->{'message'}=$message;
    $result->{$event_id}->{'agent_type_name'}=$agent_type_name;
    $result->{$event_id}->{'event_type'}=$event_type;
    $result->{$event_id}->{'event_subtype'}=$event_subtype;
    $result->{$event_id}->{'event_version'}=$event_version;
    $result->{$event_id}->{'event_transport'}=$event_transport;
  }
  
  $sth->finish;
  
  foreach my $attr_type ("attr_int","attr_int8","attr_timestamp",
			 "attr_date","attr_bytea","attr_numeric","attr_boolean",
			 "attr_hash") {
    my $sql_query = 'select "event_id","attr_id","val" from "'.$attr_type.'" '.
    		    'where "event_id" in (\''.
		     join("','",@{$event_ids}).'\')';
    $sth = $dbh->prepare($sql_query);
    $sth->execute;   
    while (my ($curr_event_id,$curr_attr_id,$curr_val) = $sth->fetchrow_array) {
        $result->{$curr_event_id}->{$attribute_id_name->{$curr_attr_id}} = $curr_val;
    }
  }

  $sql_query = 'select "event_id","attr_id",text_ref."val" from attr_text,text_ref '.
    		    'where attr_text.val = text_ref.id and "event_id" in (\''.
		     join("','",@{$event_ids}).'\')';
  $sth = $dbh->prepare($sql_query);
  $sth->execute;   
  while (my ($curr_event_id,$curr_attr_id,$curr_val) = $sth->fetchrow_array) {
      $result->{$curr_event_id}->{$attribute_id_name->{$curr_attr_id}} = $curr_val;
  }

  $sth->finish;
  
  my $eventgroup = IPFC::EventGroup->new;
  
  foreach my $event_id (keys %$result) {
	my $event = IPFC::Event->new;
	$event->add_attributes($result->{$event_id});
	$eventgroup->add_event($event);
  }
  
  return $eventgroup;
}

sub put_event($) {
# takes an IPFC::Event object
# and puts it in the database

  my $self = shift;
  my $event_object = shift;

  my $dbh = $self->{'database-handle'};
  
  if ($event_object->isa("IPFC::Event")) {
  	my $sql_query;  # will contain the full query necessary to get one event in the database
	my @sql_values; # will contain all variable bindings to get $sql_query to work

	my $attribute_name_id = IPFC::Backend::DB::Attributes->get_attribute_name_id();
	my $attribute_name_type = IPFC::Backend::DB::Attributes->get_attribute_name_type();
		
	# first, get standard stuff out of the way (events table)
	
	#print Dumper($r_hash);
	my $r_hash = $event_object;
	
	if (!defined($r_hash->{"agent_id"}->[0])) {
		IPFC::Log::logger(1,"No agent_id found !");
		return 0;
	}
	
	if (!defined($r_hash->{"table_id"}->[0])) {
		IPFC::Log::logger(1,"No table_id found !");
		return 0;
	}
	
#	if (!defined($r_hash->{"logfile_id"}->[0])) {
#		IPFC::Log::logger(1,"No logfile_id found !");
#		return 0;
#	}
	
	$sql_query = "insert into events ".
			"(agent_id,table_id";
	my $sql_placeholders = "?,?";
	@sql_values=($r_hash->{"agent_id"}->[0],$r_hash->{"table_id"}->[0]);
	
	
	if (defined($r_hash->{"time"}->[0])) {
		$sql_query .= ",time";
		$sql_placeholders .= ",?";
		push @sql_values,$r_hash->{"time"}->[0];
	}
	if (defined($r_hash->{"message"}->[0])) {
		$sql_query .= ",message";
		$sql_placeholders .= ",?";
		push @sql_values,$r_hash->{"message"}->[0];
	}
	$sql_query .= ") values (".$sql_placeholders."); ";
	
#	print $sql_query;
	
	delete $r_hash->{"agent_id"};
	delete $r_hash->{"table_id"};
	delete $r_hash->{"logfile_id"};
	
	delete $r_hash->{"time"};
	delete $r_hash->{"message"};
	
#	sleep 1;
	
	foreach my $current_attribute (keys %{$r_hash}) {
	    my $current_value = $r_hash->{$current_attribute}->[0];
	    my $attr_type = $attribute_name_type->{$current_attribute};
	    my $attr_id   = $attribute_name_id->{$current_attribute};
	    
	    if (!defined($attr_id) || !defined($attr_type)) {
	    	IPFC::Log::logger(1,"put_event: Attribute $current_attribute not found !");
		return 0;
	    }
	    

	    if ($attr_type eq "attr_text") {
	    
	       if (!defined($text_to_id{$current_value})) {
	         if (!defined($text_to_id_not_committed{$current_value})) {
	           $sql_query .= "insert into text_ref (val) select ? except select val from text_ref where val = ?;".
		 	  	  "insert into attr_text (attr_id,val) select ?,id from text_ref where val = ?;";
	           push @sql_values,$current_value,$current_value,$attr_id,$current_value;
		   push @text_not_committed,$current_value;
		 } else { # text_to_id_not_committed is defined
		   $sql_query .= "insert into attr_text (attr_id,val) values (?,?); ";
		   push @sql_values,$attr_id,$text_to_id_not_committed{$current_value};
		 }
	       } else { # text_to_id is defined
	         $sql_query .= "insert into attr_text (attr_id,val) values (?,?); ";
		 push @sql_values,$attr_id,$text_to_id{$current_value};
	       }
	       	       
	    } else { # non-text attribute
       	       $sql_query .= "insert into $attr_type (attr_id,val) values (?,?); ";
	       push @sql_values,$attr_id,$current_value;
	    }
	}

	$sql_query .= "insert into alert_queue values (currval('event_id_seq'));";
	
#	print $sql_query;

# construct the complete query (which can be quite long !)
	
	$current_sql_query .= $sql_query;
	push @current_sql_bindings,@sql_values;
	
	my $rc = 1;
	if ($#current_sql_bindings > 50) {
		$rc = $self->pre_commit;
	}
	
	return $rc;
  } else { # the paramater was not an IPFC::Event object
  	warn "The parameter to put_event was not an IPFC::Event object !";
	return 0;
  }
}

# sub put_eventgroup from IPFC::Backend::Interface

sub put_logfile($) {
  my $self = shift;
  my $logfile = shift;
  
  if ($logfile->isa("IPFC::Logfile")) {
  	my $dbh = $self->{'database-handle'};
	
	my $agent_id = $logfile->get_agent_id;
	my $wrapper_id = $logfile->get_wrapper_id;
	my $generation_id = $logfile->get_generation_id;
	my $sequence_id = $logfile->get_sequence_id;
	my $transac_id = $logfile->get_transac_id;
	my $agent_time = $logfile->get_agent_time;
	
	my $sth = $dbh->prepare("insert into logfiles ".
		   "(agent_id,wrapper_id,generation_id, sequence_id,transac_id,agent_time) values ".
		   "(?,?,?,?,?,?);")
		   or return 0;
	$sth->execute($agent_id,$wrapper_id,$generation_id,$sequence_id,$transac_id,$agent_time)
		or return 0;
	return 1;
  } else {
  	warn "The object given was not an IPFC::Logfile !";
	return 0;
  }
}
	


# sub put_logunit from IPFC::Backend::Interface

sub get_text_id($$) {
# takes an open database handle
# and a text, returns the number of the text in the database
# get_text_id can go to the database to query unknown texts and change text_ref
  my $dbh = shift;
  my $message = shift;
  my $sth; 
  my $id;
	
	if (!defined($text_to_id{$message})) {
	  if (!defined($text_to_id_not_committed{$message})) {
	  	IPFC::Log::logger(10,"Getting text id for $message");
		$sth = $dbh->prepare("select id from text_ref where val = ?::text");
		$sth->execute($message);
		($id) = $sth->fetchrow_array;
		$sth->finish;
		if (defined($id)) {
			$text_to_id_not_committed{$message} = $id;
			$id_to_text_not_committed{$id}=$message;
		}
	  } else {
	  	$id = $text_to_id_not_committed{$message};
	  }
	} else {
		$id = $text_to_id{$message};
	}
		
	return $id;
}

sub pre_commit() {
# flush all queries out to the database so that we can actually perform a commit when we want to
	my $self = shift;
	my $dbh = $self->{'database-handle'};
	
	my $return_code = 0;
	
	IPFC::Log::logger(5,"In pre_commit...");
	
	if ($current_sql_query eq "") {
	    IPFC::Log::logger(5,"nothing to pre-commit !");
	    return 1;
	}
	
	my $sth=$dbh->prepare($current_sql_query);
	if (!$sth) {
		IPFC::Log::logger(1,"Problem preparing $current_sql_query with values ".join(',',@current_sql_bindings));
		eval { $sth->finish; };
		return 0;
	} 
	
	my $rc = $sth->execute(@current_sql_bindings);
	if (!$rc) {
		IPFC::Log::logger(1,"Problem executing $current_sql_query with values ".join(',',@current_sql_bindings));
		eval { $sth->finish; };
		return 0;	
	}
	$sth->finish or return 0;

	$current_sql_query = '';
	@current_sql_bindings = ();
	
	if ($#text_not_committed +1 > 0) {
	  $sth = $dbh->prepare("select id,val from text_ref where val in (". join(",",split("","?"x($#text_not_committed + 1))).");");
	  if (!$sth) {
		  IPFC::Log::logger(1,"Problem getting new text entries ...");
		  eval { $sth->finish; };
		  return 0;
	  }
	
	  $rc = $sth->execute(@text_not_committed);
	  if (!$rc) {
		  IPFC::Log::logger(1,"Problem getting new text entries ...");
		  eval { $sth->finish; };
		  return 0;
	  }
	
	  @text_not_committed = ();
	
	  while (my ($id,$val) = $sth->fetchrow_array) {
		  $text_to_id_not_committed{$val} = $id;
		  $id_to_text_not_committed{$id} = $val;
	  } 
	  $sth->finish or return 0;
	}
	
	return 1;
}
	
sub commit($) {
# used to commit to the database
	my $self = shift;
	
	my $dbh = $self->{'database-handle'};
	
	if ($current_sql_query ne '') {
		IPFC::Log::logger(1,"There are some pending transactions while performing the commit ! Use pre_commit first !");
		die;
	};
	
	my $result = $dbh->commit;
	if (!$result) {
		die "hmm... cannot commit !";
	}
	
	IPFC::Log::logger(7,"Cleaning up after COMMIT...");
	if (%text_to_id_not_committed) {
	  foreach my $key (keys %text_to_id_not_committed) {
		$text_to_id{$key} = $text_to_id_not_committed{$key};
	  }
	}
	undef %text_to_id_not_committed;
	
	if (%id_to_text_not_committed) {
	  foreach my $key (keys %id_to_text_not_committed) {
	        $id_to_text{$key} = $id_to_text_not_committed{$key};
	  }
	}
	undef %id_to_text_not_committed;
	
	undef %id_to_text;
	undef %text_to_id;
	
	IPFC::Log::logger(7,"... done");

	return $result;
}

sub rollback($) {
# used to rollback the database
	my $self = shift;
	my $dbh = $self->{'database-handle'};
	my $result = $dbh->rollback;
	if (!$result) {
		die "hmm... cannot rollback !";
	}
	
	undef %text_to_id_not_committed;
	undef %id_to_text_not_committed;

	return $result;
}


return 1;
