home/wuectly/www/libraries/f0f/database/iterator/sqlsrv.php000060400000002430152455677530020121 0ustar00cursor); } /** * Method to fetch a row from the result set cursor as an object. * * @return mixed Either the next row from the result set or false if there are no more rows. */ protected function fetchObject() { return @sqlsrv_fetch_object($this->cursor, $this->class); } /** * Method to free up the memory used for the result set. * * @return void */ protected function freeResult() { @sqlsrv_free_stmt($this->cursor); } } home/wuectly/www/libraries/f0f/database/driver/sqlsrv.php000060400000064606152456043540017565 0ustar00disconnect(); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 12.1 * @throws RuntimeException */ public function connect() { if ($this->connection) { return; } // Build the connection configuration array. $config = array( 'Database' => $this->options['database'], 'uid' => $this->options['user'], 'pwd' => $this->options['password'], 'CharacterSet' => 'UTF-8', 'ReturnDatesAsStrings' => true); // Make sure the SQLSRV extension for PHP is installed and enabled. if (!function_exists('sqlsrv_connect')) { throw new RuntimeException('PHP extension sqlsrv_connect is not available.'); } // Attempt to connect to the server. if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config))) { throw new RuntimeException('Database sqlsrv_connect failed'); } // Make sure that DB warnings are not returned as errors. sqlsrv_configure('WarningsReturnAsErrors', 0); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } // Set charactersets. $this->utf = $this->setUtf(); } /** * Disconnects the database. * * @return void * * @since 12.1 */ public function disconnect() { // Close the connection. if (is_resource($this->connection)) { foreach ($this->disconnectHandlers as $h) { call_user_func_array($h, array( &$this)); } sqlsrv_close($this->connection); } $this->connection = null; } /** * Get table constraints * * @param string $tableName The name of the database table. * * @return array Any constraints available for the table. * * @since 12.1 */ protected function getTableConstraints($tableName) { $this->connect(); $query = $this->getQuery(true); $this->setQuery( 'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $query->quote($tableName) ); return $this->loadColumn(); } /** * Rename constraints. * * @param array $constraints Array(strings) of table constraints * @param string $prefix A string * @param string $backup A string * * @return void * * @since 12.1 */ protected function renameConstraints($constraints = array(), $prefix = null, $backup = null) { $this->connect(); foreach ($constraints as $constraint) { $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint)); $this->execute(); } } /** * Method to escape a string for usage in an SQL statement. * * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need * to handle the escaping ourselves. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 12.1 */ public function escape($text, $extra = false) { $result = addslashes($text); $result = str_replace("\'", "''", $result); $result = str_replace('\"', '"', $result); $result = str_replace('\/', '/', $result); if ($extra) { // We need the below str_replace since the search in sql server doesn't recognize _ character. $result = str_replace('_', '[_]', $result); } return $result; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 12.1 */ public function connected() { // TODO: Run a blank query here return true; } /** * Drops a table from the database. * * @param string $tableName The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return F0FDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 */ public function dropTable($tableName, $ifExists = true) { $this->connect(); $query = $this->getQuery(true); if ($ifExists) { $this->setQuery( 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName ); } else { $this->setQuery('DROP TABLE ' . $tableName); } $this->execute(); return $this; } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. * * @since 12.1 */ public function getAffectedRows() { $this->connect(); return sqlsrv_rows_affected($this->cursor); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return mixed The collation in use by the database or boolean false if not supported. * * @since 12.1 */ public function getCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Method to get the database connection collation, as reported by the driver. If the connector doesn't support * reporting this value please return an empty string. * * @return string */ public function getConnectionCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Get the number of returned rows for the previous executed SQL statement. * * @param resource $cursor An optional database cursor resource to extract the row count from. * * @return integer The number of returned rows. * * @since 12.1 */ public function getNumRows($cursor = null) { $this->connect(); return sqlsrv_num_rows($cursor ? $cursor : $this->cursor); } /** * Retrieves field information about the given tables. * * @param mixed $table A table name * @param boolean $typeOnly True to only return field types. * * @return array An array of fields. * * @since 12.1 * @throws RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $result = array(); $table_temp = $this->replacePrefix((string) $table); // Set the query to get the table fields statement. $this->setQuery( 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' . ' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp) ); $fields = $this->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type); } } // If we want the whole field data object add that to the list. else { foreach ($fields as $field) { if (stristr(strtolower($field->Type), "nvarchar")) { $field->Default = ""; } $result[$field->Field] = $field; } } return $result; } /** * Shows the table CREATE statement that creates the given tables. * * This is unsupported by MSSQL. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 12.1 * @throws RuntimeException */ public function getTableCreate($tables) { $this->connect(); return ''; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 12.1 * @throws RuntimeException */ public function getTableKeys($table) { $this->connect(); // TODO To implement. return array(); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 12.1 * @throws RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';'); $tables = $this->loadColumn(); return $tables; } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 12.1 */ public function getVersion() { $this->connect(); $version = sqlsrv_server_info($this->connection); return $version['SQLServerVersion']; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object &$object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. * * @since 12.1 * @throws RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = array(); $values = array(); $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)'; foreach (get_object_vars($object) as $k => $v) { // Only process non-null scalars. if (is_array($v) or is_object($v) or $v === null) { continue; } if (!$this->checkFieldExists($table, $k)) { continue; } if ($k[0] == '_') { // Internal field continue; } if ($k == $key && $key == 0) { continue; } $fields[] = $this->quoteName($k); $values[] = $this->Quote($v); } // Set the query and execute the insert. $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values))); if (!$this->execute()) { return false; } $id = $this->insertid(); if ($key && $id) { $object->$key = $id; } return true; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. * * @since 12.1 */ public function insertid() { $this->connect(); // TODO: SELECT IDENTITY $this->setQuery('SELECT @@IDENTITY'); return (int) $this->loadResult(); } /** * Method to get the first field of the first row of the result set from the database query. * * @return mixed The return value or null if the query failed. * * @since 12.1 * @throws RuntimeException */ public function loadResult() { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an array. if ($row = sqlsrv_fetch_array($cursor, SQLSRV_FETCH_NUMERIC)) { $ret = $row[0]; } // Free up system resources and return. $this->freeResult($cursor); // For SQLServer - we need to strip slashes $ret = stripslashes($ret); return $ret; } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. * * @since 12.1 * @throws RuntimeException * @throws Exception */ public function execute() { $this->connect(); if (!is_resource($this->connection)) { if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); } throw new RuntimeException($this->errorMsg, $this->errorNum); } // Take a local copy so that we don't modify the original query and cause issues later $query = $this->replacePrefix((string) $this->sql); if (!($this->sql instanceof F0FDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) { $query = $this->limit($query, $this->limit, $this->offset); } // Increment the query counter. $this->count++; // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; if (class_exists('JLog')) { JLog::add($query, JLog::DEBUG, 'databasequery'); } $this->timings[] = microtime(true); } // SQLSrv_num_rows requires a static or keyset cursor. if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0) { $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET); } else { $array = array(); } // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. $this->cursor = @sqlsrv_query($this->connection, $query, array(), $array); if ($this->debug) { $this->timings[] = microtime(true); if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) { $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } else { $this->callStacks[] = debug_backtrace(); } } // If an error occurred handle it. if (!$this->cursor) { // Get the error number and message before we execute any more queries. $errorNum = $this->getErrorNumber(); $errorMsg = $this->getErrorMessage($query); // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { // Get the error number and message. $this->errorNum = $this->getErrorNumber(); $this->errorMsg = $this->getErrorMessage($query); // Throw the normal query exception. if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); } throw new RuntimeException($this->errorMsg, $this->errorNum, $e); } // Since we were able to reconnect, run the query again. return $this->execute(); } // The server was not disconnected. else { // Get the error number and message from before we tried to reconnect. $this->errorNum = $errorNum; $this->errorMsg = $errorMsg; // Throw the normal query exception. if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); } throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * This function replaces a string identifier $prefix with the string held is the * tablePrefix class variable. * * @param string $query The SQL statement to prepare. * @param string $prefix The common table prefix. * * @return string The processed SQL statement. * * @since 12.1 */ public function replacePrefix($query, $prefix = '#__') { $startPos = 0; $literal = ''; $query = trim($query); $n = strlen($query); while ($startPos < $n) { $ip = strpos($query, $prefix, $startPos); if ($ip === false) { break; } $j = strpos($query, "N'", $startPos); $k = strpos($query, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos)); $startPos = $j; $j = $startPos + 1; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($query, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $query{$l} == '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($query, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($query, $startPos, $n - $startPos); } return $literal; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 12.1 * @throws RuntimeException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC))) { throw new RuntimeException('Could not connect to database'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 12.1 */ public function setUtf() { return false; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('COMMIT TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('ROLLBACK TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { if ($this->setQuery('BEGIN TRANSACTION')->execute()) { $this->transactionDepth = 1; } return; } $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } /** * Method to fetch a row from the result set cursor as an array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchArray($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC); } /** * Method to fetch a row from the result set cursor as an associative array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchAssoc($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC); } /** * Method to fetch a row from the result set cursor as an object. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * @param string $class The class name to use for the returned row object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchObject($cursor = null, $class = 'stdClass') { return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class); } /** * Method to free up the memory used for the result set. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return void * * @since 12.1 */ protected function freeResult($cursor = null) { sqlsrv_free_stmt($cursor ? $cursor : $this->cursor); } /** * Method to check and see if a field exists in a table. * * @param string $table The table in which to verify the field. * @param string $field The field to verify. * * @return boolean True if the field exists in the table. * * @since 12.1 */ protected function checkFieldExists($table, $field) { $this->connect(); $table = $this->replacePrefix((string) $table); $query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" . " ORDER BY ORDINAL_POSITION"; $this->setQuery($query); if ($this->loadResult()) { return true; } else { return false; } } /** * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set. * * @param string $query The SQL statement to process. * @param integer $limit The maximum affected rows to set. * @param integer $offset The affected row offset to set. * * @return string The processed SQL statement. * * @since 12.1 */ protected function limit($query, $limit, $offset) { if ($limit == 0 && $offset == 0) { return $query; } $start = $offset + 1; $end = $offset + $limit; $orderBy = stristr($query, 'ORDER BY'); if (is_null($orderBy) || empty($orderBy)) { $orderBy = 'ORDER BY (select 0)'; } $query = str_ireplace($orderBy, '', $query); $rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM '; $query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1); return $query; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return F0FDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $constraints = array(); if (!is_null($prefix) && !is_null($backup)) { $constraints = $this->getTableConstraints($oldTable); } if (!empty($constraints)) { $this->renameConstraints($constraints, $prefix, $backup); } $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'"); return $this->execute(); } /** * Locks a table in the database. * * @param string $tableName The name of the table to lock. * * @return F0FDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function lockTable($tableName) { return $this; } /** * Unlocks tables in the database. * * @return F0FDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function unlockTables() { return $this; } /** * Return the actual SQL Error number * * @return integer The SQL Error number * * @since 3.4.6 */ protected function getErrorNumber() { $errors = sqlsrv_errors(); return $errors[0]['SQLSTATE']; } /** * Return the actual SQL Error message * * @param string $query The SQL Query that fails * * @return string The SQL Error message * * @since 3.4.6 */ protected function getErrorMessage($query) { $errors = sqlsrv_errors(); $errorMessage = (string) $errors[0]['message']; // Replace the Databaseprefix with `#__` if we are not in Debug if (!$this->debug) { $errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage); $query = str_replace($this->tablePrefix, '#__', $query); } return $errorMessage . ' SQL=' . $query; } } home/wuectly/www/libraries/joomla/database/query/sqlsrv.php000060400000056760152456071560020252 0ustar00 * @license GNU General Public License version 2 or later; see LICENSE */ defined('JPATH_PLATFORM') or die; /** * Query Building Class. * * @since 1.7.0 */ class JDatabaseQuerySqlsrv extends JDatabaseQuery implements JDatabaseQueryLimitable { /** * The character(s) used to quote SQL statement names such as table names or field names, * etc. The child classes should define this as necessary. If a single character string the * same character is used for both sides of the quoted name, else the first character will be * used for the opening quote and the second for the closing quote. * * @var string * @since 1.7.0 */ protected $name_quotes = '`'; /** * The null or zero representation of a timestamp for the database driver. This should be * defined in child classes to hold the appropriate value for the engine. * * @var string * @since 1.7.0 */ protected $null_date = '1900-01-01 00:00:00'; /** * @var integer The affected row limit for the current SQL statement. * @since 3.2 */ protected $limit = 0; /** * @var integer The affected row offset to apply for the current SQL statement. * @since 3.2 */ protected $offset = 0; /** * Magic function to convert the query to a string. * * @return string The completed query. * * @since 1.7.0 */ public function __toString() { $query = ''; switch ($this->type) { case 'select': // Add required aliases for offset or fixGroupColumns method $columns = $this->fixSelectAliases(); $query = (string) $this->select; if ($this->group) { $this->fixGroupColumns($columns); } $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->selectRowNumber === null) { if ($this->group) { $query .= (string) $this->group; } if ($this->having) { $query .= (string) $this->having; } } if ($this->order) { $query .= (string) $this->order; } if ($this instanceof JDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0)) { $query = $this->processLimit($query, $this->limit, $this->offset); } break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } // Columns-Values method elseif ($this->values) { if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->insert->getElements(); $tableName = array_shift($elements); $query .= 'VALUES '; $query .= (string) $this->values; if ($this->autoIncrementField) { $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;'; } if ($this->where) { $query .= (string) $this->where; } } break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; case 'update': if ($this->join) { $tmpUpdate = $this->update; $tmpFrom = $this->from; $this->update = null; $this->from = null; $updateElem = $tmpUpdate->getElements(); $updateArray = explode(' ', $updateElem[0]); // Use table alias if exists $this->update(end($updateArray)); $this->from($updateElem[0]); $query .= (string) $this->update; $query .= (string) $this->set; $query .= (string) $this->from; $this->update = $tmpUpdate; $this->from = $tmpFrom; // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } else { $query .= (string) $this->update; $query .= (string) $this->set; } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; default: $query = parent::__toString(); break; } return $query; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * @param string $value The value to cast as a char. * * @param string $len The length of the char. * * @return string Returns the cast value. * * @since 1.7.0 */ public function castAsChar($value, $len = null) { if (!$len) { return 'CAST(' . $value . ' as NVARCHAR(30))'; } else { return 'CAST(' . $value . ' as NVARCHAR(' . $len . '))'; } } /** * Gets the function to determine the length of a character string. * * @param string $field A value. * @param string $operator Comparison operator between charLength integer value and $condition * @param string $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 1.7.0 */ public function charLength($field, $operator = null, $condition = null) { return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : ''); } /** * Concatenates an array of column names or values. * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. * * @since 1.7.0 */ public function concatenate($values, $separator = null) { if ($separator) { return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')'; } else { return '(' . implode('+', $values) . ')'; } } /** * Gets the current date and time. * * @return string * * @since 1.7.0 */ public function currentTimestamp() { return 'GETDATE()'; } /** * Get the length of a string in bytes. * * @param string $value The string to measure. * * @return integer * * @since 1.7.0 */ public function length($value) { return 'LEN(' . $value . ')'; } /** * Add to the current date and time. * Usage: * $query->select($query->dateAdd()); * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * * @param datetime $date The date to add to; type may be time or datetime. * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @since 3.2.0 * @note Not all drivers support all units. * @link http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information */ public function dateAdd($date, $interval, $datePart) { return 'DATEADD(' . $datePart . ', ' . $interval . ', ' . $date . ')'; } /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 3.0.0 */ public function processLimit($query, $limit, $offset = 0) { if ($limit) { $total = $offset + $limit; $position = stripos($query, 'SELECT'); $distinct = stripos($query, 'SELECT DISTINCT'); if ($position === $distinct) { $query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $total, $position, 15); } else { $query = substr_replace($query, 'SELECT TOP ' . (int) $total, $position, 6); } } if (!$offset) { return $query; } return PHP_EOL . 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS RowNumber FROM (' . $query . PHP_EOL . ') AS A) AS A WHERE RowNumber > ' . (int) $offset; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return JDatabaseQuery Returns this object to allow chaining. * * @since 3.0.0 */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Split a string of sql expression into an array of individual columns. * Single line or line end comments and multi line comments are stripped off. * Always return at least one column. * * @param string $string Input string of sql expression like select expression. * * @return array[] The columns from the input string separated into an array. * * @since 3.7.0 */ protected function splitSqlExpression($string) { // Append whitespace as equivalent to the last comma $string .= ' '; $colIdx = 0; $start = 0; $open = false; $openC = 0; $comment = false; $endString = ''; $length = strlen($string); $columns = array(); $column = array(); $current = ''; $previous = null; $operators = array( '+' => '', '-' => '', '*' => '', '/' => '', '%' => '', '&' => '', '|' => '', '~' => '', '^' => '', ); $addBlock = function ($block) use (&$column, &$colIdx) { if (isset($column[$colIdx])) { $column[$colIdx] .= $block; } else { $column[$colIdx] = $block; } }; for ($i = 0; $i < $length; $i++) { $current = substr($string, $i, 1); $current2 = substr($string, $i, 2); $current3 = substr($string, $i, 3); $lenEndString = strlen($endString); $testEnd = substr($string, $i, $lenEndString); if ($current == '[' || $current == '"' || $current == "'" || $current2 == '--' || ($current2 == '/*') || ($current == '#' && $current3 != '#__') || ($lenEndString && $testEnd == $endString)) { if ($open) { if ($testEnd === $endString) { if ($comment) { if ($lenEndString > 1) { $i += ($lenEndString - 1); } // Move cursor after close tag of comment $start = $i + 1; $comment = false; } elseif ($current == "'" || $current == ']' || $current == '"') { // Check for escaped quote like '', ]] or "" $n = 1; while ($i + $n < $length && $string[$i + $n] == $current) { $n++; } // Jump to the last quote $i += $n - 1; if ($n % 2 === 0) { // There is only escaped quote continue; } elseif ($n > 2) { // The last right close quote is not escaped $current = $string[$i]; } } $open = false; $endString = ''; } } else { $open = true; if ($current == '#' || $current2 == '--') { $endString = "\n"; $comment = true; } elseif ($current2 == '/*') { $endString = '*/'; $comment = true; } elseif ($current == '[') { $endString = ']'; } else { $endString = $current; } if ($comment && $start < $i) { // Add string exists before comment $addBlock(substr($string, $start, $i - $start)); $previous = $string[$i - 1]; $start = $i; } } } elseif (!$open) { if ($current == '(') { $openC++; $previous = $current; } elseif ($current == ')') { $openC--; $previous = $current; } elseif ($current == '.') { if ($i === $start && $colIdx > 0 && !isset($column[$colIdx])) { // Remove whitepace placed before dot $colIdx--; } $previous = $current; } elseif ($openC === 0) { if (ctype_space($current)) { // Normalize whitepace $string[$i] = ' '; if ($start < $i) { // Add text placed before whitespace $addBlock(substr($string, $start, $i - $start)); $colIdx++; $previous = $string[$i - 1]; } elseif (isset($column[$colIdx])) { if ($colIdx > 1 || !isset($operators[$previous])) { // There was whitespace after comment $colIdx++; } } // Move cursor forward $start = $i + 1; } elseif (isset($operators[$current]) && ($current !== '*' || $previous !== '.')) { if ($start < $i) { // Add text before operator $addBlock(substr($string, $start, $i - $start)); $colIdx++; } elseif (!isset($column[$colIdx]) && isset($operators[$previous])) { // Do not create whitespace between operators $colIdx--; } // Add operator $addBlock($current); $previous = $current; $colIdx++; // Move cursor forward $start = $i + 1; } else { $previous = $current; } } } if (($current == ',' && !$open && $openC == 0) || $i == $length - 1) { if ($start < $i && !$comment) { // Save remaining text $addBlock(substr($string, $start, $i - $start)); } $columns[] = $column; // Reset values $column = array(); $colIdx = 0; $previous = null; // Column saved, move cursor forward after comma $start = $i + 1; } } return $columns; } /** * Add required aliases to columns for select statement in subquery. * * @return array[] Array of columns with added missing aliases. * * @since 3.7.0 */ protected function fixSelectAliases() { $operators = array( '+' => '', '-' => '', '*' => '', '/' => '', '%' => '', '&' => '', '|' => '', '~' => '', '^' => '', ); // Split into array and remove comments $columns = $this->splitSqlExpression(implode(',', $this->select->getElements())); foreach ($columns as $i => $column) { $size = count($column); if ($size == 0) { continue; } if ($size > 2 && strcasecmp($column[$size - 2], 'AS') === 0) { // Alias exists, replace it to uppercase $columns[$i][$size - 2] = 'AS'; continue; } if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false) { // This words are reserved, they are not column names array_shift($column); $size--; } $lastWord = strtoupper($column[$size - 1]); $length = strlen($lastWord); $lastChar = $lastWord[$length - 1]; if ($lastChar == '*') { // Skip on wildcard continue; } if ($lastChar == ')' || ($size == 1 && $lastChar == "'") || $lastWord[0] == '@' || $lastWord == 'NULL' || $lastWord == 'END' || is_numeric($lastWord)) { /* Ends with: * - SQL function * - single static value like 'only '+'string' * - @@var * - NULL * - CASE ... END * - Numeric */ $columns[$i][] = 'AS'; $columns[$i][] = $this->quoteName('columnAlias' . $i); continue; } if ($size == 1) { continue; } $lastChar2 = substr($column[$size - 2], -1); // Check if column ends with '- a.x' or '- a. x' if (isset($operators[$lastChar2]) || ($size > 2 && $lastChar2 === '.' && isset($operators[substr($column[$size - 3], -1)]))) { // Ignore plus signs if column start with them if ($size != 2 || ltrim($column[0], '+') !== '' || $column[1][0] === "'") { // If operator exists before last word then alias is required for subquery $columns[$i][] = 'AS'; $columns[$i][] = $this->quoteName('columnAlias' . $i); continue; } } elseif ($column[$size - 1][0] !== '.' && $lastChar2 !== '.') { // If columns is like name name2 then second word is alias. // Add missing AS before the alias, exception for 'a. x' and 'a .x' array_splice($columns[$i], -1, 0, 'AS'); } } $selectColumns = array(); foreach ($columns as $i => $column) { $selectColumns[$i] = implode(' ', $column); } $this->select = new JDatabaseQueryElement('SELECT', $selectColumns); return $columns; } /** * Add missing columns names to GROUP BY clause. * * @param array[] $selectColumns Array of columns from splitSqlExpression method. * * @return JDatabaseQuery Returns this object to allow chaining. * * @since 3.7.0 */ protected function fixGroupColumns($selectColumns) { // Cache tables columns static $cacheCols = array(); // Known columns of all included tables $knownColumnsByAlias = array(); $iquotes = array('"' => '', '[' => '', "'" => ''); $nquotes = array('"', '[', ']'); // Aggregate functions $aFuncs = array( 'AVG(', 'CHECKSUM_AGG(', 'COUNT(', 'COUNT_BIG(', 'GROUPING(', 'GROUPING_ID(', 'MIN(', 'MAX(', 'SUM(', 'STDEV(', 'STDEVP(', 'VAR(', 'VARP(', ); // Aggregated columns $filteredColumns = array(); // Aliases found in SELECT statement $knownAliases = array(); $wildcardTables = array(); foreach ($selectColumns as $i => $column) { $size = count($column); if ($size === 0) { continue; } if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false) { // These words are reserved, they are not column names array_shift($selectColumns[0]); array_shift($column); $size--; } if ($size > 2 && $column[$size - 2] === 'AS') { // Save and remove AS alias $alias = $column[$size - 1]; if (isset($iquotes[$alias[0]])) { $alias = substr($alias, 1, -1); } // Remove alias $selectColumns[$i] = $column = array_slice($column, 0, -2); if ($size === 3 || ($size === 4 && strpos('+-*/%&|~^', $column[0][0]) !== false)) { $lastWord = $column[$size - 3]; if ($lastWord[0] === "'" || $lastWord === 'NULL' || is_numeric($lastWord)) { unset($selectColumns[$i]); continue; } } // Remember pair alias => column expression $knownAliases[$alias] = implode(' ', $column); } $aggregated = false; foreach ($column as $j => $block) { if (substr($block, -2) === '.*') { // Found column ends with .* if (isset($iquotes[$block[0]])) { // Quoted table $wildcardTables[] = substr($block, 1, -3); } else { $wildcardTables[] = substr($block, 0, -2); } } elseif (str_ireplace($aFuncs, '', $block) != $block) { $aggregated = true; } if ($block[0] === "'") { // Shrink static strings which could contain column name $column[$j] = "''"; } } if (!$aggregated) { // Without aggregated columns and aliases $filteredColumns[] = implode(' ', $selectColumns[$i]); } // Without aliases and static strings $selectColumns[$i] = implode(' ', $column); } // If select statement use table.* expression if ($wildcardTables) { // Split FROM statement into list of tables $tables = $this->splitSqlExpression(implode(',', $this->from->getElements())); foreach ($tables as $i => $table) { $table = implode(' ', $table); // Exclude subquery from the FROM clause if (strpos($table, '(') === false) { // Unquote $table = str_replace($nquotes, '', $table); $table = str_replace('#__', $this->db->getPrefix(), $table); $table = explode(' ', $table); $alias = end($table); $table = $table[0]; // Chek if exists a wildcard with current alias table? if (in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); } if ($this->join || $table != $alias) { foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $alias . '.' . $name; } } else { foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $name; } } } } } // Now we need to get all tables from any joins // Go through all joins and add them to the tables array if ($this->join) { foreach ($this->join as $join) { // Unquote and replace prefix $joinTbl = str_replace($nquotes, '', (string) $join); $joinTbl = str_replace("#__", $this->db->getPrefix(), $joinTbl); // Exclude subquery if (preg_match('/JOIN\s+(\w+)(?:\s+AS)?(?:\s+(\w+))?/i', $joinTbl, $matches)) { $table = $matches[1]; $alias = isset($matches[2]) ? $matches[2] : $table; // Chek if exists a wildcard with current alias table? if (in_array($alias, $wildcardTables, true)) { if (!isset($cacheCols[$table])) { $cacheCols[$table] = $this->db->getTableColumns($table); } foreach ($cacheCols[$table] as $name => $type) { $knownColumnsByAlias[$alias][] = $alias . '.' . $name; } } } } } } $selectExpression = implode(',', $selectColumns); // Split into the right columns $groupColumns = $this->splitSqlExpression(implode(',', $this->group->getElements())); // Remove column aliases from GROUP statement - SQLSRV does not support it foreach ($groupColumns as $i => $column) { $groupColumns[$i] = implode(' ', $column); $column = str_replace($nquotes, '', $groupColumns[$i]); if (isset($knownAliases[$column])) { // Be sure that this is not a valid column name if (!preg_match('/\b' . preg_quote($column, '/') . '\b/', $selectExpression)) { // Replace column alias by column expression $groupColumns[$i] = $knownAliases[$column]; } } } // Find all alias.* and fill with proper table column names foreach ($filteredColumns as $i => $column) { if (substr($column, -2) === '.*') { unset($filteredColumns[$i]); // Extract alias.* columns into GROUP BY statement $groupColumns = array_merge($groupColumns, $knownColumnsByAlias[substr($column, 0, -2)]); } } $groupColumns = array_merge($groupColumns, $filteredColumns); if ($this->order) { // Remove direction suffixes $dir = array(" DESC\v", " ASC\v"); $orderColumns = $this->splitSqlExpression(implode(',', $this->order->getElements())); foreach ($orderColumns as $i => $column) { $column = implode(' ', $column); $orderColumns[$i] = $column = trim(str_ireplace($dir, '', "$column\v"), "\v"); if (isset($knownAliases[str_replace($nquotes, '', $column)])) { unset($orderColumns[$i]); } if (str_ireplace($aFuncs, '', $column) != $column) { // Do not add aggregate expression unset($orderColumns[$i]); } } $groupColumns = array_merge($groupColumns, $orderColumns); } // Get a unique string of all column names that need to be included in the group statement $this->group = new JDatabaseQueryElement('GROUP BY', array_unique($groupColumns)); return $this; } /** * Return correct rand() function for MSSQL. * * Ensure that the rand() function is MSSQL compatible. * * Usage: * $query->Rand(); * * @return string The correct rand function. * * @since 3.5 */ public function Rand() { return ' NEWID() '; } } home/wuectly/www/libraries/fof/database/driver/sqlsrv.php000060400000065001152456132770017656 0ustar00disconnect(); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 12.1 * @throws RuntimeException */ public function connect() { if ($this->connection) { return; } // Build the connection configuration array. $config = array( 'Database' => $this->options['database'], 'uid' => $this->options['user'], 'pwd' => $this->options['password'], 'CharacterSet' => 'UTF-8', 'ReturnDatesAsStrings' => true); // Make sure the SQLSRV extension for PHP is installed and enabled. if (!function_exists('sqlsrv_connect')) { throw new RuntimeException('PHP extension sqlsrv_connect is not available.'); } // Attempt to connect to the server. if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config))) { throw new RuntimeException('Database sqlsrv_connect failed'); } // Make sure that DB warnings are not returned as errors. sqlsrv_configure('WarningsReturnAsErrors', 0); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } // Set charactersets. $this->utf = $this->setUtf(); } /** * Disconnects the database. * * @return void * * @since 12.1 */ public function disconnect() { // Close the connection. if (is_resource($this->connection)) { foreach ($this->disconnectHandlers as $h) { call_user_func_array($h, array( &$this)); } sqlsrv_close($this->connection); } $this->connection = null; } /** * Get table constraints * * @param string $tableName The name of the database table. * * @return array Any constraints available for the table. * * @since 12.1 */ protected function getTableConstraints($tableName) { $this->connect(); $query = $this->getQuery(true); $this->setQuery( 'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $query->quote($tableName) ); return $this->loadColumn(); } /** * Rename constraints. * * @param array $constraints Array(strings) of table constraints * @param string $prefix A string * @param string $backup A string * * @return void * * @since 12.1 */ protected function renameConstraints($constraints = array(), $prefix = null, $backup = null) { $this->connect(); foreach ($constraints as $constraint) { $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint)); $this->execute(); } } /** * Method to escape a string for usage in an SQL statement. * * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need * to handle the escaping ourselves. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 12.1 */ public function escape($text, $extra = false) { $result = addslashes($text); $result = str_replace("\'", "''", $result); $result = str_replace('\"', '"', $result); $result = str_replace('\/', '/', $result); if ($extra) { // We need the below str_replace since the search in sql server doesn't recognize _ character. $result = str_replace('_', '[_]', $result); } return $result; } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 12.1 */ public function connected() { // TODO: Run a blank query here return true; } /** * Drops a table from the database. * * @param string $tableName The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return FOFDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 */ public function dropTable($tableName, $ifExists = true) { $this->connect(); $query = $this->getQuery(true); if ($ifExists) { $this->setQuery( 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName ); } else { $this->setQuery('DROP TABLE ' . $tableName); } $this->execute(); return $this; } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. * * @since 12.1 */ public function getAffectedRows() { $this->connect(); return sqlsrv_rows_affected($this->cursor); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return mixed The collation in use by the database or boolean false if not supported. * * @since 12.1 */ public function getCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Method to get the database connection collation, as reported by the driver. If the connector doesn't support * reporting this value please return an empty string. * * @return string */ public function getConnectionCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Get the number of returned rows for the previous executed SQL statement. * * @param resource $cursor An optional database cursor resource to extract the row count from. * * @return integer The number of returned rows. * * @since 12.1 */ public function getNumRows($cursor = null) { $this->connect(); return sqlsrv_num_rows($cursor ? $cursor : $this->cursor); } /** * Retrieves field information about the given tables. * * @param mixed $table A table name * @param boolean $typeOnly True to only return field types. * * @return array An array of fields. * * @since 12.1 * @throws RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $result = array(); $table_temp = $this->replacePrefix((string) $table); // Set the query to get the table fields statement. $this->setQuery( 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' . ' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp) ); $fields = $this->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type); } } // If we want the whole field data object add that to the list. else { foreach ($fields as $field) { if (stristr(strtolower($field->Type), "nvarchar")) { $field->Default = ""; } $result[$field->Field] = $field; } } return $result; } /** * Shows the table CREATE statement that creates the given tables. * * This is unsupported by MSSQL. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 12.1 * @throws RuntimeException */ public function getTableCreate($tables) { $this->connect(); return ''; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 12.1 * @throws RuntimeException */ public function getTableKeys($table) { $this->connect(); // TODO To implement. return array(); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 12.1 * @throws RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';'); $tables = $this->loadColumn(); return $tables; } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 12.1 */ public function getVersion() { $this->connect(); $version = sqlsrv_server_info($this->connection); return $version['SQLServerVersion']; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object &$object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. * * @since 12.1 * @throws RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = array(); $values = array(); $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)'; foreach (get_object_vars($object) as $k => $v) { // Only process non-null scalars. if (is_array($v) or is_object($v) or $v === null) { continue; } if (!$this->checkFieldExists($table, $k)) { continue; } if ($k[0] == '_') { // Internal field continue; } if ($k == $key && $key == 0) { continue; } $fields[] = $this->quoteName($k); $values[] = $this->Quote($v); } // Set the query and execute the insert. $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values))); if (!$this->execute()) { return false; } $id = $this->insertid(); if ($key && $id) { $object->$key = $id; } return true; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. * * @since 12.1 */ public function insertid() { $this->connect(); // TODO: SELECT IDENTITY $this->setQuery('SELECT @@IDENTITY'); return (int) $this->loadResult(); } /** * Method to get the first field of the first row of the result set from the database query. * * @return mixed The return value or null if the query failed. * * @since 12.1 * @throws RuntimeException */ public function loadResult() { $ret = null; // Execute the query and get the result set cursor. if (!($cursor = $this->execute())) { return null; } // Get the first row from the result set as an array. if ($row = sqlsrv_fetch_array($cursor, SQLSRV_FETCH_NUMERIC)) { $ret = $row[0]; } // Free up system resources and return. $this->freeResult($cursor); // For SQLServer - we need to strip slashes $ret = stripslashes($ret); return $ret; } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. * * @since 12.1 * @throws RuntimeException * @throws Exception */ public function execute() { $this->connect(); if (!is_resource($this->connection)) { if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); } throw new RuntimeException($this->errorMsg, $this->errorNum); } // Take a local copy so that we don't modify the original query and cause issues later $query = $this->replacePrefix((string) $this->sql); if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) { $query = $this->limit($query, $this->limit, $this->offset); } // Increment the query counter. $this->count++; // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; if (class_exists('JLog')) { JLog::add($query, JLog::DEBUG, 'databasequery'); } $this->timings[] = microtime(true); } // SQLSrv_num_rows requires a static or keyset cursor. if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0) { $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET); } else { $array = array(); } // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. $this->cursor = @sqlsrv_query($this->connection, $query, array(), $array); if ($this->debug) { $this->timings[] = microtime(true); if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) { $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } else { $this->callStacks[] = debug_backtrace(); } } // If an error occurred handle it. if (!$this->cursor) { // Get the error number and message before we execute any more queries. $errorNum = $this->getErrorNumber(); $errorMsg = $this->getErrorMessage($query); // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { // Get the error number and message. $this->errorNum = $this->getErrorNumber(); $this->errorMsg = $this->getErrorMessage($query); // Throw the normal query exception. if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); } throw new RuntimeException($this->errorMsg, $this->errorNum, $e); } // Since we were able to reconnect, run the query again. return $this->execute(); } // The server was not disconnected. else { // Get the error number and message from before we tried to reconnect. $this->errorNum = $errorNum; $this->errorMsg = $errorMsg; // Throw the normal query exception. if (class_exists('JLog')) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); } throw new RuntimeException($this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * This function replaces a string identifier $prefix with the string held is the * tablePrefix class variable. * * @param string $query The SQL statement to prepare. * @param string $prefix The common table prefix. * * @return string The processed SQL statement. * * @since 12.1 */ public function replacePrefix($query, $prefix = '#__') { $startPos = 0; $literal = ''; $query = trim($query); $n = strlen($query); while ($startPos < $n) { $ip = strpos($query, $prefix, $startPos); if ($ip === false) { break; } $j = strpos($query, "N'", $startPos); $k = strpos($query, '"', $startPos); if (($k !== false) && (($k < $j) || ($j === false))) { $quoteChar = '"'; $j = $k; } else { $quoteChar = "'"; } if ($j === false) { $j = $n; } $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos)); $startPos = $j; $j = $startPos + 1; if ($j >= $n) { break; } // Quote comes first, find end of quote while (true) { $k = strpos($query, $quoteChar, $j); $escaped = false; if ($k === false) { break; } $l = $k - 1; while ($l >= 0 && $query[$l] == '\\') { $l--; $escaped = !$escaped; } if ($escaped) { $j = $k + 1; continue; } break; } if ($k === false) { // Error in the query - no end quote; ignore it break; } $literal .= substr($query, $startPos, $k - $startPos + 1); $startPos = $k + 1; } if ($startPos < $n) { $literal .= substr($query, $startPos, $n - $startPos); } return $literal; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 12.1 * @throws RuntimeException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC))) { throw new RuntimeException('Could not connect to database'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 12.1 */ public function setUtf() { return false; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('COMMIT TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('ROLLBACK TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 12.1 * @throws RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { if ($this->setQuery('BEGIN TRANSACTION')->execute()) { $this->transactionDepth = 1; } return; } $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } /** * Method to fetch a row from the result set cursor as an array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchArray($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC); } /** * Method to fetch a row from the result set cursor as an associative array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchAssoc($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC); } /** * Method to fetch a row from the result set cursor as an object. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * @param string $class The class name to use for the returned row object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 12.1 */ protected function fetchObject($cursor = null, $class = 'stdClass') { return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class); } /** * Method to free up the memory used for the result set. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return void * * @since 12.1 */ protected function freeResult($cursor = null) { sqlsrv_free_stmt($cursor ? $cursor : $this->cursor); } /** * Method to check and see if a field exists in a table. * * @param string $table The table in which to verify the field. * @param string $field The field to verify. * * @return boolean True if the field exists in the table. * * @since 12.1 */ protected function checkFieldExists($table, $field) { $this->connect(); $table = $this->replacePrefix((string) $table); $query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" . " ORDER BY ORDINAL_POSITION"; $this->setQuery($query); if ($this->loadResult()) { return true; } else { return false; } } /** * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set. * * @param string $query The SQL statement to process. * @param integer $limit The maximum affected rows to set. * @param integer $offset The affected row offset to set. * * @return string The processed SQL statement. * * @since 12.1 */ protected function limit($query, $limit, $offset) { if ($limit == 0 && $offset == 0) { return $query; } $start = $offset + 1; $end = $offset + $limit; $orderBy = stristr($query, 'ORDER BY'); if (is_null($orderBy) || empty($orderBy)) { $orderBy = 'ORDER BY (select 0)'; } $query = str_ireplace($orderBy, '', $query); $rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM '; $query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1); return $query; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return FOFDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $constraints = array(); if (!is_null($prefix) && !is_null($backup)) { $constraints = $this->getTableConstraints($oldTable); } if (!empty($constraints)) { $this->renameConstraints($constraints, $prefix, $backup); } $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'"); return $this->execute(); } /** * Locks a table in the database. * * @param string $tableName The name of the table to lock. * * @return FOFDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function lockTable($tableName) { return $this; } /** * Unlocks tables in the database. * * @return FOFDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 12.1 * @throws RuntimeException */ public function unlockTables() { return $this; } /** * Return the actual SQL Error number * * @return integer The SQL Error number * * @since 3.4.6 */ protected function getErrorNumber() { $errors = sqlsrv_errors(); return $errors[0]['SQLSTATE']; } /** * Return the actual SQL Error message * * @param string $query The SQL Query that fails * * @return string The SQL Error message * * @since 3.4.6 */ protected function getErrorMessage($query) { $errors = sqlsrv_errors(); $errorMessage = (string) $errors[0]['message']; // Replace the Databaseprefix with `#__` if we are not in Debug if (!$this->debug) { $errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage); $query = str_replace($this->tablePrefix, '#__', $query); } return $errorMessage . ' SQL=' . $query; } } home/wuectly/www/libraries/f0f/database/query/sqlsrv.php000060400000021007152456172410017422 0ustar00type) { case 'select': $query .= (string) $this->select; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->group) { $query .= (string) $this->group; } if ($this->order) { $query .= (string) $this->order; } if ($this->having) { $query .= (string) $this->having; } if ($this instanceof F0FDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0)) { $query = $this->processLimit($query, $this->limit, $this->offset); } break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } // Columns-Values method elseif ($this->values) { if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->insert->getElements(); $tableName = array_shift($elements); $query .= 'VALUES '; $query .= (string) $this->values; if ($this->autoIncrementField) { $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;'; } if ($this->where) { $query .= (string) $this->where; } } break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; case 'update': $query .= (string) $this->update; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } $query .= (string) $this->set; if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; default: $query = parent::__toString(); break; } return $query; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * @param string $value The value to cast as a char. * * @return string Returns the cast value. * * @since 11.1 */ public function castAsChar($value) { return 'CAST(' . $value . ' as NVARCHAR(10))'; } /** * Gets the function to determine the length of a character string. * * @param string $field A value. * @param string $operator Comparison operator between charLength integer value and $condition * @param string $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 11.1 */ public function charLength($field, $operator = null, $condition = null) { return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : ''); } /** * Concatenates an array of column names or values. * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. * * @since 11.1 */ public function concatenate($values, $separator = null) { if ($separator) { return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')'; } else { return '(' . implode('+', $values) . ')'; } } /** * Gets the current date and time. * * @return string * * @since 11.1 */ public function currentTimestamp() { return 'GETDATE()'; } /** * Get the length of a string in bytes. * * @param string $value The string to measure. * * @return integer * * @since 11.1 */ public function length($value) { return 'LEN(' . $value . ')'; } /** * Add to the current date and time. * Usage: * $query->select($query->dateAdd()); * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * * @param datetime $date The date to add to; type may be time or datetime. * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @since 13.1 * @note Not all drivers support all units. * @link http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information */ public function dateAdd($date, $interval, $datePart) { return "DATEADD('" . $datePart . "', '" . $interval . "', '" . $date . "'" . ')'; } /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 12.1 */ public function processLimit($query, $limit, $offset = 0) { if ($limit == 0 && $offset == 0) { return $query; } $start = $offset + 1; $end = $offset + $limit; $orderBy = stristr($query, 'ORDER BY'); if (is_null($orderBy) || empty($orderBy)) { $orderBy = 'ORDER BY (select 0)'; } $query = str_ireplace($orderBy, '', $query); $rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM '; $query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1); $query = 'SELECT * FROM (' . $query . ') A WHERE A.RowNumber BETWEEN ' . $start . ' AND ' . $end; return $query; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return F0FDatabaseQuery Returns this object to allow chaining. * * @since 12.1 */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Return correct rand() function for MSSQL. * * Ensure that the rand() function is MSSQL compatible. * * Usage: * $query->Rand(); * * @return string The correct rand function. * * @since 3.5 */ public function Rand() { return ' NEWID() '; } } home/wuectly/www/libraries/fof/database/iterator/sqlsrv.php000060400000002430152456575750020222 0ustar00cursor); } /** * Method to fetch a row from the result set cursor as an object. * * @return mixed Either the next row from the result set or false if there are no more rows. */ protected function fetchObject() { return @sqlsrv_fetch_object($this->cursor, $this->class); } /** * Method to free up the memory used for the result set. * * @return void */ protected function freeResult() { @sqlsrv_free_stmt($this->cursor); } } home/wuectly/www/libraries/fof/database/query/sqlsrv.php000060400000021007152456624100017517 0ustar00type) { case 'select': $query .= (string) $this->select; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->group) { $query .= (string) $this->group; } if ($this->order) { $query .= (string) $this->order; } if ($this->having) { $query .= (string) $this->having; } if ($this instanceof FOFDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0)) { $query = $this->processLimit($query, $this->limit, $this->offset); } break; case 'insert': $query .= (string) $this->insert; // Set method if ($this->set) { $query .= (string) $this->set; } // Columns-Values method elseif ($this->values) { if ($this->columns) { $query .= (string) $this->columns; } $elements = $this->insert->getElements(); $tableName = array_shift($elements); $query .= 'VALUES '; $query .= (string) $this->values; if ($this->autoIncrementField) { $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;'; } if ($this->where) { $query .= (string) $this->where; } } break; case 'delete': $query .= (string) $this->delete; $query .= (string) $this->from; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; case 'update': $query .= (string) $this->update; if ($this->join) { // Special case for joins foreach ($this->join as $join) { $query .= (string) $join; } } $query .= (string) $this->set; if ($this->where) { $query .= (string) $this->where; } if ($this->order) { $query .= (string) $this->order; } break; default: $query = parent::__toString(); break; } return $query; } /** * Casts a value to a char. * * Ensure that the value is properly quoted before passing to the method. * * @param string $value The value to cast as a char. * * @return string Returns the cast value. * * @since 11.1 */ public function castAsChar($value) { return 'CAST(' . $value . ' as NVARCHAR(10))'; } /** * Gets the function to determine the length of a character string. * * @param string $field A value. * @param string $operator Comparison operator between charLength integer value and $condition * @param string $condition Integer value to compare charLength with. * * @return string The required char length call. * * @since 11.1 */ public function charLength($field, $operator = null, $condition = null) { return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : ''); } /** * Concatenates an array of column names or values. * * @param array $values An array of values to concatenate. * @param string $separator As separator to place between each value. * * @return string The concatenated values. * * @since 11.1 */ public function concatenate($values, $separator = null) { if ($separator) { return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')'; } else { return '(' . implode('+', $values) . ')'; } } /** * Gets the current date and time. * * @return string * * @since 11.1 */ public function currentTimestamp() { return 'GETDATE()'; } /** * Get the length of a string in bytes. * * @param string $value The string to measure. * * @return integer * * @since 11.1 */ public function length($value) { return 'LEN(' . $value . ')'; } /** * Add to the current date and time. * Usage: * $query->select($query->dateAdd()); * Prefixing the interval with a - (negative sign) will cause subtraction to be used. * * @param datetime $date The date to add to; type may be time or datetime. * @param string $interval The string representation of the appropriate number of units * @param string $datePart The part of the date to perform the addition on * * @return string The string with the appropriate sql for addition of dates * * @since 13.1 * @note Not all drivers support all units. * @link http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information */ public function dateAdd($date, $interval, $datePart) { return "DATEADD('" . $datePart . "', '" . $interval . "', '" . $date . "'" . ')'; } /** * Method to modify a query already in string format with the needed * additions to make the query limited to a particular number of * results, or start at a particular offset. * * @param string $query The query in string format * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return string * * @since 12.1 */ public function processLimit($query, $limit, $offset = 0) { if ($limit == 0 && $offset == 0) { return $query; } $start = $offset + 1; $end = $offset + $limit; $orderBy = stristr($query, 'ORDER BY'); if (is_null($orderBy) || empty($orderBy)) { $orderBy = 'ORDER BY (select 0)'; } $query = str_ireplace($orderBy, '', $query); $rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM '; $query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1); $query = 'SELECT * FROM (' . $query . ') A WHERE A.RowNumber BETWEEN ' . $start . ' AND ' . $end; return $query; } /** * Sets the offset and limit for the result set, if the database driver supports it. * * Usage: * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record) * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record) * * @param integer $limit The limit for the result set * @param integer $offset The offset for the result set * * @return FOFDatabaseQuery Returns this object to allow chaining. * * @since 12.1 */ public function setLimit($limit = 0, $offset = 0) { $this->limit = (int) $limit; $this->offset = (int) $offset; return $this; } /** * Return correct rand() function for MSSQL. * * Ensure that the rand() function is MSSQL compatible. * * Usage: * $query->Rand(); * * @return string The correct rand function. * * @since 3.5 */ public function Rand() { return ' NEWID() '; } } home/wuectly/www/libraries/joomla/database/iterator/sqlsrv.php000060400000002207152457212030020707 0ustar00 * @license GNU General Public License version 2 or later; see LICENSE */ defined('JPATH_PLATFORM') or die; /** * SQL server database iterator. * * @since 3.0.0 */ class JDatabaseIteratorSqlsrv extends JDatabaseIterator { /** * Get the number of rows in the result set for the executed SQL given by the cursor. * * @return integer The number of rows in the result set. * * @since 3.0.0 * @see Countable::count() */ public function count() { return sqlsrv_num_rows($this->cursor); } /** * Method to fetch a row from the result set cursor as an object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 3.0.0 */ protected function fetchObject() { return sqlsrv_fetch_object($this->cursor, $this->class); } /** * Method to free up the memory used for the result set. * * @return void * * @since 3.0.0 */ protected function freeResult() { sqlsrv_free_stmt($this->cursor); } } home/wuectly/www/libraries/joomla/database/driver/sqlsrv.php000060400000066027152457272150020374 0ustar00 * @license GNU General Public License version 2 or later; see LICENSE */ defined('JPATH_PLATFORM') or die; /** * SQL Server database driver * * @link https://msdn.microsoft.com/en-us/library/cc296152(SQL.90).aspx * @since 3.0.0 */ class JDatabaseDriverSqlsrv extends JDatabaseDriver { /** * The name of the database driver. * * @var string * @since 3.0.0 */ public $name = 'sqlsrv'; /** * The type of the database server family supported by this driver. * * @var string * @since CMS 3.5.0 */ public $serverType = 'mssql'; /** * The character(s) used to quote SQL statement names such as table names or field names, * etc. The child classes should define this as necessary. If a single character string the * same character is used for both sides of the quoted name, else the first character will be * used for the opening quote and the second for the closing quote. * * @var string * @since 3.0.0 */ protected $nameQuote = '[]'; /** * The null or zero representation of a timestamp for the database driver. This should be * defined in child classes to hold the appropriate value for the engine. * * @var string * @since 3.0.0 */ protected $nullDate = '1900-01-01 00:00:00'; /** * @var string The minimum supported database version. * @since 3.0.0 */ protected static $dbMinimum = '10.50.1600.1'; /** * Test to see if the SQLSRV connector is available. * * @return boolean True on success, false otherwise. * * @since 3.0.0 */ public static function isSupported() { return function_exists('sqlsrv_connect'); } /** * Constructor. * * @param array $options List of options used to configure the connection * * @since 3.0.0 */ public function __construct($options) { // Get some basic values from the options. $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost'; $options['user'] = (isset($options['user'])) ? $options['user'] : ''; $options['password'] = (isset($options['password'])) ? $options['password'] : ''; $options['database'] = (isset($options['database'])) ? $options['database'] : ''; $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true; // Finalize initialisation parent::__construct($options); } /** * Connects to the database if needed. * * @return void Returns void if the database connected successfully. * * @since 3.0.0 * @throws RuntimeException */ public function connect() { if ($this->connection) { return; } // Build the connection configuration array. $config = array( 'Database' => $this->options['database'], 'uid' => $this->options['user'], 'pwd' => $this->options['password'], 'CharacterSet' => 'UTF-8', 'ReturnDatesAsStrings' => true, ); // Make sure the SQLSRV extension for PHP is installed and enabled. if (!self::isSupported()) { throw new JDatabaseExceptionUnsupported('The sqlsrv extension for PHP is not installed or enabled..'); } // Attempt to connect to the server. if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config))) { throw new JDatabaseExceptionConnecting('Database sqlsrv_connect failed, ' . print_r(sqlsrv_errors(), true)); } // Make sure that DB warnings are not returned as errors. sqlsrv_configure('WarningsReturnAsErrors', 0); // If auto-select is enabled select the given database. if ($this->options['select'] && !empty($this->options['database'])) { $this->select($this->options['database']); } // Set charactersets. $this->utf = $this->setUtf(); // Set QUOTED_IDENTIFIER always ON sqlsrv_query($this->connection, 'SET QUOTED_IDENTIFIER ON'); } /** * Disconnects the database. * * @return void * * @since 3.0.0 */ public function disconnect() { // Close the connection. if (is_resource($this->connection)) { foreach ($this->disconnectHandlers as $h) { call_user_func_array($h, array( &$this)); } sqlsrv_close($this->connection); } $this->connection = null; } /** * Get table constraints * * @param string $tableName The name of the database table. * * @return array Any constraints available for the table. * * @since 3.0.0 */ protected function getTableConstraints($tableName) { $this->connect(); $query = $this->getQuery(true); $this->setQuery( 'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $query->quote($tableName) ); return $this->loadColumn(); } /** * Rename constraints. * * @param array $constraints Array(strings) of table constraints * @param string $prefix A string * @param string $backup A string * * @return void * * @since 3.0.0 */ protected function renameConstraints($constraints = array(), $prefix = null, $backup = null) { $this->connect(); foreach ($constraints as $constraint) { $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint)); $this->execute(); } } /** * Method to escape a string for usage in an SQL statement. * * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need * to handle the escaping ourselves. * * @param string $text The string to be escaped. * @param boolean $extra Optional parameter to provide extra escaping. * * @return string The escaped string. * * @since 3.0.0 */ public function escape($text, $extra = false) { if (is_int($text)) { return $text; } if (is_float($text)) { // Force the dot as a decimal point. return str_replace(',', '.', $text); } $result = str_replace("'", "''", $text); // SQL Server does not accept NULL byte in query string $result = str_replace("\0", "' + CHAR(0) + N'", $result); // Fix for SQL Server escape sequence, see https://support.microsoft.com/en-us/kb/164291 $result = str_replace( array("\\\n", "\\\r", "\\\\\r\r\n"), array("\\\\\n\n", "\\\\\r\r", "\\\\\r\n\r\n"), $result ); if ($extra) { // Escape special chars $result = str_replace( array('[', '_', '%'), array('[[]', '[_]', '[%]'), $result ); } return $result; } /** * Quotes and optionally escapes a string to database requirements for use in database queries. * * @param mixed $text A string or an array of strings to quote. * @param boolean $escape True (default) to escape the string, false to leave it unchanged. * * @return string The quoted input string. * * @note Accepting an array of strings was added in 3.1.4. * @since 1.7.0 */ public function quote($text, $escape = true) { if (is_array($text)) { return parent::quote($text, $escape); } // To support unicode on MSSQL we have to add prefix N return 'N\'' . ($escape ? $this->escape($text) : $text) . '\''; } /** * Quotes a binary string to database requirements for use in database queries. * * @param mixed $data A binary string to quote. * * @return string The binary quoted input string. * * @since 3.9.12 */ public function quoteBinary($data) { // ODBC syntax for hexadecimal literals return '0x' . bin2hex($data); } /** * Determines if the connection to the server is active. * * @return boolean True if connected to the database engine. * * @since 3.0.0 */ public function connected() { // TODO: Run a blank query here return true; } /** * Drops a table from the database. * * @param string $tableName The name of the database table to drop. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. * * @return JDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 3.0.0 */ public function dropTable($tableName, $ifExists = true) { $this->connect(); $query = $this->getQuery(true); if ($ifExists) { $this->setQuery( 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName ); } else { $this->setQuery('DROP TABLE ' . $tableName); } $this->execute(); return $this; } /** * Get the number of affected rows for the previous executed SQL statement. * * @return integer The number of affected rows. * * @since 3.0.0 */ public function getAffectedRows() { $this->connect(); return sqlsrv_rows_affected($this->cursor); } /** * Method to get the database collation in use by sampling a text field of a table in the database. * * @return mixed The collation in use by the database or boolean false if not supported. * * @since 3.0.0 */ public function getCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Method to get the database connection collation, as reported by the driver. If the connector doesn't support * reporting this value please return an empty string. * * @return string */ public function getConnectionCollation() { // TODO: Not fake this return 'MSSQL UTF-8 (UCS2)'; } /** * Get the number of returned rows for the previous executed SQL statement. * * @param resource $cursor An optional database cursor resource to extract the row count from. * * @return integer The number of returned rows. * * @since 3.0.0 */ public function getNumRows($cursor = null) { $this->connect(); return sqlsrv_num_rows($cursor ? $cursor : $this->cursor); } /** * Retrieves field information about the given tables. * * @param mixed $table A table name * @param boolean $typeOnly True to only return field types. * * @return array An array of fields. * * @since 3.0.0 * @throws RuntimeException */ public function getTableColumns($table, $typeOnly = true) { $result = array(); $table_temp = $this->replacePrefix((string) $table); // Set the query to get the table fields statement. $this->setQuery( 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' . ' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp) ); $fields = $this->loadObjectList(); // If we only want the type as the value add just that to the list. if ($typeOnly) { foreach ($fields as $field) { $result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type); } } // If we want the whole field data object add that to the list. else { foreach ($fields as $field) { $field->Default = preg_replace("/(^(\(\(|\('|\(N'|\()|(('\)|(?Default); $result[$field->Field] = $field; } } return $result; } /** * Shows the table CREATE statement that creates the given tables. * * This is unsupported by MSSQL. * * @param mixed $tables A table name or a list of table names. * * @return array A list of the create SQL for the tables. * * @since 3.0.0 * @throws RuntimeException */ public function getTableCreate($tables) { $this->connect(); return ''; } /** * Get the details list of keys for a table. * * @param string $table The name of the table. * * @return array An array of the column specification for the table. * * @since 3.0.0 * @throws RuntimeException */ public function getTableKeys($table) { $this->connect(); // TODO To implement. return array(); } /** * Method to get an array of all tables in the database. * * @return array An array of all the tables in the database. * * @since 3.0.0 * @throws RuntimeException */ public function getTableList() { $this->connect(); // Set the query to get the tables statement. $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';'); $tables = $this->loadColumn(); return $tables; } /** * Get the version of the database connector. * * @return string The database connector version. * * @since 3.0.0 */ public function getVersion() { $this->connect(); $version = sqlsrv_server_info($this->connection); return $version['SQLServerVersion']; } /** * Inserts a row into a table based on an object's properties. * * @param string $table The name of the database table to insert into. * @param object &$object A reference to an object whose public properties match the table fields. * @param string $key The name of the primary key. If provided the object property is updated. * * @return boolean True on success. * * @since 3.0.0 * @throws RuntimeException */ public function insertObject($table, &$object, $key = null) { $fields = array(); $values = array(); $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)'; foreach (get_object_vars($object) as $k => $v) { // Only process non-null scalars. if (is_array($v) or is_object($v) or $v === null) { continue; } if (!$this->checkFieldExists($table, $k)) { continue; } if ($k[0] == '_') { // Internal field continue; } if ($k == $key && $key == 0) { continue; } $fields[] = $this->quoteName($k); $values[] = $this->Quote($v); } // Set the query and execute the insert. $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values))); if (!$this->execute()) { return false; } $id = $this->insertid(); if ($key && $id) { $object->$key = $id; } return true; } /** * Method to get the auto-incremented value from the last INSERT statement. * * @return integer The value of the auto-increment field from the last inserted row. * * @since 3.0.0 */ public function insertid() { $this->connect(); // TODO: SELECT IDENTITY $this->setQuery('SELECT @@IDENTITY'); return (int) $this->loadResult(); } /** * Execute the SQL statement. * * @return mixed A database cursor resource on success, boolean false on failure. * * @since 3.0.0 * @throws RuntimeException * @throws Exception */ public function execute() { $this->connect(); // Take a local copy so that we don't modify the original query and cause issues later $query = $this->replacePrefix((string) $this->sql); if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) { $query = $this->limit($query, $this->limit, $this->offset); } if (!is_resource($this->connection)) { JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum); } // Increment the query counter. $this->count++; // Reset the error values. $this->errorNum = 0; $this->errorMsg = ''; // If debugging is enabled then let's log the query. if ($this->debug) { // Add the query to the object queue. $this->log[] = $query; JLog::add($query, JLog::DEBUG, 'databasequery'); $this->timings[] = microtime(true); } // SQLSrv_num_rows requires a static or keyset cursor. if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0) { $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET); } else { $array = array(); } // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. $this->cursor = @sqlsrv_query($this->connection, $query, array(), $array); if ($this->debug) { $this->timings[] = microtime(true); if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) { $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); } else { $this->callStacks[] = debug_backtrace(); } } // If an error occurred handle it. if (!$this->cursor) { // Get the error number and message before we execute any more queries. $errorNum = $this->getErrorNumber(); $errorMsg = $this->getErrorMessage(); // Check if the server was disconnected. if (!$this->connected()) { try { // Attempt to reconnect. $this->connection = null; $this->connect(); } // If connect fails, ignore that exception and throw the normal exception. catch (RuntimeException $e) { // Get the error number and message. $this->errorNum = $this->getErrorNumber(); $this->errorMsg = $this->getErrorMessage(); // Throw the normal query exception. JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e); } // Since we were able to reconnect, run the query again. return $this->execute(); } // The server was not disconnected. else { // Get the error number and message from before we tried to reconnect. $this->errorNum = $errorNum; $this->errorMsg = $errorMsg; // Throw the normal query exception. JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum); } } return $this->cursor; } /** * This function replaces a string identifier $prefix with the string held is the * tablePrefix class variable. * * @param string $query The SQL statement to prepare. * @param string $prefix The common table prefix. * * @return string The processed SQL statement. * * @since 3.0.0 */ public function replacePrefix($query, $prefix = '#__') { $query = trim($query); if (strpos($query, "'")) { $parts = explode("'", $query); for ($nIndex = 0, $size = count($parts); $nIndex < $size; $nIndex = $nIndex + 2) { if (strpos($parts[$nIndex], $prefix) !== false) { $parts[$nIndex] = str_replace($prefix, $this->tablePrefix, $parts[$nIndex]); } } $query = implode("'", $parts); } else { $query = str_replace($prefix, $this->tablePrefix, $query); } return $query; } /** * Select a database for use. * * @param string $database The name of the database to select for use. * * @return boolean True if the database was successfully selected. * * @since 3.0.0 * @throws RuntimeException */ public function select($database) { $this->connect(); if (!$database) { return false; } if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC))) { throw new JDatabaseExceptionConnecting('Could not connect to SQL Server database.'); } return true; } /** * Set the connection to use UTF-8 character encoding. * * @return boolean True on success. * * @since 3.0.0 */ public function setUtf() { return false; } /** * Method to commit a transaction. * * @param boolean $toSavepoint If true, commit to the last savepoint. * * @return void * * @since 3.0.0 * @throws RuntimeException */ public function transactionCommit($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('COMMIT TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $this->transactionDepth--; } /** * Method to roll back a transaction. * * @param boolean $toSavepoint If true, rollback to the last savepoint. * * @return void * * @since 3.0.0 * @throws RuntimeException */ public function transactionRollback($toSavepoint = false) { $this->connect(); if (!$toSavepoint || $this->transactionDepth <= 1) { if ($this->setQuery('ROLLBACK TRANSACTION')->execute()) { $this->transactionDepth = 0; } return; } $savepoint = 'SP_' . ($this->transactionDepth - 1); $this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth--; } } /** * Method to initialize a transaction. * * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. * * @return void * * @since 3.0.0 * @throws RuntimeException */ public function transactionStart($asSavepoint = false) { $this->connect(); if (!$asSavepoint || !$this->transactionDepth) { if ($this->setQuery('BEGIN TRANSACTION')->execute()) { $this->transactionDepth = 1; } return; } $savepoint = 'SP_' . $this->transactionDepth; $this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint)); if ($this->execute()) { $this->transactionDepth++; } } /** * Method to fetch a row from the result set cursor as an array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 3.0.0 */ protected function fetchArray($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC); } /** * Method to fetch a row from the result set cursor as an associative array. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 3.0.0 */ protected function fetchAssoc($cursor = null) { return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC); } /** * Method to fetch a row from the result set cursor as an object. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * @param string $class The class name to use for the returned row object. * * @return mixed Either the next row from the result set or false if there are no more rows. * * @since 3.0.0 */ protected function fetchObject($cursor = null, $class = 'stdClass') { // Class has to be loaded for sqlsrv on windows platform class_exists($class); return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class); } /** * Method to free up the memory used for the result set. * * @param mixed $cursor The optional result set cursor from which to fetch the row. * * @return void * * @since 3.0.0 */ protected function freeResult($cursor = null) { sqlsrv_free_stmt($cursor ? $cursor : $this->cursor); } /** * Method to check and see if a field exists in a table. * * @param string $table The table in which to verify the field. * @param string $field The field to verify. * * @return boolean True if the field exists in the table. * * @since 3.0.0 */ protected function checkFieldExists($table, $field) { $this->connect(); $table = $this->replacePrefix((string) $table); $query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field' ORDER BY ORDINAL_POSITION"; $this->setQuery($query); if ($this->loadResult()) { return true; } else { return false; } } /** * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set. * * @param string $query The SQL statement to process. * @param integer $limit The maximum affected rows to set. * @param integer $offset The affected row offset to set. * * @return string The processed SQL statement. * * @since 3.0.0 */ protected function limit($query, $limit, $offset) { if ($limit) { $total = $offset + $limit; $position = stripos($query, 'SELECT'); $distinct = stripos($query, 'SELECT DISTINCT'); if ($position === $distinct) { $query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $total, $position, 15); } else { $query = substr_replace($query, 'SELECT TOP ' . (int) $total, $position, 6); } } if (!$offset) { return $query; } return PHP_EOL . 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS RowNumber FROM (' . $query . PHP_EOL . ') AS A) AS A WHERE RowNumber > ' . (int) $offset; } /** * Renames a table in the database. * * @param string $oldTable The name of the table to be renamed * @param string $newTable The new name for the table. * @param string $backup Table prefix * @param string $prefix For the table - used to rename constraints in non-mysql databases * * @return JDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 3.0.0 * @throws RuntimeException */ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) { $constraints = array(); if (!is_null($prefix) && !is_null($backup)) { $constraints = $this->getTableConstraints($oldTable); } if (!empty($constraints)) { $this->renameConstraints($constraints, $prefix, $backup); } $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'"); return $this->execute(); } /** * Locks a table in the database. * * @param string $tableName The name of the table to lock. * * @return JDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 3.0.0 * @throws RuntimeException */ public function lockTable($tableName) { return $this; } /** * Unlocks tables in the database. * * @return JDatabaseDriverSqlsrv Returns this object to support chaining. * * @since 3.0.0 * @throws RuntimeException */ public function unlockTables() { return $this; } /** * Return the actual SQL Error number * * @return integer The SQL Error number * * @since 3.4.6 */ protected function getErrorNumber() { $errors = sqlsrv_errors(); return $errors[0]['code']; } /** * Return the actual SQL Error message * * @return string The SQL Error message * * @since 3.4.6 */ protected function getErrorMessage() { $errors = sqlsrv_errors(); $errorMessage = (string) $errors[0]['message']; // Replace the Databaseprefix with `#__` if we are not in Debug if (!$this->debug) { $errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage); } return $errorMessage; } /** * Get the query strings to alter the character set and collation of a table. * * @param string $tableName The name of the table * * @return string[] The queries required to alter the table's character set and collation * * @since CMS 3.5.0 */ public function getAlterTableCharacterSet($tableName) { return array(); } /** * Return the query string to create new Database. * Each database driver, other than MySQL, need to override this member to return correct string. * * @param stdClass $options Object used to pass user and database name to database driver. * This object must have "db_name" and "db_user" set. * @param boolean $utf True if the database supports the UTF-8 character set. * * @return string The query that creates database * * @since 3.0.1 */ protected function getCreateDatabaseQuery($options, $utf) { return 'CREATE DATABASE ' . $this->quoteName($options->db_name); } }