ENHANCEMENT: added PDO for SQLite adapter to support PHP < 5.3

ENHANCEMENT: rewrite of the build process
This commit is contained in:
Andreas Piening 2010-01-21 11:32:21 +00:00
parent 29ca870f76
commit d34170e370
4 changed files with 347 additions and 58 deletions

6
README
View File

@ -17,20 +17,20 @@ Installation
copy the sqlite3 folder to your project root so that it becomes a sibling of cms, sapphire and co copy the sqlite3 folder to your project root so that it becomes a sibling of cms, sapphire and co
add this to your _config.php add this to your _config.php
define('SS_DATABASE_CLASS','SQLite3Database'); define('SS_DATABASE_CLASS','SQLiteDatabase');
you are done! you are done!
Config Config
------ ------
you can set the path for storing your SQLite db file or make use of the :memory: feature like this: you can set the path for storing your SQLite db file or make use of the :memory: feature in sqlite3/_config.php like this:
$databaseConfig = array( $databaseConfig = array(
'path' => '/some/path', 'path' => '/some/path',
'memory' => true, 'memory' => true,
); );
make sure the webserver has sufficient privileges to write to that folder and that it is protected from external access. make sure the webserver has sufficient privileges to write to that folder and that it is protected from external access.

View File

@ -1,13 +1,20 @@
<?php <?php
if(defined('SS_DATABASE_CLASS') && SS_DATABASE_CLASS == 'SQLite3Database') { if(defined('SS_DATABASE_CLASS') && (SS_DATABASE_CLASS == 'SQLiteDatabase' || SS_DATABASE_CLASS == 'SQLite3Database' || SS_DATABASE_CLASS == 'SQLitePDODatabase')) {
global $databaseConfig; global $databaseConfig;
$databaseConfig = array( $databaseConfig = array(
'type' => 'SQLite3Database',
'database' => (defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : '') . $database . (defined('SS_DATABASE_SUFFIX') ? SS_DATABASE_SUFFIX : ''), 'database' => (defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : '') . $database . (defined('SS_DATABASE_SUFFIX') ? SS_DATABASE_SUFFIX : ''),
'path' => defined('SS_SQLITE3_DATABASE_PATH') && SS_SQLITE3_DATABASE_PATH ? SS_SQLITE3_DATABASE_PATH : ASSETS_PATH, 'path' => defined('SS_SQLITE_DATABASE_PATH') && SS_SQLITE_DATABASE_PATH ? SS_SQLITE_DATABASE_PATH : ASSETS_PATH,
'key' => defined('SS_SQLITE3_DATABASE_KEY') && SS_SQLITE3_DATABASE_KEY ? SS_SQLITE3_DATABASE_KEY : 'SQLite3DatabaseKey',
'memory' => true, 'memory' => true,
); );
// The SQLite3 class is available in PHP 5.3 and newer
if(SS_DATABASE_CLASS == 'SQLitePDODatabase' || version_compare(phpversion(), '5.3.0', '<')) {
$databaseConfig['type'] = 'SQLitePDODatabase';
} else {
$databaseConfig['type'] = 'SQLite3Database';
$databaseConfig['key'] = defined('SS_SQLITE_DATABASE_KEY') && SS_SQLITE_DATABASE_KEY ? SS_SQLITE_DATABASE_KEY : 'SQLite3DatabaseKey';
}
} }

View File

