• database: The database to connect with
  • *
  • server: The server, eg, localhost
  • *
  • port: The port on which the server is listening (optional)
  • *
  • instance: Instance of the server, MS SQL only (optional)
  • *
  • username: The username to log on with
  • *
  • password: The password to log on with
  • *
  • database: The database to connect to
  • */ public function __construct($parameters) { $connect = PDODatabase::getConnect($parameters); $connectWithDB = $connect . ';dbname=' . $parameters['database']; try { // Try connect to the database, if it does not exist, create it $this->dbConn = new PDO($connectWithDB, $parameters['username'], $parameters['password']); } catch (PDOException $e) { if (!self::createDatabase($connect, $parameters['username'], $parameters['password'], $parameters['database'])) { $this->databaseError("Could not connect to the database, make sure the server is available and user credentials are correct"); } } parent::__construct(); } /** * Build the connection string from input. * @param array $parameters The connection details. * @return string $connect The connection string. **/ public function getConnect($parameters) { switch ($parameters['type']) { case "mysql": $port = '3306'; $type = 'mysql'; $instance = ''; break; case "postgresql": $port = '5432'; $type = 'pgsql'; $instance = ''; break; case "mssql": $port = '1433'; if (isset($parameters['instance']) && $parameters['instance'] != '') { $instance = '\\' . $parameters['instance']; } else { $instance = ''; } $type = 'mssql'; break; default: $this->databaseError("This database server is not available"); } if (isset($parameters['port']) && is_numeric($parameters['port'])) { $port = $parameters['port']; } $connect = $type . ':host=' . $parameters['server'] . $instance . ';port=' . $port; return $connect; } /** * Returns true if this database supports collations */ public function supportsCollations() { $collations = false; switch (PDO::ATTR_DRIVER_NAME) { case "pgsql": // Generally supported in PostgreSQL (supported versions) case "mssql": // Generally supported in MS SQL (supported versions) $collations = true; break; case "mysql": if ($this->getVersion() >= 4.1) { // Supported in MySQL since 4.1 $collations = true; } break; } return $collations; } /** * Get the database version. * @return float */ public function getVersion() { switch ($type) { case "mysql": case "postgresql": $query = "SELECT VERSION()"; break; case "mssql": $query = "SELECT @@VERSION"; break; } $stmt = $dbConn->prepare($query); $stmt->execute(); $dbVersion = $stmt->fetchColumn(); $version = ereg_replace("([A-Za-z-])", "", $dbVersion); return substr(trim($version), 0, 3); // Just get the major and minor version } /** * Query the database. * @var string $sql The query to be issued to the database. * @return result Return the result of the quers (if any). */ public function query($sql, $errorLevel = E_USER_ERROR) { if(isset($_REQUEST['previewwrite']) && in_array(strtolower(substr($sql,0,6)), array('insert','update'))) { echo "

    Will execute: $sql

    "; return; } //Debug::backtrace(); if(isset($_REQUEST['showqueries'])) { Debug::message("\n" . $sql . "\n"); $starttime = microtime(true); } $stmt = $dbConn->prepare($sql); $handle = $stmt->execute(); // Execute and save the return value (true or false) $result = $stmt->fetchAll(); // Get the result itself if(isset($_REQUEST['showqueries'])) { $duration = microtime(true) - $starttime; Debug::message("\n" . $duration . "\n"); } if(!$handle && $errorLevel) { $error = $stmt->errorInfo(); $this->databaseError("Couldn't run query: $sql | " . $error[2], $errorLevel); } return new PDOQuery($result); } /** * Get the ID for the next new record for the table. * Get the autogenerated ID from the previous INSERT query. * @return int */ public function getGeneratedID() { } /** * 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); $stmt = $dbConn->prepare("SELECT MAX(ID)+1 FROM :table"); $stmt->bindParam(":table", $table); $handle = $stmt->execute(); $result = $stmt->fetchColumn(); return $handle ? $result : 1; } /** * Determine if the the table is active. * @return bool */ public function isActive() { return $this->active ? true : false; } /** * Create the database and connect to it. This can be called if the * initial database connection is not successful because the database * does not exist. * @param string $connect Connection string * @param string $username Database username * @param string $password Database Password * @param string $database Database to which to create * @return boolean Returns true if successful */ public function createDatabase($connect, $username, $password, $database) { try { $dbConn = new PDO($connect, $username, $password); $stmt = $dbConn->prepare("CREATE DATABASE :database"); $stmt->bindParam(":database", $database); $stmt->execute(); $this->active = true; } catch (PDOException $e) { $this->databaseError($e->getMessage()); return false; } return true; } /** * Create a new table with an integer primary key called ID. * @var string $tableName The name of the table. * @return void. */ public function createTable($tableName, $fields = null, $indexes = null) { $fieldSchemas = $indexSchemas = ""; if ($fields) { foreach($fields as $k => $v) $fieldSchemas .= "`$k` $v,\n"; } switch ($parameters['type']) { case "mysql": $stmt = $dbConn->prepare("CREATE TABLE :tableName (ID INT(11) NOT NULL AUTO_INCREMENT, $fieldSchemas PRIMARY KEY (ID)) TYPE=MyISAM"); break; case "postgresql": $stmt = $dbConn->prepare("CREATE TABLE :tableName (ID SERIAL, $fieldSchemas PRIMARY KEY (ID))"); break; case "mssql": $stmt = $dbConn->prepare("CREATE TABLE :tableName (ID INT(11) IDENTITY(1,1), $fieldSchemas PRIMARY KEY (ID))"); break; default: $this->databaseError("This database server is not available"); } $stmt->bindParam(":tableName", $tableName); $stmt->execute(); if ($indexes) { alterTable($tableName, null, $indexes, null, null); } } /** * Alter fields and indexes in existing table. * @var string $tableName The name of the table. * @var string $newFields Fields to add. * @var string $newIndexes Indexes to add. * @var string $alteredFields Fields to change. * @var string $alteredIndexes Indexes to change. * @return void. */ public function alterTable($table, $newFields, $newIndexes, $alteredFields, $alteredIndexes) { if ($newFields) { $stmt = $dbConn->prepare("ALTER TABLE :table ADD :field :type"); $stmt->bindParam(':table', $table); $stmt->bindParam(':field', $field); $stmt->bindParam(':type', $type); foreach ($newFields as $k => $v) { $field = $k; $type = $v; $stmt->execute(); } } if ($newIndexes) { $stmt = $dbConn->prepare("CREATE INDEX :name ON :table :column"); $stmt->bindParam(':table', $table); $stmt->bindParam(':name', $name); $stmt->bindParam(':column', $column); foreach ($newIndexes as $k => $v) { $name = $k; $column = $v; $stmt->execute(); } } if ($alteredFields) { switch ($parameters['type']) { case "mysql": $stmt = $dbConn->prepare("ALTER TABLE :table CHANGE :field :field :type"); break; case "postgresql": $stmt = $dbConn->prepare(" BEGIN; ALTER TABLE :table RENAME :field TO oldfield; ALTER TABLE :table ADD COLUMN :field :type; UPDATE :table SET :field = CAST(oldfield AS :type); ALTER TABLE :table DROP COLUMN oldfield; COMMIT; "); break; case "mssql": $stmt = $dbConn->prepare("ALTER TABLE :table ALTER COLUMN :field :type"); break; default: $this->databaseError("This database server is not available"); } $stmt->bindParam(':table', $table); $stmt->bindParam(':field', $field); $stmt->bindParam(':type', $type); foreach ($alteredFields as $k => $v) { $field = $k; $type = $v; $stmt->execute(); } } if ($alteredIndexes) { $drop = $dbConn->prepare("DROP INDEX :drop"); $drop->bindParam(':drop', $drop); $stmt = $dbConn->prepare("CREATE INDEX :name ON :table :column"); $stmt->bindParam(':table', $table); $stmt->bindParam(':name', $name); $stmt->bindParam(':column', $column); foreach ($newIndexes as $k => $v) { $drop = $k; $drop->execute(); $name = $k; $column = $v; $stmt->execute(); } } } /** * Rename an existing table, the TO is necessary for PostgreSQL and MS SQL. * @var string $oldTableName The name of the existing table. * @var string $newTableName How the table should be named from now on. * @return void. */ public function renameTable($oldTableName, $newTableName) { $stmt = $dbConn->prepare("ALTER TABLE :oldTableName RENAME TO :newTableName"); $stmt->bindParam(":oldTableName", $oldTableName); $stmt->bindParam(":newTableName", $newTableName); $stmt->execute(); } /** * Checks a table's integrity and repairs it if necessary - only available in MySQL, not supported in PostgreSQL and MS SQL. * @var string $tableName The name of the table. * @return boolean Return true if the table has integrity after the method is complete. */ public function checkAndRepairTable($tableName) { if ($parameters['type'] == "mysql") { if (!$this->runTableCheckCommand("CHECK TABLE `$tableName`")) { if(!Database::$supressOutput) { echo "
  • Table $tableName: repaired
  • "; } return $this->runTableCheckCommand("REPAIR TABLE `$tableName` USE_FRM"); } else { return true; } } else { $this->databaseError("Checking and repairing of tables is only supported in MySQL, for other databases please do manual checks"); return false; } } /** * Helper function used by checkAndRepairTable. * @param string $sql Query to run. * @return boolean Returns if the query returns a successful result. */ protected function runTableCheckCommand($sql) { foreach($dbConn->query($sql) as $testRecord) { if(strtolower($testRecord['Msg_text']) != 'ok') { return false; } } return true; } /** * Add the given field to the given table. * @param string $tableName The name of the table on which to create the field. * @param string $fieldName The field to create. * @param string $fieldSpec The datatype of the field. * @return void */ public function createField($tableName, $fieldName, $fieldSpec) { $stmt = $dbConn->prepare("ALTER TABLE :tableName ADD :fieldName :fieldSpec"); $stmt->bindParam(":tableName", $tableName); $stmt->bindParam(":fieldName", $fieldName); $stmt->bindParam(":fieldSpec", $fieldSpec); $stmt->execute(); } /** * Change the database type of the given field. * @param string $table The table where to change the field. * @param string $field The field to change. * @param string $type The new type of the field * @return void */ public function alterField($table, $field, $type) { switch ($parameters['type']) { case "mysql": $stmt = $dbConn->prepare("ALTER TABLE :table CHANGE :field :field :type"); break; case "postgresql": $stmt = $dbConn->prepare(" BEGIN; ALTER TABLE :table RENAME :field TO oldfield; ALTER TABLE :table ADD COLUMN :field :type; UPDATE :table SET :field = CAST(oldfield AS :type); ALTER TABLE :table DROP COLUMN oldfield; COMMIT; "); break; case "mssql": $stmt = $dbConn->prepare("ALTER TABLE :table ALTER COLUMN :field :type"); break; default: $this->databaseError("This database server is not available"); } $stmt->bindParam(':table', $table); $stmt->bindParam(':field', $field); $stmt->bindParam(':type', $type); $stmt->execute(); } /** * 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. * @return void */ public function createIndex($tableName, $indexName, $indexSpec) { $stmt = $dbConn->prepare("CREATE INDEX :name ON :table :column"); $stmt->bindParam(':table', $tableName); $stmt->bindParam(':name', $indexName); $stmt->bindParam(':column', $indexSpec); $stmt->execute(); } /** * 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. * @return void */ public function alterIndex($tableName, $indexName, $indexSpec) { $drop = $dbConn->prepare("DROP INDEX :drop"); $drop->bindParam(':drop', $indexName); $stmt = $dbConn->prepare("CREATE INDEX :name ON :table :column"); $stmt->bindParam(':table', $tableName); $stmt->bindParam(':name', $indexName); $stmt->bindParam(':column', $indexSpec); $drop->execute(); $stmt->execute(); } /** * Get a list of all the fields for the given table. * @param string $able Table of which to show the fields. * Returns a map of field name => field spec. */ public function fieldList($table) { // to be done - SHOW is used extensively but very MySQL specific } /** * Get a list of all the indexes for the given table. * @param string $able Table of which to show the indexes. * Returns a map of indexes. */ public function indexList($table) { // to be done - SHOW is used extensively but very MySQL specific } /** * Returns a list of all the tables in the column. * Table names will all be in lowercase. * Returns a map of a table. */ public function tableList() { // to be done - SHOW is used extensively but very MySQL specific } /** * Return the number of rows affected (DELETE, INSERT, or UPDATE) by the previous operation. */ public function affectedRows() { return $stmt->rowCount(); } } /** * A result-set from a database query (array). */ class PDOQuery extends Query { private $result; /** * Hook the result-set given into a Query class, suitable for use by sapphire. * @param array $result The array of all returned values. */ public function __construct(PDODatabase $result) { $this->result = $result; parent::__construct(); } public function __destroy() { $this->result = null; } public function seek($row) { return in_array($row, $this->result); } public function numRecords() { return count($this->result); } public function nextRecord() { } } ?>