Compare commits

...

26 Commits

Author SHA1 Message Date
Damian Mooyman
34fb10580a
Merge pull request #54 from dhensby/pulls/transaction-fixes
FIX Make sure nested transactions get reset on implicit commits
2018-06-28 10:56:18 +12:00
Daniel Hensby
9d76e2a042
FIX Make sure nested transactions get reset on implicit commits 2018-06-20 12:49:08 +01:00
Damian Mooyman
8b519f9bcf
Add license file
Fixes #52
2018-06-07 10:01:22 +12:00
Loz Calver
579050c8f7
Update branch alias 2018-05-24 12:09:46 +01:00
Daniel Hensby
7be531dd7f
Merge pull request #49 from kinglozzer/generators
Update SQLServerQuery to use generators
2018-03-17 15:06:11 +00:00
Loz Calver
321b9fe890 Update SQLServerQuery to use generators 2018-03-16 16:52:47 +00:00
Damian Mooyman
23d4614204
Merge pull request #48 from dhensby/pulls/nested-transactions
FIX Support nested transactions
2018-02-15 14:21:52 +13:00
Daniel Hensby
6b3959ba48
FIX Support nested transactions 2018-02-08 21:37:59 +00:00
Daniel Hensby
406fcee3cd
Merge pull request #47 from NightJar/twowards-ss4-support
WIP: Stable SilverStripe 4 Support
2018-01-19 15:38:56 +00:00
Dylan Wagstaff
76fb2b29dc Fix class loading issues 2018-01-19 16:27:18 +13:00
Damian Mooyman
2b6a70e529
Merge remote-tracking branch 'origin/1'
# Conflicts:
#	code/MSSQLDatabase.php
#	code/MSSQLSchemaManager.php
#	composer.json
2017-12-07 16:05:30 +13:00
Damian Mooyman
aa21b10005
Merge pull request #1 from silverstripe-security/patch/1/SS-2017-008
[SS-2017-008] Fix SQL injection in search engine
2017-12-07 15:58:57 +13:00
Daniel Hensby
ada270c884
[SS-2017-008] Fix SQL injection in search engine 2017-11-22 11:52:50 +00:00
Damian Mooyman
0f8c146e99 Merge pull request #43 from brettt89/fix-dropping-indexes-without-checking
Fix Dropping indexes on dev/build upgrade path
2017-02-15 15:34:17 +13:00
Brett Tasker
dac4be1a51 Fix DROP INDEX not checking correct schema 2017-02-15 15:24:46 +13:00
Sean Harvey
761b42f24a Merge pull request #42 from ajoneil/1
Fix index issues in /dev/build
2017-02-13 08:39:51 +13:00
Andrew O'Neil
d9ec128735 Fix index issues in /dev/build 2017-02-03 11:47:48 +11:00
Damian Mooyman
6a25ac4ab4 BUG Fix installer for 4.0 (#40) 2016-10-26 14:24:22 +13:00
Damian Mooyman
a34d67c753 Rename SS_ prefixed classes (#39) 2016-09-09 18:55:53 +12:00
Ingo Schommer
bdc3197351 Merge pull request #37 from open-sausages/pulls/4.0/namespace-everything
Upgrade code for core namespaces
2016-09-08 16:11:32 +12:00
Damian Mooyman
da6cefac06 Upgrade code for core namespaces 2016-08-30 13:08:11 +12:00
Sean Harvey
551ccfd352 Merge pull request #35 from tractorcow/pulls/docs
Updated docs
2016-08-17 09:40:54 +12:00
Damian Mooyman
97d8750f3d
Updated docs
Fix support for freetds
Fixes #19
Fixes #32
2016-08-16 18:11:58 +12:00
Damian Mooyman
9309cf3a50 API Namespace MSSQL connector (#31)
API Update to support customtable names
Update to support new namespaced orm
Cleanup PHPDoc
2016-07-15 13:57:54 +12:00
Daniel Hensby
f3574a616b Merge pull request #33 from silverstripe/fixes/ifindexexists
FIX: Check index exists before dropping
2016-07-13 10:02:34 +01:00
Will Rossiter
2e64e44087 FIX: Check index exists before dropping 2016-07-13 14:47:38 +12:00
15 changed files with 435 additions and 224 deletions

View File

@ -19,6 +19,7 @@ before_test:
- echo extension=php_curl.dll >> php.ini - echo extension=php_curl.dll >> php.ini
- echo extension=php_gd2.dll >> php.ini - echo extension=php_gd2.dll >> php.ini
- echo extension=php_tidy.dll >> php.ini - echo extension=php_tidy.dll >> php.ini
- echo extension=php_fileinfo.dll >> php.ini
- php -r "readfile('http://getcomposer.org/installer');" | php - php -r "readfile('http://getcomposer.org/installer');" | php
- php -r "readfile('https://dl.dropboxusercontent.com/u/7129062/sqlsrv_unofficial_3.0.2.2.zip');" > sqlsrv.zip - php -r "readfile('https://dl.dropboxusercontent.com/u/7129062/sqlsrv_unofficial_3.0.2.2.zip');" > sqlsrv.zip
- unzip sqlsrv.zip - unzip sqlsrv.zip
@ -40,4 +41,4 @@ test_script:
environment: environment:
DB: MSSQL DB: MSSQL
CORE_RELEASE: 3 CORE_RELEASE: master

8
.upgrade.yml Normal file
View File

@ -0,0 +1,8 @@
mappings:
MSSQLAzureDatabase: SilverStripe\MSSQL\MSSQLAzureDatabase
MSSQLDatabase: SilverStripe\MSSQL\MSSQLDatabase
MSSQLDatabaseConfigurationHelper: SilverStripe\MSSQL\MSSQLDatabaseConfigurationHelper
MSSQLQueryBuilder: SilverStripe\MSSQL\MSSQLQueryBuilder
MSSQLSchemaManager: SilverStripe\MSSQL\MSSQLSchemaManager
SQLServerConnector: SilverStripe\MSSQL\SQLServerConnector
SQLServerQuery: SilverStripe\MSSQL\SQLServerQuery

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2018, SilverStripe Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -8,19 +8,27 @@ Allows SilverStripe to use SQL Server databases.
* Sean Harvey (Nickname: halkyon) * Sean Harvey (Nickname: halkyon)
<sean (at) silverstripe (dot) com> <sean (at) silverstripe (dot) com>
* Damian Mooyman (@tractorcow)
## Requirements ## Requirements
* SilverStripe 3.0+ * SilverStripe 4+
* SQL Server 2008, 2008 R2, or 2012. * SQL Server 2008, 2008 R2, or 2012.
`mssql` PHP api is no longer supported as of 2.0
### *nix ### *nix
* PHP with mssql extension and [FreeTDS](http://freetds.org) Linux support is only available via the PDO extension. This requires:
* [dblib](http://www.php.net/manual/en/ref.pdo-dblib.php)
* [FreeTDS](http://freetds.org)
### Windows ### Windows
* PHP with [SQL Server Driver for PHP](http://www.microsoft.com/en-us/download/details.aspx?id=20098) "sqlsrv" 3.0+ On windows you can either connect via PDO or `sqlsrv`. Both options require the
[SQL Server Driver for PHP](https://msdn.microsoft.com/library/dn865013.aspx?f=255&MSPPError=-2147217396). "sqlsrv" 3.0+
Note: [SQL Server Express](http://www.microsoft.com/express/Database/) can also be used which is provided free by Microsoft. However, it has limitations such as 10GB maximum database storage. Note: [SQL Server Express](http://www.microsoft.com/express/Database/) can also be used which is provided free by Microsoft. However, it has limitations such as 10GB maximum database storage.
@ -29,7 +37,7 @@ Note: [SQL Server Express](http://www.microsoft.com/express/Database/) can also
These steps will install the latest SilverStripe stable, along with this module using [Composer](http://getcomposer.org/): These steps will install the latest SilverStripe stable, along with this module using [Composer](http://getcomposer.org/):
* Install SilverStripe: `composer create-project silverstripe/installer /my/website/folder` * Install SilverStripe: `composer create-project silverstripe/installer /my/website/folder`
* Install module: `cd /my/website/folder && composer require silverstripe/mssql "*"` * Install module: `cd /my/website/folder && composer require silverstripe/mssql ^2`
* Open the SilverStripe installer by browsing to install.php, e.g. **http://localhost/silverstripe/install.php** * Open the SilverStripe installer by browsing to install.php, e.g. **http://localhost/silverstripe/install.php**
* Select **SQL Server 2008+** in the database list and enter your SQL Server database details * Select **SQL Server 2008+** in the database list and enter your SQL Server database details

View File

@ -1,25 +1,32 @@
--- ---
name: mssqlconnectors name: mssqlconnectors
--- ---
Injector: SilverStripe\Core\Injector\Injector:
# Connect using PDO # Connect using PDO
MSSQLPDODatabase: MSSQLPDODatabase:
class: 'MSSQLDatabase' class: 'SilverStripe\MSSQL\MSSQLDatabase'
properties: properties:
connector: %$PDOConnector connector: %$PDOConnector
schemaManager: %$MSSQLSchemaManager schemaManager: %$MSSQLSchemaManager
queryBuilder: %$MSSQLQueryBuilder queryBuilder: %$MSSQLQueryBuilder
# Uses sqlsrv_connect # Uses sqlsrv_connect
MSSQLDatabase: MSSQLDatabase:
class: 'MSSQLDatabase' class: 'SilverStripe\MSSQL\MSSQLDatabase'
properties: properties:
connector: %$SQLServerConnector connector: %$SQLServerConnector
schemaManager: %$MSSQLSchemaManager schemaManager: %$MSSQLSchemaManager
queryBuilder: %$MSSQLQueryBuilder queryBuilder: %$MSSQLQueryBuilder
# Uses sqlsrv_connect to connect to a MS Azure Database # Uses sqlsrv_connect to connect to a MS Azure Database
MSSQLAzureDatabase: MSSQLAzureDatabase:
class: 'MSSQLAzureDatabase' class: 'SilverStripe\MSSQL\MSSQLAzureDatabase'
properties: properties:
connector: %$SQLServerConnector connector: %$SQLServerConnector
schemaManager: %$MSSQLSchemaManager schemaManager: %$MSSQLSchemaManager
queryBuilder: %$MSSQLQueryBuilder queryBuilder: %$MSSQLQueryBuilder
SQLServerConnector:
class: 'SilverStripe\MSSQL\SQLServerConnector'
type: prototype
MSSQLSchemaManager:
class: 'SilverStripe\MSSQL\MSSQLSchemaManager'
MSSQLQueryBuilder:
class: 'SilverStripe\MSSQL\MSSQLQueryBuilder'

View File

@ -1,11 +1,17 @@
<?php <?php
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\MSSQL\MSSQLDatabaseConfigurationHelper;
// PDO connector for MS SQL Server // PDO connector for MS SQL Server
/** @skipUpgrade */
DatabaseAdapterRegistry::register(array( DatabaseAdapterRegistry::register(array(
'class' => 'MSSQLPDODatabase', 'class' => 'MSSQLPDODatabase',
'module' => 'mssql',
'title' => 'SQL Server 2008 (using PDO)', 'title' => 'SQL Server 2008 (using PDO)',
'helperPath' => dirname(__FILE__).'/code/MSSQLDatabaseConfigurationHelper.php', 'helperPath' => __DIR__.'/code/MSSQLDatabaseConfigurationHelper.php',
'supported' => (class_exists('PDO') && in_array('sqlsrv', PDO::getAvailableDrivers())), 'helperClass' => MSSQLDatabaseConfigurationHelper::class,
'supported' => !!MSSQLDatabaseConfigurationHelper::getPDODriver(),
'missingExtensionText' => 'missingExtensionText' =>
'Either the <a href="http://www.php.net/manual/en/book.pdo.php">PDO Extension</a> or 'Either the <a href="http://www.php.net/manual/en/book.pdo.php">PDO Extension</a> or
the <a href="http://www.php.net/manual/en/ref.pdo-sqlsrv.php">SQL Server PDO Driver</a> the <a href="http://www.php.net/manual/en/ref.pdo-sqlsrv.php">SQL Server PDO Driver</a>
@ -13,10 +19,13 @@ DatabaseAdapterRegistry::register(array(
)); ));
// Basic driver using sqlsrv connector // Basic driver using sqlsrv connector
/** @skipUpgrade */
DatabaseAdapterRegistry::register(array( DatabaseAdapterRegistry::register(array(
'class' => 'MSSQLDatabase', 'class' => 'MSSQLDatabase',
'module' => 'mssql',
'title' => 'SQL Server 2008 (using sqlsrv)', 'title' => 'SQL Server 2008 (using sqlsrv)',
'helperPath' => dirname(__FILE__).'/code/MSSQLDatabaseConfigurationHelper.php', 'helperPath' => __DIR__.'/code/MSSQLDatabaseConfigurationHelper.php',
'helperClass' => MSSQLDatabaseConfigurationHelper::class,
'supported' => function_exists('sqlsrv_connect'), 'supported' => function_exists('sqlsrv_connect'),
'missingExtensionText' => 'missingExtensionText' =>
'The <a href="http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx">sqlsrv</a> 'The <a href="http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx">sqlsrv</a>
@ -31,10 +40,13 @@ DatabaseAdapterRegistry::register(array(
)); ));
// MS Azure uses an online database // MS Azure uses an online database
/** @skipUpgrade */
DatabaseAdapterRegistry::register(array( DatabaseAdapterRegistry::register(array(
'class' => 'MSSQLAzureDatabase', 'class' => 'MSSQLAzureDatabase',
'module' => 'mssql',
'title' => 'MS Azure Database (using sqlsrv)', 'title' => 'MS Azure Database (using sqlsrv)',
'helperPath' => dirname(__FILE__).'/code/MSSQLDatabaseConfigurationHelper.php', 'helperPath' => __DIR__.'/code/MSSQLDatabaseConfigurationHelper.php',
'helperClass' => MSSQLDatabaseConfigurationHelper::class,
'supported' => function_exists('sqlsrv_connect'), 'supported' => function_exists('sqlsrv_connect'),
'missingExtensionText' => 'missingExtensionText' =>
'The <a href="http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx">sqlsrv</a> 'The <a href="http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx">sqlsrv</a>

View File

@ -1,29 +1,30 @@
<?php <?php
namespace SilverStripe\MSSQL;
/** /**
* Specific support for SQL Azure databases running on Windows Azure. * Specific support for SQL Azure databases running on Windows Azure.
* Currently only supports the SQLSRV driver from Microsoft. * Currently only supports the SQLSRV driver from Microsoft.
* *
* Some important things about SQL Azure: * Some important things about SQL Azure:
* *
* Selecting a database is not supported. * Selecting a database is not supported.
* In order to change the database currently in use, you need to connect to * In order to change the database currently in use, you need to connect to
* the database using the "Database" parameter with sqlsrv_connect() * the database using the "Database" parameter with sqlsrv_connect()
* *
* Multiple active result sets are not supported. This means you can't * Multiple active result sets are not supported. This means you can't
* have two query results open at once. * have two query results open at once.
* *
* Fulltext indexes are not supported. * Fulltext indexes are not supported.
* *
* @author Sean Harvey <sean at silverstripe dot com> * @author Sean Harvey <sean at silverstripe dot com>
* @package mssql
*/ */
class MSSQLAzureDatabase extends MSSQLDatabase class MSSQLAzureDatabase extends MSSQLDatabase
{ {
/** /**
* List of parameters used to create new Azure connections between databases * List of parameters used to create new Azure connections between databases
* *
* @var array * @var array
*/ */
protected $parameters = array(); protected $parameters = array();
@ -47,15 +48,15 @@ class MSSQLAzureDatabase extends MSSQLDatabase
* - database: The database to connect to * - database: The database to connect to
* - windowsauthentication: Not supported for Azure * - windowsauthentication: Not supported for Azure
*/ */
protected function connect($parameters) public function connect($parameters)
{ {
$this->parameters = $parameters; $this->parameters = $parameters;
$this->connectDatabase($parameters['database']); $this->connectDatabase($parameters['database']);
} }
/** /**
* Connect to a database using the provided parameters * Connect to a database using the provided parameters
* *
* @param string $database * @param string $database
*/ */
protected function connectDatabase($database) protected function connectDatabase($database)
@ -85,10 +86,11 @@ class MSSQLAzureDatabase extends MSSQLDatabase
* to reinitialize the database connection with the requested * to reinitialize the database connection with the requested
* database name. * database name.
* @see http://msdn.microsoft.com/en-us/library/windowsazure/ee336288.aspx * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee336288.aspx
* *
* @param type $name The database name to switch to * @param string $name The database name to switch to
* @param type $create * @param bool $create
* @param type $errorLevel * @param bool|int $errorLevel
* @return bool
*/ */
public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR)
{ {

View File

@ -1,4 +1,18 @@
<?php <?php
namespace SilverStripe\MSSQL;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\ClassInfo;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Connect\Database;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\PaginatedList;
use SilverStripe\ORM\Queries\SQLSelect;
/** /**
* Microsoft SQL Server 2008+ connector class. * Microsoft SQL Server 2008+ connector class.
* *
@ -35,11 +49,11 @@
* *
* References: * References:
* @see http://freetds.org * @see http://freetds.org
*
* @package mssql
*/ */
class MSSQLDatabase extends SS_Database class MSSQLDatabase extends Database
{ {
use Configurable;
use Injectable;
/** /**
* Words that will trigger an error if passed to a SQL Server fulltext search * Words that will trigger an error if passed to a SQL Server fulltext search
@ -64,27 +78,32 @@ class MSSQLDatabase extends SS_Database
*/ */
protected $fullTextEnabled = null; protected $fullTextEnabled = null;
/**
* @var bool
*/
protected $transactionNesting = 0;
/** /**
* Set the default collation of the MSSQL nvarchar fields that we create. * Set the default collation of the MSSQL nvarchar fields that we create.
* We don't apply this to the database as a whole, so that we can use unicode collations. * We don't apply this to the database as a whole, so that we can use unicode collations.
* *
* @param string $collation * @param string $collation
*/ */
public static function set_collation($collation) public static function set_collation($collation)
{ {
Config::inst()->update('MSSQLDatabase', 'collation', $collation); static::config()->set('collation', $collation);
} }
/** /**
* The default collation of the MSSQL nvarchar fields that we create. * The default collation of the MSSQL nvarchar fields that we create.
* We don't apply this to the database as a whole, so that we can use * We don't apply this to the database as a whole, so that we can use
* unicode collations. * unicode collations.
* *
* @return string * @return string
*/ */
public static function get_collation() public static function get_collation()
{ {
return Config::inst()->get('MSSQLDatabase', 'collation'); return static::config()->get('collation');
} }
/** /**
@ -94,7 +113,7 @@ class MSSQLDatabase extends SS_Database
* - username: The username to log on with * - username: The username to log on with
* - password: The password to log on with * - password: The password to log on with
* - database: The database to connect to * - database: The database to connect to
* - windowsauthentication: Set to true to use windows authentication * - windowsauthentication: Set to true to use windows authentication
* instead of username/password * instead of username/password
*/ */
public function connect($parameters) public function connect($parameters)
@ -119,7 +138,7 @@ class MSSQLDatabase extends SS_Database
} }
return $this->fullTextEnabled; return $this->fullTextEnabled;
} }
/** /**
* Checks whether the current SQL Server version has full-text * Checks whether the current SQL Server version has full-text
* support installed and full-text is enabled for this database. * support installed and full-text is enabled for this database.
@ -133,7 +152,7 @@ class MSSQLDatabase extends SS_Database
if (!$isInstalled) { if (!$isInstalled) {
return false; return false;
} }
// Check if current database is enabled // Check if current database is enabled
$database = $this->getSelectedDatabase(); $database = $this->getSelectedDatabase();
$enabledForDb = $this->preparedQuery( $enabledForDb = $this->preparedQuery(
@ -157,11 +176,11 @@ class MSSQLDatabase extends SS_Database
{ {
return "sqlsrv"; return "sqlsrv";
} }
public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR)
{ {
$this->fullTextEnabled = null; $this->fullTextEnabled = null;
return parent::selectDatabase($name, $create, $errorLevel); return parent::selectDatabase($name, $create, $errorLevel);
} }
@ -191,20 +210,25 @@ class MSSQLDatabase extends SS_Database
* Picks up the fulltext-indexed tables from the database and executes search on all of them. * Picks up the fulltext-indexed tables from the database and executes search on all of them.
* Results are obtained as ID-ClassName pairs which is later used to reconstruct the DataObjectSet. * Results are obtained as ID-ClassName pairs which is later used to reconstruct the DataObjectSet.
* *
* @param array classesToSearch computes all descendants and includes them. Check is done via WHERE clause. * @param array $classesToSearch computes all descendants and includes them. Check is done via WHERE clause.
* @param string $keywords Keywords as a space separated string * @param string $keywords Keywords as a space separated string
* @return object DataObjectSet of result pages * @param int $start
* @param int $pageLength
* @param string $sortBy
* @param string $extraFilter
* @param bool $booleanSearch
* @param string $alternativeFileFilter
* @param bool $invertedMatch
* @return PaginatedList DataObjectSet of result pages
*/ */
public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false)
{ {
if (isset($objects)) { $start = (int)$start;
$results = new ArrayList($objects); $pageLength = (int)$pageLength;
} else { $results = new ArrayList();
$results = new ArrayList();
}
if (!$this->fullTextEnabled()) { if (!$this->fullTextEnabled()) {
return $results; return new PaginatedList($results);
} }
if (!in_array(substr($sortBy, 0, 9), array('"Relevanc', 'Relevance'))) { if (!in_array(substr($sortBy, 0, 9), array('"Relevanc', 'Relevance'))) {
user_error("Non-relevance sort not supported.", E_USER_ERROR); user_error("Non-relevance sort not supported.", E_USER_ERROR);
@ -239,34 +263,33 @@ class MSSQLDatabase extends SS_Database
// Create one query per each table, $columns not used. We want just the ID and the ClassName of the object from this query. // Create one query per each table, $columns not used. We want just the ID and the ClassName of the object from this query.
foreach ($tables as $tableName => $columns) { foreach ($tables as $tableName => $columns) {
$baseClass = ClassInfo::baseDataClass($tableName); $class = DataObject::getSchema()->tableClass($tableName);
$join = $this->fullTextSearchMSSQL($tableName, $keywords); $join = $this->fullTextSearchMSSQL($tableName, $keywords);
if (!$join) { if (!$join) {
return $results; return new PaginatedList($results);
} // avoid "Null or empty full-text predicate" } // avoid "Null or empty full-text predicate"
// Check if we need to add ShowInSearch // Check if we need to add ShowInSearch
$where = null; $where = null;
if (strpos($tableName, 'SiteTree') === 0) { if ($class === 'SilverStripe\\CMS\\Model\\SiteTree') {
$where = array("\"$tableName\".\"ShowInSearch\"!=0"); $where = array("\"$tableName\".\"ShowInSearch\"!=0");
} elseif (strpos($tableName, 'File') === 0) { } elseif ($class === 'SilverStripe\\Assets\\File') {
// File.ShowInSearch was added later, keep the database driver backwards compatible // File.ShowInSearch was added later, keep the database driver backwards compatible
// by checking for its existence first // by checking for its existence first
$fields = $this->fieldList($tableName); $fields = $this->getSchemaManager()->fieldList($tableName);
if (array_key_exists('ShowInSearch', $fields)) { if (array_key_exists('ShowInSearch', $fields)) {
$where = array("\"$tableName\".\"ShowInSearch\"!=0"); $where = array("\"$tableName\".\"ShowInSearch\"!=0");
} }
} }
$queries[$tableName] = DataList::create($tableName)->where($where, '')->dataQuery()->query(); $queries[$tableName] = DataList::create($class)->where($where)->dataQuery()->query();
$queries[$tableName]->setOrderBy(array()); $queries[$tableName]->setOrderBy(array());
// Join with CONTAINSTABLE, a full text searcher that includes relevance factor // Join with CONTAINSTABLE, a full text searcher that includes relevance factor
$queries[$tableName]->setFrom(array("\"$tableName\" INNER JOIN $join AS \"ft\" ON \"$tableName\".\"ID\"=\"ft\".\"KEY\"")); $queries[$tableName]->setFrom(array("\"$tableName\" INNER JOIN $join AS \"ft\" ON \"$tableName\".\"ID\"=\"ft\".\"KEY\""));
// Join with the base class if needed, as we want to test agains the ClassName // Join with the base class if needed, as we want to test agains the ClassName
if ($tableName != $baseClass) { if ($tableName != $tableName) {
$queries[$tableName]->setFrom("INNER JOIN \"$baseClass\" ON \"$baseClass\".\"ID\"=\"$tableName\".\"ID\""); $queries[$tableName]->setFrom("INNER JOIN \"$tableName\" ON \"$tableName\".\"ID\"=\"$tableName\".\"ID\"");
} }
$queries[$tableName]->setSelect(array("\"$tableName\".\"ID\"")); $queries[$tableName]->setSelect(array("\"$tableName\".\"ID\""));
@ -278,7 +301,7 @@ class MSSQLDatabase extends SS_Database
if (count($allClassesToSearch)) { if (count($allClassesToSearch)) {
$classesPlaceholder = DB::placeholders($allClassesToSearch); $classesPlaceholder = DB::placeholders($allClassesToSearch);
$queries[$tableName]->addWhere(array( $queries[$tableName]->addWhere(array(
"\"$baseClass\".\"ClassName\" IN ($classesPlaceholder)" => "\"$tableName\".\"ClassName\" IN ($classesPlaceholder)" =>
$allClassesToSearch $allClassesToSearch
)); ));
} }
@ -289,6 +312,7 @@ class MSSQLDatabase extends SS_Database
$querySQLs = array(); $querySQLs = array();
$queryParameters = array(); $queryParameters = array();
foreach ($queries as $query) { foreach ($queries as $query) {
/** @var SQLSelect $query */
$querySQLs[] = $query->sql($parameters); $querySQLs[] = $query->sql($parameters);
$queryParameters = array_merge($queryParameters, $parameters); $queryParameters = array_merge($queryParameters, $parameters);
} }
@ -326,9 +350,9 @@ class MSSQLDatabase extends SS_Database
/** /**
* Allow auto-increment primary key editing on the given table. * Allow auto-increment primary key editing on the given table.
* Some databases need to enable this specially. * Some databases need to enable this specially.
* *
* @param $table The name of the table to have PK editing allowed on * @param string $table The name of the table to have PK editing allowed on
* @param $allow True to start, false to finish * @param bool $allow True to start, false to finish
*/ */
public function allowPrimaryKeyEditing($table, $allow = true) public function allowPrimaryKeyEditing($table, $allow = true)
{ {
@ -338,11 +362,10 @@ class MSSQLDatabase extends SS_Database
/** /**
* Returns a SQL fragment for querying a fulltext search index * Returns a SQL fragment for querying a fulltext search index
* *
* @param $tableName specific - table name * @param string $tableName specific - table name
* @param $keywords string The search query * @param string $keywords The search query
* @param $fields array The list of field names to search on, or null to include all * @param array $fields The list of field names to search on, or null to include all
* * @return string Clause, or null if keyword set is empty or the string with JOIN clause to be added to SQL query
* @returns null if keyword set is empty or the string with JOIN clause to be added to SQL query
*/ */
public function fullTextSearchMSSQL($tableName, $keywords, $fields = null) public function fullTextSearchMSSQL($tableName, $keywords, $fields = null)
{ {
@ -361,7 +384,7 @@ class MSSQLDatabase extends SS_Database
// Remove stopwords, concat with ANDs // Remove stopwords, concat with ANDs
$keywordList = explode(' ', $keywords); $keywordList = explode(' ', $keywords);
$keywordList = $this->removeStopwords($keywordList); $keywordList = $this->removeStopwords($keywordList);
// remove any empty values from the array // remove any empty values from the array
$keywordList = array_filter($keywordList); $keywordList = array_filter($keywordList);
if (empty($keywordList)) { if (empty($keywordList)) {
@ -408,7 +431,7 @@ class MSSQLDatabase extends SS_Database
/** /**
* This is a quick lookup to discover if the database supports particular extensions * This is a quick lookup to discover if the database supports particular extensions
* Currently, MSSQL supports no extensions * Currently, MSSQL supports no extensions
* *
* @param array $extensions List of extensions to check for support of. The key of this array * @param array $extensions List of extensions to check for support of. The key of this array
* will be an extension name, and the value the configuration for that extension. This * will be an extension name, and the value the configuration for that extension. This
* could be one of partitions, tablespaces, or clustering * could be one of partitions, tablespaces, or clustering
@ -426,17 +449,23 @@ class MSSQLDatabase extends SS_Database
return false; return false;
} }
} }
/** /**
* Start transaction. READ ONLY not supported. * Start transaction. READ ONLY not supported.
*
* @param bool $transactionMode
* @param bool $sessionCharacteristics
*/ */
public function transactionStart($transactionMode = false, $sessionCharacteristics = false) public function transactionStart($transactionMode = false, $sessionCharacteristics = false)
{ {
if ($this->connector instanceof SQLServerConnector) { if ($this->transactionNesting > 0) {
$this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
} elseif ($this->connector instanceof SQLServerConnector) {
$this->connector->transactionStart(); $this->connector->transactionStart();
} else { } else {
$this->query('BEGIN TRANSACTION'); $this->query('BEGIN TRANSACTION');
} }
++$this->transactionNesting;
} }
public function transactionSavepoint($savepoint) public function transactionSavepoint($savepoint)
@ -446,24 +475,74 @@ class MSSQLDatabase extends SS_Database
public function transactionRollback($savepoint = false) public function transactionRollback($savepoint = false)
{ {
// Named transaction
if ($savepoint) { if ($savepoint) {
$this->query("ROLLBACK TRANSACTION \"$savepoint\""); $this->query("ROLLBACK TRANSACTION \"$savepoint\"");
return true;
}
// Fail if transaction isn't available
if (!$this->transactionNesting) {
return false;
}
--$this->transactionNesting;
if ($this->transactionNesting > 0) {
$this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting);
} elseif ($this->connector instanceof SQLServerConnector) { } elseif ($this->connector instanceof SQLServerConnector) {
$this->connector->transactionRollback(); $this->connector->transactionRollback();
} else { } else {
$this->query('ROLLBACK TRANSACTION'); $this->query('ROLLBACK TRANSACTION');
} }
return true;
} }
public function transactionEnd($chain = false) public function transactionEnd($chain = false)
{ {
if ($this->connector instanceof SQLServerConnector) { // Fail if transaction isn't available
$this->connector->transactionEnd(); if (!$this->transactionNesting) {
} else { return false;
$this->query('COMMIT TRANSACTION'); }
--$this->transactionNesting;
if ($this->transactionNesting <= 0) {
$this->transactionNesting = 0;
if ($this->connector instanceof SQLServerConnector) {
$this->connector->transactionEnd();
} else {
$this->query('COMMIT TRANSACTION');
}
}
return true;
}
/**
* In error condition, set transactionNesting to zero
*/
protected function resetTransactionNesting()
{
$this->transactionNesting = 0;
}
public function query($sql, $errorLevel = E_USER_ERROR)
{
$this->inspectQuery($sql);
return parent::query($sql, $errorLevel);
}
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
{
$this->inspectQuery($sql);
return parent::preparedQuery($sql, $parameters, $errorLevel);
}
protected function inspectQuery($sql)
{
// Any DDL discards transactions.
$isDDL = $this->getConnector()->isQueryDDL($sql);
if ($isDDL) {
$this->resetTransactionNesting();
} }
} }
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false) public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false)
{ {
if ($exact) { if ($exact) {
@ -474,7 +553,7 @@ class MSSQLDatabase extends SS_Database
$comp = 'NOT ' . $comp; $comp = 'NOT ' . $comp;
} }
} }
// Field definitions are case insensitive by default, // Field definitions are case insensitive by default,
// change used collation for case sensitive searches. // change used collation for case sensitive searches.
$collateClause = ''; $collateClause = '';
@ -505,7 +584,7 @@ class MSSQLDatabase extends SS_Database
/** /**
* Function to return an SQL datetime expression for MSSQL * Function to return an SQL datetime expression for MSSQL
* used for querying a datetime in a certain format * used for querying a datetime in a certain format
* *
* @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
* @param string $format to be used, supported specifiers: * @param string $format to be used, supported specifiers:
* %Y = Year (four digits) * %Y = Year (four digits)
@ -571,7 +650,7 @@ class MSSQLDatabase extends SS_Database
/** /**
* Function to return an SQL datetime expression for MSSQL. * Function to return an SQL datetime expression for MSSQL.
* used for querying a datetime addition * used for querying a datetime addition
* *
* @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
* @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes, +1 YEAR * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes, +1 YEAR
* supported qualifiers: * supported qualifiers:
@ -616,7 +695,7 @@ class MSSQLDatabase extends SS_Database
/** /**
* Function to return an SQL datetime expression for MSSQL. * Function to return an SQL datetime expression for MSSQL.
* used for querying a datetime substraction * used for querying a datetime substraction
* *
* @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
* @param string $date2 to be substracted of $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"'
* @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which is the result of the substraction * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which is the result of the substraction

View File

@ -1,24 +1,31 @@
<?php <?php
namespace SilverStripe\MSSQL;
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
use SilverStripe\Dev\Install\DatabaseConfigurationHelper;
use PDO;
use Exception;
/** /**
* This is a helper class for the SS installer. * This is a helper class for the SS installer.
* *
* It does all the specific checking for MSSQLDatabase * It does all the specific checking for MSSQLDatabase
* to ensure that the configuration is setup correctly. * to ensure that the configuration is setup correctly.
*
* @package mssql
*/ */
class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{ {
protected function isAzure($databaseConfig) protected function isAzure($databaseConfig)
{ {
/** @skipUpgrade */
return $databaseConfig['type'] === 'MSSQLAzureDatabase'; return $databaseConfig['type'] === 'MSSQLAzureDatabase';
} }
/** /**
* Create a connection of the appropriate type * Create a connection of the appropriate type
* *
* @skipUpgrade
* @param array $databaseConfig * @param array $databaseConfig
* @param string $error Error message passed by value * @param string $error Error message passed by value
* @return mixed|null Either the connection object, or null if error * @return mixed|null Either the connection object, or null if error
@ -34,7 +41,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
'UID' => $databaseConfig['username'], 'UID' => $databaseConfig['username'],
'PWD' => $databaseConfig['password'] 'PWD' => $databaseConfig['password']
); );
// Azure has additional parameter requirements // Azure has additional parameter requirements
if ($this->isAzure($databaseConfig)) { if ($this->isAzure($databaseConfig)) {
$parameters['database'] = $databaseConfig['database']; $parameters['database'] = $databaseConfig['database'];
@ -44,7 +51,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
if ($conn) { if ($conn) {
return $conn; return $conn;
} }
// Get error // Get error
if ($errors = sqlsrv_errors()) { if ($errors = sqlsrv_errors()) {
$error = ''; $error = '';
@ -56,8 +63,14 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
} }
return null; return null;
case 'MSSQLPDODatabase': case 'MSSQLPDODatabase':
$driver = $this->getPDODriver();
if (!$driver) {
$error = 'No supported PDO driver';
return null;
}
// May throw a PDOException if fails // May throw a PDOException if fails
$conn = @new PDO('sqlsrv:Server='.$databaseConfig['server'], $databaseConfig['username'], $databaseConfig['password']); $conn = @new PDO($driver.':Server='.$databaseConfig['server'], $databaseConfig['username'], $databaseConfig['password']);
if ($conn) { if ($conn) {
return $conn; return $conn;
} else { } else {
@ -65,7 +78,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
return null; return null;
} }
default: default:
$error = 'Invalid connection type'; $error = 'Invalid connection type: ' . $databaseConfig['type'];
return null; return null;
} }
} catch (Exception $ex) { } catch (Exception $ex) {
@ -73,13 +86,30 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
return null; return null;
} }
} }
/**
* Get supported PDO driver
*
* @return null
*/
public static function getPDODriver() {
if (!class_exists('PDO')) {
return null;
}
foreach(PDO::getAvailableDrivers() as $driver) {
if(in_array($driver, array('sqlsrv', 'dblib'))) {
return $driver;
}
}
return null;
}
/** /**
* Helper function to quote a string value * Helper function to quote a string value
* *
* @param mixed $conn Connection object/resource * @param mixed $conn Connection object/resource
* @param string $value Value to quote * @param string $value Value to quote
* @return string Quoted strieng * @return string Quoted string
*/ */
protected function quote($conn, $value) protected function quote($conn, $value)
{ {
@ -92,11 +122,12 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
} else { } else {
user_error('Invalid database connection', E_USER_ERROR); user_error('Invalid database connection', E_USER_ERROR);
} }
return null;
} }
/** /**
* Helper function to execute a query * Helper function to execute a query
* *
* @param mixed $conn Connection object/resource * @param mixed $conn Connection object/resource
* @param string $sql SQL string to execute * @param string $sql SQL string to execute
* @return array List of first value from each resulting row * @return array List of first value from each resulting row
@ -132,7 +163,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{ {
$conn = $this->createConnection($databaseConfig, $error); $conn = $this->createConnection($databaseConfig, $error);
$success = !empty($conn); $success = !empty($conn);
return array( return array(
'success' => $success, 'success' => $success,
'error' => $error 'error' => $error
@ -143,7 +174,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
{ {
$conn = $this->createConnection($databaseConfig, $error); $conn = $this->createConnection($databaseConfig, $error);
$success = !empty($conn); $success = !empty($conn);
return array( return array(
'success' => $success, 'success' => $success,
'connection' => $conn, 'connection' => $conn,
@ -160,7 +191,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
/** /**
* Ensure that the SQL Server version is at least 10.00.2531 (SQL Server 2008 SP1). * Ensure that the SQL Server version is at least 10.00.2531 (SQL Server 2008 SP1).
* *
* @see http://www.sqlteam.com/article/sql-server-versions * @see http://www.sqlteam.com/article/sql-server-versions
* @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc * @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc
* @return array Result - e.g. array('success' => true, 'error' => 'details of error') * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
@ -189,6 +220,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
public function requireDatabaseOrCreatePermissions($databaseConfig) public function requireDatabaseOrCreatePermissions($databaseConfig)
{ {
$conn = $this->createConnection($databaseConfig, $error); $conn = $this->createConnection($databaseConfig, $error);
/** @skipUpgrade */
if (empty($conn)) { if (empty($conn)) {
$success = false; $success = false;
$alreadyExists = false; $alreadyExists = false;
@ -227,7 +259,7 @@ class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
$permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions(NULL,'DATABASE') WHERE permission_name like 'create table';"); $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions(NULL,'DATABASE') WHERE permission_name like 'create table';");
$success = $permissions[0] > 0; $success = $permissions[0] > 0;
} }
return array( return array(
'success' => $success, 'success' => $success,
'applies' => true 'applies' => true

View File

@ -1,17 +1,21 @@
<?php <?php
namespace SilverStripe\MSSQL;
use InvalidArgumentException;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\Connect\DBQueryBuilder;
/** /**
* Builds a SQL query string from a SQLExpression object * Builds a SQL query string from a SQLExpression object
*
* @package mssql
*/ */
class MSSQLQueryBuilder extends DBQueryBuilder class MSSQLQueryBuilder extends DBQueryBuilder
{ {
protected function buildSelectQuery(SQLSelect $query, array &$parameters) protected function buildSelectQuery(SQLSelect $query, array &$parameters)
{ {
list($limit, $offset) = $this->parseLimit($query); list($limit, $offset) = $this->parseLimit($query);
// If not using ofset then query generation is quite straightforward // If not using ofset then query generation is quite straightforward
if (empty($offset)) { if (empty($offset)) {
$sql = parent::buildSelectQuery($query, $parameters); $sql = parent::buildSelectQuery($query, $parameters);
@ -21,7 +25,7 @@ class MSSQLQueryBuilder extends DBQueryBuilder
} }
return $sql; return $sql;
} }
// When using offset we must use a subselect // When using offset we must use a subselect
// @see http://stackoverflow.com/questions/2135418/equivalent-of-limit-and-offset-for-sql-server // @see http://stackoverflow.com/questions/2135418/equivalent-of-limit-and-offset-for-sql-server
$orderby = $query->getOrderBy(); $orderby = $query->getOrderBy();
@ -49,13 +53,13 @@ class MSSQLQueryBuilder extends DBQueryBuilder
$firstCol = reset($selects); $firstCol = reset($selects);
$orderByClause = "ORDER BY $firstCol"; $orderByClause = "ORDER BY $firstCol";
} }
// Build main query SQL // Build main query SQL
$sql = parent::buildSelectQuery($query, $parameters); $sql = parent::buildSelectQuery($query, $parameters);
// Inject row number into selection // Inject row number into selection
$sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} ROW_NUMBER() OVER ('.$orderByClause.') AS Number, ', $sql); $sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} ROW_NUMBER() OVER ('.$orderByClause.') AS Number, ', $sql);
// Sub-query this SQL // Sub-query this SQL
if (empty($limit)) { if (empty($limit)) {
$limitCondition = "Number > ?"; $limitCondition = "Number > ?";
@ -67,13 +71,13 @@ class MSSQLQueryBuilder extends DBQueryBuilder
} }
return "SELECT * FROM ($sql) AS Numbered WHERE $limitCondition ORDER BY Number"; return "SELECT * FROM ($sql) AS Numbered WHERE $limitCondition ORDER BY Number";
} }
public function buildLimitFragment(SQLSelect $query, array &$parameters) public function buildLimitFragment(SQLSelect $query, array &$parameters)
{ {
// Limit is handled at the buildSelectQuery level // Limit is handled at the buildSelectQuery level
return ''; return '';
} }
public function buildOrderByFragment(SQLSelect $query, array &$parameters) public function buildOrderByFragment(SQLSelect $query, array &$parameters)
{ {
// If doing a limit/offset at the same time then don't build the orde by fragment here // If doing a limit/offset at the same time then don't build the orde by fragment here
@ -83,10 +87,10 @@ class MSSQLQueryBuilder extends DBQueryBuilder
} }
return ''; return '';
} }
/** /**
* Extracts the limit and offset from the limit clause * Extracts the limit and offset from the limit clause
* *
* @param SQLSelect $query * @param SQLSelect $query
* @return array Two item array with $limit and $offset as values * @return array Two item array with $limit and $offset as values
* @throws InvalidArgumentException * @throws InvalidArgumentException

View File

@ -1,25 +1,27 @@
<?php <?php
namespace SilverStripe\MSSQL;
use SilverStripe\ORM\Connect\DBSchemaManager;
/** /**
* Represents and handles all schema management for a MS SQL database * Represents and handles all schema management for a MS SQL database
*
* @package mssql
*/ */
class MSSQLSchemaManager extends DBSchemaManager class MSSQLSchemaManager extends DBSchemaManager
{ {
/** /**
* Stores per-request cached constraint checks that come from the database. * Stores per-request cached constraint checks that come from the database.
* *
* @var array * @var array
*/ */
protected static $cached_checks = array(); protected static $cached_checks = array();
/** /**
* Builds the internal MS SQL Server index name given the silverstripe table and index name * Builds the internal MS SQL Server index name given the silverstripe table and index name
* *
* @param string $tableName * @param string $tableName
* @param string $indexName * @param string $indexName
* @param string $prefix The optional prefix for the index. Defaults to "ix" for indexes. * @param string $prefix The optional prefix for the index. Defaults to "ix" for indexes.
* @return string The name of the index * @return string The name of the index
*/ */
@ -36,7 +38,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* This will set up the full text search capabilities. * This will set up the full text search capabilities.
* *
* @param string $name Name of full text catalog to use * @param string $name Name of full text catalog to use
*/ */
public function createFullTextCatalog($name = 'ftCatalog') public function createFullTextCatalog($name = 'ftCatalog')
@ -49,7 +51,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Check that a fulltext catalog has been created yet. * Check that a fulltext catalog has been created yet.
* *
* @param string $name Name of full text catalog to use * @param string $name Name of full text catalog to use
* @return boolean * @return boolean
*/ */
@ -75,7 +77,7 @@ class MSSQLSchemaManager extends DBSchemaManager
if (!$this->database->fullTextEnabled()) { if (!$this->database->fullTextEnabled()) {
return; return;
} }
$this->query("EXEC sp_fulltext_catalog 'ftCatalog', 'Rebuild';"); $this->query("EXEC sp_fulltext_catalog 'ftCatalog', 'Rebuild';");
// Busy wait until it's done updating, but no longer than 15 seconds. // Busy wait until it's done updating, but no longer than 15 seconds.
@ -93,7 +95,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Check if a fulltext index exists on a particular table name. * Check if a fulltext index exists on a particular table name.
* *
* @param string $tableName * @param string $tableName
* @return boolean TRUE index exists | FALSE index does not exist | NULL no support * @return boolean TRUE index exists | FALSE index does not exist | NULL no support
*/ */
@ -115,7 +117,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* MSSQL stores the primary key column with an internal identifier, * MSSQL stores the primary key column with an internal identifier,
* so a lookup needs to be done to determine it. * so a lookup needs to be done to determine it.
* *
* @param string $tableName Name of table with primary key column "ID" * @param string $tableName Name of table with primary key column "ID"
* @return string Internal identifier for primary key * @return string Internal identifier for primary key
*/ */
@ -135,7 +137,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Gets the identity column of a table * Gets the identity column of a table
* *
* @param string $tableName * @param string $tableName
* @return string|null * @return string|null
*/ */
@ -158,7 +160,7 @@ class MSSQLSchemaManager extends DBSchemaManager
{ {
$this->query("CREATE DATABASE \"$name\""); $this->query("CREATE DATABASE \"$name\"");
} }
public function dropDatabase($name) public function dropDatabase($name)
{ {
$this->query("DROP DATABASE \"$name\""); $this->query("DROP DATABASE \"$name\"");
@ -174,7 +176,7 @@ class MSSQLSchemaManager extends DBSchemaManager
} }
return false; return false;
} }
public function databaseList() public function databaseList()
{ {
return $this->query('SELECT NAME FROM sys.sysdatabases')->column(); return $this->query('SELECT NAME FROM sys.sysdatabases')->column();
@ -182,13 +184,14 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Create a new table. * Create a new table.
* @param $tableName The name of the table * @param string $tableName The name of the table
* @param $fields A map of field names to field types * @param array $fields A map of field names to field types
* @param $indexes A map of indexes * @param array $indexes A map of indexes
* @param $options An map of additional options. The available keys are as follows: * @param array $options An map of additional options. The available keys are as follows:
* - 'MSSQLDatabase'/'MySQLDatabase'/'PostgreSQLDatabase' - database-specific options such as "engine" for MySQL. * - 'MSSQLDatabase'/'MySQLDatabase'/'PostgreSQLDatabase' - database-specific options such as "engine" for MySQL.
* - 'temporary' - If true, then a temporary table will be created * - 'temporary' - If true, then a temporary table will be created
* @return The table name generated. This may be different from the table name, for example with temporary tables. * @param array $advancedOptions
* @return string The table name generated. This may be different from the table name, for example with temporary tables.
*/ */
public function createTable($tableName, $fields = null, $indexes = null, $options = null, $advancedOptions = null) public function createTable($tableName, $fields = null, $indexes = null, $options = null, $advancedOptions = null)
{ {
@ -227,18 +230,20 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Alter a table's schema. * Alter a table's schema.
* @param $table The name of the table to alter * @param string $tableName The name of the table to alter
* @param $newFields New fields, a map of field name => field schema * @param array $newFields New fields, a map of field name => field schema
* @param $newIndexes New indexes, a map of index name => index type * @param array $newIndexes New indexes, a map of index name => index type
* @param $alteredFields Updated fields, a map of field name => field schema * @param array $alteredFields Updated fields, a map of field name => field schema
* @param $alteredIndexes Updated indexes, a map of index name => index type * @param array $alteredIndexes Updated indexes, a map of index name => index type
* @param array $alteredOptions
* @param array $advancedOptions
*/ */
public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions=null, $advancedOptions=null) public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, $alteredIndexes = null, $alteredOptions=null, $advancedOptions=null)
{ {
$alterList = array(); $alterList = array();
// drop any fulltext indexes that exist on the table before altering the structure // drop any fulltext indexes that exist on the table before altering the structure
if ($this->fullTextIndexExists($tableName)) { if ($this->fulltextIndexExists($tableName)) {
$alterList[] = "\nDROP FULLTEXT INDEX ON \"$tableName\";"; $alterList[] = "\nDROP FULLTEXT INDEX ON \"$tableName\";";
} }
@ -276,7 +281,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Given the table and column name, retrieve the constraint name for that column * Given the table and column name, retrieve the constraint name for that column
* in the table. * in the table.
* *
* @param string $tableName Table name column resides in * @param string $tableName Table name column resides in
* @param string $columnName Column name the constraint is for * @param string $columnName Column name the constraint is for
* @return string|null * @return string|null
@ -294,11 +299,11 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Given a table and column name, return a check constraint clause for that column in * Given a table and column name, return a check constraint clause for that column in
* the table. * the table.
* *
* This is an expensive query, so it is cached per-request and stored by table. The initial * This is an expensive query, so it is cached per-request and stored by table. The initial
* call for a table that has not been cached will query all columns and store that * call for a table that has not been cached will query all columns and store that
* so subsequent calls are fast. * so subsequent calls are fast.
* *
* @param string $tableName Table name column resides in * @param string $tableName Table name column resides in
* @param string $columnName Column name the constraint is for * @param string $columnName Column name the constraint is for
* @return string The check string * @return string The check string
@ -325,7 +330,7 @@ class MSSQLSchemaManager extends DBSchemaManager
$checks[$record['COLUMN_NAME']] = $record['CHECK_CLAUSE']; $checks[$record['COLUMN_NAME']] = $record['CHECK_CLAUSE'];
} }
self::$cached_checks[$tableName] = $checks; self::$cached_checks[$tableName] = $checks;
// Return via cached records // Return via cached records
return $this->getConstraintCheckClause($tableName, $columnName); return $this->getConstraintCheckClause($tableName, $columnName);
} }
@ -333,7 +338,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return the name of the default constraint applied to $tableName.$colName. * Return the name of the default constraint applied to $tableName.$colName.
* Will return null if no such constraint exists * Will return null if no such constraint exists
* *
* @param string $tableName Name of the table * @param string $tableName Name of the table
* @param string $colName Name of the column * @param string $colName Name of the column
* @return string|null * @return string|null
@ -354,7 +359,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Get enum values from a constraint check clause. * Get enum values from a constraint check clause.
* *
* @param string $clause Check clause to parse values from * @param string $clause Check clause to parse values from
* @return array Enum values * @return array Enum values
*/ */
@ -395,8 +400,10 @@ class MSSQLSchemaManager extends DBSchemaManager
// drop *ALL* indexes on a table before proceeding // drop *ALL* indexes on a table before proceeding
// this won't drop primary keys, though // this won't drop primary keys, though
$indexes = $this->indexNames($tableName); $indexes = $this->indexNames($tableName);
$indexes = array_filter($indexes);
foreach ($indexes as $indexName) { foreach ($indexes as $indexName) {
$alterQueries[] = "DROP INDEX \"$indexName\" ON \"$tableName\";"; $alterQueries[] = "IF EXISTS (SELECT name FROM sys.indexes WHERE name = '$indexName' AND object_id = object_id(SCHEMA_NAME() + '.$tableName')) DROP INDEX \"$indexName\" ON \"$tableName\";";
} }
$prefix = "ALTER TABLE \"$tableName\" "; $prefix = "ALTER TABLE \"$tableName\" ";
@ -413,7 +420,7 @@ class MSSQLSchemaManager extends DBSchemaManager
// SET null / not null // SET null / not null
$nullFragment = empty($matches['null']) ? '' : " {$matches['null']}"; $nullFragment = empty($matches['null']) ? '' : " {$matches['null']}";
$alterQueries[] = "$prefix ALTER COLUMN \"$colName\" {$matches['definition']}$nullFragment;"; $alterQueries[] = "$prefix ALTER COLUMN \"$colName\" {$matches['definition']}$nullFragment;";
// Add a default back // Add a default back
if (!empty($matches['default'])) { if (!empty($matches['default'])) {
$alterQueries[] = "$prefix ADD {$matches['default']} FOR \"$colName\";"; $alterQueries[] = "$prefix ADD {$matches['default']} FOR \"$colName\";";
@ -583,10 +590,10 @@ class MSSQLSchemaManager extends DBSchemaManager
{ {
$this->query($this->getIndexSqlDefinition($tableName, $indexName, $indexSpec)); $this->query($this->getIndexSqlDefinition($tableName, $indexName, $indexSpec));
} }
/** /**
* Return SQL for dropping and recreating an index * Return SQL for dropping and recreating an index
* *
* @param string $tableName Name of table to create this index against * @param string $tableName Name of table to create this index against
* @param string $indexName Name of this index * @param string $indexName Name of this index
* @param array|string $indexSpec Index specification, either as a raw string * @param array|string $indexSpec Index specification, either as a raw string
@ -602,10 +609,13 @@ class MSSQLSchemaManager extends DBSchemaManager
// Consolidate/Cleanup spec into array format // Consolidate/Cleanup spec into array format
$indexSpec = $this->parseIndexSpec($indexName, $indexSpec); $indexSpec = $this->parseIndexSpec($indexName, $indexSpec);
$drop = "IF EXISTS (SELECT name FROM sys.indexes WHERE name = '$index') DROP INDEX $index ON \"$tableName\";"; $drop = "IF EXISTS (SELECT name FROM sys.indexes WHERE name = '$index' AND object_id = object_id(SCHEMA_NAME() + '.$tableName')) DROP INDEX $index ON \"$tableName\";";
// create a type-specific index // create a type-specific index
if ($indexSpec['type'] == 'fulltext' && $this->database->fullTextEnabled()) { if ($indexSpec['type'] == 'fulltext') {
if(!$this->database->fullTextEnabled()) {
return '';
}
// enable fulltext on this table // enable fulltext on this table
$this->createFullTextCatalog(); $this->createFullTextCatalog();
$primary_key = $this->getPrimaryKey($tableName); $primary_key = $this->getPrimaryKey($tableName);
@ -687,6 +697,7 @@ class MSSQLSchemaManager extends DBSchemaManager
* For a given table name, get all the internal index names, * For a given table name, get all the internal index names,
* except for those that are primary keys and fulltext indexes. * except for those that are primary keys and fulltext indexes.
* *
* @param string $tableName
* @return array * @return array
*/ */
public function indexNames($tableName) public function indexNames($tableName)
@ -694,11 +705,11 @@ class MSSQLSchemaManager extends DBSchemaManager
return $this->preparedQuery(' return $this->preparedQuery('
SELECT ind.name FROM sys.indexes ind SELECT ind.name FROM sys.indexes ind
INNER JOIN sys.tables t ON ind.object_id = t.object_id INNER JOIN sys.tables t ON ind.object_id = t.object_id
WHERE is_primary_key = 0 AND t.name = ?', WHERE is_primary_key = 0 AND t.name = ? AND ind.name IS NOT NULL',
array($tableName) array($tableName)
)->column(); )->column();
} }
public function tableList() public function tableList()
{ {
$tables = array(); $tables = array();
@ -712,7 +723,7 @@ class MSSQLSchemaManager extends DBSchemaManager
* Return a boolean type-formatted string * Return a boolean type-formatted string
* We use 'bit' so that we can do numeric-based comparisons * We use 'bit' so that we can do numeric-based comparisons
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function boolean($values) public function boolean($values)
@ -724,7 +735,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a date type-formatted string. * Return a date type-formatted string.
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function date($values) public function date($values)
@ -735,7 +746,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a decimal type-formatted string * Return a decimal type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function decimal($values) public function decimal($values)
@ -758,7 +769,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a enum type-formatted string * Return a enum type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function enum($values) public function enum($values)
@ -776,6 +787,9 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* @todo Make this work like {@link MySQLDatabase::set()} * @todo Make this work like {@link MySQLDatabase::set()}
*
* @param array $values
* @return string
*/ */
public function set($values) public function set($values)
{ {
@ -785,7 +799,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a float type-formatted string. * Return a float type-formatted string.
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function float($values) public function float($values)
@ -796,7 +810,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a int type-formatted string * Return a int type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function int($values) public function int($values)
@ -807,7 +821,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a bigint type-formatted string * Return a bigint type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function bigint($values) public function bigint($values)
@ -819,10 +833,10 @@ class MSSQLSchemaManager extends DBSchemaManager
* Return a datetime type-formatted string * Return a datetime type-formatted string
* For MS SQL, we simply return the word 'timestamp', no other parameters are necessary * For MS SQL, we simply return the word 'timestamp', no other parameters are necessary
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function ss_datetime($values) public function datetime($values)
{ {
return 'datetime null'; return 'datetime null';
} }
@ -830,7 +844,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a text type-formatted string * Return a text type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function text($values) public function text($values)
@ -843,7 +857,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a time type-formatted string. * Return a time type-formatted string.
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function time($values) public function time($values)
@ -854,7 +868,7 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a varchar type-formatted string * Return a varchar type-formatted string
* *
* @params array $values Contains a tokenised list of info about this data type * @param array $values Contains a tokenised list of info about this data type
* @return string * @return string
*/ */
public function varchar($values) public function varchar($values)
@ -866,6 +880,8 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Return a 4 digit numeric type. * Return a 4 digit numeric type.
*
* @param array $values
* @return string * @return string
*/ */
public function year($values) public function year($values)
@ -875,6 +891,9 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* This returns the column which is the primary key for each table * This returns the column which is the primary key for each table
*
* @param bool $asDbValue
* @param bool $hasAutoIncPK
* @return string * @return string
*/ */
public function IdColumn($asDbValue = false, $hasAutoIncPK = true) public function IdColumn($asDbValue = false, $hasAutoIncPK = true)
@ -899,6 +918,10 @@ class MSSQLSchemaManager extends DBSchemaManager
/** /**
* Returns the values of the given enum field * Returns the values of the given enum field
* NOTE: Experimental; introduced for db-abstraction and may changed before 2.4 is released. * NOTE: Experimental; introduced for db-abstraction and may changed before 2.4 is released.
*
* @param string $tableName
* @param string $fieldName
* @return array
*/ */
public function enumValuesForField($tableName, $fieldName) public function enumValuesForField($tableName, $fieldName)
{ {
@ -918,6 +941,9 @@ class MSSQLSchemaManager extends DBSchemaManager
* *
* For instance, MSSQL uses 'BIGINT', while MySQL uses 'UNSIGNED' * For instance, MSSQL uses 'BIGINT', while MySQL uses 'UNSIGNED'
* and PostgreSQL uses 'INT'. * and PostgreSQL uses 'INT'.
*
* @param string $type
* @return string
*/ */
public function dbDataType($type) public function dbDataType($type)
{ {
@ -930,7 +956,7 @@ class MSSQLSchemaManager extends DBSchemaManager
return ''; return '';
} }
} }
protected function indexKey($table, $index, $spec) protected function indexKey($table, $index, $spec)
{ {
return $index; return $index;

View File

@ -1,16 +1,18 @@
<?php <?php
namespace SilverStripe\MSSQL;
use SilverStripe\ORM\Connect\DBConnector;
/** /**
* Database connector driver for sqlsrv_ library * Database connector driver for sqlsrv_ library
*
* @package mssql
*/ */
class SQLServerConnector extends DBConnector class SQLServerConnector extends DBConnector
{ {
/** /**
* Connection to the DBMS. * Connection to the DBMS.
* *
* @var resource * @var resource
*/ */
protected $dbConn = null; protected $dbConn = null;
@ -19,11 +21,11 @@ class SQLServerConnector extends DBConnector
* Stores the affected rows of the last query. * Stores the affected rows of the last query.
* Used by sqlsrv functions only, as sqlsrv_rows_affected * Used by sqlsrv functions only, as sqlsrv_rows_affected
* accepts a result instead of a database handle. * accepts a result instead of a database handle.
* *
* @var integer * @var integer
*/ */
protected $lastAffectedRows; protected $lastAffectedRows;
/** /**
* Name of the currently selected database * Name of the currently selected database
* *
@ -46,21 +48,21 @@ class SQLServerConnector extends DBConnector
'CharacterSet' => $charset, 'CharacterSet' => $charset,
'MultipleActiveResultSets' => $multiResultSets 'MultipleActiveResultSets' => $multiResultSets
); );
if (!(defined('MSSQL_USE_WINDOWS_AUTHENTICATION') && MSSQL_USE_WINDOWS_AUTHENTICATION == true) if (!(defined('MSSQL_USE_WINDOWS_AUTHENTICATION') && MSSQL_USE_WINDOWS_AUTHENTICATION == true)
&& empty($parameters['windowsauthentication']) && empty($parameters['windowsauthentication'])
) { ) {
$options['UID'] = $parameters['username']; $options['UID'] = $parameters['username'];
$options['PWD'] = $parameters['password']; $options['PWD'] = $parameters['password'];
} }
// Required by MS Azure database // Required by MS Azure database
if ($selectDB && !empty($parameters['database'])) { if ($selectDB && !empty($parameters['database'])) {
$options['Database'] = $parameters['database']; $options['Database'] = $parameters['database'];
} }
$this->dbConn = sqlsrv_connect($parameters['server'], $options); $this->dbConn = sqlsrv_connect($parameters['server'], $options);
if (empty($this->dbConn)) { if (empty($this->dbConn)) {
$this->databaseError("Couldn't connect to SQL Server database"); $this->databaseError("Couldn't connect to SQL Server database");
} elseif ($selectDB && !empty($parameters['database'])) { } elseif ($selectDB && !empty($parameters['database'])) {
@ -68,7 +70,7 @@ class SQLServerConnector extends DBConnector
$this->selectedDatabase = $parameters['database']; $this->selectedDatabase = $parameters['database'];
} }
} }
/** /**
* Start transaction. READ ONLY not supported. * Start transaction. READ ONLY not supported.
*/ */
@ -79,7 +81,7 @@ class SQLServerConnector extends DBConnector
$this->databaseError("Couldn't start the transaction."); $this->databaseError("Couldn't start the transaction.");
} }
} }
/** /**
* Commit everything inside this transaction so far * Commit everything inside this transaction so far
*/ */
@ -90,7 +92,7 @@ class SQLServerConnector extends DBConnector
$this->databaseError("Couldn't commit the transaction."); $this->databaseError("Couldn't commit the transaction.");
} }
} }
/** /**
* Rollback or revert to a savepoint if your queries encounter problems * Rollback or revert to a savepoint if your queries encounter problems
* If you encounter a problem at any point during a transaction, you may * If you encounter a problem at any point during a transaction, you may
@ -108,7 +110,7 @@ class SQLServerConnector extends DBConnector
{ {
return $this->lastAffectedRows; return $this->lastAffectedRows;
} }
public function getLastError() public function getLastError()
{ {
$errorMessages = array(); $errorMessages = array();
@ -130,7 +132,7 @@ class SQLServerConnector extends DBConnector
{ {
// Reset state // Reset state
$this->lastAffectedRows = 0; $this->lastAffectedRows = 0;
// Run query // Run query
$parsedParameters = $this->parameterValues($parameters); $parsedParameters = $this->parameterValues($parameters);
if (empty($parsedParameters)) { if (empty($parsedParameters)) {
@ -138,13 +140,13 @@ class SQLServerConnector extends DBConnector
} else { } else {
$handle = sqlsrv_query($this->dbConn, $sql, $parsedParameters); $handle = sqlsrv_query($this->dbConn, $sql, $parsedParameters);
} }
// Check for error // Check for error
if (!$handle) { if (!$handle) {
$this->databaseError($this->getLastError(), $errorLevel, $sql, $parsedParameters); $this->databaseError($this->getLastError(), $errorLevel, $sql, $parsedParameters);
return null; return null;
} }
// Report result // Report result
$this->lastAffectedRows = sqlsrv_rows_affected($handle); $this->lastAffectedRows = sqlsrv_rows_affected($handle);
return new SQLServerQuery($this, $handle); return new SQLServerQuery($this, $handle);
@ -190,19 +192,19 @@ class SQLServerConnector extends DBConnector
$this->selectDatabase('Master'); $this->selectDatabase('Master');
$this->selectedDatabase = null; $this->selectedDatabase = null;
} }
/** /**
* Quotes a string, including the "N" prefix so unicode * Quotes a string, including the "N" prefix so unicode
* strings are saved to the database correctly. * strings are saved to the database correctly.
* *
* @param string $string String to be encoded * @param string $value String to be encoded
* @return string Processed string ready for DB * @return string Processed string ready for DB
*/ */
public function quoteString($value) public function quoteString($value)
{ {
return "N'" . $this->escapeString($value) . "'"; return "N'" . $this->escapeString($value) . "'";
} }
public function escapeString($value) public function escapeString($value)
{ {
$value = str_replace("'", "''", $value); $value = str_replace("'", "''", $value);

View File

@ -1,23 +1,26 @@
<?php <?php
namespace SilverStripe\MSSQL;
use DateTime;
use SilverStripe\ORM\Connect\Query;
/** /**
* A result-set from a MSSQL database. * A result-set from a MSSQL database.
*
* @package mssql
*/ */
class SQLServerQuery extends SS_Query class SQLServerQuery extends Query
{ {
/** /**
* The SQLServerConnector object that created this result set. * The SQLServerConnector object that created this result set.
* *
* @var SQLServerConnector * @var SQLServerConnector
*/ */
private $connector; private $connector;
/** /**
* The internal MSSQL handle that points to the result set. * The internal MSSQL handle that points to the result set.
* *
* @var resource * @var resource
*/ */
private $handle; private $handle;
@ -40,13 +43,21 @@ class SQLServerQuery extends SS_Query
} }
} }
public function seek($row) public function getIterator()
{ {
if (!is_resource($this->handle)) { if (is_resource($this->handle)) {
return false; while ($data = sqlsrv_fetch_array($this->handle, SQLSRV_FETCH_ASSOC)) {
} // special case for sqlsrv - date values are DateTime coming out of the sqlsrv drivers,
// so we convert to the usual Y-m-d H:i:s value!
foreach ($data as $name => $value) {
if ($value instanceof DateTime) {
$data[$name] = $value->format('Y-m-d H:i:s');
}
}
user_error('MSSQLQuery::seek() not supported in sqlsrv', E_USER_WARNING); yield $data;
}
}
} }
public function numRecords() public function numRecords()
@ -62,28 +73,4 @@ class SQLServerQuery extends SS_Query
user_error('MSSQLQuery::numRecords() not supported in this version of sqlsrv', E_USER_WARNING); user_error('MSSQLQuery::numRecords() not supported in this version of sqlsrv', E_USER_WARNING);
} }
} }
public function nextRecord()
{
if (!is_resource($this->handle)) {
return false;
}
if ($data = sqlsrv_fetch_array($this->handle, SQLSRV_FETCH_ASSOC)) {
// special case for sqlsrv - date values are DateTime coming out of the sqlsrv drivers,
// so we convert to the usual Y-m-d H:i:s value!
foreach ($data as $name => $value) {
if ($value instanceof DateTime) {
$data[$name] = $value->format('Y-m-d H:i:s');
}
}
return $data;
} else {
// Free the handle if there are no more results - sqlsrv crashes if there are too many handles
sqlsrv_free_stmt($this->handle);
$this->handle = null;
}
return false;
}
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "silverstripe/mssql", "name": "silverstripe/mssql",
"description": "Adds MSSQL support to SilverStripe", "description": "Adds MSSQL support to SilverStripe",
"type": "silverstripe-module", "type": "silverstripe-vendormodule",
"keywords": ["silverstripe", "mssql", "database"], "keywords": ["silverstripe", "mssql", "database"],
"authors": [ "authors": [
{ {
@ -14,11 +14,20 @@
} }
], ],
"require": { "require": {
"silverstripe/framework": "^3.2" "silverstripe/framework": "^4"
},
"suggest": {
"ext-sqlsrv": "Required to support MSSQLDatabase as the server type",
"ext-pdo_sqlsrv": "Required to support MSSQLPDODatabase as the server type"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0.x-dev" "dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"SilverStripe\\MSSQL\\": "code/"
} }
}, },
"prefer-stable": true, "prefer-stable": true,

View File

@ -1,4 +1,9 @@
<?php <?php
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
class MSSQLDatabaseQueryTest extends SapphireTest class MSSQLDatabaseQueryTest extends SapphireTest
{ {
@ -23,8 +28,8 @@ class MSSQLDatabaseQueryTest extends SapphireTest
class MSSQLDatabaseQueryTestDataObject extends DataObject implements TestOnly class MSSQLDatabaseQueryTestDataObject extends DataObject implements TestOnly
{ {
public static $db = array( private static $db = array(
'TestDate' => 'Date', 'TestDate' => 'Date',
'TestDatetime' => 'SS_Datetime' 'TestDatetime' => 'Datetime'
); );
} }