<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>
'mssql_n_adodb',
'mssql_adodb',
'odbc_mssql_adodb',
- 'oci8po_adodb'
+ 'oci8po_adodb',
+ 'sqlite3_pdo',
);
$databases = array ();
foreach($supported as $driver) {
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 {
} 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";
}
databasesettingssub_oci8po
databasesettingssub_odbc_mssql
databasesettingssub_postgres7
+databasesettingssub_sqlite3_pdo
databasesettingswillbecreated
databasetype
databaseuser
siteshortname
sitesummary
skipdbencodingtest
+sqlite3_pdo
+sqliteextensionisnotpresentinphp
status
this_direction
tableprefix
<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 :';
$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';
--- /dev/null
+<?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;
+ }
+}
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) {
}
}
- ///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];
- }
+ /**
+ * 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');
+ }
- if (!$this->columns[$table] = array_change_key_case($this->db->MetaColumns($this->prefix.$table), CASE_LOWER)) {
- $this->columns[$table] = array();
- }
+ /**
+ * 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');
+ }
- return $this->columns[$table];
+ /**
+ * 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;
}
- protected function report_error($sql, $params, $obj) {
+ /**
+ * 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) {
+ $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);
+ }
+
+ /**
+ * 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 {
- if ($select) {
- $select = "WHERE $select";
- }
- $sql = "DELETE FROM {$this->prefix}$table $select";
+ $this->lastError = null;
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
- $sth = $this->dbh->prepare($sql);
- return $sth->execute($params);
+ 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;
}
}
+ /**
+ * 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);
- $sth = $this->dbh->prepare($sql);
- 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;
+ }
- protected function create_recordset($sth) {
- return new pdo_moodle_recordset($sth);
+ /**
+ * 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) {
- try {
- list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
- error('TODO');
+ 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;
+ }
- } catch (PDOException $ex) {
- $this->report_error($sql, $params, $ex);
+ /**
+ * 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']);
- public function get_fieldset_sql($sql, array $params=null) {
- try {
- list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
- error('TODO');
+ if (empty($params)) {
+ return false;
+ }
- } catch (PDOException $ex) {
- $this->report_error($sql, $params, $ex);
+ $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() {
}
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;
+ try {
+ $this->pdb->commit();
+ return true;
+ } catch(PDOException $ex) {
+ return false;
+ }
}
+
public function rollback_sql() {
- $this->pdb->rollBack();
- return true;
+ try {
+ $this->pdb->rollBack();
+ return true;
+ } catch(PDOException $ex) {
+ return false;
+ }
}
-
}
*/
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() {
--- /dev/null
+<?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);
+ }
+}
\ No newline at end of file