@ -2,7 +2,7 @@
/** /**
* SQLite connector class. * SQLite connector class.
* @package SQLite3Database * @package SQLite3
*/ */
class SQLite3Database extends SS_Database { class SQLite3Database extends SS_Database {
@ -10,39 +10,39 @@ class SQLite3Database extends SS_Database {
* Connection to the DBMS. * Connection to the DBMS.
* @var object * @var object
*/ */
private $dbConn; protected $dbConn;
/** /**
* True if we are connected to a database. * True if we are connected to a database.
* @var boolean * @var boolean
*/ */
private $active; protected $active;
/** /**
* The name of the database. * The name of the database.
* @var string * @var string
*/ */
private $database; protected $database;
/* /*
* This holds the name of the original database * This holds the name of the original database
* So if you switch to another for unit tests, you * So if you switch to another for unit tests, you
* can then switch back in order to drop the temp database * can then switch back in order to drop the temp database
*/ */
private $database_original; protected $database_original;
/* /*
* This holds the parameters that the original connection was created with, * This holds the parameters that the original connection was created with,
* so we can switch back to it if necessary (used for unit tests) * so we can switch back to it if necessary (used for unit tests)
*/ */
private $parameters; protected $parameters;
/* /*
* Actually SQLite supports transactions (they are used below), but they * Actually SQLite supports transactions (they are used below), but they
* work signifficantly different to the transactions in Postgres on which * work signifficantly different to the transactions in Postgres on which
* the unit test are based upon... ;( * the unit test are based upon... ;(
*/ */
private $supportsTransactions=false; protected $supportsTransactions=false;
/** /**
* Connect to a SQLite3 database. * Connect to a SQLite3 database.
@ -111,7 +111,7 @@ class SQLite3Database extends SS_Database {
* The version of SQLite3. * The version of SQLite3.
* @var float * @var float
*/ */
private $sqliteVersion; protected $sqliteVersion;
/** /**
* Get the version of SQLite3. * Get the version of SQLite3.
@ -248,6 +248,83 @@ class SQLite3Database extends SS_Database {
return false; return false;
} }
static protected $supported_field_types = array('boolean', 'int', 'date', 'decimal', 'double', 'enum', 'float', 'int', 'ss_datetime', 'text', 'time', 'varchar', 'year', 'IdColumn');
/**
* Generate the following table in the database, modifying whatever already exists
* as necessary.
* @todo Change detection for CREATE TABLE $options other than "Engine"
*
* @param string $table The name of the table
* @param string $fieldSchema A list of the fields to create, in the same form as DataObject::$db
* @param string $indexSchema A list of indexes to create. See {@link requireIndex()}
* @param array $options
*/
function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK=true, $options = false, $extensions=false) {
$targetFields['ID'] = $this->IdColumn();
if($fieldSchema) {
foreach($fieldSchema as $fieldName => $fieldSpec) {
//Is this an array field?
$arrayValue='';
if(strpos($fieldSpec, '[')!==false){
//If so, remove it and store that info separately
$pos=strpos($fieldSpec, '[');
$arrayValue=substr($fieldSpec, $pos);
$fieldSpec=substr($fieldSpec, 0, $pos);
}
$fieldObj = eval(ViewableData::castingObjectCreator($fieldSpec));
$check = $fieldObj->class;
$contrainfunction = false;
while(!$contrainfunction) {
if(array_search(strtolower($check), self::$supported_field_types) !== false && method_exists($this, $check)) $contrainfunction = $check;
$check = get_parent_class($check);
if($check == 'DBField') break;
}
if(!$contrainfunction) user_error('SQLQuery::requireTable(): Unrecognised data type ' . $fieldObj->class, E_USER_ERROR);
$targetFields[$fieldName] = call_user_func(array($this, $contrainfunction), array('table' => $table) + self::cast($fieldObj));
}
}
if(!isset($this->tableList[strtolower($table)])) {
$this->createTable($table, $targetFields, null, $options, $extensions);
$this->alterationMessage("Table $table: created","created");
} else {
$currentFields = $this->fieldList($table);
$fieldschanged = false;
foreach($targetFields as $f => $c) {
if(empty($currentFields[$f])) {
$this->alterationMessage("Field $table.$f: created as $c","created");
$fieldschanged = true;
} else if($currentFields[$f] != $c) {
$this->alterationMessage("Field $table.$f: changed to $c <i style=\"color: #AAA\">(from {$currentFields[$f]})</i>","changed");
$fieldschanged = true;
}
}
if($fieldschanged) {
$this->changeTable($table, $currentFields, $targetFields);
$this->alterationMessage("Table $table: changed","changed");
}
}
// Create custom indexes
if($indexSchema) {
foreach($indexSchema as $indexName => $indexDetails) {
$this->createIndex($table, $indexName, $indexDetails);
}
}
}
private static function cast($obj) {
foreach((Array)$obj as $key => $val) $arr[str_replace("\0*\0",'',$key)] = $val;
return $arr;
}
public function clearTable($table) { public function clearTable($table) {
$this->dbConn->query("DELETE FROM \"$table\""); $this->dbConn->query("DELETE FROM \"$table\"");
} }
@ -257,7 +334,10 @@ class SQLite3Database extends SS_Database {
if(!isset($fields['ID'])) $fields['ID'] = "INTEGER PRIMARY KEY AUTOINCREMENT"; if(!isset($fields['ID'])) $fields['ID'] = "INTEGER PRIMARY KEY AUTOINCREMENT";
$fieldSchemata = array(); $fieldSchemata = array();
if($fields) foreach($fields as $k => $v) $fieldSchemata[] = "\"$k\" $v"; if($fields) foreach($fields as $k => $v) {
$fieldSchemata[] = "\"$k\" $v";
$this->alterationMessage("Field $table.$k: created as $v","created");
}
$fieldSchemas = implode(",\n",$fieldSchemata); $fieldSchemas = implode(",\n",$fieldSchemata);
// Switch to "CREATE TEMPORARY TABLE" for temporary tables // Switch to "CREATE TEMPORARY TABLE" for temporary tables
@ -269,23 +349,41 @@ class SQLite3Database extends SS_Database {
return $table; return $table;
} }
/** /**
* Alter a table's schema. * Alter a table's schema.
* @param $table The name of the table to alter * @param $table The name of the table to alter
* @param $newFields New fields, a map of field name => field schema * @param $newFields New fields, a map of field name => field schema
* @param $newIndexes New indexes, a map of index name => index type * @param $newIndexes New indexes, a map of index name => index type
* @param $alteredFields Updated fields, a map of field name => field schema * @param $alteredFields Updated fields, a map of field name => field schema
* @param $alteredIndexes Updated indexes, a map of index name => index type * @param $alteredIndexes Updated indexes, a map of index name => index type
*/ */
public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null) { public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null) {
if($newFields) foreach($newFields as $fieldName => $fieldSpec) $this->createField($tableName, $fieldName, $fieldSpec); if($newFields) foreach($newFields as $fieldName => $fieldSpec) $this->createField($tableName, $fieldName, $fieldSpec);
if($alteredFields) foreach($alteredFields as $fieldName => $fieldSpec) $this->alterField($tableName, $fieldName, $fieldSpec);
if($newIndexes) foreach($newIndexes as $indexName => $indexSpec) $this->createIndex($tableName, $indexName, $indexSpec);
if($alteredFields) foreach($alteredFields as $fieldName => $fieldSpec) $this->alterField($tableName, $fieldName, $fieldSpec); if($alteredIndexes) foreach($alteredIndexes as $indexName => $indexSpec) $this->alterIndex($tableName, $indexName, $indexSpec);
}
public function changeTable($table, $currentFields, $targetFields) {
if($newIndexes) foreach($newIndexes as $indexName => $indexSpec) $this->createIndex($tableName, $indexName, $indexSpec); $newFields = array_merge($currentFields, $targetFields);
foreach($newFields as $f => $c) $newFieldSpecs[] = "\"$f\" $c";
if($alteredIndexes) foreach($alteredIndexes as $indexName => $indexSpec) $this->alterIndex($tableName, $indexName, $indexSpec); $queries = array(
"BEGIN TRANSACTION",
"CREATE TABLE \"{$table}_new\"(" . implode(',', $newFieldSpecs) . ")",
"INSERT INTO \"{$table}_new\" (\"" . (implode('","', array_keys($currentFields))) . "\") SELECT \"" . (implode('","', array_keys($currentFields))) . "\" FROM \"$table\"",
"DROP TABLE \"$table\"",
"ALTER TABLE \"{$table}_new\" RENAME TO \"$table\"",
"COMMIT"
);
foreach($queries as $query) $this->query($query.';');
} }
@ -301,9 +399,8 @@ class SQLite3Database extends SS_Database {
* @return boolean Return true if the table has integrity after the method is complete. * @return boolean Return true if the table has integrity after the method is complete.
*/ */
public function checkAndRepairTable($tableName) { public function checkAndRepairTable($tableName) {
// it's a pitty, vacuuming doesn't work -> locking issue // it's a pitty, vacuuming doesn't work -> locking issue
// $this->runTableCheckCommand("VACUUM"); // $this->runTableCheckCommand("VACUUM");
$this->runTableCheckCommand("REINDEX \"$tableName\"");
return true; return true;
} }
@ -419,11 +516,21 @@ class SQLite3Database extends SS_Database {
* @param string $indexSpec The specification of the index, see Database::requireIndex() for more details. * @param string $indexSpec The specification of the index, see Database::requireIndex() for more details.
*/ */
public function createIndex($tableName, $indexName, $indexSpec) { public function createIndex($tableName, $indexName, $indexSpec) {
$cleanIndexName = $this->getDbSqlDefinition($tableName, $indexName, $indexSpec);
$this->query("DROP INDEX IF EXISTS " . $cleanIndexName); $name = "\"$tableName.$indexName\"";
$spec = $this->convertIndexSpec($tableName, $indexName, $indexSpec);
$currSpec = array(); $diff = false;
foreach(DB::query("PRAGMA index_info($name)") as $i) $currSpec[] = $i['name'];
foreach($spec as $s) if(array_search($s, $currSpec) === false) $diff = true;
if(count($spec) == count($currSpec) && !$diff) return;
$this->query("DROP INDEX IF EXISTS $name");
$this->query("CREATE INDEX $name ON \"$tableName\" (\"" . implode('","', $spec) . "\")");
$this->alterationMessage("Index $name: created as " . implode(', ', $spec),"created");
$this->query("CREATE INDEX \"$cleanIndexName\" ON \"$tableName\" (" . $this->convertIndexSpec($indexSpec) . ")");
} }
/* /*
@ -432,15 +539,19 @@ class SQLite3Database extends SS_Database {
* Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific * Some indexes may be arrays, such as fulltext and unique indexes, and this allows database-specific
* arrays to be created. * arrays to be created.
*/ */
public function convertIndexSpec($indexSpec, $asDbValue=false, $table=''){ public function convertIndexSpec($tableName, $indexName, $indexSpec){
$indexSpecNew = is_array($indexSpec) ? $indexSpec['value'] : $indexSpec; if(is_array($indexSpec)) {
$indexSpecNew = $indexSpec['value'];
} else if(preg_match('/\((.+)\)/', $indexSpec, $matches)) {
$indexSpecNew = $matches[1];
} else {
$indexSpecNew = $indexName;
}
$indexSpecNew = preg_match('/[a-z_ ]*\((.+)\)/i',$indexSpecNew,$matches) ? $matches[1] : $indexSpecNew; foreach(explode(',', $indexSpecNew) as $field) $indexOn[]=trim($field);
$indexSpecNew = preg_replace('/[\s\(\)]/', '', $indexSpecNew); return $indexOn;
return $indexSpecNew;
} }
/** /**
@ -458,6 +569,8 @@ class SQLite3Database extends SS_Database {
* @param string $indexSpec The specification of the index, see Database::requireIndex() for more details. * @param string $indexSpec The specification of the index, see Database::requireIndex() for more details.
*/ */
public function alterIndex($tableName, $indexName, $indexSpec) { public function alterIndex($tableName, $indexName, $indexSpec) {
// Debug::show($tableName . " alterIndex($tableName, $indexName, $indexSpec)");
// SS_Backtrace::backtrace();
$this->createIndex($tableName, $indexName, $indexSpec); $this->createIndex($tableName, $indexName, $indexSpec);
} }
@ -518,9 +631,9 @@ class SQLite3Database extends SS_Database {
* @params array $values Contains a tokenised list of info about this data type * @params array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function boolean($values, $asDbValue=false){ public function boolean($values){
return 'BOOL not null default ' . (int)$values['default']; return 'BOOL NOT NULL DEFAULT ' . (isset($values['default']) ? (int)$values['default'] : 0);
} }
@ -544,7 +657,7 @@ class SQLite3Database extends SS_Database {
*/ */
public function decimal($values, $asDbValue=false){ public function decimal($values, $asDbValue=false){
return "NUMERIC not null DEFAULT 0"; return "NUMERIC NOT NULL DEFAULT 0";
} }
@ -560,17 +673,14 @@ class SQLite3Database extends SS_Database {
public function enum($values){ public function enum($values){
$bt=debug_backtrace(); $tablefield = $values['table'] . '.' . $values['name'];
if(basename($bt[0]['file']) == 'Database.php') { if(empty($this->enum_map)) $this->query("CREATE TABLE IF NOT EXISTS SQLiteEnums (TableColumn TEXT PRIMARY KEY, EnumList TEXT)");
$column = $bt[0]['args'][0]['table'].'.'.$bt[0]['args'][0]['name']; if(empty($this->enum_map[$tablefield]) || $this->enum_map[$tablefield] != implode(',', $values['enum'])) {
if(empty($this->enum_map)) $this->query("CREATE TABLE IF NOT EXISTS SQLiteEnums (TableColumn TEXT PRIMARY KEY, EnumList TEXT)"); $this->query("REPLACE INTO SQLiteEnums (TableColumn, EnumList) VALUES (\"{$tablefield}\", \"" . implode(', ', $values['enum']) . "\")");
if(empty($this->enum_map[$column]) || $this->enum_map[$column] != implode(',', $values['enums'])) { $this->enum_map[$tablefield] = implode(',', $values['enum']);
$this->query("REPLACE INTO SQLiteEnums (TableColumn,EnumList) VALUES (\"$column\",\"".implode(',', $values['enums'])."\")");
$this->enum_map[$column] = implode(',', $values['enums']);
}
} }
return 'TEXT DEFAULT \'' . $values['default'] . '\''; return 'TEXT DEFAULT \'' . ($values['default'] ? $values['default'] : $values['enum'][0]) . '\'';
} }
@ -586,6 +696,18 @@ class SQLite3Database extends SS_Database {
} }
/**
* Return a Double type-formatted string
*
* @params array $values Contains a tokenised list of info about this data type
* @return string
*/
public function Double($values, $asDbValue=false){
return "REAL";
}
/** /**
* Return a int type-formatted string * Return a int type-formatted string
* *
@ -594,7 +716,7 @@ class SQLite3Database extends SS_Database {
*/ */
public function int($values, $asDbValue=false){ public function int($values, $asDbValue=false){
return "INTEGER($values[precision]) $values[null] DEFAULT " . (int)$values['default']; return 'INTEGER(11) NOT NULL DEFAULT ' . (isset($values['default']) ? (int)$values['default'] : 0);
} }
@ -605,7 +727,7 @@ class SQLite3Database extends SS_Database {
* @params array $values Contains a tokenised list of info about this data type * @params array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function SS_Datetime($values, $asDbValue=false){ public function ss_datetime($values, $asDbValue=false){
return "DATETIME"; return "DATETIME";
@ -643,7 +765,7 @@ class SQLite3Database extends SS_Database {
*/ */
public function varchar($values, $asDbValue=false){ public function varchar($values, $asDbValue=false){
return 'VARCHAR(' . $values['precision'] . ') COLLATE NOCASE'; return 'VARCHAR(' . $values['size'] . ') COLLATE NOCASE';
} }
@ -700,7 +822,7 @@ class SQLite3Database extends SS_Database {
/** /**
* Get the actual enum fields from the constraint value: * Get the actual enum fields from the constraint value:
*/ */
private function EnumValuesFromConstraint($constraint){ protected function EnumValuesFromConstraint($constraint){
$constraint=substr($constraint, strpos($constraint, 'ANY (ARRAY[')+11); $constraint=substr($constraint, strpos($constraint, 'ANY (ARRAY[')+11);
$constraint=substr($constraint, 0, -11); $constraint=substr($constraint, 0, -11);
$constraints=Array(); $constraints=Array();
@ -738,6 +860,8 @@ class SQLite3Database extends SS_Database {
* This changes the index name depending on database requirements. * This changes the index name depending on database requirements.
*/ */
function modifyIndex($index, $spec){ function modifyIndex($index, $spec){
// Debug::show("modifyIndex($index, $spec)");
// SS_Backtrace::backtrace();
return $index; return $index;
} }
@ -980,13 +1104,13 @@ class SQLite3Query extends SS_Query {
* The SQLite3Database object that created this result set. * The SQLite3Database object that created this result set.
* @var SQLite3Database * @var SQLite3Database
*/ */
private $database; protected $database;
/** /**
* The internal sqlite3 handle that points to the result set. * The internal sqlite3 handle that points to the result set.
* @var resource * @var resource
*/ */
private $handle; protected $handle;
/** /**
* Hook the result-set given into a Query class, suitable for use by sapphire. * Hook the result-set given into a Query class, suitable for use by sapphire.

158
code/SQLitePDODatabase.php Normal file
View File

@ -0,0 +1,158 @@
<?php
/**
* SQLite connector class.
* @package SQLite3
*/
class SQLitePDODatabase extends SQLite3Database {
/*
* Uses whatever connection details are in the $parameters array to connect to a database of a given name
*/
function connectDatabase(){
$this->enum_map = array();
$parameters=$this->parameters;
$dbName = !isset($this->database) ? $parameters['database'] : $dbName=$this->database;
//assumes that the path to dbname will always be provided:
$file = $parameters['path'] . '/' . $dbName;
// use the very lightspeed SQLite In-Memory feature for testing
if(SapphireTest::using_temp_db()) $file = ':memory:';
$this->dbConn = new PDO("sqlite:$file");
//By virtue of getting here, the connection is active:
$this->active=true;
$this->database = $dbName;
if(!$this->dbConn || !empty($error)) {
$this->databaseError("Couldn't connect to SQLite database");
return false;
}
return true;
}
public function query($sql, $errorLevel = E_USER_ERROR) {
if(isset($_REQUEST['previewwrite']) && in_array(strtolower(substr($sql,0,strpos($sql,' '))), array('insert','update','delete','replace'))) {
Debug::message("Will execute: $sql");
return;
}
if(isset($_REQUEST['showqueries'])) {
$starttime = microtime(true);
}
// @todo This is a very ugly hack to rewrite the update statement of SiteTree::doPublish()
// @see SiteTree::doPublish() There is a hack for MySQL already, maybe it's worth moving this to SiteTree or that other hack to Database...
if(preg_replace('/[\W\d]*/i','',$sql) == 'UPDATESiteTree_LiveSETSortSiteTreeSortFROMSiteTreeWHERESiteTree_LiveIDSiteTreeIDANDSiteTree_LiveParentID') {
preg_match('/\d+/i',$sql,$matches);
$sql = 'UPDATE "SiteTree_Live"
SET "Sort" = (SELECT "SiteTree"."Sort" FROM "SiteTree" WHERE "SiteTree_Live"."ID" = "SiteTree"."ID")
WHERE "ParentID" = ' . $matches[0];
}
$handle = $this->dbConn->query($sql);
if(isset($_REQUEST['showqueries'])) {
$endtime = round(microtime(true) - $starttime,4);
Debug::message("\n$sql\n{$endtime}ms\n", false);
}
DB::$lastQuery=$handle;
if(!$handle && $errorLevel) {
$msg = $this->dbConn->errorInfo();
$this->databaseError("Couldn't run query: $sql | " . $msg[2], $errorLevel);
}
return new SQLitePDOQuery($this, $handle);
}
public function getGeneratedID($table) {
return $this->dbConn->lastInsertId();
}
/*
* This will return text which has been escaped in a database-friendly manner
*/
function addslashes($value){
return sqlite_escape_string($value);
}
}
/**
* A result-set from a SQLitePDO database.
* @package SQLite3
*/
class SQLitePDOQuery extends SQLite3Query {
/**
* Hook the result-set given into a Query class, suitable for use by sapphire.
* @param database The database object that created this query.
* @param handle the internal sqlitePDO handle that is points to the resultset.
*/
public function __construct(SQLitePDODatabase $database, PDOStatement $handle) {
$this->database = $database;
$this->handle = $handle;
}
public function __destroy() {
$this->handle->closeCursor();
}
public function seek($row) {
$this->handle->execute();
$i=0;
while($i < $row && $row = $this->handle->fetch()) $i++;
return (bool) $row;
}
public function numRecords() {
return $this->handle->rowCount();
}
public function nextRecord() {
$this->handle->setFetchMode( PDO::FETCH_CLASS, 'ResultRow');
if($data = $this->handle->fetch(PDO::FETCH_CLASS)) {
foreach($data->get() as $columnName => $value) {
if(preg_match('/^"([a-z0-9_]+)"\."([a-z0-9_]+)"$/i', $columnName, $matches)) $columnName = $matches[2];
else if(preg_match('/^"([a-z0-9_]+)"$/i', $columnName, $matches)) $columnName = $matches[1];
else $columnName = trim($columnName,"\"' \t");
$output[$columnName] = is_null($value) ? null : (string)$value;
}
return $output;
} else {
return false;
}
}
}
/**
* This is necessary for a case where we have ambigous fields in the result.
* E.g. we have something like the following:
* SELECT Child1.value, Child2.value FROM Parent LEFT JOIN Child1 LEFT JOIN Child2
* We get value twice in the result set. We want the last not empty value.
* The fetch assoc syntax does'nt work because it gives us the last value everytime, empty or not.
* The fetch num does'nt work because there is no function to retrieve the field names to create the map.
* In this approach we make use of PDO fetch class to pass the result values to an
* object and let the __set() function do the magic decision to choose the right value.
*/
class ResultRow {
private $_datamap=array();
function __set($key,$val) {
if($val || !isset($this->_datamap[$key])) $this->_datamap[$key] = $val;
}
function get() {
return $this->_datamap;
}
}