Skip to content

Commit

Permalink
Add MS SQL TOP style syntax support
Browse files Browse the repository at this point in the history
  • Loading branch information
treffynnon committed Aug 28, 2013
2 parents 4e9f892 + 33c3937 commit baff6ed
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 28 deletions.
2 changes: 2 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ Changelog
* Add PSR-1 compliant camelCase method calls to Idiorm (PHP 5.3+ required) [[crhayes](https://github.com/crhayes)] - [issue #108](https://github.com/j4mie/idiorm/issues/108)
* Add static method `get_config()` to access current configuration [[javierd](https://github.com/mikejestes)] - [issue #141](https://github.com/j4mie/idiorm/issues/141)
* Add logging callback functionality [[lalop](https://github.com/lalop)] - [issue #130](https://github.com/j4mie/idiorm/issues/130)
* Add support for MS SQL ``TOP`` limit style (automatically used for PDO drivers: sqlsrv, dblib and mssql) [[numkem](https://github.com/numkem)] - [issue #116](https://github.com/j4mie/idiorm/issues/116)
* Uses table aliases in `WHERE` clauses [[vicvicvic](https://github.com/vicvicvic)] - [issue #140](https://github.com/j4mie/idiorm/issues/140)
* Ignore result columns when calling an aggregate function [[tassoevan](https://github.com/tassoevan)] - [issue #120](https://github.com/j4mie/idiorm/issues/120)
* Improve documentation [[bruston](https://github.com/bruston)] - [issue #111](https://github.com/j4mie/idiorm/issues/111)
* Improve PHPDoc on `get_db()` [[mailopl](https://github.com/mailopl)] - [issue #106](https://github.com/j4mie/idiorm/issues/106)
* Improve documentation [[sjparsons](https://github.com/sjparsons)] - [issue #103](https://github.com/j4mie/idiorm/issues/103)
* Make tests/bootstrap.php [[JoelMarcey](https://github.com/JoelMarcey)] - [issue #143](https://github.com/j4mie/idiorm/issues/143)

#### 1.3.0 - release 2013-01-31

Expand Down
16 changes: 16 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,22 @@ the table, you can use the following configuration:
'role' => 'role_id',
));
Limit clause style
^^^^^^^^^^^^^^^^^^

Setting: ``limit_clause_style``

You can specify the limit clause style in the configuration. This is to facilitate
a MS SQL style limit clause that uses the ``TOP`` syntax.

Acceptable values are ``ORM::LIMIT_STYLE_TOP_N`` and ``ORM::LIMIT_STYLE_LIMIT``.

.. note::

If the PDO driver you are using is one of sqlsrv, dblib or mssql then Idiorm
will automatically select the ``ORM::LIMIT_STYLE_TOP_N`` for you unless you
override the setting.

Query logging
^^^^^^^^^^^^^

Expand Down
73 changes: 66 additions & 7 deletions idiorm.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class ORM implements ArrayAccess {

const DEFAULT_CONNECTION = 'default';

// Limit clause style
const LIMIT_STYLE_TOP_N = "top";
const LIMIT_STYLE_LIMIT = "limit";

// ------------------------ //
// --- CLASS PROPERTIES --- //
// ------------------------ //
Expand All @@ -64,6 +68,7 @@ class ORM implements ArrayAccess {
'password' => null,
'driver_options' => null,
'identifier_quote_character' => null, // if this is null, will be autodetected
'limit_clause_style' => null, // if this is null, will be autodetected
'logging' => false,
'logger' => null,
'caching' => false,
Expand Down Expand Up @@ -210,6 +215,13 @@ public static function get_config($key = null, $connection_name = self::DEFAULT_
}
}

/**
* Delete all configs in _config array.
*/
public static function reset_config() {
self::$_config = array();
}

/**
* Despite its slightly odd name, this is actually the factory
* method used to acquire instances of the class. It is named
Expand Down Expand Up @@ -268,6 +280,14 @@ public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION)
self::_setup_db_config($connection_name);
self::$_db[$connection_name] = $db;
self::_setup_identifier_quote_character($connection_name);
self::_setup_limit_clause_style($connection_name);
}

/**
* Delete all registered PDO objects in _db array.
*/
public static function reset_db() {
self::$_db = array();
}

/**
Expand All @@ -280,7 +300,20 @@ public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION)
protected static function _setup_identifier_quote_character($connection_name) {
if (is_null(self::$_config[$connection_name]['identifier_quote_character'])) {
self::$_config[$connection_name]['identifier_quote_character'] =
self::_detect_identifier_quote_character($connection_name);
self::_detect_identifier_quote_character($connection_name);
}
}

/**
* Detect and initialise the limit clause style ("SELECT TOP 5" /
* "... LIMIT 5"). If this has been specified manually using
* ORM::configure('limit_clause_style', 'top'), this will do nothing.
* @param string $connection_name Which connection to use
*/
public static function _setup_limit_clause_style($connection_name) {
if (is_null(self::$_config[$connection_name]['limit_clause_style'])) {
self::$_config[$connection_name]['limit_clause_style'] =
self::_detect_limit_clause_style($connection_name);
}
}

Expand All @@ -307,6 +340,23 @@ protected static function _detect_identifier_quote_character($connection_name) {
}
}

/**
* Returns a constant after determining the appropriate limit clause
* style
* @param string $connection_name Which connection to use
* @return string Limit clause style keyword/constant
*/
protected static function _detect_limit_clause_style($connection_name) {
switch(self::$_db[$connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME)) {
case 'sqlsrv':
case 'dblib':
case 'mssql':
return ORM::LIMIT_STYLE_TOP_N;
default:
return ORM::LIMIT_STYLE_LIMIT;
}
}

/**
* Returns the PDO instance used by the the ORM to communicate with
* the database. This can be called if any low-level DB access is
Expand Down Expand Up @@ -1318,13 +1368,19 @@ protected function _build_select() {
* Build the start of the SELECT statement
*/
protected function _build_select_start() {
$fragment = 'SELECT ';
$result_columns = join(', ', $this->_result_columns);

if (!is_null($this->_limit) &&
self::$_config[$this->_connection_name]['limit_clause_style'] === ORM::LIMIT_STYLE_TOP_N) {
$fragment .= "TOP {$this->_limit} ";
}

if ($this->_distinct) {
$result_columns = 'DISTINCT ' . $result_columns;
}

$fragment = "SELECT {$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
$fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);

if (!is_null($this->_table_alias)) {
$fragment .= " " . $this->_quote_identifier($this->_table_alias);
Expand Down Expand Up @@ -1402,14 +1458,17 @@ protected function _build_order_by() {
* Build LIMIT
*/
protected function _build_limit() {
if (!is_null($this->_limit)) {
$clause = 'LIMIT';
$fragment = '';
if (!is_null($this->_limit) &&
self::$_config[$this->_connection_name]['limit_clause_style'] == ORM::LIMIT_STYLE_LIMIT) {
if (self::$_db[$this->_connection_name]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
$clause = 'ROWS';
$fragment = 'ROWS';
} else {
$fragment = 'LIMIT';
}
return "$clause " . $this->_limit;
$fragment .= " {$this->_limit}";
}
return '';
return $fragment;
}

/**
Expand Down
8 changes: 2 additions & 6 deletions test/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::configure('logging', false, self::ALTERNATE);
ORM::configure('caching', false);
ORM::configure('caching', false, self::ALTERNATE);
ORM::set_db(null);
ORM::set_db(null, self::ALTERNATE);
ORM::reset_config();
ORM::reset_db();
}

// Test caching. This is a bit of a hack.
Expand Down
7 changes: 3 additions & 4 deletions test/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::set_db(null);

ORM::configure('id_column', 'id');
ORM::reset_config();
ORM::reset_db();
}

protected function setUpIdColumnOverrides() {
Expand Down Expand Up @@ -118,6 +116,7 @@ public function testGetConfigArray() {
'logger' => null,
'caching' => false,
'return_result_sets' => false,
'limit_clause_style' => 'limit',
);
$this->assertEquals($expected, ORM::get_config());
}
Expand Down
4 changes: 2 additions & 2 deletions test/IdiormResultSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::set_db(null);
ORM::reset_config();
ORM::reset_db();
}

public function testGet() {
Expand Down
6 changes: 2 additions & 4 deletions test/MulitpleConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::configure('logging', false, self::ALTERNATE);
ORM::set_db(null);
ORM::set_db(null, self::ALTERNATE);
ORM::reset_config();
ORM::reset_db();
}

public function testMultiplePdoConnections() {
Expand Down
4 changes: 2 additions & 2 deletions test/ORMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::set_db(null);
ORM::reset_config();
ORM::reset_db();
}

public function testStaticAtrributes() {
Expand Down
32 changes: 32 additions & 0 deletions test/QueryBuilderMssqlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

class QueryBuilderMssqlTest extends PHPUnit_Framework_TestCase {

public function setUp() {
// Enable logging
ORM::configure('logging', true);

// Set up the dummy database connection
$db = new MockMsSqlPDO('sqlite::memory:');
ORM::set_db($db);
}

public function tearDown() {
ORM::reset_config();
ORM::reset_db();
}

public function testFindOne() {
ORM::for_table('widget')->find_one();
$expected = 'SELECT TOP 1 * FROM "widget"';
$this->assertEquals($expected, ORM::get_last_query());
}

public function testLimit() {
ORM::for_table('widget')->limit(5)->find_many();
$expected = 'SELECT TOP 5 * FROM "widget"';
$this->assertEquals($expected, ORM::get_last_query());
}

}

4 changes: 2 additions & 2 deletions test/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public function setUp() {
}

public function tearDown() {
ORM::configure('logging', false);
ORM::set_db(null);
ORM::reset_config();
ORM::reset_db();
}

public function testFindMany() {
Expand Down
22 changes: 21 additions & 1 deletion test/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MockDifferentPDOStatement extends MockPDOStatement { }
*
*/
class MockPDO extends PDO {

/**
* Return a dummy PDO statement
*/
Expand All @@ -62,3 +62,23 @@ public function prepare($statement, $driver_options = array()) {
return $this->last_query;
}
}

class MockMsSqlPDO extends MockPDO {

public $fake_driver = 'mssql';

/**
* If we are asking for the name of the driver, check if a fake one
* has been set.
*/
public function getAttribute($attribute) {
if ($attribute == self::ATTR_DRIVER_NAME) {
if (!is_null($this->fake_driver)) {
return $this->fake_driver;
}
}

return parent::getAttribute($attribute);
}

}

0 comments on commit baff6ed

Please sign in to comment.