Skip to content

Commit

Permalink
Merge pull request #86 from potfur/quoting
Browse files Browse the repository at this point in the history
Added quoting #84
  • Loading branch information
vlucas committed Aug 31, 2015
2 parents df497f4 + a1aa5c8 commit 75b6d81
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 18 deletions.
71 changes: 57 additions & 14 deletions lib/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/
class Query implements \Countable, \IteratorAggregate, \ArrayAccess
{
const ALL_FIELDS = '*';

/**
* @var \Spot\Mapper
*/
Expand Down Expand Up @@ -93,10 +95,24 @@ public function builder()
public function noQuote($noQuote = true)
{
$this->_noQuote = $noQuote;
$this->reEscapeFrom();

return $this;
}

/**
* Re-escape from part of query according to new noQuote value
*/
protected function reEscapeFrom()
{
$part = $this->builder()->getQueryPart('from');
$this->builder()->resetQueryPart('from');

foreach($part as $node) {
$this->from($this->unescapeIdentifier($node['table']), $this->unescapeIdentifier($node['alias']));
}
}

/**
* Return DBAL Query builder expression
*
Expand Down Expand Up @@ -149,7 +165,7 @@ public function entityName()
*/
public function select()
{
call_user_func_array([$this->builder(), 'select'], func_get_args());
call_user_func_array([$this->builder(), 'select'], $this->escapeIdentifier(func_get_args()));

return $this;
}
Expand All @@ -161,7 +177,7 @@ public function select()
*/
public function delete()
{
call_user_func_array([$this->builder(), 'delete'], func_get_args());
call_user_func_array([$this->builder(), 'delete'], $this->escapeIdentifier(func_get_args()));

return $this;
}
Expand All @@ -173,7 +189,7 @@ public function delete()
*/
public function from()
{
call_user_func_array([$this->builder(), 'from'], func_get_args());
call_user_func_array([$this->builder(), 'from'], $this->escapeIdentifier(func_get_args()));

return $this;
}
Expand Down Expand Up @@ -269,7 +285,7 @@ public function whereFieldSql($field, $sql, array $params = [])

return $builder->createPositionalParameter($param);
}, $sql);
$builder->andWhere($this->escapeField($field) . ' ' . $sql);
$builder->andWhere($this->escapeIdentifier($field) . ' ' . $sql);

