--- loncom/interface/lonmysql.pm 2003/03/10 21:22:36 1.8 +++ loncom/interface/lonmysql.pm 2004/04/19 21:29:15 1.20 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # MySQL utility functions # -# $Id: lonmysql.pm,v 1.8 2003/03/10 21:22:36 matthew Exp $ +# $Id: lonmysql.pm,v 1.20 2004/04/19 21:29:15 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -32,6 +32,8 @@ package Apache::lonmysql; use strict; use DBI; use Apache::lonnet(); +use POSIX qw(strftime mktime); + ###################################################################### ###################################################################### @@ -71,17 +73,18 @@ To create a table, you need a descriptio for &create_table for a description of what is needed. $table_id = &create_table({ - columns => { - id => { - type => 'INT', - restrictions => 'NOT NULL', - primary_key => 'yes', - auto_inc => 'yes' - } - verbage => { type => 'TEXT' }, - }, - column_order => [qw/id verbage idx_verbage/], - fulltext => [qw/verbage/], + id => 'tableid', # usually you will use the returned id + columns => ( + { name => 'id', + type => 'INT', + restrictions => 'NOT NULL', + primary_key => 'yes', + auto_inc => 'yes' + }, + { name => 'verbage', + type => 'TEXT' }, + ), + fulltext => [qw/verbage/], }); The above command will create a table with two columns, 'id' and 'verbage'. @@ -230,6 +233,10 @@ an array reference which holds the order The statement handler for row inserts. +=item row_replace_sth + +The statement handler for row inserts. + =back Col_order and row_insert_sth are kept internally by lonmysql and are not @@ -343,6 +350,33 @@ sub connect_to_db { =pod +=item &verify_sql_connection() + +Inputs: none. + +Returns: 0 (failure) or 1 (success) + +Checks to make sure the database can be connected to. It does not +initialize anything in the lonmysql package. + +=cut + +############################### +sub verify_sql_connection { + my $connection; + if (! ($connection = DBI->connect("DBI:mysql:loncapa","www", + $Apache::lonnet::perlvar{'lonSqlAccess'}, + { RaiseError=>0,PrintError=>0}))) { + return 0; + } + undef($connection); + return 1; +} + +############################### + +=pod + =item &disconnect_from_db() Inputs: none. @@ -362,6 +396,9 @@ sub disconnect_from_db { if (exists($Tables{$_}->{'row_insert_sth'})) { delete($Tables{$_}->{'row_insert_sth'}); } + if (exists($Tables{$_}->{'row_replace_sth'})) { + delete($Tables{$_}->{'row_replace_sth'}); + } } $dbh->disconnect if ($dbh); $debugstring = "Disconnected from database."; @@ -388,6 +425,26 @@ sub number_of_rows { return undef if (! defined(&update_table_info($table_id))); return $Tables{&translate_id($table_id)}->{'Rows'}; } +############################### + +=pod + +=item &get_dbh() + +Input: nothing + +Returns: the database handler, or undef on error. + +This routine allows the programmer to gain access to the database handler. +Be careful. + +=cut + +############################### +sub get_dbh { + return undef if (! defined(&connect_to_db())); + return $dbh; +} ############################### @@ -502,7 +559,27 @@ sub update_table_info { $debugstring = "Retrieved table info for $tablename"; return 1; } +############################### +=pod + +=item &col_order() + +Inputs: table id + +Returns: array with column order + +=cut + + +sub col_order { + my $table_id=shift; + if (&update_table_info($table_id)) { + return @{$Tables{$table_id}->{'Col_order'}}; + } else { + return (); + } +} ############################### =pod @@ -530,6 +607,18 @@ Input formats: ... }, ], + 'PRIMARY KEY' => (index_col_name,...), + KEY => [{ name => 'idx_name', + columns => (col1,col2,..),},], + INDEX => [{ name => 'idx_name', + columns => (col1,col2,..),},], + UNIQUE => [{ index => 'yes', + name => 'idx_name', + columns => (col1,col2,..),},], + FULLTEXT => [{ index => 'yes', + name => 'idx_name', + columns => (col1,col2,..),},], + } Returns: @@ -546,7 +635,12 @@ sub create_table { ################################## my @Columns; my $col_des; - my $table_id = &get_new_table_id(); + my $table_id; + if (exists($table_des->{'id'})) { + $table_id = $table_des->{'id'}; + } else { + $table_id = &get_new_table_id(); + } my $tablename = &translate_id($table_id); my $request = "CREATE TABLE IF NOT EXISTS ".$tablename." "; foreach my $coldata (@{$table_des->{'columns'}}) { @@ -577,9 +671,39 @@ sub create_table { # skip blank items. push (@Columns,$col_des) if ($col_des ne ''); } - if (exists($table_des->{'fulltext'}) && (@{$table_des->{'fulltext'}})) { - push (@Columns,'FULLTEXT ('.join(',',@{$table_des->{'fulltext'}}).')'); + if (exists($table_des->{'PRIMARY KEY'})) { + push (@Columns,'PRIMARY KEY ('.join(',',@{$table_des->{'PRIMARY KEY'}}) + .')'); + } + # + foreach my $indextype ('KEY','INDEX') { + next if (!exists($table_des->{$indextype})); + foreach my $indexdescription (@{$table_des->{$indextype}}) { + my $text = $indextype.' '; + if (exists($indexdescription->{'name'})) { + $text .=$indexdescription->{'name'}; + } + $text .= ' ('.join(',',@{$indexdescription->{'columns'}}).')'; + push (@Columns,$text); + } + } + # + foreach my $indextype ('UNIQUE','FULLTEXT') { + next if (! exists($table_des->{$indextype})); + foreach my $indexdescription (@{$table_des->{$indextype}}) { + my $text = $indextype.' '; + if (exists($indexdescription->{'index'}) && + $indexdescription->{'index'} eq 'yes') { + $text .= 'INDEX '; + } + if (exists($indexdescription->{'name'})) { + $text .=$indexdescription->{'name'}; + } + $text .= ' ('.join(',',@{$indexdescription->{'columns'}}).')'; + push (@Columns,$text); + } } + # $request .= "(".join(", ",@Columns).") "; unless($table_des->{'permanent'} eq 'yes') { $request.="COMMENT = 'temporary' "; @@ -590,7 +714,8 @@ sub create_table { ############################################# my $count = $dbh->do($request); if (! defined($count)) { - $errorstring = "$dbh ATTEMPTED:\n".$request."\nRESULTING ERROR:\n"; + $errorstring = "$dbh ATTEMPTED:\n".$request."\nRESULTING ERROR:\n". + $dbh->errstr(); return undef; } # @@ -652,7 +777,13 @@ sub get_rows { return undef; } my $tablename = &translate_id($table_id); - my $request = 'SELECT * FROM '.$tablename.' WHERE '.$condition; + my $request; + if (defined($condition) && $condition ne '') { + $request = 'SELECT * FROM '.$tablename.' WHERE '.$condition; + } else { + $request = 'SELECT * FROM '.$tablename; + $condition = 'no condition'; + } my $sth=$dbh->prepare($request); $sth->execute(); if ($sth->err) { @@ -725,6 +856,67 @@ sub store_row { return 1; } +############################### + +=pod + +=item &replace_row() + +Inputs: table id, row data + +returns undef on error, 1 on success. + +Acts like &store_row() but uses the 'REPLACE' command instead of 'INSERT'. + +=cut + +############################### +sub replace_row { + my ($table_id,$rowdata) = @_; + # + return undef if (! defined(&connect_to_db())); + my $table_status = &check_table($table_id); + return undef if (! defined($table_status)); + if (! $table_status) { + $errorstring = "table $table_id does not exist."; + return undef; + } + # + my $tablename = &translate_id($table_id); + # + my $sth; + if (exists($Tables{$tablename}->{'row_replace_sth'})) { + $sth = $Tables{$tablename}->{'row_replace_sth'}; + } else { + # Build the insert statement handler + return undef if (! defined(&update_table_info($table_id))); + my $replace_request = 'REPLACE INTO '.$tablename.' VALUES('; + foreach (@{$Tables{$tablename}->{'Col_order'}}) { + $replace_request.="?,"; + } + chop $replace_request; + $replace_request.=")"; + $sth=$dbh->prepare($replace_request); + $Tables{$tablename}->{'row_replace_sth'}=$sth; + } + my @Parameters; + if (ref($rowdata) eq 'ARRAY') { + @Parameters = @$rowdata; + } elsif (ref($rowdata) eq 'HASH') { + foreach (@{$Tables{$tablename}->{'Col_order'}}) { + push(@Parameters,$rowdata->{$_}); + } + } + $sth->execute(@Parameters); + if ($sth->err) { + $errorstring = "$dbh ATTEMPTED replace @Parameters RESULTING ERROR:\n". + $sth->errstr; + return undef; + } + $debugstring = "Stored row."; + return 1; +} + ########################################### =pod @@ -741,18 +933,20 @@ sub tables_in_db { return undef if (!defined(&connect_to_db())); my $sth=$dbh->prepare('SHOW TABLES'); $sth->execute(); - if ($sth->err) { - $errorstring = "$dbh ATTEMPTED:\n".'SHOW TABLES'. + $sth->execute(); + my $aref = $sth->fetchall_arrayref; + if ($sth->err()) { + $errorstring = + "$dbh ATTEMPTED:\n".'fetchall_arrayref after SHOW TABLES'. "\nRESULTING ERROR:\n".$sth->errstr; return undef; } - my $aref = $sth->fetchall_arrayref; - my @table_list=(); + my @table_list; foreach (@$aref) { - push @table_list,$_->[0]; + push(@table_list,$_->[0]); } - $debugstring = "Got list of tables in DB: @table_list"; - return @table_list; + $debugstring = "Got list of tables in DB: ".join(',',@table_list); + return(@table_list); } ########################################### @@ -800,7 +994,7 @@ sub check_table { my @Table_list = &tables_in_db(); my $result = 0; foreach (@Table_list) { - if (/^$table_id$/) { + if ($_ eq $table_id) { $result = 1; last; } @@ -831,18 +1025,79 @@ sub remove_from_table { return undef if (!defined(&connect_to_db())); # $table_id = &translate_id($table_id); - my $command = 'DELETE FROM '.$table_id.' WHERE '.$dbh->quote($column). + my $command = 'DELETE FROM '.$table_id.' WHERE '.$column. " LIKE BINARY ".$dbh->quote($value); my $sth = $dbh->prepare($command); - $sth->execute(); - if ($sth->err) { + unless ($sth->execute()) { $errorstring = "ERROR on execution of ".$command."\n".$sth->errstr; return undef; } + $debugstring = $command; my $rows = $sth->rows; return $rows; } +########################################### + +=pod + +=item drop_table($table_id) + +Issues a 'drop table if exists' command + +=cut + +########################################### + +sub drop_table { + my ($table_id) = @_; + return undef if (!defined(&connect_to_db())); + # + $table_id = &translate_id($table_id); + my $command = 'DROP TABLE IF EXISTS '.$table_id; + my $sth = $dbh->prepare($command); + $sth->execute(); + if ($sth->err) { + $errorstring = "ERROR on execution of ".$command."\n".$sth->errstr; + return undef; + } + $debugstring = $command; + delete($Tables{$table_id}); # remove any knowledge of the table + return 1; # if we got here there was no error, so return a 'true' value +} + + + + +# ---------------------------- convert 'time' format into a datetime sql format +sub sqltime { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = + localtime(&unsqltime($_[0])); + $mon++; $year+=1900; + return "$year-$mon-$mday $hour:$min:$sec"; +} + +sub maketime { + my %th=@_; + return POSIX::mktime(($th{'seconds'},$th{'minutes'},$th{'hours'}, + $th{'day'},$th{'month'}-1, + $th{'year'}-1900,0,0,$th{'dlsav'})); +} + + +######################################### +# +# Retro-fixing of un-backward-compatible time format + +sub unsqltime { + my $timestamp=shift; + if ($timestamp=~/^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/) { + $timestamp=&maketime('year'=>$1,'month'=>$2,'day'=>$3, + 'hours'=>$4,'minutes'=>$5,'seconds'=>$6); + } + return $timestamp; +} + 1;