Index: language/en/install.php =================================================================== --- language/en/install.php (revision 10488) +++ language/en/install.php (working copy) @@ -147,6 +147,7 @@ 'DLL_MBSTRING' => 'Multi-byte character support', 'DLL_MSSQL' => 'MSSQL Server 2000+', 'DLL_MSSQL_ODBC' => 'MSSQL Server 2000+ via ODBC', + 'DLL_MSSQLNATIVE' => 'MSSQL Server 2005+ [ Native ]', 'DLL_MYSQL' => 'MySQL', 'DLL_MYSQLI' => 'MySQL with MySQLi Extension', 'DLL_ORACLE' => 'Oracle', @@ -214,6 +215,7 @@
  • SQLite 2.8.2+
  • Firebird 2.1+
  • MS SQL Server 2000 or above (directly or via ODBC)
  • +
  • MS SQL Server 2005 or above (native)
  • Oracle
  • Index: docs/CHANGELOG.html =================================================================== --- docs/CHANGELOG.html (revision 10488) +++ docs/CHANGELOG.html (working copy) @@ -96,6 +96,7 @@
  • [Fix] Honor minimum and maximum password length in generated passwords as much as we can. (Bug #13181)
  • [Fix] No longer return the character O in generated random strings and passwords. (Bug #57345)
  • [Fix] Some XHTML corrections in subsilver2. (Bug #57505)
  • +
  • [Feature] Support for Microsoft's Native SQL Server Driver for PHP (Bug #57055 - Patch by Chris Pucci at Microsoft)
  • 1.ii. Changes since 3.0.6

    Index: includes/db/db_tools.php =================================================================== --- includes/db/db_tools.php (revision 10488) +++ includes/db/db_tools.php (working copy) @@ -160,6 +160,36 @@ 'VARBINARY' => '[varchar] (255)', ), + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + 'oracle' => array( 'INT:' => 'number(%d)', 'BINT' => 'number(20)', @@ -261,7 +291,7 @@ * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. * @var array */ - var $supported_dbms = array('firebird', 'mssql', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); + var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); /** * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). @@ -307,6 +337,10 @@ $this->sql_layer = 'mssql'; break; + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + default: $this->sql_layer = $this->db->sql_layer; break; @@ -368,6 +402,7 @@ switch ($this->sql_layer) { case 'mssql': + case 'mssqlnative': $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; break; @@ -386,6 +421,7 @@ switch ($this->sql_layer) { case 'mssql': + case 'mssqlnative': $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; break; @@ -425,6 +461,7 @@ break; case 'mssql': + case 'mssqlnative': $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : ''); $statements[] = $table_sql; break; @@ -453,6 +490,7 @@ case 'firebird': case 'mssql': + case 'mssqlnative': // We need the data here $old_return_statements = $this->return_statements; $this->return_statements = true; @@ -970,6 +1008,7 @@ // same deal with PostgreSQL, we must perform more complex operations than // we technically could case 'mssql': + case 'mssqlnative': $sql = "SELECT c.name FROM syscolumns c LEFT JOIN sysobjects o ON c.id = o.id @@ -1187,6 +1226,7 @@ break; case 'mssql': + case 'mssqlnative': $sql .= " {$column_type} "; $sql_default = " {$column_type} "; @@ -1335,6 +1375,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; break; @@ -1455,6 +1496,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; break; @@ -1549,6 +1591,7 @@ switch ($this->sql_layer) { case 'mssql': + case 'mssqlnative': $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; break; @@ -1652,6 +1695,7 @@ break; case 'mssql': + case 'mssqlnative': $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; $sql .= '[' . implode("],\n\t\t[", $column) . ']'; @@ -1745,6 +1789,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; break; } @@ -1774,6 +1819,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; break; } @@ -1791,7 +1837,7 @@ { $index_array = array(); - if ($this->sql_layer == 'mssql') + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') { $sql = "EXEC sp_statistics '$table_name'"; $result = $this->db->sql_query($sql); @@ -1900,6 +1946,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; if (!empty($column_data['default'])) Index: includes/db/mssqlnative.php =================================================================== --- includes/db/mssqlnative.php (revision 0) +++ includes/db/mssqlnative.php (revision 10490) @@ -0,0 +1,610 @@ +m_cursor = 0; + $this->m_rows = array(); + $this->m_num_fields = sqlsrv_num_fields($queryresult); + $this->m_field_meta = sqlsrv_field_metadata($queryresult); + + while ($row = sqlsrv_fetch_array($queryresult, SQLSRV_FETCH_ASSOC)) + { + if ($row !== null) + { + foreach($row as $k => $v) + { + if (is_object($v) && method_exists($v, 'format')) + { + $row[$k] = $v->format("Y-m-d\TH:i:s\Z"); + } + } + $this->m_rows[] = $row;//read results into memory, cursors are not supported + } + } + + $this->m_row_count = count($this->m_rows); + sqlsrv_free_stmt($queryresult); + } + + private function array_to_obj($array, &$obj) + { + foreach ($array as $key => $value) + { + if (is_array($value)) + { + $obj->$key = new stdClass(); + array_to_obj($value, $obj->$key); + } + else + { + $obj->$key = $value; + } + } + return $obj; + } + + public function fetch($mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass') + { + if ($this->m_cursor >= $this->m_row_count || $this->m_row_count == 0) + { + return false; + } + + $ret = false; + $arr_num = array(); + + if ($mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH) + { + foreach($this->m_rows[$this->m_cursor] as $key => $value) + { + $arr_num[] = $value; + } + } + + switch ($mode) + { + case SQLSRV_FETCH_ASSOC: + $ret = $this->m_rows[$this->m_cursor]; + break; + case SQLSRV_FETCH_NUMERIC: + $ret = $arr_num; + break; + case 'OBJECT': + $ret = $this->array_to_obj($this->m_rows[$this->m_cursor], $o = new $object_class); + break; + case SQLSRV_FETCH_BOTH: + default: + $ret = $this->m_rows[$this->m_cursor] + $arr_num; + break; + } + $this->m_cursor++; + return $ret; + } + + public function get($pos, $fld) + { + return $this->m_rows[$pos][$fld]; + } + + public function num_rows() + { + return $this->m_row_count; + } + + public function seek($iRow) + { + $this->m_cursor = min($iRow, $this->m_row_count); + } + + public function num_fields() + { + return $this->m_num_fields; + } + + public function field_name($nr) + { + $arr_keys = array_keys($this->m_rows[0]); + return $arr_keys[$nr]; + } + + public function field_type($nr) + { + $i = 0; + $int_type = -1; + $str_type = ''; + + foreach ($this->m_field_meta as $meta) + { + if ($nr == $i) + { + $int_type = $meta['Type']; + break; + } + $i++; + } + + //http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table + switch ($int_type) + { + case SQLSRV_SQLTYPE_BIGINT: $str_type = 'bigint'; break; + case SQLSRV_SQLTYPE_BINARY: $str_type = 'binary'; break; + case SQLSRV_SQLTYPE_BIT: $str_type = 'bit'; break; + case SQLSRV_SQLTYPE_CHAR: $str_type = 'char'; break; + case SQLSRV_SQLTYPE_DATETIME: $str_type = 'datetime'; break; + case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $str_type = 'decimal'; break; + case SQLSRV_SQLTYPE_FLOAT: $str_type = 'float'; break; + case SQLSRV_SQLTYPE_IMAGE: $str_type = 'image'; break; + case SQLSRV_SQLTYPE_INT: $str_type = 'int'; break; + case SQLSRV_SQLTYPE_MONEY: $str_type = 'money'; break; + case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $str_type = 'nchar'; break; + case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $str_type = 'numeric'; break; + case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $str_type = 'nvarchar'; break; + case SQLSRV_SQLTYPE_NTEXT: $str_type = 'ntext'; break; + case SQLSRV_SQLTYPE_REAL: $str_type = 'real'; break; + case SQLSRV_SQLTYPE_SMALLDATETIME: $str_type = 'smalldatetime'; break; + case SQLSRV_SQLTYPE_SMALLINT: $str_type = 'smallint'; break; + case SQLSRV_SQLTYPE_SMALLMONEY: $str_type = 'smallmoney'; break; + case SQLSRV_SQLTYPE_TEXT: $str_type = 'text'; break; + case SQLSRV_SQLTYPE_TIMESTAMP: $str_type = 'timestamp'; break; + case SQLSRV_SQLTYPE_TINYINT: $str_type = 'tinyint'; break; + case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $str_type = 'uniqueidentifier'; break; + case SQLSRV_SQLTYPE_UDT: $str_type = 'UDT'; break; + case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $str_type = 'varbinary'; break; + case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $str_type = 'varchar'; break; + case SQLSRV_SQLTYPE_XML: $str_type = 'xml'; break; + default: $str_type = $int_type; + } + return $str_type; + } + + public function free() + { + unset($this->m_rows); + return; + } +} + +/** +* @package dbal +*/ +class dbal_mssqlnative extends dbal +{ + var $m_insert_id = NULL; + var $last_query_text = ''; + + /** + * Connect to server + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + # Test for driver support, to avoid suppressed fatal error + if (!function_exists('sqlsrv_connect')) + { + trigger_error('Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx\n', E_USER_ERROR); + } + + //set up connection variables + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + //connect to database + error_reporting(E_ALL); + $this->db_connect_id = sqlsrv_connect($this->server, array( + 'Database' => $this->dbname, + 'UID' => $this->user, + 'PWD' => $sqlpassword + )); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @return string sql server version + */ + function sql_server_info($raw = false) + { + global $cache; + + if (empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $arr_server_info = sqlsrv_server_info($this->db_connect_id); + $this->sql_server_version = $arr_server_info['SQLServerVersion']; + + if (!empty($cache)) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
    ' . $this->sql_server_version : 'MSSQL'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return sqlsrv_begin_transaction($this->db_connect_id); + break; + + case 'commit': + return sqlsrv_commit($this->db_connect_id); + break; + + case 'rollback': + return sqlsrv_rollback($this->db_connect_id); + break; + } + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + if ($offset === false || $offset == 0) + { + if (strpos($query, "SELECT") === false) + { + $query = "TOP {$total} " . $query; + } + else + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); + } + } + else + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); + $query = 'SELECT * + FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 + FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3 + WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); + } + + $result = $this->sql_query($query, $cache_ttl); + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); + + // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; + } + } + return $row; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + $seek = new result_mssqlnative($query_id); + $row = $seek->seek($rownum); + return ($row = $seek->fetch()) ? $row : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id !== false) + { + $row = @sqlsrv_fetch_array($result_id); + $id = $row[0]; + @sqlsrv_free_stmt($result_id); + return $id; + } + else + { + return false; + } + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[$query_id])) + { + unset($this->open_queries[$query_id]); + return @sqlsrv_free_stmt($query_id); + } + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); + $error_message = ''; + + if ($errors != null) + { + foreach ($errors as $error) + { + $error_message .= "SQLSTATE: ".$error[ 'SQLSTATE']."\n"; + $error_message .= "code: ".$error[ 'code']."\n"; + $error_message .= "message: ".$error[ 'message']."\n"; + } + $this->last_error_result = $error_message; + $error = $this->last_error_result; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + return $error; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlsrv_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); + if ($result = @sqlsrv_query($this->db_connect_id, $query)) + { + @sqlsrv_next_result($result); + while ($row = @sqlsrv_fetch_array($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); + @sqlsrv_free_stmt($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlsrv_query($this->db_connect_id, $query); + while ($void = @sqlsrv_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + @sqlsrv_free_stmt($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } + + /** + * Utility method used to retrieve number of rows + * Emulates mysql_num_rows + * Used in acp_database.php -> write_data_mssqlnative() + */ + function mssqlnative_num_rows($res) + { + if ($res !== false) + { + $row = new result_mssqlnative($res); + $num_rows = $row->num_rows(); + return $num_rows; + } + else + { + return false; + } + } +} + +?> \ No newline at end of file Property changes on: includes/db/mssqlnative.php ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + LF Index: includes/cache.php =================================================================== --- includes/cache.php (revision 10488) +++ includes/cache.php (working copy) @@ -299,6 +299,7 @@ { case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $sql = 'SELECT user_id, bot_agent, bot_ip FROM ' . BOTS_TABLE . ' WHERE bot_active = 1 Index: includes/functions_convert.php =================================================================== --- includes/functions_convert.php (revision 10488) +++ includes/functions_convert.php (working copy) @@ -1643,6 +1643,7 @@ case 'mssql': case 'sqlite': + case 'mssqlnative': $sql = implode(' UNION ALL ', preg_replace('#^(.*?)$#', 'SELECT \1', $sql_subary)); break; Index: includes/acp/acp_database.php =================================================================== --- includes/acp/acp_database.php (revision 10488) +++ includes/acp/acp_database.php (working copy) @@ -109,6 +109,7 @@ case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $extractor = new mssql_extractor($download, $store, $format, $filename, $time); break; @@ -138,6 +139,7 @@ case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $extractor->flush('TRUNCATE TABLE ' . $table_name . "GO\n"); break; @@ -1509,6 +1511,10 @@ { $this->write_data_mssql($table_name); } + else if($db->sql_layer === 'mssqlnative') + { + $this->write_data_mssqlnative($table_name); + } else { $this->write_data_odbc($table_name); @@ -1608,7 +1614,103 @@ } $this->flush($sql_data); } + + function write_data_mssqlnative($table_name) + { + global $db; + $ary_type = $ary_name = $meta_array = array(); + $ident_set = false; + $sql_data = ''; + // Grab all of the data from current table. + $sql = "SELECT * FROM $table_name"; + $result = $db->sql_query($sql); + + $retrieved_data = $db->mssqlnative_num_rows($result); + + $meta_array = sqlsrv_field_metadata($result); + $i_num_fields = sqlsrv_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $info = $db->mssqlnative_fieldInfo($table_name, $meta_array[$i]['Name']); + $ary_type[$i] = $info->type(); + $ary_name[$i] = $info->name(); + } + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $db->sql_query($sql); + $row2 = $db->sql_fetchrow($result2); + + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $db->sql_freeresult($result2); + } + + while ($row = $db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + function write_data_odbc($table_name) { global $db; Index: includes/functions_admin.php =================================================================== --- includes/functions_admin.php (revision 10488) +++ includes/functions_admin.php (working copy) @@ -3041,6 +3041,7 @@ case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $sql = 'SELECT ((SUM(size) * 8.0) * 1024.0) as dbsize FROM sysfiles'; $result = $db->sql_query($sql, 7200); Index: includes/functions_install.php =================================================================== --- includes/functions_install.php (revision 10488) +++ includes/functions_install.php (working copy) @@ -95,6 +95,16 @@ 'AVAILABLE' => true, '2.0.x' => true, ), + 'mssqlnative' => array( + 'LABEL' => 'MS SQL Server 2005+ [ Native ]', + 'SCHEMA' => 'mssql', + 'MODULE' => 'sqlsrv', + 'DELIM' => 'GO', + 'COMMENTS' => 'remove_comments', + 'DRIVER' => 'mssqlnative', + 'AVAILABLE' => true, + '2.0.x' => false, + ), 'oracle' => array( 'LABEL' => 'Oracle', 'SCHEMA' => 'oracle', @@ -220,6 +230,7 @@ case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $sql = "SELECT name FROM sysobjects WHERE type='U'"; @@ -313,6 +324,7 @@ case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $prefix_length = 90; break; Index: includes/message_parser.php =================================================================== --- includes/message_parser.php (revision 10488) +++ includes/message_parser.php (working copy) @@ -1284,6 +1284,7 @@ { case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $sql = 'SELECT * FROM ' . SMILIES_TABLE . ' ORDER BY LEN(code) DESC'; Index: install/install_install.php =================================================================== --- install/install_install.php (revision 10488) +++ install/install_install.php (working copy) @@ -1203,6 +1203,7 @@ { case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $sql_query = preg_replace('#\# MSSQL IDENTITY (phpbb_[a-z_]+) (ON|OFF) \##s', 'SET IDENTITY_INSERT \1 \2;', $sql_query); break; @@ -1375,7 +1376,7 @@ $sql_ary[] = 'UPDATE ' . $data['table_prefix'] . "config SET config_value = 'phpbb_captcha_gd' WHERE config_name = 'captcha_plugin'"; - + $sql_ary[] = 'UPDATE ' . $data['table_prefix'] . "config SET config_value = '1' WHERE config_name = 'captcha_gd'"; Index: install/schemas/mssql_schema.sql =================================================================== --- install/schemas/mssql_schema.sql (revision 10488) +++ install/schemas/mssql_schema.sql (working copy) @@ -4,9 +4,6 @@ */ -BEGIN TRANSACTION -GO - /* Table: 'phpbb_attachments' */ @@ -1733,8 +1730,3 @@ ) ON [PRIMARY] GO - - -COMMIT -GO - Index: install/database_update.php =================================================================== --- install/database_update.php (revision 10488) +++ install/database_update.php (working copy) @@ -1743,7 +1743,37 @@ 'VCHAR_CI' => '[varchar] (255)', 'VARBINARY' => '[varchar] (255)', ), - + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[varchar] (100)', + 'STEXT_UNI' => '[varchar] (255)', + 'TEXT_UNI' => '[varchar] (4000)', + 'MTEXT_UNI' => '[text]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[varchar] (255)', + 'VCHAR_UNI:'=> '[varchar] (%d)', + 'VCHAR_CI' => '[varchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + 'oracle' => array( 'INT:' => 'number(%d)', 'BINT' => 'number(20)', @@ -1845,7 +1875,7 @@ * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. * @var array */ - var $supported_dbms = array('firebird', 'mssql', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); + var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite'); /** * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). @@ -1890,6 +1920,10 @@ case 'mssql_odbc': $this->sql_layer = 'mssql'; break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; default: $this->sql_layer = $this->db->sql_layer; @@ -2322,6 +2356,7 @@ // same deal with PostgreSQL, we must perform more complex operations than // we technically could case 'mssql': + case 'mssqlnative': $sql = "SELECT c.name FROM syscolumns c LEFT JOIN sysobjects o ON c.id = o.id @@ -2425,7 +2460,7 @@ */ function sql_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql') + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') { $sql = "EXEC sp_statistics '$table_name'"; $result = $this->db->sql_query($sql); @@ -2530,7 +2565,7 @@ */ function sql_unique_index_exists($table_name, $index_name) { - if ($this->sql_layer == 'mssql') + if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') { $sql = "EXEC sp_statistics '$table_name'"; $result = $this->db->sql_query($sql); @@ -2769,6 +2804,7 @@ break; case 'mssql': + case 'mssqlnative': $sql .= " {$column_type} "; $sql_default = " {$column_type} "; @@ -2918,6 +2954,7 @@ break; case 'mssql': + case 'mssqlnative': // Does not support AFTER, only through temporary table $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; break; @@ -3042,6 +3079,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; break; @@ -3136,6 +3174,7 @@ switch ($this->sql_layer) { case 'mssql': + case 'mssqlnative': $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; break; @@ -3172,6 +3211,7 @@ break; case 'mssql': + case 'mssqlnative': $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; $sql .= '[' . implode("],\n\t\t[", $column) . ']'; @@ -3265,6 +3305,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; break; } @@ -3294,6 +3335,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]'; break; } @@ -3326,6 +3368,7 @@ break; case 'mssql': + case 'mssqlnative': $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; if (!empty($column_data['default'])) Index: install/install_convert.php =================================================================== --- install/install_convert.php (revision 10488) +++ install/install_convert.php (working copy) @@ -1248,6 +1248,7 @@ { case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' ON'); break; } @@ -1375,6 +1376,7 @@ { case 'mssql': case 'mssql_odbc': + case 'mssqlnative': $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' OFF'); break;