Skip to content

Commit

Permalink
Implementing a basic Dialects system and a way for them to translate a
Browse files Browse the repository at this point in the history
query object to the target driver
  • Loading branch information
lorenzo committed Jan 23, 2013
1 parent ad713c3 commit 01d4523
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 61 deletions.
26 changes: 26 additions & 0 deletions lib/Cake/Model/Datasource/Database/Dialect/PostgresDialect.php
@@ -0,0 +1,26 @@
<?php
/**
*
* PHP Version 5.4
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The Open Group Test Suite License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2013, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.Test.Case.Model.Datasource.Database
* @since CakePHP(tm) v 3.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\Model\Datasource\Database\Dialect;

trait PostgresDialect {

protected function _selectQueryTranslator($query) {
return $query;
}

}
64 changes: 5 additions & 59 deletions lib/Cake/Model/Datasource/Database/Driver.php
Expand Up @@ -2,26 +2,16 @@

namespace Cake\Model\Datasource\Database;

use \Cake\Model\Datasource\Database\SqlDialect;

/**
* Represents a database diver containing all specificities for
* a database engine including its SQL dialect
*
**/
abstract class Driver {

/**
* String used to start a database identifier quoting to make it safe
*
* @var string
**/
public $startQuote = '"';

/**
* String used to end a database identifier quoting to make it safe
*
* @var string
**/
public $endQuote = '"';
use SqlDialect;

/**
* Establishes a connection to the database server
Expand All @@ -40,10 +30,9 @@ public abstract function disconnect();

/**
* Returns correct connection resource or object that is internally used
* If first argument is passed, it will set internal conenction object or
* result to the value passed
* If first argument is passed,
*
* @return mixed connection object used internally
* @return void
**/
public abstract function connection($connection = null);

Expand Down Expand Up @@ -130,49 +119,6 @@ public function rollbackSavePointSQL($name) {
**/
public abstract function quote($value, $type);

/**
* Quotes a database identifier (a column name, table name, etc..) to
* be used safely in queries without the risk of using reserver words
*
* @param string $identifier
* @return string
**/
public function quoteIdentifier($identifier) {
$identifier = trim($identifier);

if ($identifier === '*') {
return '*';
}

if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $identifier)) { // string, string.string
if (strpos($identifier, '.') === false) { // string
return $this->startQuote . $identifier . $this->endQuote;
}
$items = explode('.', $identifier);
return $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote;
}

if (preg_match('/^[\w-]+\.\*$/', $identifier)) { // string.*
return $this->startQuote . str_replace('.*', $this->endQuote . '.*', $identifier);
}

if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) { // Functions
return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
}

if (preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+AS\s*([\w-]+)$/i', $identifier, $matches)) {
return preg_replace(
'/\s{2,}/', ' ', $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3])
);
}

if (preg_match('/^[\w-_\s]*[\w-_]+/', $identifier)) {
return $this->startQuote . $identifier . $this->endQuote;
}

return $identifier;
}

/**
* Returns last id generated for a table or sequence in database
*
Expand Down
19 changes: 18 additions & 1 deletion lib/Cake/Model/Datasource/Database/Driver/Postgres.php
@@ -1,13 +1,30 @@
<?php

/**
*
* PHP Version 5.4
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The Open Group Test Suite License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2013, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.Test.Case.Model.Datasource.Database
* @since CakePHP(tm) v 3.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\Model\Datasource\Database\Driver;

use Cake\Model\Datasource\Database\Statement;
use Cake\Model\Datasource\Database\Dialect\PostgresDialect;
use PDO;

class Postgres extends \Cake\Model\Datasource\Database\Driver {

use PDODriver { connect as protected _connect; }
use PostgresDialect;

/**
* Base configuration settings for Postgres driver
Expand Down
6 changes: 5 additions & 1 deletion lib/Cake/Model/Datasource/Database/Query.php
Expand Up @@ -116,7 +116,11 @@ public function sql() {
return $sql .= $this->{'_build' . ucFirst($name) . 'Part'}($parts, $sql);
};

$this->build($builder);
// TODO: Should Query actually get the driver or just let the connection decide where
// to get the query translator?
$translator = $this->connection()->driver()->queryTranslator($this->_type);
$query = $translator($this);
$query->build($builder->bindTo($query));

This comment has been minimized.

Copy link
@markstory

markstory Jan 28, 2013

Member

Is there a way to use the connection to get a query, that way the connection could inject it's dialect into the query. Something like:

$query = $connection->query();

I might not fully understand this comment or the problem though.

return $sql;
}

Expand Down
95 changes: 95 additions & 0 deletions lib/Cake/Model/Datasource/Database/SQLDialect.php
@@ -0,0 +1,95 @@
<?php
/**
*
* PHP Version 5.4
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The Open Group Test Suite License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2013, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.Test.Case.Model.Datasource.Database
* @since CakePHP(tm) v 3.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\Model\Datasource\Database;

trait SQLDialect {

/**
* String used to start a database identifier quoting to make it safe
*
* @var string
**/
public $startQuote = '"';

/**
* String used to end a database identifier quoting to make it safe
*
* @var string
**/
public $endQuote = '"';

/**
* Quotes a database identifier (a column name, table name, etc..) to
* be used safely in queries without the risk of using reserver words
*
* @param string $identifier
* @return string
**/
public function quoteIdentifier($identifier) {
$identifier = trim($identifier);

if ($identifier === '*') {
return '*';
}

if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $identifier)) { // string, string.string
if (strpos($identifier, '.') === false) { // string
return $this->startQuote . $identifier . $this->endQuote;
}
$items = explode('.', $identifier);
return $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote;
}

if (preg_match('/^[\w-]+\.\*$/', $identifier)) { // string.*
return $this->startQuote . str_replace('.*', $this->endQuote . '.*', $identifier);
}

if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) { // Functions
return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')';
}

if (preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+AS\s*([\w-]+)$/i', $identifier, $matches)) {
return preg_replace(
'/\s{2,}/', ' ', $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3])
);
}

if (preg_match('/^[\w-_\s]*[\w-_]+/', $identifier)) {
return $this->startQuote . $identifier . $this->endQuote;
}

return $identifier;
}

public function queryTranslator($type) {
return function($query) use ($type) {
return $this->{'_' . $type . 'QueryTranslator'}($query);
};
}

protected function _selectQueryTranslator($query) {
if (is_array($query->clause('distinct'))) {
$query->group($query->clause('distinct'), true);
$query->distinct(false);
}

return $query;
}

}

This comment has been minimized.

Copy link
@markstory

markstory Jan 28, 2013

Member

This is a neat idea and nicer than having tons of additional objects. It also solves the problem of being able to reuse the postgres dialect between a pdo and procedural interface should someone want to use it. Nice stuff.


0 comments on commit 01d4523

Please sign in to comment.