return $this;
}
Expand Down Expand Up @@ -487,8 +503,8 @@ public function search($fields, $query, array $options = [])
*/
public function order(array $order)
{
foreach ($order as $field => $order) {
$this->builder()->addOrderBy($this->fieldWithAlias($field), $order);
foreach ($order as $field => $sorting) {
$this->builder()->addOrderBy($this->fieldWithAlias($field), $sorting);
}

return $this;
Expand Down Expand Up @@ -651,19 +667,46 @@ public function escape($string)
}

/**
* Escape/quote direct user input
* Removes escape/quote character
*
* @param string $field
* @param string $identifier
* @return string
* @throws Exception
*/
public function escapeField($field)
public function unescapeIdentifier($identifier)
{
if ($this->_noQuote) {
return $field;
if (strpos($identifier, ".") !== false) {
$parts = array_map(array($this, "unescapeIdentifier"), explode(".", $identifier));

return implode(".", $parts);
}

return trim($identifier, $this->mapper()->connection()->getDatabasePlatform()->getIdentifierQuoteCharacter());
}

/**
* Escape/quote identifier
*
* @param string|array $identifier
* @return string|array
*/
public function escapeIdentifier($identifier)
{
if (is_array($identifier)) {
array_walk($identifier, function(&$identifier) {
$identifier = $this->escapeIdentifier($identifier);
});
return $identifier;
}

if ($this->_noQuote || $identifier === self::ALL_FIELDS) {
return $identifier;
}

if (strpos($identifier, ' ') !== false || strpos($identifier, '(') !== false) {
return $identifier; // complex expression, ain't quote it, do it manually!
}

return $this->mapper()->connection()->quoteIdentifier($field);
return $this->mapper()->connection()->quoteIdentifier(trim($identifier));
}

/**
Expand All @@ -676,7 +719,7 @@ public function fieldWithAlias($field, $escaped = true)
{
$field = $this->_tableName . '.' . $field;

return $escaped ? $this->escapeField($field) : $field;
return $escaped ? $this->escapeIdentifier($field) : $field;
}

/**
Expand Down
41 changes: 37 additions & 4 deletions lib/Query/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class Resolver
*/
protected $mapper;

protected $_noQuote;

/**
* Constructor Method
*
Expand All @@ -27,6 +29,20 @@ public function __construct(Mapper $mapper)
$this->mapper = $mapper;
}

/**
* Set field and value quoting on/off - maily used for testing output SQL
* since quoting is different per platform
*
* @param bool $noQuote
* @return $this
*/
public function noQuote($noQuote = true)
{
$this->_noQuote = $noQuote;

return $this;
}

/**
* Migrate table structure changes to database
*
Expand Down Expand Up @@ -83,7 +99,7 @@ public function migrateCreateSchema()
$fieldIndexes = $this->mapper->entityManager()->fieldKeys();

$schema = new \Doctrine\DBAL\Schema\Schema();
$table = $schema->createTable($table);
$table = $schema->createTable($this->escapeIdentifier($table));

foreach ($fields as $field => $fieldInfo) {
$fieldType = $fieldInfo['type'];
Expand Down Expand Up @@ -138,7 +154,7 @@ public function read(Query $query)
public function create($table, array $data)
{
$connection = $this->mapper->connection();
$result = $connection->insert($table, $data);
$result = $connection->insert($this->escapeIdentifier($table), $data);

return $result;
}
Expand All @@ -156,7 +172,7 @@ public function update($table, array $data, array $where)
{
$connection = $this->mapper->connection();

return $connection->update($table, $data, $where);
return $connection->update($this->escapeIdentifier($table), $data, $where);
}

/**
Expand Down Expand Up @@ -185,6 +201,8 @@ public function truncate($table, $cascade = false)
$mapper = $this->mapper;
$connection = $mapper->connection();

$table = $this->escapeIdentifier($table);

// SQLite doesn't support TRUNCATE
if ($mapper->connectionIs("sqlite")) {
$sql = "DELETE FROM " . $table;
Expand All @@ -210,11 +228,26 @@ public function dropTable($table)
$result = false;
$connection = $this->mapper->connection();
try {
$result = $connection->getSchemaManager()->dropTable($table);
$result = $connection->getSchemaManager()->dropTable($this->escapeIdentifier($table));
} catch (\Exception $e) {
$result = false;
}

return $result;
}

/**
* Escape/quote identifier
*
* @param string $identifier
* @return string
*/
public function escapeIdentifier($identifier)
{
if($this->_noQuote) {
return $identifier;
}

return $this->mapper->connection()->quoteIdentifier(trim($identifier));
}
}
43 changes: 43 additions & 0 deletions tests/QuerySql.php
Original file line number Diff line number Diff line change
Expand Up @@ -371,4 +371,47 @@ public function testCustomQueryWithSqlAndNamedParams()

$this->assertSame($postCount, $i);
}

/**
* @dataProvider identifierProvider
*/
public function testEscapingIdentifier($identifier, $expected)
{
$mapper = test_spot_mapper('SpotTest\Entity\Post');
$quote = $mapper->connection()->getDatabasePlatform()->getIdentifierQuoteCharacter();

$this->assertEquals(
sprintf($expected, $quote),
$mapper->where(['id !=' => null])->escapeIdentifier($identifier)
);
}

public function identifierProvider()
{
return [
['table', '%1$stable%1$s'],
['table.field', '%1$stable%1$s.%1$sfield%1$s'],
['count field', 'count field'],
['distinct(field)', 'distinct(field)'],
['max(field) as max', 'max(field) as max'],
];
}

public function testEscapingInQuery()
{
$mapper = test_spot_mapper('SpotTest\Entity\Post');

$expected = str_replace(
'`',
$mapper->connection()->getDatabasePlatform()->getIdentifierQuoteCharacter(),
'SELECT * FROM `test_posts` `test_posts` WHERE `test_posts`.`title` LIKE ? AND `test_posts`.`status` >= ?'
);

$query = $mapper->where(['title :like' => 'lorem', 'status >=' => 1])->toSql();

$this->assertEquals(
$expected,
$query
);
}
}

0 comments on commit 75b6d81

Please sign in to comment.