Example #1
0
 /**
  * The MySQL PDO driver has issues if you try to reuse a prepared statement
  * without any placeholders.
  * 
  * @return void
  */
 private function regenerateStatement()
 {
     $is_pdo = $this->database->getExtension() == 'pdo';
     $is_mysql = $this->database->getType() == 'mysql';
     if ($this->placeholders || !$is_pdo || !$is_mysql) {
         return;
     }
     $this->statement = $this->database->getConnection()->prepare($this->sql);
 }
 /**
  * Configures the result set
  *
  * @internal
  *
  * @param  fDatabase $database       The database object this result was created from
  * @param  string    $character_set  MSSQL only: the character set to transcode from since MSSQL doesn't do UTF-8
  * @return fUnbufferedResult
  */
 public function __construct($database, $character_set = NULL)
 {
     if (!$database instanceof fDatabase) {
         throw new fProgrammerException('The database object provided does not appear to be a descendant of fDatabase');
     }
     $this->database = $database;
     $this->extension = $this->database->getExtension();
     $this->character_set = $character_set;
 }
Example #3
0
 /** 
  * Seeks to the specified zero-based row for the specified SQL query
  * 
  * @throws fNoRowsException  When the query did not return any rows
  * 
  * @param  integer $row  The row number to seek to (zero-based)
  * @return void
  */
 public function seek($row)
 {
     if (!$this->returned_rows) {
         throw new fNoRowsException('The query did not return any rows');
     }
     if ($row >= $this->returned_rows || $row < 0) {
         throw new fProgrammerException('The row requested does not exist');
     }
     $this->pointer = $row;
     switch ($this->database->getExtension()) {
         case 'mssql':
             $success = mssql_data_seek($this->result, $row);
             break;
         case 'mysql':
             $success = mysql_data_seek($this->result, $row);
             break;
         case 'mysqli':
             if (is_object($this->result)) {
                 $success = mysqli_data_seek($this->result, $row);
             } else {
                 $success = TRUE;
             }
             break;
         case 'pgsql':
             $success = pg_result_seek($this->result, $row);
             break;
         case 'sqlite':
             $success = sqlite_seek($this->result, $row);
             break;
         case 'ibm_db2':
         case 'oci8':
         case 'odbc':
         case 'pdo':
         case 'sqlsrv':
             // Do nothing since we already changed the pointer
             $success = TRUE;
             break;
     }
     if (!$success) {
         throw new fSQLException('There was an error seeking to row %s', $row);
     }
     $this->advanceCurrentRow();
 }
 /**
  * Translates the structure of `CREATE TABLE` statements to the database specific syntax
  * 
  * @param  string $sql                The SQL to translate
  * @param  array  &$extra_statements  Any extra SQL statements that need to be added
  * @return string  The translated SQL
  */
 private function translateCreateTableStatements($sql, &$extra_statements)
 {
     if (!preg_match('#^\\s*CREATE\\s+TABLE\\s+(["`\\[]?\\w+["`\\]]?)#i', $sql, $table_matches)) {
         return $sql;
     }
     $table = $table_matches[1];
     if ($this->database->getType() == 'db2') {
         // Data type translation
         $regex = array('#\\btext\\b#i' => 'CLOB', '#("[^"]+"|\\w+)\\s+boolean(.*?)(,|\\)|$)#im' => '\\1 CHAR(1)\\2 CHECK(\\1 IN (\'0\', \'1\'))\\3', '#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\b#i' => 'INTEGER GENERATED BY DEFAULT AS IDENTITY');
         $sql = preg_replace(array_keys($regex), array_values($regex), $sql);
     } elseif ($this->database->getType() == 'mssql') {
         // Data type translation
         $regex = array('#\\bblob\\b#i' => 'IMAGE', '#\\btimestamp\\b#i' => 'DATETIME', '#\\btime\\b#i' => 'DATETIME', '#\\bdate\\b#i' => 'DATETIME', '#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\b#i' => 'INTEGER IDENTITY(1,1)', '#\\bboolean\\b#i' => 'BIT', '#\\bvarchar\\b#i' => 'NVARCHAR', '#\\bchar\\b#i' => 'NCHAR', '#\\btext\\b#i' => 'NTEXT');
         $sql = preg_replace(array_keys($regex), array_values($regex), $sql);
     } elseif ($this->database->getType() == 'mysql') {
         // Data type translation
         $regex = array('#\\btext\\b#i' => 'MEDIUMTEXT', '#\\bblob\\b#i' => 'LONGBLOB', '#\\btimestamp\\b#i' => 'DATETIME', '#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\b#i' => 'INTEGER AUTO_INCREMENT');
         $sql = preg_replace(array_keys($regex), array_values($regex), $sql);
         // Make sure MySQL uses InnoDB tables, translate check constraints to enums and fix column-level foreign key definitions
         preg_match_all('#(?<=,|\\()\\s*(["`]?\\w+["`]?)\\s+(?:[a-z]+)(?:\\(\\d+\\))?(?:\\s+unsigned|\\s+zerofill|\\s+character\\s+set\\s+[^ ]+|\\s+collate\\s+[^ ]+|\\s+NULL|\\s+NOT\\s+NULL|(\\s+DEFAULT\\s+(?:[^, \']*|\'(?:\'\'|[^\']+)*\'))|\\s+UNIQUE|\\s+PRIMARY\\s+KEY|(\\s+CHECK\\s*\\(\\w+\\s+IN\\s+(\\(\\s*(?:(?:[^, \']+|\'(?:\'\'|[^\']+)*\')\\s*,\\s*)*\\s*(?:[^, \']+|\'(?:\'\'|[^\']+)*\')\\))\\)))*(\\s+REFERENCES\\s+["`]?\\w+["`]?\\s*\\(\\s*["`]?\\w+["`]?\\s*\\)\\s*(?:\\s+(?:ON\\s+DELETE|ON\\s+UPDATE)\\s+(?:CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL|SET\\s+DEFAULT))*(?:\\s+(?:DEFERRABLE|NOT\\s+DEFERRABLE))?)?\\s*(,|\\s*(?=\\)))#mi', $sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             // MySQL has the enum data type, so we switch check constraints to that
             if (!empty($match[3])) {
                 $replacement = "\n " . $match[1] . ' enum' . $match[4] . $match[2] . $match[5] . $match[6];
                 $sql = str_replace($match[0], $replacement, $sql);
                 // This allows us to do a str_replace below for converting foreign key syntax
                 $match[0] = $replacement;
             }
             // Even InnoDB table types don't allow specify foreign key constraints in the column
             // definition, so we move it to its own definition on the next line
             if (!empty($match[5])) {
                 $updated_match_0 = str_replace($match[5], ",\nFOREIGN KEY (" . $match[1] . ') ' . $match[5], $match[0]);
                 $sql = str_replace($match[0], $updated_match_0, $sql);
             }
         }
         $sql = preg_replace('#\\)\\s*;?\\s*$#D', ')ENGINE=InnoDB', $sql);
     } elseif ($this->database->getType() == 'oracle') {
         // Data type translation
         $regex = array('#\\bbigint\\b#i' => 'INTEGER', '#\\bboolean\\b#i' => 'NUMBER(1)', '#\\btext\\b#i' => 'CLOB', '#\\bvarchar\\b#i' => 'VARCHAR2', '#\\btime\\b#i' => 'TIMESTAMP');
         $sql = preg_replace(array_keys($regex), array_values($regex), $sql);
         // Create sequences and triggers for Oracle
         if (stripos($sql, 'autoincrement') !== FALSE && preg_match('#(?<=,|\\()\\s*("?\\w+"?)\\s+(?:[a-z]+)(?:\\((?:\\d+)\\))?.*?\\bAUTOINCREMENT\\b[^,\\)]*(?:,|\\s*(?=\\)))#mi', $sql, $matches)) {
             $column = $matches[1];
             $table_column = substr(str_replace('"', '', $table) . '_' . str_replace('"', '', $column), 0, 26);
             $sequence_name = $table_column . '_seq';
             $trigger_name = $table_column . '_trg';
             $sequence = 'CREATE SEQUENCE ' . $sequence_name;
             $trigger = 'CREATE OR REPLACE TRIGGER ' . $trigger_name . "\n";
             $trigger .= "BEFORE INSERT ON " . $table . "\n";
             $trigger .= "FOR EACH ROW\n";
             $trigger .= "BEGIN\n";
             $trigger .= "  IF :new." . $column . " IS NULL THEN\n";
             $trigger .= "\tSELECT " . $sequence_name . ".nextval INTO :new." . $column . " FROM dual;\n";
             $trigger .= "  END IF;\n";
             $trigger .= "END;";
             $extra_statements[] = $sequence;
             $extra_statements[] = $trigger;
             $sql = preg_replace('#\\s+autoincrement\\b#i', '', $sql);
         }
     } elseif ($this->database->getType() == 'postgresql') {
         // Data type translation
         $regex = array('#\\bblob\\b#i' => 'BYTEA', '#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\b#i' => 'SERIAL');
         $sql = preg_replace(array_keys($regex), array_values($regex), $sql);
     } elseif ($this->database->getType() == 'sqlite') {
         // Data type translation
         if ($this->database->getExtension() == 'pdo') {
             $sql = preg_replace('#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\s+primary\\s+key\\b#i', 'INTEGER PRIMARY KEY AUTOINCREMENT', $sql);
         } else {
             $sql = preg_replace('#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\s+primary\\s+key\\b#i', 'INTEGER PRIMARY KEY', $sql);
         }
         // Create foreign key triggers for SQLite
         if (stripos($sql, 'REFERENCES') !== FALSE) {
             preg_match_all('#(?:(?<=,|\\()\\s*(["`\\[]?\\w+["`\\]]?)\\s+(?:[a-z]+)(?:\\((?:\\d+)\\))?(?:(\\s+NOT\\s+NULL)|(?:\\s+DEFAULT\\s+(?:[^, \']*|\'(?:\'\'|[^\']+)*\'))|(?:\\s+UNIQUE)|(?:\\s+PRIMARY\\s+KEY(?:\\s+AUTOINCREMENT)?)|(?:\\s+CHECK\\s*\\(\\w+\\s+IN\\s+\\(\\s*(?:(?:[^, \']+|\'(?:\'\'|[^\']+)*\')\\s*,\\s*)*\\s*(?:[^, \']+|\'(?:\'\'|[^\']+)*\')\\)\\)))*(\\s+REFERENCES\\s+(["`\\[]?\\w+["`\\]]?)\\s*\\(\\s*(["`\\[]?\\w+["`\\]]?)\\s*\\)\\s*(?:\\s+(?:ON\\s+DELETE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL)))?(?:\\s+(?:ON\\s+UPDATE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL)))?)?\\s*(?:,|\\s*(?=\\)))|(?<=,|\\()\\s*FOREIGN\\s+KEY\\s*(?:(["`\\[]?\\w+["`\\]]?)|\\((["`\\[]?\\w+["`\\]]?)\\))\\s+REFERENCES\\s+(["`\\[]?\\w+["`\\]]?)\\s*\\(\\s*(["`\\[]?\\w+["`\\]]?)\\s*\\)\\s*(?:\\s+(?:ON\\s+DELETE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL)))?(?:\\s+(?:ON\\s+UPDATE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL)))?\\s*(?:,|\\s*(?=\\))))#mi', $sql, $matches, PREG_SET_ORDER);
             $not_null_columns = array();
             foreach ($matches as $match) {
                 $fields_to_unquote = array(1, 4, 5, 9, 10, 11);
                 foreach ($fields_to_unquote as $field) {
                     if (isset($match[$field])) {
                         $match[$field] = str_replace(array('[', '"', '`', ']'), '', $match[$field]);
                     }
                 }
                 // Find all of the not null columns
                 if (!empty($match[2])) {
                     $not_null_columns[] = $match[1];
                 }
                 // If neither of these fields is matched, we don't have a foreign key
                 if (empty($match[3]) && empty($match[10])) {
                     continue;
                 }
                 // 8 and 9 will be an either/or set, so homogenize
                 if (empty($match[9]) && !empty($match[8])) {
                     $match[9] = $match[8];
                 }
                 // Handle column level foreign key inserts/updates
                 if ($match[1]) {
                     $this->createSQLiteForeignKeyTriggerValidInsertUpdate($extra_statements, $table, $match[1], $match[4], $match[5], in_array($match[1], $not_null_columns));
                     // Handle table level foreign key inserts/update
                 } elseif ($match[9]) {
                     $this->createSQLiteForeignKeyTriggerValidInsertUpdate($extra_statements, $table, $match[9], $match[10], $match[11], in_array($match[9], $not_null_columns));
                 }
                 // If none of these fields is matched, we don't have on delete or on update clauses
                 if (empty($match[6]) && empty($match[7]) && empty($match[12]) && empty($match[13])) {
                     continue;
                 }
                 // Handle column level foreign key delete/update clauses
                 if (!empty($match[3])) {
                     if ($match[6]) {
                         $this->createSQLiteForeignKeyTriggerOnDelete($extra_statements, $table, $match[1], $match[4], $match[5], $match[6]);
                     }
                     if (!empty($match[7])) {
                         $this->createSQLiteForeignKeyTriggerOnUpdate($extra_statements, $table, $match[1], $match[4], $match[5], $match[7]);
                     }
                     continue;
                 }
                 // Handle table level foreign key delete/update clauses
                 if ($match[12]) {
                     $this->createSQLiteForeignKeyTriggerOnDelete($extra_statements, $table, $match[9], $match[10], $match[11], $match[12]);
                 }
                 if ($match[13]) {
                     $this->createSQLiteForeignKeyTriggerOnUpdate($extra_statements, $table, $match[9], $match[10], $match[11], $match[13]);
                 }
             }
         }
     }
     return $sql;
 }
