[Phplib-commit] CVS: php-lib-stable/php db_odbc.inc,1.4,1.5
Brought to you by:
nhruby,
richardarcher
|
From: Richard A. <ric...@us...> - 2002-07-11 23:47:25
|
Update of /cvsroot/phplib/php-lib-stable/php
In directory usw-pr-cvs1:/tmp/cvs-serv25628
Modified Files:
db_odbc.inc
Log Message:
Merging in major changes from -devel tree.
These changes are extensive and only minimally tested.
They are intended to be mostly back-compatible, but there are probably some issues.
Some specifics:
Brings the style up to date with the MySQL class.
Fixes numerous bugs, especially in num_rows.
Add pseudo-locking and sequence numbers.
Pulled db-specific constants out into class vars.
Extended metadata function in line with -devel classes.
Normalised error message reporting.
Probably lots of other stuff.
Index: db_odbc.inc
===================================================================
RCS file: /cvsroot/phplib/php-lib-stable/php/db_odbc.inc,v
retrieving revision 1.4
retrieving revision 1.5
diff -C2 -d -r1.4 -r1.5
*** db_odbc.inc 14 Mar 2002 20:41:07 -0000 1.4
--- db_odbc.inc 11 Jul 2002 23:47:21 -0000 1.5
***************
*** 10,29 ****
class DB_Sql {
var $Host = "";
var $Database = "";
var $User = "";
var $Password = "";
- var $UseODBCCursor = 0;
! var $Link_ID = 0;
! var $Query_ID = 0;
var $Record = array();
var $Row = 0;
!
var $Errno = 0;
var $Error = "";
! var $Auto_Free = 0; ## set this to 1 to automatically free results
! var $PConnect = 0; ## Set to 1 to use persistent database connections
/* public: constructor */
--- 10,52 ----
class DB_Sql {
+
+ /* public: connection parameters */
var $Host = "";
var $Database = "";
var $User = "";
var $Password = "";
! /* public: configuration parameters */
! var $Auto_Free = 0; ## Set to 1 to automatically free results
! var $PConnect = 0; ## Set to 1 to use persistent database connections
! var $Debug = 0; ## Set to 1 for debugging messages
! var $Halt_On_Error = "yes"; ## "yes" (halt with message), "no" (ignore errors quietly), "report" (ignore errror, but spit a warning)
! var $Seq_Table = "db_sequence"; ## Name of the sequence table
! var $Seq_ID_Col = "p_nextid"; ## Name of the Sequence ID column in $Seq_Table
! var $Seq_Name_Col = "p_seq_name"; ## Name of the Sequence Name column in $Seq_Table
! var $Lock_Table = "db_sequence"; ## Name of the lock table
! var $Lock_Name_Col = "p_seq_name"; ## Name of the lock Name column
! var $Lock_ID_Col = "p_nextid"; ## Name of the lock Name column
! var $Lock_Timeout = 5; ## Wait this long for a lock
! var $Lock_Override = 1; ## Set to 1 to override lock after $Lock_Timeout seconds
! var $UseODBCCursor = 0; ## Set to 1 to enable execution of stored procedures on the server
!
! /* public: result array and current row number */
var $Record = array();
var $Row = 0;
! var $RowCount = -1; ## used to remember num_rows() return value
!
! /* public: current error number and error text */
var $Errno = 0;
var $Error = "";
! /* public: this is an api revision, not a CVS revision. */
! var $type = "odbc";
! var $revision = "1.2";
!
! /* private: link and query handles */
! var $Link_ID = 0;
! var $Query_ID = 0;
!
/* public: constructor */
***************
*** 32,36 ****
}
! function connect() {
if ( 0 == $this->Link_ID ) {
if(!$this->PConnect) {
--- 55,80 ----
}
! /* public: some trivial reporting */
! function link_id() {
! return $this->Link_ID;
! }
!
! function query_id() {
! return $this->Query_ID;
! }
!
! /* public: connection management */
! function connect($Database = "", $Host = "", $User = "", $Password = "") {
! /* Handle defaults */
! if ("" == $Database)
! $Database = $this->Database;
! if ("" == $Host)
! $Host = $this->Host;
! if ("" == $User)
! $User = $this->User;
! if ("" == $Password)
! $Password = $this->Password;
!
! /* establish connection, select database */
if ( 0 == $this->Link_ID ) {
if(!$this->PConnect) {
***************
*** 43,50 ****
}
}
}
-
- function query($Query_String) {
/* No empty queries, please, since PHP4 chokes on them. */
if ($Query_String == "")
--- 87,102 ----
}
}
+
+ return $this->Link_ID;
}
+ /* public: discard the query result */
+ function free() {
+ odbc_free_result($this->Query_ID);
+ $this->Query_ID = 0;
+ }
+
+ /* public: perform a query */
+ function query($Query_String) {
/* No empty queries, please, since PHP4 chokes on them. */
if ($Query_String == "")
***************
*** 55,61 ****
return 0;
! $this->connect();
!
! # printf("<br>Debug: query = %s<br>\n", $Query_String);
# re...@ne... suggested that we use this instead of the odbc_exec().
--- 107,121 ----
return 0;
! if (!$this->connect()) {
! return 0; /* we already complained in connect() about that. */
! };
!
! # New query, discard previous result.
! if ($this->Query_ID) {
! $this->free();
! }
!
! if ($this->Debug)
! printf("Debug: query = %s<br>\n", $Query_String);
# re...@ne... suggested that we use this instead of the odbc_exec().
***************
*** 64,136 ****
# $this->Query_Ok = odbc_execute($this->Query_ID);
! $this->Query_ID = odbc_exec($this->Link_ID,$Query_String);
! $this->Row = 0;
odbc_binmode($this->Query_ID, 1);
odbc_longreadlen($this->Query_ID, 4096);
if (!$this->Query_ID) {
- $this->Errno = 1;
- $this->Error = "General Error (The ODBC interface cannot return detailed error messages).";
$this->halt("Invalid SQL: ".$Query_String);
}
return $this->Query_ID;
}
!
function next_record() {
$this->Record = array();
! $stat = odbc_fetch_into($this->Query_ID, ++$this->Row, &$this->Record);
if (!$stat) {
if ($this->Auto_Free) {
! odbc_free_result($this->Query_ID);
! $this->Query_ID = 0;
! };
} else {
- // add to Record[<key>]
$count = odbc_num_fields($this->Query_ID);
! for ($i=1; $i<=$count; $i++)
! $this->Record[strtolower(odbc_field_name ($this->Query_ID, $i)) ] = $this->Record[ $i - 1 ];
}
return $stat;
}
!
! function seek($pos) {
$this->Row = $pos;
}
! function metadata($table) {
! $count = 0;
! $id = 0;
! $res = array();
! $this->connect();
! $id = odbc_exec($this->Link_ID, "select * from $table");
! if (!$id) {
! $this->Errno = 1;
! $this->Error = "General Error (The ODBC interface cannot return detailed error messages).";
! $this->halt("Metadata query failed.");
! }
! $count = odbc_num_fields($id);
! for ($i=1; $i<=$count; $i++) {
! $res[$i]["table"] = $table;
! $name = odbc_field_name ($id, $i);
! $res[$i]["name"] = $name;
! $res[$i]["type"] = odbc_field_type ($id, $name);
! $res[$i]["len"] = 0; // can we determine the width of this column?
! $res[$i]["flags"] = ""; // any optional flags to report?
}
!
! odbc_free_result($id);
! return $res;
}
function affected_rows() {
return odbc_num_rows($this->Query_ID);
}
!
function num_rows() {
# Many ODBC drivers don't support odbc_num_rows() on SELECT statements.
$num_rows = odbc_num_rows($this->Query_ID);
- //printf ($num_rows."<br>");
# This is a workaround. It is intended to be ugly.
--- 124,337 ----
# $this->Query_Ok = odbc_execute($this->Query_ID);
! $this->Query_ID = odbc_exec($this->Link_ID, $Query_String);
! $this->RowCount = -1; # reset num_rows() return value
! $this->Row = 0;
! $this->Errno = 0;
! $this->Error = "";
odbc_binmode($this->Query_ID, 1);
odbc_longreadlen($this->Query_ID, 4096);
if (!$this->Query_ID) {
$this->halt("Invalid SQL: ".$Query_String);
}
+
+ # Will return nada if it fails. That's fine.
return $this->Query_ID;
}
!
! /* public: walk result set */
function next_record() {
+ if (!$this->Query_ID) {
+ $this->halt("next_record called with no query pending.");
+ return 0;
+ }
+
$this->Record = array();
! $this->Row += 1;
! $stat = odbc_fetch_row($this->Query_ID, $this->Row);
! $this->Errno = 0;
! $this->Error = "";
!
if (!$stat) {
if ($this->Auto_Free) {
! $this->free();
! }
} else {
$count = odbc_num_fields($this->Query_ID);
! for ($i=1; $i<=$count; $i++) {
! $this->Record[strtolower(odbc_field_name($this->Query_ID, $i))] = odbc_result($this->Query_ID, $i);
! }
}
return $stat;
}
!
! /* public: position in result set */
! function seek($pos = 0) {
$this->Row = $pos;
+ return 1;
}
! /* public: get the time in msecs */
! function getmicrotime() {
! list($usec, $sec) = explode(" ", microtime());
! return (float)$usec + (float)$sec;
! }
! /* public: table locking */
! /*
! ODBC is not guaranteed do table locking natively. This function emulates
! locking with certain constraints. The intention is to provide at least the
! minimum functionality required to implement sequences via nextid().
!
! This function maintains a list of locked tables in a lock table. To lock
! a table it inserts a row into the lock table with the locked table name
! suffixed with '_p_lock' as a primary key and with a value of 0.
! The suffix and value are used so that the sequence table can be used as
! the lock table if desired.
! While this row exists a lock will not be granted to another process.
! To protect against threads failing to release a lock, if a lock is not
! obtained within $this->Lock_Timeout seconds the lock may be deleted.
! The timeout value needs to be set to a reasonable length based on the
! expected transaction time on the locked table.
!
! This method will only be effective if the locked table is accessed
! exclusively via this class. This is not a database-enforced lock and
! there is nothing preventing other applications from modifying a table
! while it is 'locked'.
!
! The function uses microtime to prevent multiple threads all hitting the
! table simultaneously on the tick of a second.
!
! The function halt()s if a lock cannot be obtained.
! */
! function lock($table, $mode="write") {
! if (!$this->connect()) {
! return 0; /* we already complained in connect() about that. */
! };
!
! $getsql = sprintf("INSERT into %s (%s, %s) VALUES ('%s', 0)",
! $this->Lock_Table,
! $this->Lock_Name_Col,
! $this->Lock_ID_Col,
! strtolower($table)."_p_lock");
! $delsql = sprintf("DELETE FROM %s where %s='%s' AND %s=0",
! $this->Lock_Table,
! $this->Lock_Name_Col,
! strtolower($table)."_p_lock",
! $this->Lock_ID_Col);
! $selsql = sprintf("SELECT * FROM %s where %s='%s' AND %s=0",
! $this->Lock_Table,
! $this->Lock_Name_Col,
! strtolower($table)."_p_lock",
! $this->Lock_ID_Col);
! $timeout = $this->getmicrotime() + $this->Lock_Timeout;
! $got_lock = 0;
! $override = $this->Lock_Override;
! while (!$got_lock) {
! $got_lock = @odbc_exec($this->Link_ID, $getsql);
! if ($this->Debug && !$got_lock) {
! echo "missed lock... looping\n";
! flush();
! }
! $currtime = $this->getmicrotime();
! if (!$got_lock) {
! if ($timeout < $currtime) {
! if (!$override) {
! # try to select existing lock
! if (!@odbc_exec($this->Link_ID, $selsql)) {
! # lock select failed. Either the table does not exist or the lock was
! # released just this instant. Try to get a lock to see which...
! $got_lock = @odbc_exec($this->Link_ID, $getsql);
! if (!$got_lock) {
! $this->halt("Lock select failed. Does the table $this->Lock_Table exist?");
! }
! return $got_lock;
! }
! $this->halt("lock() failed.");
! return 0;
! } else {
! # delete existing lock
! if ($this->Debug) {
! echo "overriding lock\n";
! }
!
! if (!@odbc_exec($this->Link_ID, $delsql)) {
! # lock override failed. Either the table does not exist or the lock was
! # released just this instant. Try to get a lock to see which...
! $got_lock = @odbc_exec($this->Link_ID, $getsql);
! if (!$got_lock) {
! $this->halt("Lock override failed. Does the table $this->Lock_Table exist?");
! }
! return $got_lock;
! } else {
! # just deleted the lock so try to get it straight away
! $got_lock = @odbc_exec($this->Link_ID, $getsql);
! $timeout = $currtime + $this->Lock_Timeout; # reset the timer
! $override = 0; # override once only
! # fall through to wait loop
! }
! }
! }
! }
!
! if (!$got_lock) {
! $waittime = $currtime + 0.5;
! while ($waittime > $this->getmicrotime()) {
! ;
! }
! }
}
! if ($this->Debug && !$got_lock) {
! echo "missed lock... bug!\n";
! } else {
! echo "got lock\n";
! flush();
! }
! return $got_lock;
}
+ function unlock($table = "") {
+ if (!$this->connect()) {
+ return 0; /* we already complained in connect() about that. */
+ };
+
+ # Note: this unlocks ALL tables if $table is blank!
+ if ($table == "") {
+ $delsql = sprintf("DELETE FROM %s where %s LIKE '%%_p_lock' AND %s=0",
+ $this->Lock_Table,
+ $this->Lock_Name_Col,
+ $this->Lock_ID_Col);
+ } else {
+ $delsql = sprintf("DELETE FROM %s where %s='%s' AND %s=0",
+ $this->Lock_Table,
+ $this->Lock_Name_Col,
+ strtolower($table)."_p_lock",
+ $this->Lock_ID_Col);
+ }
+
+ $res = @odbc_exec($this->Link_ID, $delsql);
+ if (!$res) {
+ $this->halt("unlock() failed.");
+ }
+ return $res;
+ }
+
+ /* public: evaluate the result (size, width) */
function affected_rows() {
return odbc_num_rows($this->Query_ID);
}
!
function num_rows() {
+ # Due to a strange problem with the odbc_fetch_row function it is only
+ # possible to walk through the result set once. By storing the row count
+ # this problem is avoided.
+ # Once the number of rows has been calculated it is stored in $RowCount.
+ if ($this->RowCount != -1) {
+ return $this->RowCount;
+ }
+
# Many ODBC drivers don't support odbc_num_rows() on SELECT statements.
$num_rows = odbc_num_rows($this->Query_ID);
# This is a workaround. It is intended to be ugly.
***************
*** 158,191 ****
}
return $num_rows;
}
!
function num_fields() {
return count($this->Record)/2;
}
function nf() {
return $this->num_rows();
}
!
function np() {
print $this->num_rows();
}
!
! function f($Field_Name) {
! return $this->Record[strtolower($Field_Name)];
}
!
! function p($Field_Name) {
! print $this->f($Field_Name);
}
!
function halt($msg) {
printf("</td></tr></table><b>Database error:</b> %s<br>\n", $msg);
printf("<b>ODBC Error</b>: %s (%s)<br>\n",
$this->Errno,
$this->Error);
- die("Session halted.");
}
}
?>
--- 359,585 ----
}
+ $this->RowCount = $num_rows;
return $num_rows;
}
!
function num_fields() {
+ # NOTE: this only works after next_record has been called!
return count($this->Record)/2;
}
+ /* public: shorthand notation */
function nf() {
return $this->num_rows();
}
!
function np() {
print $this->num_rows();
}
!
! function f($Name) {
! if (isset($this->Record[$Name])) {
! return $this->Record[strtolower($Name)];
! }
! return "";
}
!
! function p($Name) {
! print $this->f($Name);
}
!
! /* public: sequence numbers */
! function nextid($seq_name) {
! if (!$this->connect()) {
! return 0; /* we already complained in connect() about that. */
! };
!
! if ($this->lock($this->Seq_Table)) {
! /* get sequence number (locked) and increment */
! $q = sprintf("select %s from %s where %s = '%s'",
! $this->Seq_ID_Col,
! $this->Seq_Table,
! $this->Seq_Name_Col,
! $seq_name);
! $id = odbc_exec($this->Link_ID, $q);
! $res = 0;
! if (odbc_fetch_row($id, 1)) {
! $res = array();
! $count = odbc_num_fields($id);
! for ($i=1; $i<=$count; $i++) {
! $res[strtolower(odbc_field_name($id, $i))] = odbc_result($id, $i);
! }
! }
!
! /* No current value, make one */
! if (!is_array($res)) {
! $currentid = 0;
! $q = sprintf("insert into %s ( %s, %s ) values('%s', %s)",
! $this->Seq_Table,
! $this->Seq_Name_Col,
! $this->Seq_ID_Col,
! $seq_name,
! $currentid);
! $id = odbc_exec($this->Link_ID, $q);
! } else {
! $currentid = $res[$this->Seq_ID_Col];
! }
! $nextid = $currentid + 1;
! $q = sprintf("update %s set %s = '%s' where %s = '%s'",
! $this->Seq_Table,
! $this->Seq_ID_Col,
! $nextid,
! $this->Seq_Name_Col,
! $seq_name);
! $id = odbc_exec($this->Link_ID, $q);
! $this->unlock();
! } else {
! $this->halt("cannot lock ".$this->Seq_Table." - has it been created?");
! return 0;
! }
! return $nextid;
! }
!
! /* public: return table metadata */
! function metadata($table = "", $full = false) {
! $count = 0;
! $id = 0;
! $res = array();
!
! /*
! * Due to compatibility problems with Table we changed the behavior
! * of metadata();
! * If $full is set, metadata returns additional information
! *
! * This information is always returned:
! * $result[]:
! * [0]["table"] table name
! * [0]["name"] field name
! * [0]["type"] field type
! * [0]["len"] field length
! * [0]["flags"] field flags
! *
! * If $full is set this information is also returned:
! * $result[]:
! * ["num_fields"] number of metadata records
! * [0]["php_type"] the corresponding PHP-type
! * [0]["php_subtype"] the subtype of PHP-type
! * ["meta"][field name] index of field named "field name"
! * This one could be used if you have a field name, but no index.
! * Test: if (isset($result['meta']['myfield'])) { ...
! * [unique] = field names which have an unique key, separated by space
! */
!
! // if no $table specified, assume that we are working with a query result
! if ($table) {
! $this->connect();
! $id = odbc_exec($this->Link_ID, "select * from $table");
! if (!$id) {
! $this->halt("Metadata query failed.");
! return false;
! }
! } else {
! $id = $this->Query_ID;
! if (!$id) {
! $this->halt("No query specified.");
! return false;
! }
! }
!
! $count = odbc_num_fields($id);
!
! for ($i=1; $i<=$count; $i++) {
! $res[$i]["table"] = $table;
! $res[$i]["name"] = odbc_field_name ($id, $i);
! $res[$i]["type"] = odbc_field_type ($id, $i);
! $res[$i]["len"] = odbc_field_len ($id, $i);
! $res[$i]["flags"] = ""; // any optional flags to report?
! }
!
! if ($full) {
! $uniq = array();
! $res["num_fields"] = $count;
!
! # ODBC result set starts at 1
! for ($i=1; $i<=$count; $i++) {
! $res["meta"][$res[$i]["name"]] = $i;
! switch ($res[$i]["type"]) {
! case "var string":
! case "string" :
! case "char" :
! $res[$i]["php_type"]="string";
! $res[$i]["php_subtype"]="";
! break;
! case "timestamp" :
! case "datetime" :
! case "date" :
! case "time" :
! $res[$i]["php_type"]="string";
! $res[$i]["php_subtype"]="date";
! break;
! case "blob" :
! $res[$i]["php_type"]="string";
! $res[$i]["php_subtype"]="blob";
! break;
! case "real" :
! $res[$i]["php_type"]="double";
! $res[$i]["php_subtype"]="";
! break;
! case "long" :
! default :
! $res[$i]["php_type"]="int";
! $res[$i]["php_subtype"]="";
! break;
! }
! if ( ereg("(unique_key|primary_key)",$res[$i]["flags"]) ) {
! $uniq[]=$res[$i]["name"];
! }
! }
! $res["unique"]=join(" ",$uniq);
! }
!
! // free the result only if we were called on a table
! if ($table) {
! odbc_free_result($id);
! }
! return $res;
! }
!
! /* public: find available table names */
! function table_names() {
! $this->connect();
! $h = odbc_tables($this->Link_ID);
! $i = 0;
! while(odbc_fetch_row($h)) {
! if (odbc_result($h, 4) == "TABLE") {
! $return[$i]["table_name"] = odbc_result($h, 3);
! $return[$i]["tablespace_name"] = odbc_result($h, 1);
! $return[$i]["database"] = odbc_result($h, 1);
! $i += 1;
! }
! }
! odbc_free_result($h);
! return $return;
! }
!
! /* private: error handling */
function halt($msg) {
+ $this->Errno = 1;
+ $this->Error = "General Error (The ODBC interface cannot return detailed error messages).";
+ if ($this->Halt_On_Error == "no")
+ return;
+
+ $this->haltmsg($msg);
+
+ if ($this->Halt_On_Error != "report")
+ die("Session halted.");
+ }
+
+ function haltmsg($msg) {
printf("</td></tr></table><b>Database error:</b> %s<br>\n", $msg);
printf("<b>ODBC Error</b>: %s (%s)<br>\n",
$this->Errno,
$this->Error);
}
+
}
?>
|