From: timriker <timriker@c11ca15a-4712-0410-83d8-924469b57eb5>
Date: Sun, 3 Nov 2002 05:21:01 +0000 (+0000)
Subject: add initial SQLite support
X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=281d349fc98d4423b03312fc514639fcc610ff80;p=infobot.git

add initial SQLite support


git-svn-id: https://svn.code.sf.net/p/infobot/code/trunk@595 c11ca15a-4712-0410-83d8-924469b57eb5
---

diff --git a/blootbot/.cvsignore b/blootbot/.cvsignore
index 357cc01..e74fd0a 100644
--- a/blootbot/.cvsignore
+++ b/blootbot/.cvsignore
@@ -1,3 +1,4 @@
 blootbot-*
 blootbot.pid
+blootbot.sqlite
 Temp
diff --git a/blootbot/INSTALL b/blootbot/INSTALL
index a85aaa1..8ab8336 100644
--- a/blootbot/INSTALL
+++ b/blootbot/INSTALL
@@ -21,6 +21,7 @@ Method of installation.
 
 - Choose your database:
 	- MySQL, read INSTALL.mysql (supported)
+	- SQLite, read INSTALL.sqlite (unsupported, may work)
 	- PgSQL, read INSTALL.pgsql (unsupported, may work)
 	- Berkeley DBM, read INSTALL.dbm (unsupported, may work)
 
diff --git a/blootbot/INSTALL.dbm b/blootbot/INSTALL.dbm
index 93b3db6..02d9c4c 100644
--- a/blootbot/INSTALL.dbm
+++ b/blootbot/INSTALL.dbm
@@ -7,7 +7,7 @@ INSTALLATION of dbm
 - If the bot crashes, the dbm file may increase in size dramatically, from
   900k-1400k to 16m-24m
 - dbm is a slow but simple form of db.  If you want performance, try mysql
-  or pgsql (NOT YET)
+  or sqlite (NOT SUPPORTED) or pgsql (NOT YET)
 
 = To convert dbm file to mysql table:
 	- run 'scripts/dbm2mysql.pl old-db' to convert dbm database file
diff --git a/blootbot/INSTALL.sqlite b/blootbot/INSTALL.sqlite
new file mode 100644
index 0000000..a216df2
--- /dev/null
+++ b/blootbot/INSTALL.sqlite
@@ -0,0 +1,5 @@
+INSTALL.sqlite
+----------------
+
+- Install SQLite libraries and DBI Perl modules.
+	- Debian: (apt-get install libsqlite0 libdbd-sqlite-perl)
diff --git a/blootbot/files/sample/sample.config.broken b/blootbot/files/sample/sample.config.broken
index d5b43e9..d43fa1c 100644
--- a/blootbot/files/sample/sample.config.broken
+++ b/blootbot/files/sample/sample.config.broken
@@ -45,6 +45,7 @@ set maxLogSize		10000000
 # [str] Ability to remember/tell factoids
 #	none	-- disable.
 #	mysql	-- MySQL
+#	sqlite  -- SQLite (libdbd-sqlite-perl)
 #	pgsql	-- PostGreSQL (NOT SUPPORTED YET)
 #	dbm	-- Berkeley DBM
 ### REQUIRED by factoids,freshmeat,karma,seen,...