Example #5
0
 /**
  * Adds a number of math functions to SQLite that MSSQL, MySQL and PostgreSQL have by default
  * 
  * @return void
  */
 private function createSQLiteFunctions()
 {
     $function = array();
     $functions[] = array('acos', 'acos', 1);
     $functions[] = array('asin', 'asin', 1);
     $functions[] = array('atan', 'atan', 1);
     $functions[] = array('atan2', 'atan2', 2);
     $functions[] = array('ceil', 'ceil', 1);
     $functions[] = array('ceiling', 'ceil', 1);
     $functions[] = array('cos', 'cos', 1);
     $functions[] = array('cot', array('fSQLTranslation', 'sqliteCotangent'), 1);
     $functions[] = array('degrees', 'rad2deg', 1);
     $functions[] = array('exp', 'exp', 1);
     $functions[] = array('floor', 'floor', 1);
     $functions[] = array('ln', 'log', 1);
     $functions[] = array('log', array('fSQLTranslation', 'sqliteLogBaseFirst'), 2);
     $functions[] = array('ltrim', 'ltrim', 1);
     $functions[] = array('pi', 'pi', 0);
     $functions[] = array('power', 'pow', 2);
     $functions[] = array('radians', 'deg2rad', 1);
     $functions[] = array('rtrim', 'rtrim', 1);
     $functions[] = array('sign', array('fSQLTranslation', 'sqliteSign'), 1);
     $functions[] = array('sqrt', 'sqrt', 1);
     $functions[] = array('sin', 'sin', 1);
     $functions[] = array('tan', 'tan', 1);
     $functions[] = array('trim', 'trim', 1);
     if ($this->database->getExtension() == 'sqlite') {
         $functions[] = array('current_date', array('fSQLTranslation', 'sqliteDate'), 0);
         $functions[] = array('current_time', array('fSQLTranslation', 'sqliteTime'), 0);
         $functions[] = array('current_timestamp', array('fSQLTranslation', 'sqliteTimestamp'), 0);
         // If SQLite was compiled with ISO-8859-* string handling, we override as best we can
         // with custom functions that return the correct values. We can't fix LIKE and GLOB
         // but they don't matter as much since the encoding only affects case transformations.
         if (strtolower(sqlite_libencoding()) != 'utf-8') {
             $functions[] = array('length', array('fSQLTranslation', 'sqliteLength'), 1);
             $functions[] = array('substr', array('fSQLTranslation', 'sqliteSubstr'), 3);
         }
     }
     foreach ($functions as $function) {
         if ($this->database->getExtension() == 'pdo') {
             $this->database->getConnection()->sqliteCreateFunction($function[0], $function[1], $function[2]);
         } else {
             sqlite_create_function($this->database->getConnection(), $function[0], $function[1], $function[2]);
         }
     }
 }
