2010-01-14 21:42:43 +01:00
< ? php
/**
* SQLite connector class .
2010-01-21 12:32:21 +01:00
* @ package SQLite3
2010-01-14 21:42:43 +01:00
*/
class SQLite3Database extends SS_Database {
2010-01-17 11:48:22 +01:00
/**
* Connection to the DBMS .
* @ var object
*/
2010-01-21 12:32:21 +01:00
protected $dbConn ;
2010-01-17 11:48:22 +01:00
/**
* True if we are connected to a database .
* @ var boolean
*/
2010-01-21 12:32:21 +01:00
protected $active ;
2010-01-17 11:48:22 +01:00
/**
* The name of the database .
* @ var string
*/
2010-01-21 12:32:21 +01:00
protected $database ;
2010-01-17 11:48:22 +01:00
/*
* This holds the name of the original database
* So if you switch to another for unit tests , you
* can then switch back in order to drop the temp database
*/
2010-01-21 12:32:21 +01:00
protected $database_original ;
2010-01-17 11:48:22 +01:00
/*
* This holds the parameters that the original connection was created with ,
* so we can switch back to it if necessary ( used for unit tests )
*/
2010-01-21 12:32:21 +01:00
protected $parameters ;
2010-01-17 11:48:22 +01:00
/*
* Actually SQLite supports transactions ( they are used below ), but they
* work signifficantly different to the transactions in Postgres on which
* the unit test are based upon ... ;(
*/
2010-01-21 12:32:21 +01:00
protected $supportsTransactions = false ;
2010-01-17 11:48:22 +01:00
2010-01-22 12:48:57 +01:00
/*
* if we ' re on a In - Memory db
*/
protected $lives_in_memory = false ;
2010-01-24 07:57:42 +01:00
public static $default_pragma = array ();
2010-01-17 11:48:22 +01:00
/**
* Connect to a SQLite3 database .
* @ param array $parameters An map of parameters , which should include :
* - database : The database to connect to
* - path : the path to the SQLite3 database file
* - key : the encryption key ( needs testing )
* - memory : use the faster In - Memory database for unit tests
*/
public function __construct ( $parameters ) {
//We will store these connection parameters for use elsewhere (ie, unit tests)
$this -> parameters = $parameters ;
$this -> connectDatabase ();
$this -> database_original = $this -> database ;
}
2010-01-22 12:48:57 +01:00
2010-01-17 11:48:22 +01:00
/*
* 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:
2010-01-14 21:42:43 +01:00
$file = $parameters [ 'path' ] . '/' . $dbName ;
2010-01-17 11:48:22 +01:00
2010-01-14 21:42:43 +01:00
// use the very lightspeed SQLite In-Memory feature for testing
2010-01-22 12:48:57 +01:00
if ( SapphireTest :: using_temp_db () && $parameters [ 'memory' ]) {
$file = ':memory:' ;
$this -> lives_in_memory = true ;
} else {
$this -> lives_in_memory = false ;
}
2010-02-08 05:20:09 +01:00
2010-03-15 07:43:49 +01:00
if ( ! file_exists ( $parameters [ 'path' ])) {
SQLiteDatabaseConfigurationHelper :: create_db_dir ( $parameters [ 'path' ]);
SQLiteDatabaseConfigurationHelper :: secure_db_dir ( $parameters [ 'path' ]);
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
$this -> dbConn = new SQLite3 ( $file , SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE , $parameters [ 'key' ]);
//By virtue of getting here, the connection is active:
$this -> active = true ;
$this -> database = $dbName ;
if ( ! $this -> dbConn ) {
$this -> databaseError ( " Couldn't connect to SQLite3 database " );
return false ;
}
2010-01-24 07:57:42 +01:00
foreach ( self :: $default_pragma as $pragma => $value ) $this -> pragma ( $pragma , $value );
if ( empty ( self :: $default_pragma [ 'locking_mode' ])) {
self :: $default_pragma [ 'locking_mode' ] = $this -> pragma ( 'locking_mode' );
}
2010-01-17 11:48:22 +01:00
return true ;
}
/**
* Not implemented , needed for PDO
*/
public function getConnect ( $parameters ) {
return null ;
}
/**
* Returns true if this database supports collations
* TODO : get rid of this ?
* @ return boolean
*/
public function supportsCollations () {
return true ;
}
/**
* The version of SQLite3 .
* @ var float
*/
2010-01-21 12:32:21 +01:00
protected $sqliteVersion ;
2010-01-17 11:48:22 +01:00
/**
* Get the version of SQLite3 .
* @ return float
*/
public function getVersion () {
if ( ! $this -> sqliteVersion ) {
$db_version = $this -> query ( " SELECT sqlite_version() " ) -> value ();
$this -> sqliteVersion = ( float ) $db_version ;
}
return $this -> sqliteVersion ;
}
2010-01-24 07:57:42 +01:00
/**
* Execute PRAGMA commands .
* works as getter and setter for connection params
* @ param String pragma name
* @ param String optional value to set
* @ return String the pragma value
*/
protected function pragma ( $pragma , $value = null ) {
if ( strlen ( $value )) {
$this -> query ( " PRAGMA $pragma = $value " );
} else {
$value = $this -> query ( " PRAGMA $pragma " ) -> value ();
}
return $value ;
}
2010-01-17 11:48:22 +01:00
/**
* Get the database server , namely SQLite3 .
* @ return string
*/
public function getDatabaseServer () {
return " sqlite " ;
}
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 );
}
2010-01-24 07:57:42 +01:00
@ $handle = $this -> dbConn -> query ( $sql );
2010-01-17 11:48:22 +01:00
if ( isset ( $_REQUEST [ 'showqueries' ])) {
$endtime = round ( microtime ( true ) - $starttime , 4 );
Debug :: message ( " \n $sql\n { $endtime } ms \n " , false );
}
DB :: $lastQuery = $handle ;
2010-01-24 07:57:42 +01:00
if ( ! $handle ) {
$this -> databaseError ( " Couldn't run query: $sql | " . $this -> dbConn -> lastErrorMsg (), $errorLevel );
}
2010-01-17 11:48:22 +01:00
return new SQLite3Query ( $this , $handle );
}
public function getGeneratedID ( $table ) {
return $this -> dbConn -> lastInsertRowID ();
}
/**
* OBSOLETE : Get the ID for the next new record for the table .
*
* @ var string $table The name od the table .
* @ return int
*/
public function getNextID ( $table ) {
user_error ( 'getNextID is OBSOLETE (and will no longer work properly)' , E_USER_WARNING );
$result = $this -> query ( " SELECT MAX(ID)+1 FROM \" $table\ " " )->value();
return $result ? $result : 1 ;
}
public function isActive () {
return $this -> active ? true : false ;
}
/*
* This will create a database based on whatever is in the $this -> database value
* So you need to have called $this -> selectDatabase () first , or used the __construct method
*/
public function createDatabase () {
$this -> dbConn = null ;
$fullpath = $this -> parameters [ 'path' ] . '/' . $this -> database ;
if ( is_writable ( $fullpath )) unlink ( $fullpath );
$this -> connectDatabase ();
}
/**
* Drop the database that this object is currently connected to .
* Use with caution .
*/
public function dropDatabase () {
//First, we need to switch back to the original database so we can drop the current one
$this -> dbConn = null ;
$db_to_drop = $this -> database ;
$this -> selectDatabase ( $this -> database_original );
$this -> connectDatabase ();
$fullpath = $this -> parameters [ 'path' ] . '/' . $db_to_drop ;
if ( is_writable ( $fullpath )) unlink ( $fullpath );
}
/**
* Returns the name of the currently selected database
*/
public function currentDatabase () {
return $this -> database ;
}
/**
* Switches to the given database .
* If the database doesn ' t exist , you should call createDatabase () after calling selectDatabase ()
*/
public function selectDatabase ( $dbname ) {
$this -> database = $dbname ;
$this -> tableList = $this -> fieldList = $this -> indexList = null ;
return true ;
}
/**
* Returns true if the named database exists .
*/
public function databaseExists ( $name ) {
$SQL_name = Convert :: raw2sql ( $name );
$result = $this -> query ( " PRAGMA database_list " );
foreach ( $result as $db ) if ( $db [ 'name' ] == 'main' && preg_match ( '/\/' . $name . '/' , $db [ 'file' ])) return true ;
if ( file_exists ( $this -> parameters [ 'path' ] . '/' . $name )) return true ;
return false ;
}
2010-01-24 07:57:42 +01:00
function beginSchemaUpdate () {
$this -> pragma ( 'locking_mode' , 'EXCLUSIVE' );
$this -> checkAndRepairTable ();
2010-01-28 06:29:08 +01:00
// if($this->TableExists('SQLiteEnums')) $this->query("DELETE FROM SQLiteEnums");
$this -> checkAndRepairTable ();
2010-01-24 07:57:42 +01:00
parent :: beginSchemaUpdate ();
}
function endSchemaUpdate () {
parent :: endSchemaUpdate ();
$this -> pragma ( 'locking_mode' , self :: $default_pragma [ 'locking_mode' ]);
}
2010-01-17 11:48:22 +01:00
public function clearTable ( $table ) {
$this -> dbConn -> query ( " DELETE FROM \" $table\ " " );
}
public function createTable ( $table , $fields = null , $indexes = null , $options = null , $advancedOptions = null ) {
2010-01-22 12:48:57 +01:00
if ( ! isset ( $fields [ 'ID' ])) $fields [ 'ID' ] = $this -> IdColumn ();
2010-01-17 11:48:22 +01:00
$fieldSchemata = array ();
2010-01-21 12:32:21 +01:00
if ( $fields ) foreach ( $fields as $k => $v ) {
$fieldSchemata [] = " \" $k\ " $v " ;
}
2010-01-17 11:48:22 +01:00
$fieldSchemas = implode ( " , \n " , $fieldSchemata );
// Switch to "CREATE TEMPORARY TABLE" for temporary tables
$temporary = empty ( $options [ 'temporary' ]) ? " " : " TEMPORARY " ;
$this -> query ( " CREATE $temporary TABLE \" $table\ " (
$fieldSchemas
) " );
2010-01-22 12:48:57 +01:00
if ( $indexes ) {
foreach ( $indexes as $indexName => $indexDetails ) {
$this -> createIndex ( $table , $indexName , $indexDetails );
}
}
2010-01-17 11:48:22 +01:00
return $table ;
}
2010-01-22 12:48:57 +01:00
/**
* Alter a table ' s schema .
* @ param $table The name of the table to alter
* @ param $newFields New fields , a map of field name => field schema
* @ param $newIndexes New indexes , a map of index name => index type
* @ param $alteredFields Updated fields , a map of field name => field schema
* @ 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 ) {
2010-01-17 11:48:22 +01:00
2010-01-22 12:48:57 +01:00
if ( $newFields ) foreach ( $newFields as $fieldName => $fieldSpec ) $this -> createField ( $tableName , $fieldName , $fieldSpec );
2010-01-17 11:48:22 +01:00
2010-01-22 12:48:57 +01:00
if ( $alteredFields ) foreach ( $alteredFields as $fieldName => $fieldSpec ) $this -> alterField ( $tableName , $fieldName , $fieldSpec );
2010-01-17 11:48:22 +01:00
2010-01-22 12:48:57 +01:00
if ( $newIndexes ) foreach ( $newIndexes as $indexName => $indexSpec ) $this -> createIndex ( $tableName , $indexName , $indexSpec );
if ( $alteredIndexes ) foreach ( $alteredIndexes as $indexName => $indexSpec ) $this -> alterIndex ( $tableName , $indexName , $indexSpec );
}
2010-01-17 11:48:22 +01:00
public function renameTable ( $oldTableName , $newTableName ) {
2010-02-08 05:20:09 +01:00
$this -> query ( " ALTER TABLE \" $oldTableName\ " RENAME TO \ " $newTableName\ " " );
2010-01-17 11:48:22 +01:00
}
2010-01-22 12:48:57 +01:00
protected static $checked_and_repaired = false ;
2010-01-17 11:48:22 +01:00
/**
* Repairs and reindexes the table . This might take a long time on a very large table .
* @ var string $tableName The name of the table .
* @ return boolean Return true if the table has integrity after the method is complete .
*/
2010-01-24 07:57:42 +01:00
public function checkAndRepairTable ( $tableName = null ) {
2010-01-17 11:48:22 +01:00
2010-01-22 12:48:57 +01:00
$ok = true ;
if ( ! SapphireTest :: using_temp_db () && ! self :: $checked_and_repaired ) {
2010-03-10 00:58:08 +01:00
$this -> alterationMessage ( " SQLite Version " . $this -> getVersion (), " repaired " );
2010-01-22 12:48:57 +01:00
$this -> alterationMessage ( " Checking database integrity " , " repaired " );
if ( $msgs = $this -> query ( 'PRAGMA integrity_check' )) foreach ( $msgs as $msg ) if ( $msg [ 'integrity_check' ] != 'ok' ) { Debug :: show ( $msg [ 'integrity_check' ]); $ok = false ; }
$this -> query ( 'VACUUM' );
$this -> alterationMessage ( " VACUUMing " , " repaired " );
self :: $checked_and_repaired = true ;
}
return $ok ;
2010-01-17 11:48:22 +01:00
}
public function createField ( $tableName , $fieldName , $fieldSpec ) {
$this -> query ( " ALTER TABLE \" $tableName\ " ADD \ " $fieldName\ " $fieldSpec " );
}
/**
* Change the database type of the given field .
* @ param string $tableName The name of the tbale the field is in .
* @ param string $fieldName The name of the field to change .
* @ param string $fieldSpec The new field specification
*/
public function alterField ( $tableName , $fieldName , $fieldSpec ) {
$oldFieldList = $this -> fieldList ( $tableName );
$fieldNameList = '"' . implode ( '","' , array_keys ( $oldFieldList )) . '"' ;
2010-01-24 07:57:42 +01:00
if ( ! empty ( $_REQUEST [ 'avoidConflict' ]) && Director :: isDev ()) $fieldSpec = preg_replace ( '/\snot null\s/i' , ' NOT NULL ON CONFLICT REPLACE ' , $fieldSpec );
2010-01-17 11:48:22 +01:00
if ( array_key_exists ( $fieldName , $oldFieldList )) {
$oldCols = array ();
foreach ( $oldFieldList as $name => $spec ) {
$newColsSpec [] = " \" $name\ " " . ( $name == $fieldName ? $fieldSpec : $spec );
}
$queries = array (
" BEGIN TRANSACTION " ,
2010-01-24 07:57:42 +01:00
" CREATE TABLE \" { $tableName } _alterfield_ { $fieldName } \" ( " . implode ( ',' , $newColsSpec ) . " ) " ,
" INSERT INTO \" { $tableName } _alterfield_ { $fieldName } \" SELECT { $fieldNameList } FROM \" $tableName\ " " ,
2010-01-17 11:48:22 +01:00
" DROP TABLE \" $tableName\ " " ,
2010-01-24 07:57:42 +01:00
" ALTER TABLE \" { $tableName } _alterfield_ { $fieldName } \" RENAME TO \" $tableName\ " " ,
2010-01-17 11:48:22 +01:00
" COMMIT "
);
2010-01-24 07:57:42 +01:00
$indexList = $this -> indexList ( $tableName );
2010-01-14 21:42:43 +01:00
foreach ( $queries as $query ) $this -> query ( $query . ';' );
2010-01-17 11:48:22 +01:00
2010-01-24 07:57:42 +01:00
foreach ( $indexList as $indexName => $indexSpec ) $this -> createIndex ( $tableName , $indexName , $indexSpec );
2010-01-17 11:48:22 +01:00
}
}
/**
* Change the database column name of the given field .
*
* @ param string $tableName The name of the tbale the field is in .
* @ param string $oldName The name of the field to change .
* @ param string $newName The new name of the field
*/
public function renameField ( $tableName , $oldName , $newName ) {
$oldFieldList = $this -> fieldList ( $tableName );
2010-03-04 23:08:24 +01:00
$oldCols = array ();
2010-01-17 11:48:22 +01:00
if ( array_key_exists ( $oldName , $oldFieldList )) {
foreach ( $oldFieldList as $name => $spec ) {
$oldCols [] = " \" $name\ " " . (( $name == $oldName ) ? " AS $newName " : '');
$newCols [] = " \" " . (( $name == $oldName ) ? $newName : $name ) . " \" " ;
$newColsSpec [] = " \" " . (( $name == $oldName ) ? $newName : $name ) . " \" $spec " ;
}
$queries = array (
" BEGIN TRANSACTION " ,
2010-01-24 07:57:42 +01:00
" CREATE TABLE \" { $tableName } _renamefield_ { $oldName } \" ( " . implode ( ',' , $newColsSpec ) . " ) " ,
2010-01-22 12:48:57 +01:00
" INSERT INTO \" { $tableName } _renamefield_ { $oldName } \" SELECT " . implode ( ',' , $oldCols ) . " FROM \" $tableName\ " " ,
2010-01-17 11:48:22 +01:00
" DROP TABLE \" $tableName\ " " ,
2010-01-24 07:57:42 +01:00
" ALTER TABLE \" { $tableName } _renamefield_ { $oldName } \" RENAME TO \" $tableName\ " " ,
2010-01-17 11:48:22 +01:00
" COMMIT "
);
2010-01-24 07:57:42 +01:00
$indexList = $this -> indexList ( $tableName );
2010-01-17 11:48:22 +01:00
foreach ( $queries as $query ) $this -> query ( $query . ';' );
2010-01-24 07:57:42 +01:00
foreach ( $indexList as $indexName => $indexSpec ) {
$renamedIndexSpec = array ();
foreach ( explode ( ',' , $indexSpec ) as $col ) $renamedIndexSpec [] = $col == $oldName ? $newName : $col ;
$this -> createIndex ( $tableName , $indexName , implode ( ',' , $renamedIndexSpec ));
}
2010-01-17 11:48:22 +01:00
}
}
public function fieldList ( $table ) {
$sqlCreate = DB :: query ( 'SELECT sql FROM sqlite_master WHERE type = "table" AND name = "' . $table . '"' ) -> record ();
2010-03-04 23:08:24 +01:00
$fieldList = array ();
2010-01-17 11:48:22 +01:00
if ( $sqlCreate && $sqlCreate [ 'sql' ]) {
2010-03-09 00:39:22 +01:00
preg_match ( '/^[\s]*CREATE[\s]+TABLE[\s]+[\'"][a-zA-Z0-9_]+[\'"][\s]*\((.+)\)[\s]*$/ims' , $sqlCreate [ 'sql' ], $matches );
2010-02-03 06:02:43 +01:00
$fields = isset ( $matches [ 1 ]) ? preg_split ( '/,(?=(?:[^\'"]*$)|(?:[^\'"]*[\'"][^\'"]*[\'"][^\'"]*)*$)/x' , $matches [ 1 ]) : array ();
2010-01-17 11:48:22 +01:00
foreach ( $fields as $field ) {
$details = preg_split ( '/\s/' , trim ( $field ));
$name = array_shift ( $details );
$name = str_replace ( '"' , '' , trim ( $name ));
$fieldList [ $name ] = implode ( ' ' , $details );
}
}
2010-03-04 23:08:24 +01:00
return $fieldList ;
2010-01-17 11:48:22 +01:00
}
/**
* Create an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
* @ param string $indexSpec The specification of the index , see Database :: requireIndex () for more details .
*/
public function createIndex ( $tableName , $indexName , $indexSpec ) {
2010-01-22 12:48:57 +01:00
$spec = $this -> convertIndexSpec ( $indexSpec , $indexName );
2010-01-24 07:57:42 +01:00
if ( ! preg_match ( '/".+"/' , $indexName )) $indexName = " \" $indexName\ " " ;
2010-01-28 06:29:08 +01:00
$this -> query ( " CREATE INDEX IF NOT EXISTS $indexName ON \" $tableName\ " ( $spec ) " );
2010-01-17 11:48:22 +01:00
}
/*
* This takes the index spec which has been provided by a class ( ie static $indexes = blah blah )
* and turns it into a proper string .
* Some indexes may be arrays , such as fulltext and unique indexes , and this allows database - specific
* arrays to be created .
*/
2010-03-04 23:08:24 +01:00
public function convertIndexSpec ( $indexSpec , $indexName = null ) {
2010-01-21 12:32:21 +01:00
if ( is_array ( $indexSpec )) {
2010-01-22 12:48:57 +01:00
$indexSpec = $indexSpec [ 'value' ];
} else if ( is_numeric ( $indexSpec )) {
$indexSpec = $indexName ;
}
if ( preg_match ( '/\((.+)\)/' , $indexSpec , $matches )) {
$indexSpec = $matches [ 1 ];
2010-01-21 12:32:21 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-22 12:48:57 +01:00
return preg_replace ( '/\s/' , '' , $indexSpec );
2010-01-17 11:48:22 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* prefix indexname with uppercase tablename if not yet done , in order to avoid ambiguity
*/
function getDbSqlDefinition ( $tableName , $indexName , $indexSpec ) {
2010-01-22 12:48:57 +01:00
return " \" $tableName . $indexName\ " " ;
2010-01-17 11:48:22 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* Alter an index on a table .
* @ param string $tableName The name of the table .
* @ param string $indexName The name of the index .
* @ param string $indexSpec The specification of the index , see Database :: requireIndex () for more details .
*/
public function alterIndex ( $tableName , $indexName , $indexSpec ) {
$this -> createIndex ( $tableName , $indexName , $indexSpec );
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* Return the list of indexes in a table .
* @ param string $table The table name .
* @ return array
*/
public function indexList ( $table ) {
$indexList = array ();
foreach ( DB :: query ( 'PRAGMA index_list("' . $table . '")' ) as $index ) {
$list = array ();
foreach ( DB :: query ( 'PRAGMA index_info("' . $index [ " name " ] . '")' ) as $details ) $list [] = $details [ 'name' ];
$indexList [ $index [ " name " ]] = implode ( ',' , $list );
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
return $indexList ;
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* Returns a list of all the tables in the database .
* Table names will all be in lowercase .
* @ return array
*/
public function tableList () {
2010-03-04 23:08:24 +01:00
$tables = array ();
2010-01-17 11:48:22 +01:00
foreach ( $this -> query ( 'SELECT name FROM sqlite_master WHERE type = "table"' ) as $record ) {
//$table = strtolower(reset($record));
$table = reset ( $record );
$tables [ $table ] = $table ;
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
//Return an empty array if there's nothing in this database
return isset ( $tables ) ? $tables : Array ();
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
function TableExists ( $tableName ){
2010-01-24 07:57:42 +01:00
2010-01-17 11:48:22 +01:00
$result = $this -> query ( 'SELECT name FROM sqlite_master WHERE type = "table" AND name="' . $tableName . '"' ) -> first ();
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
if ( $result )
return true ;
else
return false ;
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* Return the number of rows affected by the previous operation .
* @ return int
*/
public function affectedRows () {
return $this -> dbConn -> changes ();
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/**
* Return a boolean type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
2010-01-21 12:32:21 +01:00
public function boolean ( $values ){
2010-01-24 07:57:42 +01:00
2010-01-21 12:32:21 +01:00
return 'BOOL NOT NULL DEFAULT ' . ( isset ( $values [ 'default' ]) ? ( int ) $values [ 'default' ] : 0 );
2010-01-17 11:48:22 +01:00
}
/**
* Return a date type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function date ( $values ){
return " TEXT " ;
}
/**
* Return a decimal type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function decimal ( $values , $asDbValue = false ){
2010-02-05 04:35:48 +01:00
$default = isset ( $values [ 'default' ]) && is_numeric ( $values [ 'default' ]) ? $values [ 'default' ] : 0 ;
return " NUMERIC NOT NULL DEFAULT " . $default ;
2010-01-17 11:48:22 +01:00
}
/**
* Return a enum type - formatted string
*
* enumus are not supported . as a workaround to store allowed values we creates an additional table
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
protected $enum_map = array ();
public function enum ( $values ){
2010-01-21 12:32:21 +01:00
$tablefield = $values [ 'table' ] . '.' . $values [ 'name' ];
if ( empty ( $this -> enum_map )) $this -> query ( " CREATE TABLE IF NOT EXISTS SQLiteEnums (TableColumn TEXT PRIMARY KEY, EnumList TEXT) " );
2010-01-22 12:48:57 +01:00
if ( empty ( $this -> enum_map [ $tablefield ]) || $this -> enum_map [ $tablefield ] != implode ( ',' , $values [ 'enums' ])) {
2010-01-28 06:29:08 +01:00
$this -> query ( " REPLACE INTO SQLiteEnums (TableColumn, EnumList) VALUES ( \" { $tablefield } \" , \" " . implode ( ',' , $values [ 'enums' ]) . " \" ) " );
2010-01-22 12:48:57 +01:00
$this -> enum_map [ $tablefield ] = implode ( ',' , $values [ 'enums' ]);
2010-01-17 11:48:22 +01:00
}
2010-01-22 12:48:57 +01:00
return " TEXT DEFAULT ' { $values [ 'default' ] } ' " ;
2010-01-17 11:48:22 +01:00
}
2010-02-03 06:02:43 +01:00
/**
* Return a set type - formatted string
* This type doesn ' t exist in SQLite as well
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function set ( $values ) {
$tablefield = $values [ 'table' ] . '.' . $values [ 'name' ];
if ( empty ( $this -> enum_map )) $this -> query ( " CREATE TABLE IF NOT EXISTS SQLiteEnums (TableColumn TEXT PRIMARY KEY, EnumList TEXT) " );
if ( empty ( $this -> enum_map [ $tablefield ]) || $this -> enum_map [ $tablefield ] != implode ( ',' , $values [ 'enums' ])) {
$this -> query ( " REPLACE INTO SQLiteEnums (TableColumn, EnumList) VALUES ( \" { $tablefield } \" , \" " . implode ( ',' , $values [ 'enums' ]) . " \" ) " );
$this -> enum_map [ $tablefield ] = implode ( ',' , $values [ 'enums' ]);
}
$default = '' ;
if ( ! empty ( $values [ 'default' ])) {
$default = str_replace ( array ( '"' , " ' " , " \\ " , " \0 " ), " " , $values [ 'default' ]);
$default = " DEFAULT ' $default ' " ;
}
return 'TEXT' . $default ;
}
2010-01-17 11:48:22 +01:00
/**
* Return a float type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function float ( $values , $asDbValue = false ){
return " REAL " ;
}
2010-01-21 12:32:21 +01:00
/**
* 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 " ;
}
2010-01-17 11:48:22 +01:00
/**
* Return a int type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function int ( $values , $asDbValue = false ){
2010-01-22 12:48:57 +01:00
return " INTEGER( { $values [ 'precision' ] } ) " . strtoupper ( $values [ 'null' ]) . " DEFAULT " . ( int ) $values [ 'default' ];
2010-01-17 11:48:22 +01:00
}
/**
* Return a datetime type - formatted string
* For SQLite3 , we simply return the word 'TEXT' , no other parameters are necessary
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
2010-01-21 12:32:21 +01:00
public function ss_datetime ( $values , $asDbValue = false ){
2010-01-17 11:48:22 +01:00
return " DATETIME " ;
}
/**
* Return a text type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function text ( $values , $asDbValue = false ){
return 'TEXT' ;
}
/**
* Return a time type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function time ( $values ){
return " TEXT " ;
}
/**
* Return a varchar type - formatted string
*
* @ params array $values Contains a tokenised list of info about this data type
* @ return string
*/
public function varchar ( $values , $asDbValue = false ){
2010-01-22 12:48:57 +01:00
return " VARCHAR( { $values [ 'precision' ] } ) COLLATE NOCASE " ;
2010-01-17 11:48:22 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
/*
* Return a 4 digit numeric type . MySQL has a proprietary 'Year' type .
* For SQLite3 we use TEXT
*/
public function year ( $values , $asDbValue = false ){
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
return " TEXT " ;
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
}
2010-01-14 21:42:43 +01:00
2010-01-17 11:48:22 +01:00
function escape_character ( $escape = false ){
if ( $escape ) return " \\ \" " ; else return " \" " ;
}
/**
* This returns the column which is the primary key for each table
* In SQLite3 it is INTEGER PRIMARY KEY AUTOINCREMENT
* SQLite3 does autoincrement ids even without the AUTOINCREMENT keyword , but the behaviour is signifficantly different
*
* @ return string
*/
function IdColumn ( $asDbValue = false ){
return 'INTEGER PRIMARY KEY AUTOINCREMENT' ;
}
/**
* Returns true if this table exists
*/
function hasTable ( $tableName ) {
$SQL_table = Convert :: raw2sql ( $table );
return ( bool )( $this -> query ( " SELECT name FROM sqlite_master WHERE type = \" table \" AND name = \" $tableName\ " " )->value());
}
/**
* Returns the SQL command to get all the tables in this database
*/
function allTablesSQL (){
return 'SELECT name FROM sqlite_master WHERE type = "table"' ;
}
/**
* Return enum values for the given field
*/
public function enumValuesForField ( $tableName , $fieldName ) {
$classnameinfo = DB :: query ( " SELECT EnumList FROM SQLiteEnums WHERE TableColumn = \" { $tableName } . { $fieldName } \" " ) -> first ();
2010-01-28 06:29:08 +01:00
$return = explode ( ',' , $classnameinfo [ 'EnumList' ]);
//for($i=0; $i<count($return);$i++) $return[$i] = trim($return[$i]);
return $return ;
2010-01-17 11:48:22 +01:00
}
/**
* Get the actual enum fields from the constraint value :
*/
2010-01-21 12:32:21 +01:00
protected function EnumValuesFromConstraint ( $constraint ){
2010-01-17 11:48:22 +01:00
$constraint = substr ( $constraint , strpos ( $constraint , 'ANY (ARRAY[' ) + 11 );
$constraint = substr ( $constraint , 0 , - 11 );
$constraints = Array ();
$segments = explode ( ',' , $constraint );
foreach ( $segments as $this_segment ){
$bits = preg_split ( '/ *:: */' , $this_segment );
array_unshift ( $constraints , trim ( $bits [ 0 ], " ' " ));
2010-01-14 21:42:43 +01:00
}
2010-01-17 11:48:22 +01:00
return $constraints ;
}
/*
* Returns the database - specific version of the now () function
*/
function now (){
return " datetime('now', 'localtime') " ;
}
/*
* Returns the database - specific version of the random () function
*/
function random (){
return 'random()' ;
}
2010-03-08 22:57:47 +01:00
/*
* This is a lookup table for data types .
* For instance , Postgres uses 'INT' , while MySQL uses 'UNSIGNED'
* So this is a DB - specific list of equivalents .
*/
function dbDataType ( $type ){
$values = Array (
'unsigned integer' => 'INT'
);
if ( isset ( $values [ $type ]))
return $values [ $type ];
else return '' ;
}
2010-01-17 11:48:22 +01:00
/*
* This will return text which has been escaped in a database - friendly manner
*/
function addslashes ( $value ){
return $this -> dbConn -> escapeString ( $value );
}
/*
* This changes the index name depending on database requirements .
*/
function modifyIndex ( $index , $spec ){
2010-01-22 12:48:57 +01:00
return str_replace ( '"' , '' , $index );
2010-01-17 11:48:22 +01:00
}
/**
* The core search engine configuration .
* @ todo There is a fulltext search for SQLite making use of virtual tables , the fts3 extension and the MATCH operator
* there are a few issues with fts :
* - shared cached lock doesn ' t allow to create virtual tables on versions prior to 3.6 . 17
* - there must not be more than one MATCH operator per statement
* - the fts3 extension needs to be available
* for now we use the MySQL implementation with the MATCH () AGAINST () uglily replaced with LIKE
*
* @ param string $keywords Keywords as a space separated string
* @ return object DataObjectSet of result pages
*/
public function searchEngine ( $classesToSearch , $keywords , $start , $pageLength , $sortBy = " Relevance DESC " , $extraFilter = " " , $booleanSearch = false , $alternativeFileFilter = " " , $invertedMatch = false ) {
$fileFilter = '' ;
$keywords = Convert :: raw2sql ( str_replace ( array ( '*' , '+' , '-' ), '' , $keywords ));
$htmlEntityKeywords = htmlentities ( utf8_decode ( $keywords ));
$extraFilters = array ( 'SiteTree' => '' , 'File' => '' );
if ( $extraFilter ) {
$extraFilters [ 'SiteTree' ] = " AND $extraFilter " ;
if ( $alternativeFileFilter ) $extraFilters [ 'File' ] = " AND $alternativeFileFilter " ;
else $extraFilters [ 'File' ] = $extraFilters [ 'SiteTree' ];
}
// Always ensure that only pages with ShowInSearch = 1 can be searched
$extraFilters [ 'SiteTree' ] .= " AND ShowInSearch <> 0 " ;
$limit = $start . " , " . ( int ) $pageLength ;
$notMatch = $invertedMatch ? " NOT " : " " ;
if ( $keywords ) {
$match [ 'SiteTree' ] = "
( Title LIKE '%$keywords%' OR MenuTitle LIKE '%$keywords%' OR Content LIKE '%$keywords%' OR MetaTitle LIKE '%$keywords%' OR MetaDescription LIKE '%$keywords%' OR MetaKeywords LIKE '%$keywords%' OR
Title LIKE '%$htmlEntityKeywords%' OR MenuTitle LIKE '%$htmlEntityKeywords%' OR Content LIKE '%$htmlEntityKeywords%' OR MetaTitle LIKE '%$htmlEntityKeywords%' OR MetaDescription LIKE '%$htmlEntityKeywords%' OR MetaKeywords LIKE '%$htmlEntityKeywords%' )
" ;
$match [ 'File' ] = " (Filename LIKE '% $keywords %' OR Title LIKE '% $keywords %' OR Content LIKE '% $keywords %') AND ClassName = 'File' " ;
// We make the relevance search by converting a boolean mode search into a normal one
$relevanceKeywords = $keywords ;
$htmlEntityRelevanceKeywords = $htmlEntityKeywords ;
$relevance [ 'SiteTree' ] = " (Title LIKE '% $relevanceKeywords %' OR MenuTitle LIKE '% $relevanceKeywords %' OR Content LIKE '% $relevanceKeywords %' OR MetaTitle LIKE '% $relevanceKeywords %' OR MetaDescription LIKE '% $relevanceKeywords %' OR MetaKeywords) + (Title LIKE '% $htmlEntityRelevanceKeywords %' OR MenuTitle LIKE '% $htmlEntityRelevanceKeywords %' OR Content LIKE '% $htmlEntityRelevanceKeywords %' OR MetaTitle LIKE '% $htmlEntityRelevanceKeywords %' OR MetaDescription LIKE '% $htmlEntityRelevanceKeywords %' OR MetaKeywords LIKE '% $htmlEntityRelevanceKeywords %') " ;
$relevance [ 'File' ] = " (Filename LIKE '% $relevanceKeywords %' OR Title LIKE '% $relevanceKeywords %' OR Content LIKE '% $relevanceKeywords %') " ;
} else {
$relevance [ 'SiteTree' ] = $relevance [ 'File' ] = 1 ;
$match [ 'SiteTree' ] = $match [ 'File' ] = " 1 = 1 " ;
}
// Generate initial queries and base table names
$baseClasses = array ( 'SiteTree' => '' , 'File' => '' );
foreach ( $classesToSearch as $class ) {
$queries [ $class ] = singleton ( $class ) -> extendedSQL ( $notMatch . $match [ $class ] . $extraFilters [ $class ], " " );
$baseClasses [ $class ] = reset ( $queries [ $class ] -> from );
}
// Make column selection lists
$select = array (
'SiteTree' => array ( " \" ClassName \" " , " \" SiteTree \" . \" ID \" " , " \" ParentID \" " , " \" Title \" " , " \" URLSegment \" " , " \" Content \" " , " \" LastEdited \" " , " \" Created \" " , " NULL AS \" Filename \" " , " NULL AS \" Name \" " , " \" CanViewType \" " , " $relevance[SiteTree] AS Relevance " ),
'File' => array ( " \" ClassName \" " , " \" File \" . \" ID \" " , " NULL AS \" ParentID \" " , " \" Title \" " , " NULL AS \" URLSegment \" " , " \" Content \" " , " \" LastEdited \" " , " \" Created \" " , " \" Filename \" " , " \" Name \" " , " NULL AS \" CanViewType \" " , " $relevance[File] AS Relevance " ),
);
// Process queries
foreach ( $classesToSearch as $class ) {
// There's no need to do all that joining
$queries [ $class ] -> from = array ( str_replace ( '`' , '' , $baseClasses [ $class ]) => $baseClasses [ $class ]);
$queries [ $class ] -> select = $select [ $class ];
$queries [ $class ] -> orderby = null ;
}
// Combine queries
$querySQLs = array ();
$totalCount = 0 ;
foreach ( $queries as $query ) {
$querySQLs [] = $query -> sql ();
$totalCount += $query -> unlimitedRowCount ();
}
$fullQuery = implode ( " UNION " , $querySQLs ) . " ORDER BY $sortBy LIMIT $limit " ;
// Get records
$records = DB :: query ( $fullQuery );
foreach ( $records as $record )
$objects [] = new $record [ 'ClassName' ]( $record );
if ( isset ( $objects )) $doSet = new DataObjectSet ( $objects );
else $doSet = new DataObjectSet ();
$doSet -> setPageLimits ( $start , $pageLength , $totalCount );
return $doSet ;
}
/*
* Does this database support transactions ?
*/
public function supportsTransactions (){
return $this -> supportsTransactions ;
}
/*
* This is a quick lookup to discover if the database supports particular extensions
*/
public function supportsExtensions ( $extensions = Array ( 'partitions' , 'tablespaces' , 'clustering' )){
2010-01-24 07:57:42 +01:00
2010-01-17 11:48:22 +01:00
if ( isset ( $extensions [ 'partitions' ]))
return true ;
elseif ( isset ( $extensions [ 'tablespaces' ]))
return true ;
elseif ( isset ( $extensions [ 'clustering' ]))
return true ;
else
return false ;
}
/*
* Start a prepared transaction
*/
public function startTransaction ( $transaction_mode = false , $session_characteristics = false ){
DB :: query ( 'BEGIN' );
}
/*
* Create a savepoint that you can jump back to if you encounter problems
*/
public function transactionSavepoint ( $savepoint ){
DB :: query ( " SAVEPOINT \" $savepoint\ " " );
}
/*
* Rollback or revert to a savepoint if your queries encounter problems
* If you encounter a problem at any point during a transaction , you may
* need to rollback that particular query , or return to a savepoint
*/
public function transactionRollback ( $savepoint = false ){
if ( $savepoint ) {
DB :: query ( " ROLLBACK TO $savepoint ; " );
} else {
DB :: query ( 'ROLLBACK;' );
}
}
/*
* Commit everything inside this transaction so far
*/
public function endTransaction (){
DB :: query ( 'COMMIT;' );
}
2010-01-14 21:42:43 +01:00
/**
* Convert a SQLQuery object into a SQL statement
*/
public function sqlQueryToString ( SQLQuery $sqlQuery ) {
if ( ! $sqlQuery -> from ) return '' ;
$distinct = $sqlQuery -> distinct ? " DISTINCT " : " " ;
if ( $sqlQuery -> delete ) {
$text = " DELETE " ;
} else if ( $sqlQuery -> select ) {
$text = " SELECT $distinct " . implode ( " , " , $sqlQuery -> select );
}
$text .= " FROM " . implode ( " " , $sqlQuery -> from );
if ( $sqlQuery -> where ) $text .= " WHERE ( " . $sqlQuery -> getFilter () . " ) " ;
if ( $sqlQuery -> groupby ) $text .= " GROUP BY " . implode ( " , " , $sqlQuery -> groupby );
if ( $sqlQuery -> having ) $text .= " HAVING ( " . implode ( " ) AND ( " , $sqlQuery -> having ) . " ) " ;
if ( $sqlQuery -> orderby ) $text .= " ORDER BY " . $this -> orderMoreSpecifically ( $sqlQuery -> select , $sqlQuery -> orderby );
if ( $sqlQuery -> limit ) {
$limit = $sqlQuery -> limit ;
// Pass limit as array or SQL string value
if ( is_array ( $limit )) {
if ( ! array_key_exists ( 'limit' , $limit )) user_error ( 'SQLQuery::limit(): Wrong format for $limit' , E_USER_ERROR );
if ( isset ( $limit [ 'start' ]) && is_numeric ( $limit [ 'start' ]) && isset ( $limit [ 'limit' ]) && is_numeric ( $limit [ 'limit' ])) {
$combinedLimit = " $limit[limit] OFFSET $limit[start] " ;
} elseif ( isset ( $limit [ 'limit' ]) && is_numeric ( $limit [ 'limit' ])) {
$combinedLimit = ( int ) $limit [ 'limit' ];
} else {
$combinedLimit = false ;
}
if ( ! empty ( $combinedLimit )) $text .= " LIMIT " . $combinedLimit ;
} else {
$text .= " LIMIT " . $sqlQuery -> limit ;
}
}
return $text ;
}
/**
* SQLite3 complains about ambiguous column names if the ORDER BY expression doesn ' t contain the table name
* and the expression matches more than one expression in the SELECT expression .
* assuming that there is no amibguity we just use the first table name
2010-01-17 11:48:22 +01:00
*
* used by SQLite3Database :: sqlQueryToString ()
*
* @ param array $select SELECT expressions as of SQLquery
* @ param string $order ORDER BY expressions to be checked and augmented as of SQLquery
* @ return string fully specified ORDER BY expression
2010-01-14 21:42:43 +01:00
*/
protected function orderMoreSpecifically ( $select , $order ) {
$altered = false ;
// split expression into order terms
$terms = explode ( ',' , $order );
2010-01-17 11:48:22 +01:00
2010-01-14 21:42:43 +01:00
foreach ( $terms as $i => $term ) {
$term = trim ( $term );
2010-01-17 11:48:22 +01:00
2010-01-14 21:42:43 +01:00
// check if table is unspecified
if ( ! preg_match ( '/\./' , $term )) {
$direction = '' ;
if ( preg_match ( '/( ASC)$|( DESC)$/i' , $term )) list ( $term , $direction ) = explode ( ' ' , $term );
// find a match in the SELECT array and replace
foreach ( $select as $s ) {
2010-02-04 06:13:11 +01:00
if ( preg_match ( '/"[a-z0-9_]+"\.[\'"]?' . $term . '[\'"]?/i' , trim ( $s ))) {
2010-01-14 21:42:43 +01:00
$terms [ $i ] = $s . ' ' . $direction ;
$altered = true ;
break ;
}
}
}
}
return implode ( ',' , $terms );
}
2010-01-28 06:29:08 +01:00
2010-02-01 06:15:10 +01:00
/**
2010-02-03 06:02:43 +01:00
* Helper functions to prepare DBMS specific SQL fragments for basic datetime operations
2010-02-01 06:15:10 +01:00
*/
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* used for querying a datetime in a certain format
* @ param string $date to be formated , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $format to be used , supported specifiers :
* % Y = Year ( four digits )
* % m = Month ( 01. . 12 )
* % d = Day ( 01. . 31 )
* % H = Hour ( 00. . 23 )
* % i = Minutes ( 00. . 59 )
* % s = Seconds ( 00. . 59 )
* % U = unix timestamp , can only be used on it ' s own
* @ return string SQL datetime expression to query for a formatted datetime
*/
function formattedDatetimeClause ( $date , $format ) {
preg_match_all ( '/%(.)/' , $format , $matches );
foreach ( $matches [ 1 ] as $match ) if ( array_search ( $match , array ( 'Y' , 'm' , 'd' , 'H' , 'i' , 's' , 'U' )) === false ) user_error ( 'formattedDatetimeClause(): unsupported format character %' . $match , E_USER_WARNING );
$translate = array (
'/%i/' => '%M' ,
'/%s/' => '%S' ,
'/%U/' => '%s' ,
);
$format = preg_replace ( array_keys ( $translate ), array_values ( $translate ), $format );
$modifiers = array ();
if ( $format == '%s' && $date != 'now' ) $modifiers [] = 'utc' ;
if ( $format != '%s' && $date == 'now' ) $modifiers [] = 'localtime' ;
if ( preg_match ( '/^now$/i' , $date )) {
$date = " 'now' " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
$date = " ' $date ' " ;
}
$modifier = empty ( $modifiers ) ? '' : " , ' " . implode ( " ', ' " , $modifiers ) . " ' " ;
return " strftime(' $format ', $date $modifier ) " ;
}
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* used for querying a datetime addition
* @ param string $date , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $interval to be added , use the format [ sign ][ integer ] [ qualifier ], e . g . - 1 Day , + 15 minutes , + 1 YEAR
* supported qualifiers :
* - years
* - months
* - days
* - hours
* - minutes
* - seconds
* This includes the singular forms as well
* @ return string SQL datetime expression to query for a datetime ( YYYY - MM - DD hh : mm : ss ) which is the result of the addition
*/
function datetimeIntervalClause ( $date , $interval ) {
$modifiers = array ();
if ( $date == 'now' ) $modifiers [] = 'localtime' ;
if ( preg_match ( '/^now$/i' , $date )) {
$date = " 'now' " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date )) {
$date = " ' $date ' " ;
}
$modifier = empty ( $modifiers ) ? '' : " , ' " . implode ( " ', ' " , $modifiers ) . " ' " ;
return " datetime( $date $modifier , ' $interval ') " ;
}
/**
* Function to return an SQL datetime expression that can be used with SQLite3
* used for querying a datetime substraction
* @ param string $date1 , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ param string $date2 to be substracted of $date1 , can be either 'now' , literal datetime like '1973-10-14 10:30:00' or field name , e . g . '"SiteTree"."Created"'
* @ return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which is the result of the substraction
*/
function datetimeDifferenceClause ( $date1 , $date2 ) {
$modifiers1 = array ();
$modifiers2 = array ();
if ( $date1 == 'now' ) $modifiers1 [] = 'localtime' ;
if ( $date2 == 'now' ) $modifiers2 [] = 'localtime' ;
if ( preg_match ( '/^now$/i' , $date1 )) {
$date1 = " 'now' " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date1 )) {
$date1 = " ' $date1 ' " ;
}
if ( preg_match ( '/^now$/i' , $date2 )) {
$date2 = " 'now' " ;
} else if ( preg_match ( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i' , $date2 )) {
$date2 = " ' $date2 ' " ;
}
$modifier1 = empty ( $modifiers1 ) ? '' : " , ' " . implode ( " ', ' " , $modifiers1 ) . " ' " ;
$modifier2 = empty ( $modifiers2 ) ? '' : " , ' " . implode ( " ', ' " , $modifiers2 ) . " ' " ;
return " strftime('%s', $date1 $modifier1 ) - strftime('%s', $date2 $modifier2 ) " ;
}
2010-01-14 21:42:43 +01:00
}
/**
* A result - set from a SQLite3 database .
* @ package SQLite3Database
*/
class SQLite3Query extends SS_Query {
2010-01-17 11:48:22 +01:00
/**
* The SQLite3Database object that created this result set .
* @ var SQLite3Database
*/
2010-01-21 12:32:21 +01:00
protected $database ;
2010-01-17 11:48:22 +01:00
/**
* The internal sqlite3 handle that points to the result set .
* @ var resource
*/
2010-01-21 12:32:21 +01:00
protected $handle ;
2010-01-17 11:48:22 +01:00
/**
* 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 sqlite3 handle that is points to the resultset .
*/
2010-01-24 07:57:42 +01:00
public function __construct ( SQLite3Database $database , $handle ) {
2010-01-17 11:48:22 +01:00
$this -> database = $database ;
$this -> handle = $handle ;
}
public function __destroy () {
$this -> handle -> finalize ();
}
public function seek ( $row ) {
$this -> handle -> reset ();
$i = 0 ;
while ( $i < $row && $row = SQLite3Result :: fetchArray ()) $i ++ ;
return ( bool ) $row ;
}
/**
* @ todo This looks terrible but there is no SQLite3 :: get_num_rows () implementation
*/
public function numRecords () {
$c = 0 ;
while ( $this -> handle -> fetchArray ()) $c ++ ;
$this -> handle -> reset ();
return $c ;
}
public function nextRecord () {
// Coalesce rather than replace common fields.
if ( @ $data = $this -> handle -> fetchArray ( SQLITE3_NUM )) {
foreach ( $data as $columnIdx => $value ) {
if ( preg_match ( '/^"([a-z0-9_]+)"\."([a-z0-9_]+)"$/i' , $this -> handle -> columnName ( $columnIdx ), $matches )) $columnName = $matches [ 2 ];
else if ( preg_match ( '/^"([a-z0-9_]+)"$/i' , $this -> handle -> columnName ( $columnIdx ), $matches )) $columnName = $matches [ 1 ];
else $columnName = trim ( $this -> handle -> columnName ( $columnIdx ), " \" ' \t " );
// $value || !$ouput[$columnName] means that the *last* occurring value is shown
// !$ouput[$columnName] means that the *first* occurring value is shown
if ( isset ( $value ) || ! isset ( $output [ $columnName ])) {
$output [ $columnName ] = is_null ( $value ) ? null : ( string ) $value ;
}
}
return $output ;
} else {
return false ;
}
}
2010-01-14 21:42:43 +01:00
}