diff --git a/blootbot/src/IRC/Schedulers.pl b/blootbot/src/IRC/Schedulers.pl
index 9c5bbed..c0c9cf2 100644
--- a/blootbot/src/IRC/Schedulers.pl
+++ b/blootbot/src/IRC/Schedulers.pl
@@ -243,10 +243,10 @@ sub seenFlushOld {
     my $max_time = &getChanConfDefault("seenMaxDays", 30) *60*60*24;
     my $delete   = 0;
 
-    if ($param{'DBType'} =~ /^pgsql|mysql/i) {
+    if ($param{'DBType'} =~ /^pgsql|mysql|sqlite/i) {
 	my $query;
 
-	if ($param{'DBType'} =~ /^mysql$/i) {
+	if ($param{'DBType'} =~ /^mysql|sqlite$/i) {
 	    $query = "SELECT nick,time FROM seen GROUP BY nick HAVING ".
 			"UNIX_TIMESTAMP() - time > $max_time";
 	} else {	# pgsql.
@@ -547,7 +547,7 @@ sub seenFlush {
     $stats{'new'}	= 0;
     $stats{'old'}	= 0;
 
-    if ($param{'DBType'} =~ /^(mysql|pgsql)$/i) {
+    if ($param{'DBType'} =~ /^(mysql|pgsql|sqlite)$/i) {
 	foreach $nick (keys %seencache) {
 	    my $retval = &dbReplace("seen", "nick", (
 			"nick" => lc $seencache{$nick}{'nick'},
diff --git a/blootbot/src/Modules/Countdown.pl b/blootbot/src/Modules/Countdown.pl
index e805d8e..b294477 100644
--- a/blootbot/src/Modules/Countdown.pl
+++ b/blootbot/src/Modules/Countdown.pl
@@ -44,7 +44,7 @@ sub Countdown {
 	### SQL SPECIFIC.
 	my ($to_days,$dayname,$monname);
 
-	if ($param{'DBType'} =~ /^mysql$/i) {
+	if ($param{'DBType'} =~ /^mysql|sqlite$/i) {
 	    $to_days = (&dbRawReturn("SELECT TO_DAYS(NOW()) - TO_DAYS('$sqldate')"))[0];
 	    $dayname = (&dbRawReturn("SELECT DAYNAME('$sqldate')"))[0];
 	    $monname = (&dbRawReturn("SELECT MONTHNAME('$sqldate')"))[0];
diff --git a/blootbot/src/Modules/UserDCC.pl b/blootbot/src/Modules/UserDCC.pl
index 578cbc7..8bd29db 100644
--- a/blootbot/src/Modules/UserDCC.pl
+++ b/blootbot/src/Modules/UserDCC.pl
@@ -358,7 +358,7 @@ sub userDCC {
 	    return;
 	}
 
-	### TODO: fix up $op to support mysql/pgsql/dbm(perl)
+	### TODO: fix up $op to support mysql/sqlite/pgsql/dbm(perl)
 	### TODO: => add db/sql specific function to fix this.
 	my @list = &searchTable("factoids", "factoid_key",
 			"factoid_value", $op);
diff --git a/blootbot/src/Process.pl b/blootbot/src/Process.pl
index cbdb6ca..d4473b4 100644
--- a/blootbot/src/Process.pl
+++ b/blootbot/src/Process.pl
@@ -338,7 +338,7 @@ sub process {
 	}
     }
 
-    if (&IsParam("factoids") and $param{'DBType'} =~ /^(mysql|pgsql|dbm)/i) {
+    if (&IsParam("factoids") and $param{'DBType'} =~ /^(mysql|sqlite|pgsql|dbm)/i) {
 	&FactoidStuff();
     } elsif ($param{'DBType'} =~ /^none$/i) {
 	return "NO FACTOIDS.";
diff --git a/blootbot/src/db_sqlite.pl b/blootbot/src/db_sqlite.pl
new file mode 100644
index 0000000..6b77aa5
--- /dev/null
+++ b/blootbot/src/db_sqlite.pl
@@ -0,0 +1,534 @@
+#
+# db_sqlite.pl: SQLite database frontend.
+#      Author: Tim Riker <Tim@Rikers.org>
+#     Version: 0.1 (20021101)
+#     Created: 20021101
+#
+
+package main;
+eval "use DBI";
+
+if (&IsParam("useStrict")) { use strict; }
+
+#####
+# &openDB($dbname, $sqluser, $sqlpass, $nofail);
+sub openDB {
+    my ($db, $user, $pass, $no_fail) = @_;
+    my $dsn = "DBI:SQLite:dbname=$db.sqlite";
+    $dbh = DBI->connect($dsn,$user,$pass);
+
+    if ($dbh) {
+	&status("Opened SQLite connection $dsn");
+    } else {
+	&ERROR("cannot connect $dsn.");
+	&ERROR("since SQLite is not available, shutting down bot!");
+	&closePID();
+	&closeSHM($shm);
+	&closeLog();
+
+	return if ($no_fail);
+
+	exit 1;
+    }
+}
+
+sub closeDB {
+    return 0 unless ($dbh);
+
+    my $hoststr = "";
+    $hoststr = " to $param{'SQLHost'}" if (exists $param{'SQLHost'});
+
+    &status("Closed SQLite connection$hoststr.");
+    $dbh->disconnect();
+
+    return 1;
+}
+
+#####
+# Usage: &dbQuote($str);
+sub dbQuote {
+    return $dbh->quote($_[0]);
+}
+
+#####
+# Usage: &dbGet($table, $select, $where);
+sub dbGet {
+    my ($table, $select, $where) = @_;
+    my $query	= "SELECT $select FROM $table";
+    $query	.= " WHERE $where" if ($where);
+
+    if (!defined $select or $select =~ /^\s*$/) {
+	&WARN("dbGet: select == NULL.");
+	return;
+    }
+
+    if (!defined $table or $table =~ /^\s*$/) {
+	&WARN("dbGet: table == NULL.");
+	return;
+    }
+
+    my $sth;
+    if (!($sth = $dbh->prepare($query))) {
+	&ERROR("Get: prepare: $DBI::errstr");
+	return;
+    }
+
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&ERROR("Get: execute: '$query'");
+	$sth->finish;
+	return 0;
+    }
+
+    my @retval = $sth->fetchrow_array;
+
+    $sth->finish;
+
+    if (scalar @retval > 1) {
+	return @retval;
+    } elsif (scalar @retval == 1) {
+	return $retval[0];
+    } else {
+	return;
+    }
+}
+
+#####
+# Usage: &dbGetCol($table, $select, $where, [$type]);
+sub dbGetCol {
+    my ($table, $select, $where, $type) = @_;
+    my $query	= "SELECT $select FROM $table";
+    $query	.= " WHERE ".$where if ($where);
+    my %retval;
+
+    my $sth = $dbh->prepare($query);
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&ERROR("GetCol: execute: '$query'");
+	$sth->finish;
+	return;
+    }
+
+    if (defined $type and $type == 2) {
+	&DEBUG("dbgetcol: type 2!");
+	while (my @row = $sth->fetchrow_array) {
+	    $retval{$row[0]} = join(':', $row[1..$#row]);
+	}
+	&DEBUG("dbgetcol: count => ".scalar(keys %retval) );
+
+    } elsif (defined $type and $type == 1) {
+	while (my @row = $sth->fetchrow_array) {
+	    # reverse it to make it easier to count.
+	    if (scalar @row == 2) {
+		$retval{$row[1]}{$row[0]} = 1;
+	    } elsif (scalar @row == 3) {
+		$retval{$row[1]}{$row[0]} = 1;
+	    }
+	    # what to do if there's only one or more than 3?
+	}
+
+    } else {
+	while (my @row = $sth->fetchrow_array) {
+	    $retval{$row[0]} = $row[1];
+	}
+    }
+
+    $sth->finish;
+
+    return %retval;
+}
+
+#####
+# Usage: &dbGetColNiceHash($table, $select, $where);
+sub dbGetColNiceHash {
+    my ($table, $select, $where) = @_;
+    $select	||= "*";
+    my $query	= "SELECT $select FROM $table";
+    $query	.= " WHERE ".$where if ($where);
+    my %retval;
+
+    my $sth = $dbh->prepare($query);
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&ERROR("GetColNiceHash: execute: '$query'");
+#	&ERROR("GetCol => $DBI::errstr");
+	$sth->finish;
+	return;
+    }
+
+    %retval = %{ $sth->fetchrow_hashref() };
+
+    $sth->finish;
+
+    return %retval;
+}
+
+####
+# Usage: &dbGetColInfo($table);
+sub dbGetColInfo {
+    my ($table) = @_;
+
+    my $query = "SHOW COLUMNS from $table";
+    my %retval;
+
+    my $sth = $dbh->prepare($query);
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&ERROR("GRI => '$query'");
+	&ERROR("GRI => $DBI::errstr");
+	$sth->finish;
+	return;
+    }
+
+    my @cols;
+    while (my @row = $sth->fetchrow_array) {
+	push(@cols, $row[0]);
+    }
+    $sth->finish;
+
+    return @cols;
+}
+
+#####
+# Usage: &dbSet($table, $primhash_ref, $hash_ref);
+#  Note: dbSet does dbQuote.
+sub dbSet {
+    my ($table, $phref, $href) = @_;
+    my $where = join(' AND ', map {
+		$_."=".&dbQuote($phref->{$_})
+	} keys %{$phref}
+    );
+
+    if (!defined $phref) {
+	&WARN("dbset: phref == NULL.");
+	return;
+    }
+
+    if (!defined $href) {
+	&WARN("dbset: href == NULL.");
+	return;
+    }
+
+    if (!defined $table) {
+	&WARN("dbset: table == NULL.");
+	return;
+    }
+
+    my $result = &dbGet($table, join(',', keys %{$phref}), $where);
+
+    my(@keys,@vals);
+    foreach (keys %{$href}) {
+	push(@keys, $_);
+	push(@vals, &dbQuote($href->{$_}) );
+    }
+
+    if (!@keys or !@vals) {
+	&WARN("dbset: keys or vals is NULL.");
+	return;
+    }
+
+    my $query;
+    if (defined $result) {
+	my @keyval;
+	for(my$i=0; $i<scalar @keys; $i++) {
+	    push(@keyval, $keys[$i]."=".$vals[$i] );
+	}
+
+	$query = "UPDATE $table SET ".
+		join(' AND ', @keyval).
+		" WHERE ".$where;
+    } else {
+	foreach (keys %{$phref}) {
+	    push(@keys, $_);
+	    push(@vals, &dbQuote($phref->{$_}) );
+	}
+
+	$query = sprintf("INSERT INTO $table (%s) VALUES (%s)",
+		join(',',@keys), join(',',@vals) );
+    }
+
+    &dbRaw("Set", $query);
+
+    return 1;
+}
+
+#####
+# Usage: &dbUpdate($table, $primkey, $primval, %hash);
+#  Note: dbUpdate does dbQuote.
+sub dbUpdate {
+    my ($table, $primkey, $primval, %hash) = @_;
+    my (@array);
+
+    foreach (keys %hash) {
+	push(@array, "$_=".&dbQuote($hash{$_}) );
+    }
+
+    &dbRaw("Update", "UPDATE $table SET ".join(', ', @array).
+		" WHERE $primkey=".&dbQuote($primval)
+    );
+
+    return 1;
+}
+
+#####
+# Usage: &dbInsert($table, $primkey, %hash);
+#  Note: dbInsert does dbQuote.
+sub dbInsert {
+    my ($table, $primkey, %hash, $delay) = @_;
+    my (@keys, @vals);
+    my $p	= "";
+
+    if ($delay) {
+	&DEBUG("dbI: delay => $delay");
+	$p	= " DELAYED";
+    }
+
+    foreach (keys %hash) {
+	push(@keys, $_);
+	push(@vals, &dbQuote($hash{$_}));
+    }
+
+    &dbRaw("Insert($table)", "INSERT $p INTO $table (".join(',',@keys).
+		") VALUES (".join(',',@vals).")"
+    );
+
+    return 1;
+}
+
+#####
+# Usage: &dbReplace($table, $key, %hash);
+#  Note: dbReplace does optional dbQuote.
+sub dbReplace {
+    my ($table, $key, %hash) = @_;
+    my (@keys, @vals);
+
+    foreach (keys %hash) {
+	if (s/^-//) {	# as is.
+	    push(@keys, $_);
+	    push(@vals, $hash{'-'.$_});
+	} else {
+	    push(@keys, $_);
+	    push(@vals, &dbQuote($hash{$_}));
+	}
+    }
+
+    if (0) {
+	&DEBUG("REPLACE INTO $table (".join(',',@keys).
+		") VALUES (". join(',',@vals). ")" );
+    }
+
+    &dbRaw("Replace($table)", "REPLACE INTO $table (".join(',',@keys).
+		") VALUES (". join(',',@vals). ")"
+    );
+
+    return 1;
+}
+
+#####
+# Usage: &dbSetRow($table, $vref, $delay);
+#  Note: dbSetRow does dbQuote.
+sub dbSetRow ($@$) {
+    my ($table, $vref, $delay) = @_;
+    my $p	= ($delay) ? " DELAYED " : "";
+
+    # see 'perldoc perlreftut'
+    my @values;
+    foreach (@{ $vref }) {
+	push(@values, &dbQuote($_) );
+    }
+
+    if (!scalar @values) {
+	&WARN("dbSetRow: values array == NULL.");
+	return;
+    }
+
+    return &dbRaw("SetRow", "INSERT $p INTO $table VALUES (".
+	join(",", @values) .")" );
+}
+
+#####
+# Usage: &dbDel($table, $primkey, $primval, [$key]);
+#  Note: dbDel does dbQuote
+sub dbDel {
+    my ($table, $primkey, $primval, $key) = @_;
+
+    &dbRaw("Del", "DELETE FROM $table WHERE $primkey=".
+		&dbQuote($primval)
+    );
+
+    return 1;
+}
+
+# Usage: &dbRaw($prefix,$rawquery);
+sub dbRaw {
+    my ($prefix,$query) = @_;
+    my $sth;
+
+    if (!($sth = $dbh->prepare($query))) {
+	&ERROR("Raw($prefix): $DBI::errstr");
+	return 0;
+    }
+
+#    &DEBUG("query => '$query'.");
+
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&ERROR("Raw($prefix): => '$query'");
+	# $DBI::errstr is printed as warning automatically.
+	$sth->finish;
+	return 0;
+    }
+
+    $sth->finish;
+
+    return 1;
+}
+
+# Usage: &dbRawReturn($rawquery);
+sub dbRawReturn {
+    my ($query) = @_;
+    my @retval;
+
+    my $sth = $dbh->prepare($query);
+    &SQLDebug($query);
+    &ERROR("RawReturn => '$query'.") unless $sth->execute;
+    while (my @row = $sth->fetchrow_array) {
+	push(@retval, $row[0]);
+    }
+    $sth->finish;
+
+    return @retval;
+}
+
+####################################################################
+##### Misc DBI stuff...
+#####
+
+#####
+# Usage: &countKeys($table, [$col]);
+sub countKeys {
+    my ($table, $col) = @_;
+    $col ||= "*";
+    &DEBUG("&countKeys($table, $col)");
+
+    return (&dbRawReturn("SELECT count($col) FROM $table"))[0];
+}
+
+# Usage: &sumKey($table, $col);
+sub sumKey {
+    my ($table, $col) = @_;
+
+    return (&dbRawReturn("SELECT sum($col) FROM $table"))[0];
+}
+
+#####
+# Usage: &randKey($table, $select);
+sub randKey {
+    my ($table, $select) = @_;
+    my $rand	= int(rand(&countKeys($table) - 1));
+    my $query	= "SELECT $select FROM $table LIMIT $rand,1";
+
+    my $sth	= $dbh->prepare($query);
+    &SQLDebug($query);
+    &WARN("randKey($query)") unless $sth->execute;
+    my @retval	= $sth->fetchrow_array;
+    $sth->finish;
+
+    return @retval;
+}
+
+#####
+# Usage: &deleteTable($table);
+sub deleteTable {
+    &dbRaw("deleteTable($_[0])", "DELETE FROM $_[0]");
+}
+
+#####
+# Usage: &searchTable($table, $select, $key, $str);
+#  Note: searchTable does dbQuote.
+sub searchTable {
+    my($table, $select, $key, $str) = @_;
+    my $origStr = $str;
+    my @results;
+
+    # allow two types of wildcards.
+    if ($str =~ /^\^(.*)\$$/) {
+	&DEBUG("searchTable: should use dbGet(), heh.");
+	$str = $1;
+    } else {
+	$str .= "%"	if ($str =~ s/^\^//);
+	$str = "%".$str if ($str =~ s/\$$//);
+	$str = "%".$str."%" if ($str eq $origStr);	# el-cheapo fix.
+    }
+
+    $str =~ s/\_/\\_/g;
+    $str =~ s/\?/_/g;	# '.' should be supported, too.
+    $str =~ s/\*/%/g;	# for sqlite.
+    # end of string fix.
+
+    my $query = "SELECT $select FROM $table WHERE $key LIKE ". 
+		&dbQuote($str);
+    my $sth = $dbh->prepare($query);
+    &DEBUG("query => '$query'.");
+    &SQLDebug($query);
+    if (!$sth->execute) {
+	&WARN("Search($query)");
+	return;
+    }
+
+    while (my @row = $sth->fetchrow_array) {
+	push(@results, $row[0]);
+    }
+    $sth->finish;
+
+    return @results;
+}
+
+sub dbCreateTable {
+    my($table)	= @_;
+    my(@path)	= ($bot_data_dir, ".","..","../..");
+    my $found	= 0;
+    my $data;
+
+    foreach (@path) {
+	my $file = "$_/setup/$table.sql";
+	&DEBUG("dbCT: file => $file");
+	next unless ( -f $file );
+
+	&DEBUG("dbCT: found!!!");
+
+	open(IN, $file);
+	while (<IN>) {
+	    chop;
+	    $data .= $_;
+	}
+
+	$found++;
+	last;
+    }
+
+    if (!$found) {
+	return 0;
+    } else {
+	&dbRaw("createTable($table)", $data);
+	return 1;
+    }
+}
+
+sub checkTables {
+    # retrieve a list of tables's from the server.
+    my %db;
+    foreach (&dbRawReturn("SELECT name FROM sqlite_master WHERE type='table'"))
+    {
+	$db{$_} = 1;
+    }
+
+    foreach ("factoids", "freshmeat", "rootwarn", "seen", "stats") {
+	next if (exists $db{$_});
+	&status("checkTables: creating $_...");
+
+	&dbCreateTable($_);
+    }
+}
+
+1;
diff --git a/blootbot/src/modules.pl b/blootbot/src/modules.pl
index 0861982..ae2dac5 100644
--- a/blootbot/src/modules.pl
+++ b/blootbot/src/modules.pl
@@ -105,12 +105,12 @@ sub loadDBModules {
 	&status("Loading pgsql support.");
 	require "$bot_src_dir/db_pgsql.pl";
 	&showProc(" (pgsql)");
-    } elsif ($param{'DBType'} =~ /^dbm$/i) {
-	&status("Loading Berkeley DBM support.");
-	$f = "$bot_src_dir/db_dbm.pl";
-	require $f;
+    } elsif ($param{'DBType'} =~ /^sqlite$|^dbm$/i) {
+	&status("Loading " . $param{'DBType'} . " support.");
+	$f="$bot_src_dir/db_" . $param{'DBType'} . ".pl";
 	$moduleAge{$f} = (stat $f)[9];
-	&showProc(" $bot_src_dir/db_dbm.pl");
+	require $f;
+	&showProc(" $bot_src_dir/db_" . $param{'DBType'} . ".pl");
     } else {
 	&status("DB support DISABLED.");
 	return;