Example #6
0
 /**
  * Gets the next row from the result and assigns it to the current row
  * 
  * @return void
  */
 private function advanceCurrentRow()
 {
     switch ($this->database->getExtension()) {
         case 'mssql':
             // For some reason the mssql extension will return an empty row even
             // when now rows were returned, so we have to explicitly check for this
             if ($this->pointer == 0 && !mssql_num_rows($this->result)) {
                 $row = FALSE;
             } else {
                 $row = mssql_fetch_assoc($this->result);
                 if (empty($row)) {
                     mssql_fetch_batch($this->result);
                     $row = mssql_fetch_assoc($this->result);
                 }
                 if (!empty($row)) {
                     $row = $this->fixDblibMSSQLDriver($row);
                 }
             }
             break;
         case 'mysql':
             $row = mysql_fetch_assoc($this->result);
             break;
         case 'mysqli':
             $row = mysqli_fetch_assoc($this->result);
             break;
         case 'oci8':
             $row = oci_fetch_assoc($this->result);
             break;
         case 'odbc':
             $row = odbc_fetch_array($this->result);
             break;
         case 'pgsql':
             $row = pg_fetch_assoc($this->result);
             break;
         case 'sqlite':
             $row = sqlite_fetch_array($this->result, SQLITE_ASSOC);
             break;
         case 'sqlsrv':
             $row = sqlsrv_fetch_array($this->result, SQLSRV_FETCH_ASSOC);
             break;
         case 'pdo':
             $row = $this->result->fetch(PDO::FETCH_ASSOC);
             break;
     }
     // Fix uppercase column names to lowercase
     if ($row && $this->database->getType() == 'oracle') {
         $new_row = array();
         foreach ($row as $column => $value) {
             $new_row[strtolower($column)] = $value;
         }
         $row = $new_row;
     }
     // This is an unfortunate fix that required for databases that don't support limit
     // clauses with an offset. It prevents unrequested columns from being returned.
     if ($row && ($this->database->getType() == 'mssql' || $this->database->getType() == 'oracle')) {
         if ($this->untranslated_sql !== NULL && isset($row['flourish__row__num'])) {
             unset($row['flourish__row__num']);
         }
     }
     // This decodes the data coming out of MSSQL into UTF-8
     if ($row && $this->database->getType() == 'mssql') {
         if ($this->character_set) {
             foreach ($row as $key => $value) {
                 if (!is_string($value) || strpos($key, '__flourish_mssqln_') === 0 || isset($row['fmssqln__' . $key]) || preg_match('#[\\x0-\\x8\\xB\\xC\\xE-\\x1F]#', $value)) {
                     continue;
                 }
                 $row[$key] = iconv($this->character_set, 'UTF-8', $value);
             }
         }
         $row = $this->decodeMSSQLNationalColumns($row);
     }
     if ($this->unescape_map) {
         foreach ($this->unescape_map as $column => $type) {
             if (!isset($row[$column])) {
                 continue;
             }
             $row[$column] = $this->database->unescape($type, $row[$column]);
         }
     }
     $this->current_row = $row;
 }