MDL-15071 new sqlite pdo driver by Andrei Bautu (with minor changes)

This commit is contained in:
skodak
2008-06-22 21:35:07 +00:00
parent 17deeebc8f
commit 55946ffda6
8 changed files with 1155 additions and 72 deletions
+1
View File
@@ -214,6 +214,7 @@
<VENDOR name="odbc_mssql" version="9.0" />
<VENDOR name="mssql_n" version="9.0" />
<VENDOR name="oracle" version="9.0" />
<VENDOR name="sqlite" version="2.0" />
</DATABASE>
<PHP version="5.2.0" level="required">
</PHP>
+10 -2
View File
@@ -168,7 +168,8 @@ $supported = array (
'mssql_n_adodb',
'mssql_adodb',
'odbc_mssql_adodb',
'oci8po_adodb'
'oci8po_adodb',
'sqlite3_pdo',
);
$databases = array ();
foreach($supported as $driver) {
@@ -320,7 +321,7 @@ if ($INSTALL['stage'] == DATABASE) {
error_reporting(0); // Hide errors
if (! $dbconnected = $DB->connect($INSTALL['dbhost'], $INSTALL['dbuser'], $INSTALL['dbpass'], $INSTALL['dbname'], false, $INSTALL['prefix'])) {
if (!$DB->create_database($INSTALL['dbhost'], $INSTALL['dbuser'], $INSTALL['dbpass'])) {
if (!$DB->create_database($INSTALL['dbhost'], $INSTALL['dbuser'], $INSTALL['dbpass'], $INSTALL['dbname'])) {
$errormsg = get_string('dbcreationerror', 'install');
$nextstage = DATABASE;
} else {
@@ -472,6 +473,13 @@ if ($nextstage == SAVE) {
} else {
$str .= '$CFG->'.$key.' = false;'."\r\n";
}
} else if (is_array($value)) {
if (empty($value)) {
$value = 'array()';
} else {
$value = 'unserialize(\'' . addsingleslashes(serialize($value)) . '\')';
}
$str .= '$CFG->'.$key.' = '. $value . ";\r\n";
} else {
$str .= '$CFG->'.$key.' = \''.addsingleslashes($value)."';\r\n";
}
+3
View File
@@ -67,6 +67,7 @@ databasesettingssub_mysqli
databasesettingssub_oci8po
databasesettingssub_odbc_mssql
databasesettingssub_postgres7
databasesettingssub_sqlite3_pdo
databasesettingswillbecreated
databasetype
databaseuser
@@ -207,6 +208,8 @@ sitenewsitems
siteshortname
sitesummary
skipdbencodingtest
sqlite3_pdo
sqliteextensionisnotpresentinphp
status
this_direction
tableprefix
+9
View File
@@ -129,6 +129,13 @@ $string['databasesettingssub_postgres7'] = '<b>Type:</b> PostgreSQL<br />
<b>User:</b> your database username<br />
<b>Password:</b> your database password<br />
<b>Tables Prefix:</b> prefix to use for all table names (mandatory)';
$string['databasesettingssub_sqlite3_pdo'] = '<b>Type:</b> SQLite 3 (PDO) <b><strong class=\"errormsg\">Experimental! (not for use in production)</strong></b><br />
<b>Host:</b> path to the directory where the database file will be saved (use a full path); use localhost or leave blank to use Moodle data directory<br />
<b>Name:</b> database name, eg moodle (optional)<br />
<b>User:</b> your database username (optional)<br />
<b>Password:</b> your database password (optional)<br />
<b>Tables Prefix:</b> optional prefix to use for all table names<br />
The name of the database file will be determined by the username, database name and password you entered above.';
$string['databasesettingswillbecreated'] = '<b>Note:</b> The installer will try to create the database automatically if not exists.';
$string['databasetype']='Database type :';
$string['databaseuser']='Database user :';
@@ -325,6 +332,8 @@ $string['sitenewsitems'] = 'News Items :';
$string['siteshortname'] = 'Site short name :';
$string['sitesummary'] ='Site summary :';
$string['skipdbencodingtest'] = 'Skip DB Encoding Test';
$string['sqlite3_pdo'] = 'SQLite 3 (PDO) <b><strong class=\"errormsg\">Experimental! (not for use in production)</strong></b>';
$string['sqliteextensionisnotpresentinphp'] = 'PHP has not been properly configured with the SQLite extension. Please check your php.ini file or recompile PHP.';
$string['tableprefix']='Table prefix :';
$string['upgradingactivitymodule']= 'Upgrading Activity Module';
$string['upgradingbackupdb'] = 'Upgrading Backup Database';
+306
View File
@@ -0,0 +1,306 @@
<?php //$Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
require_once($CFG->libdir.'/ddl/sql_generator.php');
/// This class generate SQL code to be used against SQLite
/// It extends XMLDBgenerator so everything can be
/// overriden as needed to generate correct SQL.
class sqlite_sql_generator extends sql_generator {
/// Only set values that are different from the defaults present in XMLDBgenerator
public $drop_default_value_required = true; //To specify if the generator must use some DEFAULT clause to drop defaults
public $drop_default_value = NULL; //The DEFAULT clause required to drop defaults
public $drop_primary_key = 'ALTER TABLE TABLENAME DROP PRIMARY KEY'; // Template to drop PKs
// with automatic replace for TABLENAME and KEYNAME
public $drop_unique_key = 'ALTER TABLE TABLENAME DROP KEY KEYNAME'; // Template to drop UKs
// with automatic replace for TABLENAME and KEYNAME
public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP FOREIGN KEY KEYNAME'; // Template to drop FKs
// with automatic replace for TABLENAME and KEYNAME
public $default_for_char = ''; // To define the default to set for NOT NULLs CHARs without default (null=do nothing)
public $sequence_only = true; //To avoid to output the rest of the field specs, leaving only the name and the sequence_name publiciable
public $sequence_extra_code = false; //Does the generator need to add extra code to generate the sequence fields
public $sequence_name = 'INTEGER PRIMARY KEY AUTOINCREMENT'; //Particular name for inline sequences in this generator
public $unsigned_allowed = false; // To define in the generator must handle unsigned information
public $enum_extra_code = false; //Does the generator need to add extra code to generate code for the enums in the table
public $add_after_clause = true; // Does the generator need to add the after clause for fields
public $concat_character = null; //Characters to be used as concatenation operator. If not defined
//MySQL CONCAT function will be use
public $alter_column_sql = 'ALTER TABLE TABLENAME MODIFY COLUMN COLUMNSPECS'; //The SQL template to alter columns
public $drop_index_sql = 'ALTER TABLE TABLENAME DROP INDEX INDEXNAME'; //SQL sentence to drop one index
//TABLENAME, INDEXNAME are dinamically replaced
public $rename_index_sql = null; //SQL sentence to rename one index (MySQL doesn't support this!)
//TABLENAME, OLDINDEXNAME, NEWINDEXNAME are dinamically replaced
public $rename_key_sql = null; //SQL sentence to rename one key (MySQL doesn't support this!)
//TABLENAME, OLDKEYNAME, NEWKEYNAME are dinamically replaced
/**
* Creates one new XMLDBmysql
*/
public function __construct($mdb) {
parent::__construct($mdb);
}
/**
* Given one correct xmldb_key, returns its specs
*/
public function getKeySQL($xmldb_table, $xmldb_key) {
$key = '';
switch ($xmldb_key->getType()) {
case XMLDB_KEY_PRIMARY:
if ($this->primary_keys && count($xmldb_key->getFields())>1) {
if ($this->primary_key_name !== null) {
$key = $this->getEncQuoted($this->primary_key_name);
} else {
$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');
}
$key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
}
break;
case XMLDB_KEY_UNIQUE:
if ($this->unique_keys) {
$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');
$key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
}
break;
case XMLDB_KEY_FOREIGN:
case XMLDB_KEY_FOREIGN_UNIQUE:
if ($this->foreign_keys) {
$key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');
$key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
$key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());
$key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';
}
break;
}
return $key;
}
/**
* Given one XMLDB Type, lenght and decimals, returns the DB proper SQL type
*/
public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) {
switch ($xmldb_type) {
case XMLDB_TYPE_INTEGER: // From http://www.sqlite.org/datatype3.html
if (empty($xmldb_length)) {
$xmldb_length = 10;
}
$dbtype = 'INTEGER(' . $xmldb_length . ')';
break;
case XMLDB_TYPE_NUMBER:
$dbtype = $this->number_type;
if (!empty($xmldb_length)) {
$dbtype .= '(' . $xmldb_length;
if (!empty($xmldb_decimals)) {
$dbtype .= ',' . $xmldb_decimals;
}
$dbtype .= ')';
}
break;
case XMLDB_TYPE_FLOAT:
$dbtype = 'REAL';
if (!empty($xmldb_length)) {
$dbtype .= '(' . $xmldb_length;
if (!empty($xmldb_decimals)) {
$dbtype .= ',' . $xmldb_decimals;
}
$dbtype .= ')';
}
break;
case XMLDB_TYPE_CHAR:
$dbtype = 'VARCHAR';
if (empty($xmldb_length)) {
$xmldb_length='255';
}
$dbtype .= '(' . $xmldb_length . ')';
break;
case XMLDB_TYPE_BINARY:
$dbtype = 'BLOB';
break;
case XMLDB_TYPE_DATETIME:
$dbtype = 'DATETIME';
default:
case XMLDB_TYPE_TEXT:
$dbtype = 'TEXT';
break;
}
return $dbtype;
}
/**
* Given one xmldb_table and one xmldb_field, return the SQL statements needded to create its enum
* (usually invoked from getModifyEnumSQL()
*/
public function getCreateEnumSQL($xmldb_table, $xmldb_field) {
/// For MySQL, just alter the field
return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
}
/**
* Given one xmldb_table and one xmldb_field, return the SQL statements needded to drop its enum
* (usually invoked from getModifyEnumSQL()
*/
public function getDropEnumSQL($xmldb_table, $xmldb_field) {
/// For MySQL, just alter the field
return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
}
/**
* Given one xmldb_table and one xmldb_field, return the SQL statements needded to create its default
* (usually invoked from getModifyDefaultSQL()
*/
public function getCreateDefaultSQL($xmldb_table, $xmldb_field) {
/// Just a wrapper over the getAlterFieldSQL() function for MySQL that
/// is capable of handling defaults
return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
}
/**
* Given one correct xmldb_field and the new name, returns the SQL statements
* to rename it (inside one array)
* SQLite is pretty diferent from the standard to justify this oveloading
*/
public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
// TODO: Add code to rename column
/// Need a clone of xmldb_field to perform the change leaving original unmodified
$xmldb_field_clone = clone($xmldb_field);
/// Change the name of the field to perform the change
$xmldb_field_clone->setName($xmldb_field_clone->getName() . ' ' . $newname);
$fieldsql = $this->getFieldSQL($xmldb_field_clone);
$sql = 'ALTER TABLE ' . $this->getTableName($xmldb_table) . ' CHANGE ' . $fieldsql;
return array($sql);
}
/**
* Given one xmldb_table and one xmldb_field, return the SQL statements needded to drop its default
* (usually invoked from getModifyDefaultSQL()
*/
public function getDropDefaultSQL($xmldb_table, $xmldb_field) {
/// Just a wrapper over the getAlterFieldSQL() function for MySQL that
/// is capable of handling defaults
return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
}
/**
* Given one XMLDB Field, return its enum SQL
*/
public function getEnumSQL($xmldb_field) {
return 'enum';
}
/**
* Returns the code (in array) needed to add one comment to the table
*/
function getCommentSQL ($xmldb_table) {
return array();
}
/**
* Given one xmldb_table returns one array with all the check constrainsts
* in the table (fetched from DB)
* Optionally the function allows one xmldb_field to be specified in
* order to return only the check constraints belonging to one field.
* Each element contains the name of the constraint and its description
* If no check constraints are found, returns an empty array
* MySQL doesn't have check constraints in this implementation, but
* we return them based on the enum fields in the table
*/
public function getCheckConstraintsFromDB($xmldb_table, $xmldb_field = null) {
// TODO: add code for constraints
return array();
}
/**
* Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg)
* return if such name is currently in use (true) or no (false)
* (invoked from getNameForObject()
*/
public function isNameInUse($object_name, $type, $table_name) {
// TODO: add introspection code
return false; //No name in use found
}
/**
* Returns an array of reserved words (lowercase) for this DB
*/
public static function getReservedWords() {
/// From http://www.sqlite.org/lang_keywords.html
$reserved_words = array (
'ADD', 'ALL', 'ALTER', 'AND', 'AS', 'AUTOINCREMENT',
'BETWEEN', 'BY',
'CASE', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', 'CONSTRAINT', 'CREATE', 'CROSS',
'DEFAULT', 'DEFERRABLE', 'DELETE', 'DISTINCT', 'DROP',
'ELSE', 'ESCAPE', 'EXCEPT', 'EXISTS',
'FOREIGN', 'FROM', 'FULL',
'GROUP',
'HAVING',
'IN', 'INDEX', 'INNER', 'INSERT', 'INTERSECT', 'INTO', 'IS', 'ISNULL',
'JOIN',
'LEFT', 'LIMIT',
'NATURAL', 'NOT', 'NOTNULL', 'NULL',
'ON', 'OR', 'ORDER', 'OUTER',
'PRIMARY',
'REFERENCES', 'REGEXP', 'RIGHT', 'ROLLBACK',
'SELECT', 'SET',
'TABLE', 'THEN', 'TO', 'TRANSACTION',
'UNION', 'UNIQUE', 'UPDATE', 'USING',
'VALUES',
'WHEN', 'WHERE',
);
return $reserved_words;
}
public function addslashes($s) {
// do not use php addslashes() because it depends on PHP quote settings!
$s = str_replace("'", "''", $s);
return $s;
}
}
+488 -64
View File
@@ -10,15 +10,44 @@ require_once($CFG->libdir.'/dml/pdo_moodle_recordset.php');
abstract class pdo_moodle_database extends moodle_database {
protected $pdb;
protected $debug = false;
protected $lastError = null;
//TODO: This looks incorrect now IMO. Construct should have only external and connect get all the rest of params
public function __construct($dbhost, $dbuser, $dbpass, $dbname, $dbpersist, $prefix, array $dboptions=null, $external=false) {
parent::__construct($dbhost, $dbuser, $dbpass, $dbname, $dbpersist, $prefix, $dboptions, $external);
/**
* Contructor - instantiates the database, specifying if it's external (connect to other systems) or no (Moodle DB)
* note this has effect to decide if prefix checks must be performed or no
* @param bool true means external database used
*/
public function __construct($external=false) {
parent::__construct($external);
}
public function connect() {
/**
* Connect to db
* Must be called before other methods.
* @param string $dbhost
* @param string $dbuser
* @param string $dbpass
* @param string $dbname
* @param bool $dbpersist
* @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
* @param array $dboptions driver specific options
* @return bool success
*/
public function connect($dbhost, $dbuser, $dbpass, $dbname, $dbpersist, $prefix, array $dboptions=null) {
$this->dbhost = $dbhost;
$this->dbuser = $dbuser;
$this->dbpass = $dbpass;
$this->dbname = $dbname;
$this->dbpersist = $dbpersist;
$this->prefix = $prefix;
$this->dboptions = (array)$dboptions;
try {
$this->pdb = new PDO('mysql:host='.$this->dbhost.';dbname='.$this->dbname, $this->dbuser, $this->pass, array(PDO::ATTR_PERSISTENT => $this->dbpresist));
$this->pdb = new PDO($this->get_dsn(), $this->dbuser, $this->dbpass, $this->get_pdooptions());
// generic PDO settings to match adodb's default; subclasses can change this in configure_dbconnection
$this->pdb->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$this->pdb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->configure_dbconnection();
return true;
} catch (PDOException $ex) {
@@ -26,98 +55,481 @@ abstract class pdo_moodle_database extends moodle_database {
}
}
///TODO: not needed preconfigure_dbconnection() stuff for PDO drivers?
/**
* Returns the driver-dependent DSN for PDO based on members stored by connect.
* Must be called after connect (or after $dbname, $dbhost, etc. members have been set).
* @return string driver-dependent DSN
*/
protected function get_dsn() {
return 'mysql:host='.$this->dbhost.';dbname='.$this->dbname;
}
/**
* Returns the driver-dependent connection attributes for PDO based on members stored by connect.
* Must be called after $dbname, $dbhost, etc. members have been set.
* @return array A key=>value array of PDO driver-specific connection options
*/
protected function get_pdooptions() {
return array(PDO::ATTR_PERSISTENT => $this->dbpresist);
}
protected function configure_dbconnection() {
///TODO: not needed preconfigure_dbconnection() stuff for PDO drivers?
}
public function get_columns($table, $usecache=true) {
if ($usecache and isset($this->columns[$table])) {
return $this->columns[$table];
}
if (!$this->columns[$table] = array_change_key_case($this->db->MetaColumns($this->prefix.$table), CASE_LOWER)) {
$this->columns[$table] = array();
}
return $this->columns[$table];
/**
* Returns localised database type name
* Note: can be used before connect()
* @return string
*/
public function get_name() {
return get_string($this->get_dbtype() . '_pdo', 'install');
}
protected function report_error($sql, $params, $obj) {
/**
* Returns localised database description
* Note: can be used before connect()
* @return string
*/
public function get_configuration_hints() {
return get_string('databasesettingssub_' . $this->get_dbtype() . '_pdo', 'install');
}
/**
* Returns db related part of config.php
* Note: can be used before connect()
* @return string
*/
public function export_dbconfig() {
$cfg = new stdClass();
$cfg->dbtype = $this->get_dbtype();
$cfg->dblibrary = 'pdo';
$cfg->dbhost = $this->dbhost;
$cfg->dbname = $this->dbname;
$cfg->dbuser = $this->dbuser;
$cfg->dbpass = $this->dbpass;
$cfg->prefix = $this->prefix;
$cfg->dboptions = $this->dboptions;
return $cfg;
}
/**
* Returns database server info array
* @return array
*/
public function get_server_info() {
$result = array();
try {
$result['description'] = $this->pdb->getAttribute(PDO::ATTR_SERVER_INFO);
} catch(PDOException $ex) {}
try {
$result['version'] = $this->pdb->getAttribute(PDO::ATTR_SERVER_VERSION);
} catch(PDOException $ex) {}
return $result;
}
/**
* Returns supported query parameter types
* @return bitmask
*/
protected function allowed_param_types() {
return SQL_PARAMS_QM | SQL_PARAMS_NAMED;
}
/**
* Returns last error reported by database engine.
*/
public function get_last_error() {
return $this->lastError;
}
protected function report_error($sql, $params, $e) {
debugging($e->getMessage() .'<br /><br />'. s($sql));
}
/**
* Enable/disable very detailed debugging
* TODO: do we need levels?
* @param bool $state
*/
public function set_debug($state) {
//TODO
$this->debug = $state;
}
/**
* Returns debug status
* @return bool $state
*/
public function get_debug() {
return $this->debug;
}
/**
* Enable/disable detailed sql logging
* TODO: do we need levels?
* @param bool $state
*/
public function set_logging($state) {
//TODO
}
public function execute($sql, array $params=null) {
try {
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
$sth = $this->dbh->prepare($sql);
return $sth->execute($params);
} catch (PDOException $ex) {
$this->report_error($sql, $params, $ex);
return false;
/**
* Function to print/save/ignore debuging messages related to SQL queries.
*/
protected function debug_query($sql, $params = null) {
echo '<hr /> (', $this->get_dbtype(), '): ', htmlentities($sql);
if($params) {
echo ' (parameters ';
print_r($params);
echo ')';
}
echo '<hr />';
}
/**
* Do NOT use in code, to be used by database_manager only!
* @param string $sql query
* @return bool success
*/
public function change_database_structure($sql) {
return $this->execute($sql);
}
public function delete_records_select($table, $select, array $params=null) {
try {
if ($select) {
$select = "WHERE $select";
}
$sql = "DELETE FROM {$this->prefix}$table $select";
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
$sth = $this->dbh->prepare($sql);
return $sth->execute($params);
} catch (PDOException $ex) {
$this->report_error($sql, $params, $ex);
return false;
}
}
public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
try {
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
$sth = $this->dbh->prepare($sql);
error('TODO');
return $this->create_recordset($sth);
} catch (PDOException $ex) {
$this->report_error($sql, $params, $ex);
return false;
$sql = "DELETE FROM {{$table}}";
if ($select) {
$sql .= " WHERE $select";
}
$this->writes++;
return $this->execute($sql, $params);
}
/**
* Factory method that creates a recordset for return by a query. The generic pdo_moodle_recordset
* class should fit most cases, but pdo_moodle_database subclasses can overide this method to return
* a subclass of pdo_moodle_recordset.
* @param object $sth instance of PDOStatement
* @return object instance of pdo_moodle_recordset
*/
protected function create_recordset($sth) {
return new pdo_moodle_recordset($sth);
}
public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
/**
* Execute general sql query. Should be used only when no other method suitable.
* Do NOT use this to make changes in db structure, use database_manager::execute_sql() instead!
* @param string $sql query
* @param array $params query parameters
* @return bool success
*/
public function execute($sql, array $params=null) {
try {
$this->lastError = null;
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
error('TODO');
if($this->debug) {
$this->debug_query($sql, $params);
}
$sth = $this->pdb->prepare($sql);
$sth->execute($params);
return true;
} catch (PDOException $ex) {
$this->lastError = $ex->getMessage();
$this->report_error($sql, $params, $ex);
return false;
}
}
public function get_fieldset_sql($sql, array $params=null) {
/**
* Get a number of records as an moodle_recordset. $sql must be a complete SQL query.
* Since this method is a little less readable, use of it should be restricted to
* code where it's possible there might be large datasets being returned. For known
* small datasets use get_records_sql - it leads to simpler code.
*
* The return type is as for @see function get_recordset.
*
* @param string $sql the SQL select query to execute.
* @param array $params array of sql parameters
* @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
* @return mixed an moodle_recorset object, or false if an error occured.
*/
public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
try {
$this->lastError = null;
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
error('TODO');
$sql = $this->get_limit_clauses($sql, $limitfrom, $limitnum);
if($this->debug) {
$this->debug_query($sql, $params);
}
$this->reads++;
$sth = $this->pdb->prepare($sql);
$sth->execute($params);
return $this->create_recordset($sth);
} catch (PDOException $ex) {
$this->lastError = $ex->getMessage();
$this->report_error($sql, $params, $ex);
return false;
}
}
/**
* Returns the sql statement with clauses to append used to limit a recordset range.
* @param string $sql the SQL statement to limit.
* @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
* @return string the SQL statement with limiting clauses
*/
protected function get_limit_clauses($sql, $limitfrom=0, $limitnum=0) {
return $sql;
}
/**
* Selects rows and return values of first column as array.
*
* @param string $sql The SQL query
* @param array $params array of sql parameters
* @return mixed array of values or false if an error occured
*/
public function get_fieldset_sql($sql, array $params=null) {
if(!$rs = $this->get_recordset_sql($sql, $params)) {
return false;
}
$result = array();
foreach($rs as $value) {
$result[] = reset($value);
}
$rs->close();
return $result;
}
/**
* Get a number of records as an array of objects.
*
* Return value as for @see function get_records.
*
* @param string $sql the SQL select query to execute. The first column of this SELECT statement
* must be a unique value (usually the 'id' field), as it will be used as the key of the
* returned array.
* @param array $params array of sql parameters
* @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
* @return mixed an array of objects, or empty array if no records were found, or false if an error occured.
*/
public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
if(!$rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum)) {
return false;
}
$objects = array();
$debugging = debugging('', DEBUG_DEVELOPER);
foreach($rs as $value) {
$key = reset($value);
if ($debugging && array_key_exists($key, $objects)) {
debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$key' found in column first column of '$sql'.", DEBUG_DEVELOPER);
}
$objects[$key] = (object)$value;
}
$rs->close();
return $objects;
}
/**
* Insert new record into database, as fast as possible, no safety checks, lobs not supported.
* @param string $table name
* @param mixed $params data record as object or array
* @param bool $returnit return it of inserted record
* @param bool $bulk true means repeated inserts expected
* @return mixed success or new id
*/
public function insert_record_raw($table, $params, $returnid=true, $bulk=false) {
if (!is_array($params)) {
$params = (array)$params;
}
unset($params['id']);
if (empty($params)) {
return false;
}
$this->writes++;
$fields = implode(',', array_keys($params));
$qms = array_fill(0, count($params), '?');
$qms = implode(',', $qms);
$sql = "INSERT INTO {{$table}} ($fields) VALUES($qms)";
if (!$this->execute($sql, $params)) {
return false;
}
if (!$returnid) {
return true;
}
if ($id = $this->pdb->lastInsertId()) {
return (int)$id;
}
return false;
}
/**
* Insert a record into a table and return the "id" field if required,
* Some conversions and safety checks are carried out. Lobs are supported.
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @param bool $bulk true means repeated inserts expected
* @return mixed success or new ID
*/
public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
if (!is_object($dataobject)) {
$dataobject = (object)$dataobject;
}
$columns = $this->get_columns($table);
unset($dataobject->id);
$cleaned = array();
foreach ($dataobject as $field=>$value) {
if (!isset($columns[$field])) {
continue;
}
$column = $columns[$field];
if (is_bool($value)) {
$value = (int)$value; // prevent "false" problems
}
if (!empty($column->enums)) {
if (!in_array((string)$value, $column->enums)) {
debugging('Enum value '.s($value).' not allowed in field '.$field.' table '.$table.'.');
return false;
}
}
$cleaned[$field] = $value;
}
if (empty($cleaned)) {
return false;
}
return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
}
/**
* Update record in database, as fast as possible, no safety checks, lobs not supported.
* @param string $table name
* @param mixed $params data record as object or array
* @param bool true means repeated updates expected
* @return bool success
*/
public function update_record_raw($table, $params, $bulk=false) {
if (!is_array($params)) {
$params = (array)$params;
}
if (!isset($params['id'])) {
return false;
}
$id = $params['id'];
unset($params['id']);
if (empty($params)) {
return false;
}
$sets = array();
foreach ($params as $field=>$value) {
$sets[] = "$field = ?";
}
$params[] = $id; // last ? in WHERE condition
$sets = implode(',', $sets);
$sql = "UPDATE {{$table}} SET $sets WHERE id=?";
$this->writes++;
return $this->execute($sql, $params);
}
/**
* Update a record in a table
*
* $dataobject is an object containing needed data
* Relies on $dataobject having a variable "id" to
* specify the record to update
*
* @param string $table The database table to be checked against.
* @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
* @param bool true means repeated updates expected
* @return bool success
*/
public function update_record($table, $dataobject, $bulk=false) {
if (!is_object($dataobject)) {
$dataobject = (object)$dataobject;
}
if (!isset($dataobject->id) ) {
return false;
}
$columns = $this->get_columns($table);
$cleaned = array();
foreach ($dataobject as $field=>$value) {
if (!isset($columns[$field])) {
continue;
}
if (is_bool($value)) {
$value = (int)$value; // prevent "false" problems
}
$cleaned[$field] = $value;
}
return $this->update_record_raw($table, $cleaned, $bulk);
}
/**
* Set a single field in every table row where the select statement evaluates to true.
*
* @param string $table The database table to be checked against.
* @param string $newfield the field to set.
* @param string $newvalue the value to set the field to.
* @param string $select A fragment of SQL to be used in a where clause in the SQL call.
* @param array $params array of sql parameters
* @return bool success
*/
public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
if ($select) {
$select = "WHERE $select";
}
if (is_null($params)) {
$params = array();
}
list($select, $params, $type) = $this->fix_sql_params($select, $params);
if (is_bool($newvalue)) {
$newvalue = (int)$newvalue; // prevent "false" problems
}
if (is_null($newvalue)) {
$newfield = "$newfield = NULL";
} else {
// make sure SET and WHERE clauses use the same type of parameters,
// because we don't support different types in the same query
switch($type) {
case SQL_PARAMS_NAMED:
$newfield = "$newfield = :newvalueforupdate";
$params['newvalueforupdate'] = $newvalue;
break;
case SQL_PARAMS_QM:
$newfield = "$newfield = ?";
array_unshift($params, $newvalue);
break;
default:
$this->lastError = 'Unknown parameter type in file ' . __FILE__ . ' on line ' . __LINE__ . '.';
error($this->lastError);
}
}
$sql = "UPDATE {{$table}} SET $newfield $select";
$this->writes++;
return $this->execute($sql, $params);
}
public function sql_substr() {
error('TODO');
@@ -132,16 +544,28 @@ abstract class pdo_moodle_database extends moodle_database {
}
public function begin_sql() {
$this->pdb->beginTransaction();
return true;
try {
$this->pdb->beginTransaction();
return true;
} catch(PDOException $ex) {
return false;
}
}
public function commit_sql() {
$this->pdb->commit();
return true;
}
public function rollback_sql() {
$this->pdb->rollBack();
return true;
try {
$this->pdb->commit();
return true;
} catch(PDOException $ex) {
return false;
}
}
public function rollback_sql() {
try {
$this->pdb->rollBack();
return true;
} catch(PDOException $ex) {
return false;
}
}
}
+19 -6
View File
@@ -8,30 +8,43 @@ require_once($CFG->libdir.'/dml/moodle_recordset.php');
*/
class pdo_moodle_recordset extends moodle_recordset {
private $sht;
private $sth;
protected $fields;
protected $rowCount = -1;
public function __construct($sth) {
$this->sth = $sth;
$this->sth->setFetchMode(PDO::FETCH_ASSOC);
}
public function current() {
error('TODO');
return (object)$this->fields;
}
public function key() {
error('TODO');
return $this->rowCount;
}
public function next() {
error('TODO');
$this->fields = $this->sth->fetch();
if ($this->fields) {
++$this->rowCount;
}
return $this->fields !== false;
}
public function rewind() {
error('TODO');
$this->fields = $this->sth->fetch();
if ($this->fields) {
$this->rowCount = 0;
}
}
public function valid() {
error('TODO');
if($this->rowCount < 0) {
$this->rewind();
}
return $this->fields !== FALSE;
}
public function close() {
+319
View File
@@ -0,0 +1,319 @@
<?php //$Id$
require_once($CFG->libdir.'/dml/pdo_moodle_database.php');
/**
* Experimental pdo database class
* @package dmlib
*/
class sqlite3_pdo_moodle_database extends pdo_moodle_database {
protected $database_file_extension = '.sq3.php';
/**
* Detects if all needed PHP stuff installed.
* Note: can be used before connect()
* @return mixed true if ok, string if something
*/
public function driver_installed() {
if (!extension_loaded('pdo_sqlite'))
return get_string('sqliteextensionisnotpresentinphp', 'install');
return true;
}
/**
* Returns database family type - describes SQL dialect
* Note: can be used before connect()
* @return string db family name (mysql, postgres, mssql, oracle, etc.)
*/
public function get_dbfamily() {
return 'sqlite';
}
/**
* Returns more specific database driver type
* Note: can be used before connect()
* @return string db type mysql, mysqli, postgres7
*/
protected function get_dbtype() {
return 'sqlite3';
}
protected function configure_dbconnection() {
// try to protect database file agains web access;
// this is required in case that the moodledata folder is web accessible and
// .htaccess is not in place; requires that the database file extension is php
$this->pdb->exec('CREATE TABLE IF NOT EXISTS "<?php die?>" (id int)');
$this->pdb->exec('PRAGMA synchronous=OFF');
$this->pdb->exec('PRAGMA short_column_names=1');
$this->pdb->exec('PRAGMA encoding="UTF-8"');
$this->pdb->exec('PRAGMA case_sensitive_like=0');
$this->pdb->exec('PRAGMA locking_mode=NORMAL');
}
/**
* Attempt to create the database
* @param string $dbhost
* @param string $dbuser
* @param string $dbpass
* @param string $dbname
*
* @return bool success
*/
public function create_database($dbhost, $dbuser, $dbpass, $dbname) {
$this->dbhost = $dbhost;
$this->dbuser = $dbuser;
$this->dbpass = $dbpass;
$this->dbname = $dbname;
$filepath = $this->get_dbfilepath();
$dirpath = dirname($filepath);
@mkdir($dirpath);
return touch($filepath);
}
/**
* Returns the driver-dependent DSN for PDO based on members stored by connect.
* Must be called after connect (or after $dbname, $dbhost, etc. members have been set).
* @return string driver-dependent DSN
*/
public function get_dsn() {
return 'sqlite:' . $this->get_dbfilepath();
}
/**
* Returns the file path for the database file, computed from dbname and/or dboptions.
* If dboptions['file'] is set, then it is used (use :memory: for in memory database);
* else if dboptions['path'] is set, then the file will be <dboptions path>/<dbname>.sq3.php;
* else if dbhost is set and not localhost, then the file will be <dbhost>/<dbname>.sq3.php;
* else the file will be <moodle data path>/<dbname>.sq3.php
* @return string file path to the SQLite database;
*/
public function get_dbfilepath() {
global $CFG;
if(!empty($this->dboptions['file'])) {
return $this->dboptions['file'];
}
if($this->dbhost && $this->dbhost != 'localhost') {
$path = $this->dbhost;
} else {
$path = $CFG->dataroot;
}
$path = ltrim($path, '\\/') . '/';
if(!empty($this->dbuser)) {
$path .= $this->dbuser . '_';
}
$path .= $this->dbname . '_' . md5($this->dbpass) . $this->database_file_extension;
return $path;
}
/**
* Return tables in database WITHOUT current prefix
* @return array of table names in lowercase and without prefix
*/
public function get_tables() {
$tables = array();
$sql = 'SELECT name FROM sqlite_master WHERE type="table"';
if($this->debug) {
$this->debug_query($sql);
}
$rstables = $this->pdb->query($sql);
foreach ($rstables as $table) {
$table = $table['name'];
$table = strtolower($table);
if (empty($this->prefix) || strpos($table, $this->prefix) === 0) {
$table = substr($table, strlen($this->prefix));
$tables[$table] = $table;
}
}
return $tables;
}
/**
* Return table indexes - everything lowercased
* @return array of arrays
*/
public function get_indexes($table) {
$indexes = array();
$sql = 'SELECT * FROM sqlite_master WHERE type="index" AND tbl_name="'. $this->prefix . $table . '"';
if($this->debug) {
$this->debug_query($sql);
}
$rsindexes = $this->pdb->query($sql);
foreach($rsindexes as $index) {
$index = strtolower($index['name']);
$sql = 'PRAGMA index_info("' . $index . '")';
if($this->debug) {
$this->debug_query($sql);
}
$rscolumns = $this->pdb->query($sql);
$columns = array();
foreach($rscolumns as $row) {
$columns[] = strtolower($row['name']);
}
$index = strtolower($index);
$indexes[$index]['columns'] = $columns;
}
return $indexes;
}
/**
* Returns datailed information about columns in table. This information is cached internally.
* @param string $table name
* @param bool $usecache
* @return array array of database_column_info objects indexed with column names
*/
public function get_columns($table, $usecache=true) {
if ($usecache and isset($this->columns[$table])) {
return $this->columns[$table];
}
// get table's CREATE TABLE command (we'll need it for autoincrement fields)
$sql = 'SELECT sql FROM sqlite_master WHERE type="table" AND tbl_name="'. $this->prefix . $table . '"';
if($this->debug) {
$this->debug_query($sql);
}
$createsql = $this->pdb->query($sql)->fetch();
if(!$createsql) {
return false;
}
$createsql = $createsql['sql'];
$columns = array();
$sql = 'PRAGMA table_info("'. $this->prefix . $table . '")';
if($this->debug) {
$this->debug_query($sql);
}
$rscolumns = $this->pdb->query($sql);
foreach ($rscolumns as $row) {
$columninfo = array(
'name' => strtolower($row['name']), // colum names must be lowercase
'not_null' =>(boolean)$row['notnull'],
'primary_key' => (boolean)$row['pk'],
'has_default' => !is_null($row['dflt_value']),
'default_value' => $row['dflt_value'],
);
$type = explode('(', $row['type']);
$columninfo['type'] = strtolower($type[0]);
if(count($type) > 1) {
$size = explode(',', trim($type[1], ')'));
$columninfo['max_length'] = $size[0];
if(count($size) > 1) {
$columninfo['scale'] = $size[1];
}
}
// SQLite does not have a fixed set of datatypes (ie. it accepts any string as
// datatype in the CREATE TABLE command. We try to guess which type is used here
switch(substr($columninfo['type'], 0, 3)) {
case 'int': // int integer
if($columninfo['primary_key'] && preg_match('/' . $columninfo['name'] . '\W*integer\W*primary\W*key\W*autoincrement/im', $createsql)) {
$columninfo['meta_type'] = 'R';
} else {
$columninfo['meta_type'] = 'I';
}
break;
case 'num': // number numeric
case 'rea': // real
case 'dou': // double
case 'flo': // float
$columninfo['meta_type'] = 'N';
break;
case 'var': // varchar
case 'cha': // char
$columninfo['meta_type'] = 'C';
break;
case 'tex': // text
case 'clo': // clob
$columninfo['meta_type'] = 'X';
break;
case 'blo': // blob
case 'non': // none
$columninfo['meta_type'] = 'B';
break;
case 'boo': // boolean
case 'bit': // bit
case 'log': // logical
$columninfo['meta_type'] = 'L';
break;
case 'tim': // timestamp
$columninfo['meta_type'] = 'T';
break;
case 'dat': // date datetime
$columninfo['meta_type'] = 'D';
break;
}
$columns[$columninfo['name']] = new database_column_info($columninfo);
}
$this->columns[$table] = $columns;
return $columns;
}
/**
* Returns the sql statement with clauses to append used to limit a recordset range.
* @param string $sql the SQL statement to limit.
* @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
* @return string the SQL statement with limiting clauses
*/
protected function get_limit_clauses($sql, $limitfrom=0, $limitnum=0) {
if($limitnum) {
$sql .= ' LIMIT ' . $limitnum;
if($limitfrom) {
$sql .= ' OFFSET ' . $limitfrom;
}
}
return $sql;
}
/**
* Delete the records from a table where all the given conditions met.
* If conditions not specified, table is truncated.
*
* @param string $table the table to delete from.
* @param array $conditions optional array $fieldname=>requestedvalue with AND in between
* @return returns success.
*/
public function delete_records($table, array $conditions=null) {
if (is_null($conditions)) {
return $this->execute("DELETE FROM {{$table}}");
}
list($select, $params) = $this->where_clause($conditions);
return $this->delete_records_select($table, $select, $params);
}
/**
* Returns the proper substr() function for each DB
*/
public function sql_substr() {
return 'substr';
}
/**
* Returns the proper SQL to do CONCAT between the elements passed
* Can take many parameters
*
* @param string $element
* @return string
*/
public function sql_concat() {
$elements = func_get_args();
return implode('||', $elements);
}
/**
* Returns the proper SQL to do CONCAT between the elements passed
* with a given separator
*
* @param string $separator
* @param array $elements
* @return string
*/
public function sql_concat_join($separator="' '", $elements=array()) {
// Intersperse $elements in the array.
// Add items to the array on the fly, walking it
// _backwards_ splicing the elements in. The loop definition
// should skip first and last positions.
for ($n=count($elements)-1; $n > 0 ; $n--) {
array_splice($elements, $n, 0, $separator);
}
return implode('||', $elements);
}
}