diff options
| -rw-r--r-- | adodb-datadict.inc.php | 24 | ||||
| -rw-r--r-- | datadict/datadict-firebird.inc.php | 242 | ||||
| -rw-r--r-- | drivers/adodb-firebird.inc.php | 1150 |
3 files changed, 928 insertions, 488 deletions
diff --git a/adodb-datadict.inc.php b/adodb-datadict.inc.php index bfacb5ff..c73d5f2a 100644 --- a/adodb-datadict.inc.php +++ b/adodb-datadict.inc.php @@ -353,7 +353,7 @@ class ADODB_DataDict { function nameQuote($name = NULL,$allowBrackets=false) { if (!is_string($name)) { - return FALSE; + return false; } $name = trim($name); @@ -428,14 +428,14 @@ class ADODB_DataDict { function actualType($meta) { $meta = strtoupper($meta); - + /* * Add support for custom meta types. We do this * first, that allows us to override existing types */ if (isset($this->connection->customMetaTypes[$meta])) return $this->connection->customMetaTypes[$meta]['actual']; - + return $meta; } @@ -704,23 +704,23 @@ class ADODB_DataDict { case '0': case 'NAME': $fname = $v; break; case '1': - case 'TYPE': - - $ty = $v; - $ftype = $this->actualType(strtoupper($v)); + case 'TYPE': + + $ty = $v; + $ftype = $this->actualType(strtoupper($v)); break; case 'SIZE': - $dotat = strpos($v,'.'); - if ($dotat === false) + $dotat = strpos($v,'.'); + if ($dotat === false) $dotat = strpos($v,','); - if ($dotat === false) + if ($dotat === false) $fsize = $v; else { - + $fsize = substr($v,0,$dotat); $fprec = substr($v,$dotat+1); - + } break; case 'UNSIGNED': $funsigned = true; break; diff --git a/datadict/datadict-firebird.inc.php b/datadict/datadict-firebird.inc.php index ba80699d..79d0a8f5 100644 --- a/datadict/datadict-firebird.inc.php +++ b/datadict/datadict-firebird.inc.php @@ -22,8 +22,8 @@ // security - hide paths if (!defined('ADODB_DIR')) die(); -class ADODB2_firebird extends ADODB_DataDict { - +class ADODB2_firebird extends ADODB_DataDict +{ var $databaseType = 'firebird'; var $seqField = false; var $seqPrefix = 's_'; @@ -32,79 +32,92 @@ class ADODB2_firebird extends ADODB_DataDict { var $alterCol = ' ALTER'; var $dropCol = ' DROP'; - function ActualType($meta) + function actualType($meta) { - + $meta = strtoupper($meta); - - /* - * Add support for custom meta types. We do this - * first, that allows us to override existing types - */ - if (isset($this->connection->customMetaTypes[$meta])) + + // Add support for custom meta types. + // We do this first, that allows us to override existing types + if (isset($this->connection->customMetaTypes[$meta])) { return $this->connection->customMetaTypes[$meta]['actual']; - + } + switch($meta) { - case 'C': return 'VARCHAR'; - case 'XL': - case 'X': return 'BLOB SUB_TYPE TEXT'; + case 'C': + return 'VARCHAR'; + case 'XL': + return 'BLOB SUB_TYPE BINARY'; + case 'X': + return 'BLOB SUB_TYPE TEXT'; - case 'C2': return 'VARCHAR(32765)'; // up to 32K - case 'X2': return 'VARCHAR(4096)'; + case 'C2': + return 'VARCHAR(32765)'; // up to 32K + case 'X2': + return 'VARCHAR(4096)'; - case 'V': return 'CHAR'; - case 'C1': return 'CHAR(1)'; + case 'V': + return 'CHAR'; + case 'C1': + return 'CHAR(1)'; - case 'B': return 'BLOB'; + case 'B': + return 'BLOB'; - case 'D': return 'DATE'; - case 'TS': - case 'T': return 'TIMESTAMP'; + case 'D': + return 'DATE'; + case 'TS': + case 'T': + return 'TIMESTAMP'; - case 'L': return 'SMALLINT'; - case 'I': return 'INTEGER'; - case 'I1': return 'SMALLINT'; - case 'I2': return 'SMALLINT'; - case 'I4': return 'INTEGER'; - case 'I8': return 'BIGINT'; + case 'L': + case 'I1': + case 'I2': + return 'SMALLINT'; + case 'I': + case 'I4': + return 'INTEGER'; + case 'I8': + return 'BIGINT'; - case 'F': return 'DOUBLE PRECISION'; - case 'N': return 'DECIMAL'; - default: - return $meta; + case 'F': + return 'DOUBLE PRECISION'; + case 'N': + return 'DECIMAL'; + default: + return $meta; } } - function NameQuote($name = NULL,$allowBrackets=false) + function nameQuote($name = null, $allowBrackets = false) { if (!is_string($name)) { - return FALSE; + return false; } $name = trim($name); - if ( !is_object($this->connection) ) { + if (!is_object($this->connection)) { return $name; } $quote = $this->connection->nameQuote; // if name is of the form `name`, quote it - if ( preg_match('/^`(.+)`$/', $name, $matches) ) { + if (preg_match('/^`(.+)`$/', $name, $matches)) { return $quote . $matches[1] . $quote; } // if name contains special characters, quote it - if ( !preg_match('/^[' . $this->nameRegex . ']+$/', $name) ) { + if (!preg_match('/^[' . $this->nameRegex . ']+$/', $name)) { return $quote . $name . $quote; } return $quote . $name . $quote; } - function CreateDatabase($dbname, $options=false) + function createDatabase($dbname, $options = false) { - $options = $this->_Options($options); $sql = array(); $sql[] = "DECLARE EXTERNAL FUNCTION LOWER CSTRING(80) RETURNS CSTRING(80) FREE_IT ENTRY_POINT 'IB_UDF_lower' MODULE_NAME 'ib_udf'"; @@ -112,50 +125,62 @@ class ADODB2_firebird extends ADODB_DataDict { return $sql; } - function _DropAutoIncrement($t) + function _dropAutoIncrement($tabname) { - if (strpos($t,'.') !== false) { - $tarr = explode('.',$t); - return 'DROP GENERATOR '.$tarr[0].'."s_'.$tarr[1].'"'; + if (strpos($tabname, '.') !== false) { + $tarr = explode('.', $tabname); + return 'DROP SEQUENCE ' . $tarr[0] . '."s_' . $tarr[1] . '"'; } - return 'DROP GENERATOR s_'.$t; + return 'DROP SEQUENCE s_' . $tabname; } - function _CreateSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + function _createSuffix($fname, &$ftype, $fnotnull, $fdefault, $fautoinc, $fconstraint, $funsigned) { $suffix = ''; - if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; - if ($fnotnull) $suffix .= ' NOT NULL'; - if ($fautoinc) $this->seqField = $fname; + if (strlen($fdefault)) { + $suffix .= " DEFAULT $fdefault"; + } + if ($fnotnull) { + $suffix .= ' NOT NULL'; + } + if ($fautoinc) { + $this->seqField = $fname; + } $fconstraint = preg_replace("/``/", "\"", $fconstraint); - if ($fconstraint) $suffix .= ' '.$fconstraint; + if ($fconstraint) { + $suffix .= ' ' . $fconstraint; + } return $suffix; } /** - Generate the SQL to create table. Returns an array of sql strings. - */ - function CreateTableSQL($tabname, $flds, $tableoptions=array()) + * Generate the SQL to create table. Returns an array of sql strings. + */ + function createTableSQL($tabname, $flds, $tableoptions = array()) { - list($lines,$pkey,$idxs) = $this->_GenFields($flds, true); + list($lines, $pkey, $idxs) = $this->_GenFields($flds, true); // genfields can return FALSE at times - if ($lines == null) $lines = array(); + if ($lines == null) { + $lines = array(); + } $taboptions = $this->_Options($tableoptions); - $tabname = $this->TableName ($tabname); - $sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions); + $tabname = $this->TableName($tabname); + $sql = $this->_TableSQL($tabname, $lines, $pkey, $taboptions); - if ($this->autoIncrement && !isset($taboptions['DROP'])) - { $tsql = $this->_Triggers($tabname,$taboptions); - foreach($tsql as $s) $sql[] = $s; + if ($this->autoIncrement && !isset($taboptions['DROP'])) { + $tsql = $this->_Triggers($tabname, $taboptions); + foreach ($tsql as $s) { + $sql[] = $s; + } } if (is_array($idxs)) { - foreach($idxs as $idx => $idxdef) { - $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); + foreach ($idxs as $idx => $idxdef) { + $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); $sql = array_merge($sql, $sql_idxs); } } @@ -164,44 +189,47 @@ class ADODB2_firebird extends ADODB_DataDict { } -/* -CREATE or replace TRIGGER jaddress_insert -before insert on jaddress -for each row -begin -IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN - NEW."seqField" = GEN_ID("GEN_tabname", 1); -end; -*/ - function _Triggers($tabname,$tableoptions) + /* + CREATE or replace TRIGGER jaddress_insert + before insert on jaddress + for each row + begin + IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN + NEW."seqField" = GEN_ID("GEN_tabname", 1); + end; + */ + function _triggers($tabname, $taboptions) { - if (!$this->seqField) return array(); + if (!$this->seqField) { + return array(); + } - $tab1 = preg_replace( '/"/', '', $tabname ); + $tab1 = preg_replace('/"/', '', $tabname); if ($this->schema) { - $t = strpos($tab1,'.'); - if ($t !== false) $tab = substr($tab1,$t+1); - else $tab = $tab1; + $t = strpos($tab1, '.'); + if ($t !== false) { + $tab = substr($tab1, $t + 1); + } else { + $tab = $tab1; + } $seqField = $this->seqField; - $seqname = $this->schema.'.'.$this->seqPrefix.$tab; - $trigname = $this->schema.'.t_'.$this->seqPrefix.$tab; + $seqname = $this->schema . '.' . $this->seqPrefix . $tab; + $trigname = $this->schema . '.t_' . $this->seqPrefix . $tab; } else { $seqField = $this->seqField; - $seqname = $this->seqPrefix.$tab1; - $trigname = 't_'.$seqname; + $seqname = $this->seqPrefix . $tab1; + $trigname = 't_' . $seqname; } - if (isset($tableoptions['DROP'])) - { $sql[] = "DROP GENERATOR $seqname"; - } - elseif (isset($tableoptions['REPLACE'])) - { $sql[] = "DROP GENERATOR \"$seqname\""; - $sql[] = "CREATE GENERATOR \"$seqname\""; - $sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END"; - } - else - { $sql[] = "CREATE GENERATOR $seqname"; - $sql[] = "CREATE TRIGGER $trigname FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID($seqname, 1); END"; + if (isset($taboptions['DROP'])) { + $sql[] = "DROP SEQUENCE $seqname"; + } elseif (isset($taboptions['REPLACE'])) { + $sql[] = "DROP SEQUENCE \"$seqname\""; + $sql[] = "CREATE SEQUENCE \"$seqname\""; + $sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END"; + } else { + $sql[] = "CREATE SEQUENCE $seqname"; + $sql[] = "CREATE TRIGGER $trigname FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID($seqname, 1); END"; } $this->seqField = false; @@ -211,27 +239,39 @@ end; /** * Change the definition of one column * - * As some DBM's can't do that on there own, you need to supply the complete definition of the new table, - * to allow, recreating the table and copying the content over to the new table * @param string $tabname table-name * @param string $flds column-name and type for the changed column - * @param string $tableflds='' complete definition of the new table, eg. for postgres, default '' - * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default '' + * @param string $tableflds Unused + * @param array|string $tableoptions Unused + * * @return array with SQL strings */ - function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + public function alterColumnSQL($tabname, $flds, $tableflds = '', $tableoptions = '') { - $tabname = $this->TableName ($tabname); + $tabname = $this->TableName($tabname); $sql = array(); - list($lines,$pkey,$idxs) = $this->_GenFields($flds); + list($lines, , $idxs) = $this->_GenFields($flds); // genfields can return FALSE at times - if ($lines == null) $lines = array(); + + if ($lines == null) { + $lines = array(); + } + $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' '; - foreach($lines as $v) { + + foreach ($lines as $v) { + /* + * The type must be preceded by the keyword 'TYPE' + */ + $vExplode = explode(' ', $v); + $vExplode = array_filter($vExplode); + array_splice($vExplode, 1, 0, array('TYPE')); + $v = implode(' ', $vExplode); $sql[] = $alter . $v; } + if (is_array($idxs)) { - foreach($idxs as $idx => $idxdef) { + foreach ($idxs as $idx => $idxdef) { $sql_idxs = $this->CreateIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); $sql = array_merge($sql, $sql_idxs); } diff --git a/drivers/adodb-firebird.inc.php b/drivers/adodb-firebird.inc.php index 09b7545c..e479077f 100644 --- a/drivers/adodb-firebird.inc.php +++ b/drivers/adodb-firebird.inc.php @@ -35,39 +35,113 @@ class ADODB_firebird extends ADOConnection { var $fmtTimeStamp = "'Y-m-d, H:i:s'"; var $concat_operator='||'; var $_transactionID; - var $metaTablesSQL = "select lower(rdb\$relation_name) from rdb\$relations where rdb\$relation_name not like 'RDB\$%'"; + + public $metaTablesSQL = "SELECT LOWER(rdb\$relation_name) FROM rdb\$relations"; //OPN STUFF start + var $metaColumnsSQL = "select lower(a.rdb\$field_name), a.rdb\$null_flag, a.rdb\$default_source, b.rdb\$field_length, b.rdb\$field_scale, b.rdb\$field_sub_type, b.rdb\$field_precision, b.rdb\$field_type from rdb\$relation_fields a, rdb\$fields b where a.rdb\$field_source = b.rdb\$field_name and a.rdb\$relation_name = '%s' order by a.rdb\$field_position asc"; //OPN STUFF end - var $ibasetrans; + + public $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s"; + + public $_dropSeqSQL = "DROP SEQUENCE %s"; + var $hasGenID = true; var $_bindInputArray = true; - var $buffers = 0; - var $dialect = 3; var $sysDate = "cast('TODAY' as timestamp)"; var $sysTimeStamp = "CURRENT_TIMESTAMP"; //"cast('NOW' as timestamp)"; var $ansiOuter = true; var $hasAffectedRows = true; var $poorAffectedRows = false; var $blobEncodeType = 'C'; - var $role = false; + /* + * firebird custom optionally specifies the user role + */ + public $role = false; + /* + * firebird custom optionally specifies the connection buffers + */ + public $buffers = 0; + + /* + * firebird custom optionally specifies database dialect + */ + public $dialect = 3; + var $nameQuote = ''; /// string to use to quote identifiers and names function __construct() { - // Ignore IBASE_DEFAULT we want a more practical transaction! - // if (defined('IBASE_DEFAULT')) $this->ibasetrans = IBASE_DEFAULT; - // else - $this->ibasetrans = IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED; + parent::__construct(); + $this->setTransactionMode(''); } + /** + * Sets the isolation level of a transaction. + * + * The default behavior is a more practical IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED + * instead of IBASE_DEFAULT + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:settransactionmode + * + * @param string $transaction_mode The transaction mode to set. + * + * @return void + */ + public function setTransactionMode($transaction_mode) + { + $this->_transmode = $transaction_mode; + + if (empty($transaction_mode)) { + $this->_transmode = IBASE_WAIT | IBASE_REC_VERSION | IBASE_COMMITTED; + } - // returns true or false - function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false) + } + + /** + * Connect to a database. + * + * @todo add: parameter int $port, parameter string $socket + * + * @param string|null $argHostname (Optional) The host to connect to. + * @param string|null $argUsername (Optional) The username to connect as. + * @param string|null $argPassword (Optional) The password to connect with. + * @param string|null $argDatabasename (Optional) The name of the database to start in when connected. + * @param bool $persist (Optional) Whether or not to use a persistent connection. + * + * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension + * isn't currently loaded. + */ + public function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false) { - if (!function_exists('fbird_pconnect')) return null; - if ($argDatabasename) $argHostname .= ':'.$argDatabasename; + if (!function_exists('fbird_pconnect')) + return null; + + if ($argDatabasename) + $argHostname .= ':'.$argDatabasename; + $fn = ($persist) ? 'fbird_pconnect':'fbird_connect'; + + /* + * Now merge in the standard connection parameters setting + */ + foreach ($this->connectionParameters as $options) + { + foreach($options as $k=>$v) + { + switch($k){ + case 'role': + $this->role = $v; + break; + case 'dialect': + $this->dialect = $v; + break; + case 'buffers': + $this->buffers = $v; + } + } + } + if ($this->role) $this->_connectionID = $fn($argHostname,$argUsername,$argPassword, $this->charSet,$this->buffers,$this->dialect,$this->role); @@ -75,40 +149,39 @@ class ADODB_firebird extends ADOConnection { $this->_connectionID = $fn($argHostname,$argUsername,$argPassword, $this->charSet,$this->buffers,$this->dialect); - if ($this->dialect != 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html - $this->replaceQuote = "''"; + if ($this->dialect == 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html + $this->replaceQuote = ""; } if ($this->_connectionID === false) { - $this->_handleerror(); + $this->_handleError(); return false; } - // PHP5 change. - if (function_exists('fbird_timefmt')) { - fbird_timefmt($this->fbird_datefmt,fbird_DATE ); - if ($this->dialect == 1) { - fbird_timefmt($this->fbird_datefmt,fbird_TIMESTAMP ); - } else { - fbird_timefmt($this->fbird_timestampfmt,fbird_TIMESTAMP ); - } - fbird_timefmt($this->fbird_timefmt,fbird_TIME ); + ini_set("ibase.timestampformat", $this->fbird_timestampfmt); + ini_set("ibase.dateformat", $this->fbird_datefmt); + ini_set("ibase.timeformat", $this->fbird_timefmt); - } else { - ini_set("ibase.timestampformat", $this->fbird_timestampfmt); - ini_set("ibase.dateformat", $this->fbird_datefmt); - ini_set("ibase.timeformat", $this->fbird_timefmt); - } return true; } - // returns true or false + /** + * Connect to a database with a persistent connection. + * + * @param string|null $argHostname The host to connect to. + * @param string|null $argUsername The username to connect as. + * @param string|null $argPassword The password to connect with. + * @param string|null $argDatabasename The name of the database to start in when connected. + * + * @return bool|null True if connected successfully, false if connection failed, or null if the mysqli extension + * isn't currently loaded. + */ function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) { return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,true); } - function MetaPrimaryKeys($table,$owner_notused=false,$internalKey=false) + public function metaPrimaryKeys($table,$owner_notused=false,$internalKey=false) { if ($internalKey) { return array('RDB$DB_KEY'); @@ -126,31 +199,58 @@ class ADODB_firebird extends ADOConnection { return false; } - function ServerInfo() + /** + * Get information about the current Firebird server. + * + * @return array + */ + public function serverInfo() { $arr['dialect'] = $this->dialect; switch($arr['dialect']) { - case '': - case '1': $s = 'Firebird Dialect 1'; break; - case '2': $s = 'Firebird Dialect 2'; break; - default: - case '3': $s = 'Firebird Dialect 3'; break; + case '': + case '1': + $s = 'Firebird Dialect 1'; + break; + case '2': + $s = 'Firebird Dialect 2'; + break; + default: + case '3': + $s = 'Firebird Dialect 3'; + break; } $arr['version'] = ADOConnection::_findvers($s); $arr['description'] = $s; return $arr; } - function BeginTrans() + /** + * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans(). + * + * @return bool true if succeeded or false if database does not support transactions + */ + public function beginTrans() { if ($this->transOff) return true; $this->transCnt += 1; $this->autoCommit = false; - $this->_transactionID = fbird_trans( $this->ibasetrans, $this->_connectionID ); + /* + * We manage the transaction mode via fbird_trans + */ + $this->_transactionID = fbird_trans( $this->_transmode, $this->_connectionID ); return $this->_transactionID; } - function CommitTrans($ok=true) + + /** + * Commits a transaction + * + * @param bool $ok set to false to rollback transaction, true to commit + * + * @return true/false. + */ + public function commitTrans($ok=true) { if (!$ok) { return $this->RollbackTrans(); @@ -173,31 +273,26 @@ class ADODB_firebird extends ADOConnection { function _affectedrows() { - return fbird_affected_rows( $this->_transactionID ? $this->_transactionID : $this->_connectionID ); + return fbird_affected_rows( $this->_transactionID ? $this->_transactionID : $this->_connectionID ); } - // there are some compat problems with ADODB_COUNTRECS=false and $this->_logsql currently. - // it appears that ibase extension cannot support multiple concurrent queryid's - function _Execute($sql,$inputarr=false) { - global $ADODB_COUNTRECS; - - if ($this->_logsql) { - $savecrecs = $ADODB_COUNTRECS; - $ADODB_COUNTRECS = true; // force countrecs - $ret =& ADOConnection::_Execute($sql,$inputarr); - $ADODB_COUNTRECS = $savecrecs; - } else { - $ret = ADOConnection::_Execute($sql,$inputarr); - } - return $ret; - } - - function RollbackTrans() + /** + * Rollback a smart transaction. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rollbacktrans + * + * @return bool + */ + public function rollbackTrans() { - if ($this->transOff) return true; - if ($this->transCnt) $this->transCnt -= 1; + if ($this->transOff) + return true; + if ($this->transCnt) + $this->transCnt -= 1; + $ret = false; $this->autoCommit = true; + if ($this->_transactionID) { $ret = fbird_rollback($this->_transactionID); } @@ -206,16 +301,26 @@ class ADODB_firebird extends ADOConnection { return $ret; } - function metaIndexes ($table, $primary = FALSE, $owner=false) + /** + * Get a list of indexes on the specified table. + * + * @param string $table The name of the table to get indexes for. + * @param bool $primary (Optional) Whether or not to include the primary key. + * @param bool $owner (Optional) Unused. + * + * @return array|bool An array of the indexes, or false if the query to get the indexes failed. + */ + public function metaIndexes($table, $primary = false, $owner = false) { // save old fetch mode global $ADODB_FETCH_MODE; - $false = false; $save = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { $savem = $this->SetFetchMode(FALSE); } + $table = strtoupper($table); $sql = "SELECT * FROM RDB\$INDICES WHERE RDB\$RELATION_NAME = '".$table."'"; if (!$primary) { @@ -224,18 +329,19 @@ class ADODB_firebird extends ADOConnection { $sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$FOREIGN%'"; } // get index details - $rs = $this->Execute($sql); + $rs = $this->execute($sql); if (!is_object($rs)) { // restore fetchmode if (isset($savem)) { $this->SetFetchMode($savem); } $ADODB_FETCH_MODE = $save; - return $false; + return false; } $indexes = array(); while ($row = $rs->FetchRow()) { - $index = trim(preg_replace("/[\n\r]+/",'',$row[0])); + + $index = trim($row[0]); if (!isset($indexes[$index])) { if (is_null($row[3])) { $row[3] = 0; @@ -245,12 +351,13 @@ class ADODB_firebird extends ADOConnection { 'columns' => array() ); } - $sql = "SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '".$index."' ORDER BY RDB\$FIELD_POSITION ASC"; - $rs1 = $this->Execute($sql); + $sql = sprintf("SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '%s' ORDER BY RDB\$FIELD_POSITION ASC",$index); + $rs1 = $this->execute($sql); while ($row1 = $rs1->FetchRow()) { - $indexes[$index]['columns'][$row1[2]] = $row1[1]; + $indexes[$index]['columns'][$row1[2]] = trim($row1[1]); } } + // restore fetchmode if (isset($savem)) { $this->SetFetchMode($savem); @@ -260,38 +367,60 @@ class ADODB_firebird extends ADOConnection { return $indexes; } - - // See http://community.borland.com/article/0,1410,25844,00.html - function RowLock($tables,$where,$col=false) + /** + * Lock a table row for a duration of a transaction. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:rowlock + * @link https://firebirdsql.org/refdocs/langrefupd21-notes-withlock.html + * + * @param string $table The table(s) to lock rows for. + * @param string $where (Optional) The WHERE clause to use to determine which rows to lock. + * @param string $col (Optional) The columns to select. + * + * @return bool True if the locking SQL statement executed successfully, otherwise false. + */ + public function rowLock($table,$where,$col=false) { - if ($this->autoCommit) { - $this->BeginTrans(); - } - $this->Execute("UPDATE $table SET $col=$col WHERE $where "); // is this correct - jlim? - return 1; - } + if ($this->transCnt==0) + $this->beginTrans(); - - function CreateSequence($seqname = 'adodbseq', $startID = 1) - { - $ok = $this->Execute(("CREATE GENERATOR $seqname" )); - if (!$ok) return false; - return $this->Execute("SET GENERATOR $seqname TO ".($startID-1)); + if ($where) $where = ' where '.$where; + $rs = $this->execute("SELECT $col FROM $table $where FOR UPDATE WITH LOCK"); + return !empty($rs); } - function DropSequence($seqname = 'adodbseq') + /** + * Creates a sequence in the database. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:createsequence + * + * @param string $seqname The sequence name. + * @param int $startID The start id. + * + * @return ADORecordSet|bool A record set if executed successfully, otherwise false. + */ + public function createSequence($seqname='adodbseq', $startID = 1) { - $seqname = strtoupper($seqname); - return $this->Execute("DROP GENERATOR $seqname"); + $sql = sprintf($this->_genSeqSQL,$seqname,$startID); + return $this->execute($sql); } - function GenID($seqname='adodbseq',$startID=1) + /** + * A portable method of creating sequence numbers. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:genid + * + * @param string $seqname (Optional) The name of the sequence to use. + * @param int $startID (Optional) The point to start at in the sequence. + * + * @return int + */ + public function genID($seqname='adodbseq',$startID=1) { $getnext = ("SELECT Gen_ID($seqname,1) FROM RDB\$DATABASE"); $rs = @$this->Execute($getnext); if (!$rs) { - $this->Execute(("CREATE GENERATOR $seqname" )); - $this->Execute("SET GENERATOR $seqname TO ".($startID-1).';'); + $this->Execute("CREATE SEQUENCE $seqname START WITH $startID"); $rs = $this->Execute($getnext); } if ($rs && !$rs->EOF) { @@ -308,36 +437,56 @@ class ADODB_firebird extends ADOConnection { return $this->genID; } - function SelectDB($dbName) + function selectDB($dbName) { return false; } - function _handleerror() + function _handleError() { - $this->_errorMsg = fbird_errmsg(); + $this->_errorCode = fbird_errcode(); + $this->_errorMsg = fbird_errmsg(); } - function ErrorNo() + + public function errorNo() { - if (preg_match('/error code = ([\-0-9]*)/i', $this->_errorMsg,$arr)) return (integer) $arr[1]; - else return 0; + return (integer) $this->_errorCode; } - function ErrorMsg() + function errorMsg() { return $this->_errorMsg; } - function Prepare($sql) + /** + * Prepares an SQL statement and returns a handle to use. + * This is not used by bound parameters anymore + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:prepare + * @todo update this function to handle prepared statements correctly + * + * @param string $sql The SQL to prepare. + * + * @return bool|array The SQL that was provided and the prepared parameters, + * or false if the preparation fails + */ + public function prepare($sql) { $stmt = fbird_prepare($this->_connectionID,$sql); - if (!$stmt) return false; + if (!$stmt) + return false; return array($sql,$stmt); } - // returns query ID if successful, otherwise false - // there have been reports of problems with nested queries - the code is probably not re-entrant? + /** + * Return the query id. + * + * @param string|array $sql + * @param array $iarr + * + * @return bool|object + */ function _query($sql,$iarr=false) { if ( !$this->isConnected() ) return false; @@ -352,7 +501,7 @@ class ADODB_firebird extends ADOConnection { $fn = 'fbird_execute'; $sql = $sql[1]; if (is_array($iarr)) { - if ( !isset($iarr[0]) ) + if ( !isset($iarr[0]) ) $iarr[0] = ''; // PHP5 compat hack $fnarr = array_merge( array($sql) , $iarr); $ret = call_user_func_array($fn,$fnarr); @@ -362,9 +511,9 @@ class ADODB_firebird extends ADOConnection { } } else { $fn = 'fbird_query'; - if (is_array($iarr)) + if (is_array($iarr)) { - if (sizeof($iarr) == 0) + if (sizeof($iarr) == 0) $iarr[0] = ''; // PHP5 compat hack $fnarr = array_merge( array($conn,$sql) , $iarr); $ret = call_user_func_array($fn,$fnarr); @@ -377,7 +526,7 @@ class ADODB_firebird extends ADOConnection { fbird_commit($this->_connectionID); } - $this->_handleerror(); + $this->_handleError(); return $ret; } @@ -397,122 +546,133 @@ class ADODB_firebird extends ADOConnection { $fld->max_length = $flen; $fld->scale = null; switch($ftype){ - case 7: - case 8: - if ($dialect3) { - switch($fsubtype){ - case 0: - $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); - break; - case 1: - $fld->type = 'numeric'; - $fld->max_length = $fprecision; - $fld->scale = $fscale; - break; - case 2: - $fld->type = 'decimal'; - $fld->max_length = $fprecision; - $fld->scale = $fscale; - break; - } // switch - } else { - if ($fscale !=0) { + case 7: + case 8: + if ($dialect3) { + switch($fsubtype){ + case 0: + $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); + break; + case 1: + $fld->type = 'numeric'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + case 2: $fld->type = 'decimal'; + $fld->max_length = $fprecision; $fld->scale = $fscale; - $fld->max_length = ($ftype == 7 ? 4 : 9); - } else { - $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); - } - } - break; - case 16: - if ($dialect3) { - switch($fsubtype){ - case 0: - $fld->type = 'decimal'; - $fld->max_length = 18; - $fld->scale = 0; - break; - case 1: - $fld->type = 'numeric'; - $fld->max_length = $fprecision; - $fld->scale = $fscale; - break; - case 2: - $fld->type = 'decimal'; - $fld->max_length = $fprecision; - $fld->scale = $fscale; - break; - } // switch - } - break; - case 10: - $fld->type = 'float'; - break; - case 14: - $fld->type = 'char'; - break; - case 27: + break; + } // switch + } else { if ($fscale !=0) { $fld->type = 'decimal'; - $fld->max_length = 15; - $fld->scale = 5; + $fld->scale = $fscale; + $fld->max_length = ($ftype == 7 ? 4 : 9); } else { - $fld->type = 'double'; + $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); } - break; - case 35: - if ($dialect3) { - $fld->type = 'timestamp'; - } else { - $fld->type = 'date'; - } - break; - case 12: + } + break; + case 16: + if ($dialect3) { + switch($fsubtype){ + case 0: + $fld->type = 'decimal'; + $fld->max_length = 18; + $fld->scale = 0; + break; + case 1: + $fld->type = 'numeric'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + case 2: + $fld->type = 'decimal'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + } // switch + } + break; + case 10: + $fld->type = 'float'; + break; + case 14: + $fld->type = 'char'; + break; + case 27: + if ($fscale !=0) { + $fld->type = 'decimal'; + $fld->max_length = 15; + $fld->scale = 5; + } else { + $fld->type = 'double'; + } + break; + case 35: + if ($dialect3) { + $fld->type = 'timestamp'; + } else { $fld->type = 'date'; - break; - case 13: - $fld->type = 'time'; - break; - case 37: - $fld->type = 'varchar'; - break; - case 40: - $fld->type = 'cstring'; - break; - case 261: - $fld->type = 'blob'; - $fld->max_length = -1; - break; + } + break; + case 12: + $fld->type = 'date'; + break; + case 13: + $fld->type = 'time'; + break; + case 37: + $fld->type = 'varchar'; + break; + case 40: + $fld->type = 'cstring'; + break; + case 261: + $fld->type = 'blob'; + $fld->max_length = -1; + break; } // switch } //OPN STUFF end - // returns array of ADOFieldObjects for current table - function MetaColumns($table, $normalize=true) + /** + * Return an array of information about a table's columns. + * + * @param string $table The name of the table to get the column info for. + * @param bool $normalize (Optional) Unused. + * + * @return ADOFieldObject[]|bool An array of info for each column, + * or false if it could not determine the info. + */ + public function metaColumns($table, $normalize = true) { - global $ADODB_FETCH_MODE; + + global $ADODB_FETCH_MODE; $save = $ADODB_FETCH_MODE; $ADODB_FETCH_MODE = ADODB_FETCH_NUM; - $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table))); + $rs = $this->execute(sprintf($this->metaColumnsSQL,strtoupper($table))); $ADODB_FETCH_MODE = $save; - $false = false; + if ($rs === false) { - return $false; + return false; } $retarr = array(); //OPN STUFF start - $dialect3 = ($this->dialect==3 ? true : false); + $dialect3 = $this->dialect == 3; //OPN STUFF end while (!$rs->EOF) { //print_r($rs->fields); $fld = new ADOFieldObject(); $fld->name = trim($rs->fields[0]); //OPN STUFF start - $this->_ConvertFieldType($fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3); + //print_r($rs->fields); + $this->_ConvertFieldType( + $fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3); if (isset($rs->fields[1]) && $rs->fields[1]) { $fld->not_null = true; } @@ -520,17 +680,24 @@ class ADODB_firebird extends ADOConnection { $fld->has_default = true; $d = substr($rs->fields[2],strlen('default ')); - switch ($fld->type) - { - case 'smallint': - case 'integer': $fld->default_value = (int) $d; break; - case 'char': - case 'blob': - case 'text': - case 'varchar': $fld->default_value = (string) substr($d,1,strlen($d)-2); break; - case 'double': - case 'float': $fld->default_value = (float) $d; break; - default: $fld->default_value = $d; break; + switch ($fld->type) { + case 'smallint': + case 'integer': + $fld->default_value = (int)$d; + break; + case 'char': + case 'blob': + case 'text': + case 'varchar': + $fld->default_value = (string)substr($d, 1, strlen($d) - 2); + break; + case 'double': + case 'float': + $fld->default_value = (float)$d; + break; + default: + $fld->default_value = $d; + break; } // case 35:$tt = 'TIMESTAMP'; break; } @@ -546,40 +713,79 @@ class ADODB_firebird extends ADOConnection { $rs->MoveNext(); } $rs->Close(); - if ( empty($retarr)) return $false; + if ( empty($retarr)) + return false; else return $retarr; } - function BlobEncode( $blob ) + /** + * Retrieves a list of tables based on given criteria + * + * @param string|bool $ttype (Optional) Table type = 'TABLE', 'VIEW' or false=both (default) + * @param string|bool $showSchema (Optional) schema name, false = current schema (default) + * @param string|bool $mask (Optional) filters the table by name + * + * @return array list of tables + */ + public function metaTables($ttype = false, $showSchema = false, $mask = false) + { + $save = $this->metaTablesSQL; + if (!$showSchema) { + $this->metaTablesSQL .= " WHERE (rdb\$relation_name NOT LIKE 'RDB\$%' AND rdb\$relation_name NOT LIKE 'MON\$%' AND rdb\$relation_name NOT LIKE 'SEC\$%')"; + } elseif (is_string($showSchema)) { + $this->metaTablesSQL .= $this->qstr($showSchema); + } + + if ($mask) { + $mask = $this->qstr($mask); + $this->metaTablesSQL .= " AND table_name LIKE $mask"; + } + $ret = ADOConnection::metaTables($ttype,$showSchema); + + $this->metaTablesSQL = $save; + return $ret; + } + + /** + * Encodes a blob, then assigns an id ready to be used + * + * @param string $blob The blob to be encoded + * + * @return bool success + */ + public function blobEncode( $blob ) { $blobid = fbird_blob_create( $this->_connectionID); fbird_blob_add( $blobid, $blob ); return fbird_blob_close( $blobid ); } - // since we auto-decode all blob's since 2.42, - // BlobDecode should not do any transforms - function BlobDecode($blob) + /** + * Manually decode a blob + * + * since we auto-decode all blob's since 2.42, + * BlobDecode should not do any transforms + * + * @param string $blob + * + * @return string the same blob + */ + public function blobDecode($blob) { return $blob; } - // old blobdecode function - // still used to auto-decode all blob's - function _BlobDecode_old( $blob ) - { - $blobid = fbird_blob_open($this->_connectionID, $blob ); - $realblob = fbird_blob_get( $blobid,$this->maxblobsize); // 2nd param is max size of blob -- Kevin Boillet <kevinboillet@yahoo.fr> - while($string = fbird_blob_get($blobid, 8192)){ - $realblob .= $string; - } - fbird_blob_close( $blobid ); - return( $realblob ); - } - - function _BlobDecode( $blob ) + /** + * Auto function called on read of blob to decode + * + * @param string $blob Value to decode + * + * @return string Decoded blob + */ + public function _blobDecode( $blob ) { + $blob_data = fbird_blob_info($this->_connectionID, $blob ); $blobid = fbird_blob_open($this->_connectionID, $blob ); @@ -597,10 +803,24 @@ class ADODB_firebird extends ADOConnection { return( $realblob ); } - function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') + /** + * Insert blob data into a database column directly + * from file + * + * @param string $table table to insert + * @param string $column column to insert + * @param string $path physical file name + * @param string $where string to find unique record + * @param string $blobtype BLOB or CLOB + * + * @return bool success + */ + public function updateBlobFile($table,$column,$path,$where,$blobtype='BLOB') { $fd = fopen($path,'rb'); - if ($fd === false) return false; + if ($fd === false) + return false; + $blob_id = fbird_blob_create($this->_connectionID); /* fill with data */ @@ -616,105 +836,106 @@ class ADODB_firebird extends ADOConnection { return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; } - /* - Insert a null into the blob field of the table first. - Then use UpdateBlob to store the blob. - - Usage: - - $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); - $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); - */ - function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + /** + * Insert blob data into a database column + * + * @param string $table table to insert + * @param string $column column to insert + * @param string $val value to insert + * @param string $where string to find unique record + * @param string $blobtype BLOB or CLOB + * + * @return bool success + */ + public function updateBlob($table,$column,$val,$where,$blobtype='BLOB') { - $blob_id = fbird_blob_create($this->_connectionID); + $blob_id = fbird_blob_create($this->_connectionID); - // fbird_blob_add($blob_id, $val); + // fbird_blob_add($blob_id, $val); - // replacement that solves the problem by which only the first modulus 64K / - // of $val are stored at the blob field //////////////////////////////////// - // Thx Abel Berenstein aberenstein#afip.gov.ar - $len = strlen($val); - $chunk_size = 32768; - $tail_size = $len % $chunk_size; - $n_chunks = ($len - $tail_size) / $chunk_size; + // replacement that solves the problem by which only the first modulus 64K / + // of $val are stored at the blob field //////////////////////////////////// + // Thx Abel Berenstein aberenstein#afip.gov.ar + $len = strlen($val); + $chunk_size = 32768; + $tail_size = $len % $chunk_size; + $n_chunks = ($len - $tail_size) / $chunk_size; - for ($n = 0; $n < $n_chunks; $n++) { - $start = $n * $chunk_size; - $data = substr($val, $start, $chunk_size); - fbird_blob_add($blob_id, $data); - } + for ($n = 0; $n < $n_chunks; $n++) { + $start = $n * $chunk_size; + $data = substr($val, $start, $chunk_size); + fbird_blob_add($blob_id, $data); + } - if ($tail_size) { - $start = $n_chunks * $chunk_size; - $data = substr($val, $start, $tail_size); - fbird_blob_add($blob_id, $data); - } - // end replacement ///////////////////////////////////////////////////////// + if ($tail_size) { + $start = $n_chunks * $chunk_size; + $data = substr($val, $start, $tail_size); + fbird_blob_add($blob_id, $data); + } + // end replacement ///////////////////////////////////////////////////////// - $blob_id_str = fbird_blob_close($blob_id); + $blob_id_str = fbird_blob_close($blob_id); - return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; + return $this->execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; } - function OldUpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + /** + * Returns a portably-formatted date string from a timestamp database column. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:sqldate + * + * Firebird does not support an AM/PM format, so the A indicator always shows AM + * + * @param string $fmt The date format to use. + * @param string|bool $col (Optional) The table column to date format, or if false, use NOW(). + * + * @return string The SQL DATE_FORMAT() string, or empty if the provided date format was empty. + */ + public function sqlDate($fmt, $col=false) { - $blob_id = fbird_blob_create($this->_connectionID); - fbird_blob_add($blob_id, $val); - $blob_id_str = fbird_blob_close($blob_id); - return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; - } + if (!$col) + $col = 'CURRENT_TIMESTAMP'; - // Format date column in sql string given an input format that understands Y M D - // Only since Interbase 6.0 - uses EXTRACT - // problem - does not zero-fill the day and month yet - function SQLDate($fmt, $col=false) - { - if (!$col) $col = $this->sysDate; $s = ''; $len = strlen($fmt); for ($i=0; $i < $len; $i++) { if ($s) $s .= '||'; $ch = $fmt[$i]; - switch($ch) { + $choice = strtoupper($ch); + switch($choice) { case 'Y': - case 'y': - $s .= "extract(year from $col)"; + $s .= "EXTRACT(YEAR FROM $col)"; break; case 'M': - case 'm': - $s .= "extract(month from $col)"; + $s .= "RIGHT('0' || TRIM(EXTRACT(MONTH FROM $col)),2)"; break; case 'W': - case 'w': // The more accurate way of doing this is with a stored procedure // See http://wiki.firebirdsql.org/wiki/index.php?page=DATE+Handling+Functions for details - $s .= "((extract(yearday from $col) - extract(weekday from $col - 1) + 7) / 7)"; + $s .= "((EXTRACT(YEARDAY FROM $col) - EXTRACT(WEEKDAY FROM $col - 1) + 7) / 7)"; break; case 'Q': - case 'q': - $s .= "cast(((extract(month from $col)+2) / 3) as integer)"; + $s .= "CAST(((EXTRACT(MONTH FROM $col)+2) / 3) AS INTEGER)"; break; case 'D': - case 'd': - $s .= "(extract(day from $col))"; + $s .= "RIGHT('0' || TRIM(EXTRACT(DAY FROM $col)),2)"; break; case 'H': - case 'h': - $s .= "(extract(hour from $col))"; + $s .= "RIGHT('0' || TRIM(EXTRACT(HOUR FROM $col)),2)"; break; case 'I': - case 'i': - $s .= "(extract(minute from $col))"; + $s .= "RIGHT('0' || TRIM(EXTRACT(MINUTE FROM $col)),2)"; break; case 'S': - case 's': - $s .= "CAST((extract(second from $col)) AS INTEGER)"; + //$s .= "CAST((EXTRACT(SECOND FROM $col)) AS INTEGER)"; + $s .= "RIGHT('0' || TRIM(EXTRACT(SECOND FROM $col)),2)"; + break; + case 'A': + $s .= $this->qstr('AM'); break; - default: if ($ch == '\\') { $i++; @@ -727,10 +948,44 @@ class ADODB_firebird extends ADOConnection { return $s; } + /** + * Creates a portable date offset field, for use in SQL statements. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:offsetdate + * + * @param float $dayFraction A day in floating point + * @param string|bool $date (Optional) The date to offset. If false, uses CURDATE() + * + * @return string + */ + public function offsetDate($dayFraction, $date=false) + { + if (!$date) + $date = $this->sysTimeStamp; + + $fraction = $dayFraction * 24 * 3600; + return sprintf("DATEADD (second, %s, %s) FROM RDB\$DATABASE",$fraction,$date); + } + + // Note that Interbase 6.5 uses this ROWS instead - don't you love forking wars! // SELECT col1, col2 FROM table ROWS 5 -- get 5 rows // SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2 - function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs=0) + /** + * Executes a provided SQL statement and returns a handle to the result, with the ability to supply a starting + * offset and record count. + * + * @link https://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:selectlimit + * + * @param string $sql The SQL to execute. + * @param int $nrows (Optional) The limit for the number of records you want returned. By default, all results. + * @param int $offset (Optional) The offset to use when selecting the results. By default, no offset. + * @param array|bool $inputarr (Optional) Any parameter values required by the SQL statement, or false if none. + * @param int $secs2cache (Optional) If greater than 0, perform a cached execute. By default, normal execution. + * + * @return ADORecordSet|false The query results, or false if the query failed to execute. + */ + public function selectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs2cache=0) { $nrows = (integer) $nrows; $offset = (integer) $offset; @@ -739,46 +994,89 @@ class ADODB_firebird extends ADOConnection { $str .=($offset>=0) ? "SKIP $offset " : ''; $sql = preg_replace('/^[ \t]*select/i',$str,$sql); - if ($secs) - $rs = $this->CacheExecute($secs,$sql,$inputarr); + if ($secs2cache) + $rs = $this->cacheExecute($secs2cache,$sql,$inputarr); else - $rs = $this->Execute($sql,$inputarr); + $rs = $this->execute($sql,$inputarr); return $rs; } } -/*-------------------------------------------------------------------------------------- - Class Name: Recordset ---------------------------------------------------------------------------------------*/ - -class ADORecordset_firebird extends ADORecordSet +/** + * Class ADORecordset_firebird + */ +class ADORecordset_firebird extends ADORecordSet { - var $databaseType = "firebird"; - var $bind=false; - var $_cacheType; + var $bind = false; + + /** + * @var ADOFieldObject[] Holds a cached version of the metadata + */ + private $fieldObjects = false; + + /** + * @var bool Flags if we have retrieved the metadata + */ + private $fieldObjectsRetrieved = false; + + /** + * @var array Cross-reference the objects by name for easy access + */ + private $fieldObjectsIndex = array(); - function __construct($id,$mode=false) + /** + * @var bool Flag to indicate if the result has a blob + */ + private $fieldObjectsHaveBlob = false; + + function __construct($id, $mode = false) { - global $ADODB_FETCH_MODE; + global $ADODB_FETCH_MODE; - $this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode; - parent::__construct($id); + $this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode; + parent::__construct($id); } + /** - * Get column information in the Recordset object. - * fetchField() can be used in order to obtain information about fields in - * a certain query result. If the field offset isn't specified, the next - * field that wasn't yet retrieved by fetchField() is retrieved. - * @return object containing field information. - */ - function FetchField($fieldOffset = -1) + * Returns: an object containing field information. + * + * Get column information in the Recordset object. fetchField() + * can be used in order to obtain information about fields in a + * certain query result. If the field offset isn't specified, + * the next field that wasn't yet retrieved by fetchField() + * is retrieved. + * + * $param int $fieldOffset (optional default=-1 for all + * @return mixed an ADOFieldObject, or array of objects + */ + private function _fetchField($fieldOffset = -1) { + if ($this->fieldObjectsRetrieved) { + if ($this->fieldObjects) { + // Already got the information + if ($fieldOffset == -1) { + return $this->fieldObjects; + } else { + return $this->fieldObjects[$fieldOffset]; + } + } else { + // No metadata available + return false; + } + } + + $this->fieldObjectsRetrieved = true; + $this->fieldObjectsHaveBlob = false; + + $this->_numOfFields = fbird_num_fields($this->_queryID); + for ($fieldOffset = 0; $fieldOffset < $this->_numOfFields; $fieldOffset++) { + $fld = new ADOFieldObject; - $ibf = fbird_field_info($this->_queryID,$fieldOffset); + $ibf = fbird_field_info($this->_queryID, $fieldOffset); $name = empty($ibf['alias']) ? $ibf['name'] : $ibf['alias']; @@ -798,23 +1096,55 @@ class ADORecordset_firebird extends ADORecordSet $fld->type = $ibf['type']; $fld->max_length = $ibf['length']; - /* This needs to be populated from the metadata */ + // This needs to be populated from the metadata $fld->not_null = false; $fld->has_default = false; $fld->default_value = 'null'; - return $fld; + + $this->fieldObjects[$fieldOffset] = $fld; + + $this->fieldObjectsIndex[$fld->name] = $fieldOffset; + + if ($fld->type == 'BLOB') { + $this->fieldObjectsHaveBlob = true; + } + } + + if ($fieldOffset == -1) { + return $this->fieldObjects; + } + + return $this->fieldObjects[$fieldOffset]; + } + + /** + * Fetchfield copies the oracle method, it loads the field information + * into the _fieldobjs array once, to save multiple calls to the + * fbird_ function + * + * @param int $fieldOffset (optional) + * + * @return adoFieldObject|false + */ + public function fetchField($fieldOffset = -1) + { + if ($fieldOffset == -1) { + return $this->fieldObjects; + } + + return $this->fieldObjects[$fieldOffset]; } function _initrs() { $this->_numOfRows = -1; - $this->_numOfFields = @fbird_num_fields($this->_queryID); - // cache types for blob decode check - for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { - $f1 = $this->FetchField($i); - $this->_cacheType[] = $f1->type; - } + /* + * Retrieve all of the column information first. We copy + * this method from oracle + */ + $this->_fetchField(); + } function _seek($row) @@ -822,9 +1152,30 @@ class ADORecordset_firebird extends ADORecordSet return false; } - function _fetch() + public function _fetch() { - $f = @fbird_fetch_row($this->_queryID); + $localNumeric = true; + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + // Handle either associative or fetch both + $localNumeric = false; + + $f = @fbird_fetch_assoc($this->_queryID); + if (is_array($f)) { + // Optimally do the case_upper or case_lower + if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_LOWER) { + $f = array_change_key_case($f, CASE_LOWER); + } else { + if (ADODB_ASSOC_CASE == ADODB_ASSOC_CASE_UPPER) { + $f = array_change_key_case($f, CASE_UPPER); + } + } + + } + } else { + // Numeric result + $f = @fbird_fetch_row($this->_queryID); + } + if ($f === false) { $this->fields = false; return false; @@ -833,94 +1184,143 @@ class ADORecordset_firebird extends ADORecordSet // fix missing nulls and decode blobs automatically global $ADODB_ANSI_PADDING_OFF; - //$ADODB_ANSI_PADDING_OFF=1; $rtrim = !empty($ADODB_ANSI_PADDING_OFF); - - for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { - if ($this->_cacheType[$i]=="BLOB") { - if (isset($f[$i])) - { - $f[$i] = $this->connection->_BlobDecode($f[$i]); + + /* + * Fix missing nulls, not sure why they would be missing + * + * + $nullTemplate = array_fill(0,$this->_numOfFields,null); + */ + /* + * Retrieved record is always numeric, use array_replace + * to inject missing keys + */ + //$f = array_replace($nullTemplate,$f); + + /* + * For optimal performance, only process if there + * is a possiblity of something to do + */ + if ($this->fieldObjectsHaveBlob || $rtrim) { + /* + * Create a closure for an efficient method of + * iterating over the elements + */ + $localFieldObjects = $this->fieldObjects; + $localFieldObjectIndex = $this->fieldObjectsIndex; + $localConnection = &$this->connection; + + $rowTransform = function ($value, $key) use ( + &$f, + $rtrim, + $localFieldObjects, + $localConnection, + $localNumeric, + $localFieldObjectIndex + ) { + if ($localNumeric) { + $localKey = $key; } else { - $f[$i] = null; + // Cross reference the associative key back to numeric + $localKey = $localFieldObjectIndex[strtolower($key)]; } - } else { - if (!isset($f[$i])) { - $f[$i] = null; - } else if ($rtrim && is_string($f[$i])) { - $f[$i] = rtrim($f[$i]); + + // As we iterate the elements check for blobs and padding + if ($localFieldObjects[$localKey]->type == 'BLOB') { + $f[$key] = $localConnection->_BlobDecode($value); + } else { + if ($rtrim && is_string($value)) { + $f[$key] = rtrim($value); + } } - } + + }; + // Walk the array, applying the above closure + array_walk($f, $rowTransform); } - // OPN stuff end - $this->fields = $f; - if ($this->fetchMode == ADODB_FETCH_ASSOC) { - $this->fields = $this->GetRowAssoc(); - } else if ($this->fetchMode == ADODB_FETCH_BOTH) { - $this->fields = array_merge($this->fields,$this->GetRowAssoc()); + if (!$localNumeric && $this->fetchMode & ADODB_FETCH_NUM) { + // Creates a fetch both + $fNum = array_values($f); + $f = array_merge($f, $fNum); } + + $this->fields = $f; + return true; } - /* Use associative array to get fields array */ - function Fields($colname) + /** + * Get the value of a field in the current row by column name. + * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM. + * + * @param string $colname is the field to access + * + * @return mixed the value of $colname column + */ + public function fields($colname) { - if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + return $this->fields[$colname]; + } + if (!$this->bind) { - $this->bind = array(); - for ($i=0; $i < $this->_numOfFields; $i++) { - $o = $this->FetchField($i); - $this->bind[strtoupper($o->name)] = $i; - } + // fieldsObjectIndex populated by the recordset load + $this->bind = array_change_key_case($this->fieldObjectsIndex, CASE_UPPER); } return $this->fields[$this->bind[strtoupper($colname)]]; - } function _close() { - return @fbird_free_result($this->_queryID); + return @fbird_free_result($this->_queryID); } - function MetaType($t,$len=-1,$fieldobj=false) + public function metaType($t, $len = -1, $fieldobj = false) { if (is_object($t)) { $fieldobj = $t; $t = $fieldobj->type; $len = $fieldobj->max_length; } - + $t = strtoupper($t); - - if (array_key_exists($t,$this->connection->customActualTypes)) - return $this->connection->customActualTypes[$t]; - switch ($t) { + if (array_key_exists($t, $this->connection->customActualTypes)) { + return $this->connection->customActualTypes[$t]; + } - case 'CHAR': - return 'C'; + switch ($t) { + case 'CHAR': + return 'C'; - case 'TEXT': - case 'VARCHAR': - case 'VARYING': - if ($len <= $this->blobSize) return 'C'; - return 'X'; - case 'BLOB': - return 'B'; + case 'TEXT': + case 'VARCHAR': + case 'VARYING': + if ($len <= $this->blobSize) { + return 'C'; + } + return 'X'; + case 'BLOB': + return 'B'; - case 'TIMESTAMP': - case 'DATE': return 'D'; - case 'TIME': return 'T'; - //case 'T': return 'T'; + case 'TIMESTAMP': + case 'DATE': + return 'D'; + case 'TIME': + return 'T'; + //case 'T': return 'T'; - //case 'L': return 'L'; - case 'INT': - case 'SHORT': - case 'INTEGER': return 'I'; - default: return ADODB_DEFAULT_METATYPE; + //case 'L': return 'L'; + case 'INT': + case 'SHORT': + case 'INTEGER': + return 'I'; + default: + return ADODB_DEFAULT_METATYPE; } } |
