diff --git a/src/xPDO/Om/mysql/xPDOQuery.php b/src/xPDO/Om/mysql/xPDOQuery.php
index 38988237..54dc1420 100644
--- a/src/xPDO/Om/mysql/xPDOQuery.php
+++ b/src/xPDO/Om/mysql/xPDOQuery.php
@@ -133,4 +133,15 @@ public function construct() {
$this->sql= $sql;
return (!empty ($this->sql));
}
+
+ protected function isColumnIdentifier($key)
+ {
+ $unQuoted = '(?![\d]+(?:\.|$))[A-Za-z0-9$_\x80-\xff]{1,64}';
+ $escapeChars = '`(?:[^`\x00]|``){1,128}`';
+ $dblQuote = '"(?:[^"\x00]|""){1,128}"';
+ $ident = "(?:{$escapeChars}|{$dblQuote}|{$unQuoted})";
+ $columnIdent = "(?:{$ident}\.)?{$ident}";
+
+ return (bool) preg_match("/^{$columnIdent}$/", trim($key));
+ }
}
diff --git a/src/xPDO/Om/pgsql/xPDOQuery.php b/src/xPDO/Om/pgsql/xPDOQuery.php
index 095eeec0..1bbbb0a5 100644
--- a/src/xPDO/Om/pgsql/xPDOQuery.php
+++ b/src/xPDO/Om/pgsql/xPDOQuery.php
@@ -124,4 +124,15 @@ public function construct() {
$this->sql= $sql;
return (!empty ($this->sql));
}
+
+ protected function isColumnIdentifier($key)
+ {
+ $unQuoted = '[A-Za-z_\x80-\xff][A-Za-z0-9_$\x80-\xff]{0,62}';
+ $dblQuote = '"(?:[^"\x00]|""){1,126}"';
+ $unicode = 'U&"(?:[^"\x00]|""|\\\\[0-9A-Fa-f]{4}|\\\\\\+[0-9A-Fa-f]{6}){1,}"(?:\s*UESCAPE\s*\'[^0-9A-Fa-f+\x27\\\\]\s*\')?';
+ $ident = "(?:{$unicode}|{$dblQuote}|{$unQuoted})";
+ $columnIdent = "(?:{$ident}\.)?{$ident}";
+
+ return (bool) preg_match("/^{$columnIdent}$/", trim($key));
+ }
}
diff --git a/src/xPDO/Om/sqlite/xPDOQuery.php b/src/xPDO/Om/sqlite/xPDOQuery.php
index f2364402..19a6f0b9 100644
--- a/src/xPDO/Om/sqlite/xPDOQuery.php
+++ b/src/xPDO/Om/sqlite/xPDOQuery.php
@@ -133,4 +133,16 @@ public function construct() {
$this->sql= $sql;
return (!empty ($this->sql));
}
+
+ protected function isColumnIdentifier($key)
+ {
+ $unQuoted = '[A-Za-z_][A-Za-z0-9_]*';
+ $backtick = '`(?:[^`\x00]|``){1,128}`';
+ $bracket = '\[[^\]\x00]+\]';
+ $dblQuote = '"(?:[^"\x00]|""){1,}"';
+ $ident = "(?:{$dblQuote}|{$backtick}|{$bracket}|{$unQuoted})";
+ $columnIdent = "(?:{$ident}\.)?{$ident}";
+
+ return (bool) preg_match("/^{$columnIdent}$/", trim($key));
+ }
}
diff --git a/src/xPDO/Om/sqlsrv/xPDOQuery.php b/src/xPDO/Om/sqlsrv/xPDOQuery.php
index d191ea95..be20f248 100644
--- a/src/xPDO/Om/sqlsrv/xPDOQuery.php
+++ b/src/xPDO/Om/sqlsrv/xPDOQuery.php
@@ -296,4 +296,15 @@ public function construct() {
$this->sql= $sql;
return (!empty ($this->sql));
}
+
+ protected function isColumnIdentifier($key)
+ {
+ $unQuoted = '(?![\d]+(?:\.|$))[A-Za-z_@#\x80-\xff][A-Za-z0-9_@#$\x80-\xff]{0,127}';
+ $bracket = '\[(?:[^\]\x00\xff\xff]|\]\]){1,128}\]';
+ $dblQuote = '"(?:[^"\x00]|""){1,128}"';
+ $ident = "(?:{$bracket}|{$dblQuote}|{$unQuoted})";
+ $columnIdent = "(?:{$ident}\.)?{$ident}";
+
+ return (bool) preg_match("/^{$columnIdent}$/", trim($key));
+ }
}
diff --git a/src/xPDO/Om/xPDOCriteria.php b/src/xPDO/Om/xPDOCriteria.php
index d506eae7..40172226 100644
--- a/src/xPDO/Om/xPDOCriteria.php
+++ b/src/xPDO/Om/xPDOCriteria.php
@@ -18,6 +18,7 @@
* @package xPDO\Om
*/
class xPDOCriteria {
+ /** @var xPDO $xpdo */
public $xpdo= null;
public $sql= '';
public $stmt= null;
diff --git a/src/xPDO/Om/xPDOQuery.php b/src/xPDO/Om/xPDOQuery.php
index c4e98907..f5ab862e 100644
--- a/src/xPDO/Om/xPDOQuery.php
+++ b/src/xPDO/Om/xPDOQuery.php
@@ -66,7 +66,22 @@ abstract class xPDOQuery extends xPDOCriteria {
'MIN(',
'AVG('
);
- protected $_quotable= array ('string', 'password', 'date', 'datetime', 'timestamp', 'time', 'json', 'array', 'float');
+ protected $_quotable = [
+ 'string',
+ 'password',
+ 'date',
+ 'datetime',
+ 'timestamp',
+ 'time',
+ 'json',
+ 'array',
+ 'float',
+ 'double',
+ 'object',
+ 'resource',
+ 'unknown type',
+ 'resource (closed)'
+ ];
protected $_class= null;
protected $_alias= null;
protected $_tableClass = null;
@@ -737,30 +752,42 @@ public function parseConditions($conditions, $conjunction = xPDOQuery::SQL_AND)
$key= $key_operator[1];
$operator= strtoupper($key_operator[2]);
}
- if (strpos($key, '.') !== false) {
- $key_parts= explode('.', $key);
- $alias= trim($key_parts[0], " {$this->xpdo->_escapeCharOpen}{$this->xpdo->_escapeCharClose}");
- $key= $key_parts[1];
- }
- if (!array_key_exists($key, $fieldMeta)) {
- if (array_key_exists($key, $fieldAliases)) {
- $key= $fieldAliases[$key];
- } elseif ($this->isConditionalClause($key)) {
- continue;
+ $operand = $key;
+ $isColumnIdentifier = $this->isColumnIdentifier($key);
+ if ($isColumnIdentifier) {
+ if (strpos($key, '.') !== false) {
+ $key_parts = explode('.', $key);
+ $alias = trim(
+ $key_parts[0],
+ " {$this->xpdo->_escapeCharOpen}{$this->xpdo->_escapeCharClose}"
+ );
+ $key = $key_parts[1];
}
+ if (!array_key_exists($key, $fieldMeta)) {
+ if (array_key_exists($key, $fieldAliases)) {
+ $key = $fieldAliases[$key];
+ }
+ }
+ $operand = "{$this->xpdo->escape($alias)}.{$this->xpdo->escape($key)}";
}
- if (!empty($key)) {
+ if (!empty($key) && !$this->isConditionalClause($key)) {
if ($val === null) {
$type= \PDO::PARAM_NULL;
if (!in_array($operator, array('IS', 'IS NOT'))) {
$operator= $operator === '!=' ? 'IS NOT' : 'IS';
}
}
- elseif (isset($fieldMeta[$key]) && !in_array($fieldMeta[$key]['phptype'], $this->_quotable)) {
- $type= \PDO::PARAM_INT;
- }
- else {
- $type= \PDO::PARAM_STR;
+ elseif ($isColumnIdentifier) {
+ if (isset($fieldMeta[$key]) && !in_array(
+ $fieldMeta[$key]['phptype'],
+ $this->_quotable
+ )) {
+ $type = \PDO::PARAM_INT;
+ } else {
+ $type = \PDO::PARAM_STR;
+ }
+ } else {
+ $type = $this->isQuotable($val) ? \PDO::PARAM_STR : \PDO::PARAM_INT;
}
if (in_array($operator, array('IN', 'NOT IN')) && is_array($val)) {
$vals = array();
@@ -785,12 +812,12 @@ public function parseConditions($conditions, $conjunction = xPDOQuery::SQL_AND)
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Encountered empty {$operator} condition with key {$key}");
}
$val = "(" . implode(',', $vals) . ")";
- $sql = "{$this->xpdo->escape($alias)}.{$this->xpdo->escape($key)} {$operator} {$val}";
+ $sql = "{$operand} {$operator} {$val}";
$result[]= new xPDOQueryCondition(array('sql' => $sql, 'binding' => null, 'conjunction' => $conj));
continue;
}
$field= array ();
- $field['sql']= $this->xpdo->escape($alias) . '.' . $this->xpdo->escape($key) . ' ' . $operator . ' ?';
+ $field['sql']= $operand . ' ' . $operator . ' ?';
$field['binding']= array (
'value' => $val,
'type' => $type,
@@ -808,8 +835,8 @@ public function parseConditions($conditions, $conjunction = xPDOQuery::SQL_AND)
elseif ($this->isConditionalClause($conditions)) {
$result= new xPDOQueryCondition(array(
'sql' => $conditions
- ,'binding' => null
- ,'conjunction' => $conjunction
+ ,'binding' => null
+ ,'conjunction' => $conjunction
));
}
elseif (($pktype == 'integer' && is_numeric($conditions)) || ($pktype == 'string' && is_string($conditions) && static::isValidClause($conditions))) {
@@ -937,4 +964,10 @@ public function __debugInfo()
'bindings' => $this->bindings,
];
}
+
+ protected function isQuotable($value) {
+ return in_array(gettype($value), $this->_quotable);
+ }
+
+ abstract protected function isColumnIdentifier($key);
}
diff --git a/test/complete.phpunit.xml b/test/complete.phpunit.xml
index 1531af41..fd191595 100644
--- a/test/complete.phpunit.xml
+++ b/test/complete.phpunit.xml
@@ -22,6 +22,7 @@
./xPDO/Test/Om/xPDOObjectTest.php
./xPDO/Test/Om/xPDOObjectSingleTableInheritanceTest.php
./xPDO/Test/Om/xPDOQueryTest.php
+ ./xPDO/Test/Om/xPDOQueryConditionsTest.php
./xPDO/Test/Om/xPDOQueryHavingTest.php
./xPDO/Test/Om/xPDOQueryLimitTest.php
./xPDO/Test/Om/xPDOQuerySortByTest.php
diff --git a/test/mysql.phpunit.xml b/test/mysql.phpunit.xml
index 1531af41..fd191595 100644
--- a/test/mysql.phpunit.xml
+++ b/test/mysql.phpunit.xml
@@ -22,6 +22,7 @@
./xPDO/Test/Om/xPDOObjectTest.php
./xPDO/Test/Om/xPDOObjectSingleTableInheritanceTest.php
./xPDO/Test/Om/xPDOQueryTest.php
+ ./xPDO/Test/Om/xPDOQueryConditionsTest.php
./xPDO/Test/Om/xPDOQueryHavingTest.php
./xPDO/Test/Om/xPDOQueryLimitTest.php
./xPDO/Test/Om/xPDOQuerySortByTest.php
diff --git a/test/pgsql.phpunit.xml b/test/pgsql.phpunit.xml
index bcc32639..1ba49964 100644
--- a/test/pgsql.phpunit.xml
+++ b/test/pgsql.phpunit.xml
@@ -22,6 +22,7 @@
./xPDO/Test/Om/xPDOObjectTest.php
./xPDO/Test/Om/xPDOObjectSingleTableInheritanceTest.php
./xPDO/Test/Om/xPDOQueryTest.php
+ ./xPDO/Test/Om/xPDOQueryConditionsTest.php
./xPDO/Test/Om/xPDOQueryHavingTest.php
./xPDO/Test/Om/xPDOQueryLimitTest.php
./xPDO/Test/Om/xPDOQuerySortByTest.php
diff --git a/test/sqlite.phpunit.xml b/test/sqlite.phpunit.xml
index c21b4068..b6d1ea27 100644
--- a/test/sqlite.phpunit.xml
+++ b/test/sqlite.phpunit.xml
@@ -22,6 +22,7 @@
./xPDO/Test/Om/xPDOObjectTest.php
./xPDO/Test/Om/xPDOObjectSingleTableInheritanceTest.php
./xPDO/Test/Om/xPDOQueryTest.php
+ ./xPDO/Test/Om/xPDOQueryConditionsTest.php
./xPDO/Test/Om/xPDOQueryHavingTest.php
./xPDO/Test/Om/xPDOQueryLimitTest.php
./xPDO/Test/Om/xPDOQuerySortByTest.php
diff --git a/test/xPDO/Test/Om/xPDOQueryConditionsTest.php b/test/xPDO/Test/Om/xPDOQueryConditionsTest.php
index 61cdb358..d4443940 100644
--- a/test/xPDO/Test/Om/xPDOQueryConditionsTest.php
+++ b/test/xPDO/Test/Om/xPDOQueryConditionsTest.php
@@ -29,7 +29,7 @@ public function setUpFixtures()
$this->xpdo->manager->createObjectContainer('xPDO\Test\Sample\xPDOSample');
- $sample = $this->xpdo->newObject('xPDO\Test\Sample\xPDOSample', [
+ $this->xpdo->newObject('xPDO\Test\Sample\xPDOSample', [
'parent' => 0,
'unique_varchar' => uniqid('prefix_'),
'varchar' => 'varchar',
@@ -54,13 +54,13 @@ public function setUpFixtures()
*/
public function tearDownFixtures()
{
- parent::tearDownFixtures();
-
try {
$this->xpdo->manager->removeObjectContainer('xPDO\Test\Sample\xPDOSample');
} catch (\Exception $e) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, $e->getMessage(), '', __METHOD__, __FILE__, __LINE__);
}
+
+ parent::tearDownFixtures();
}
/**
@@ -79,7 +79,7 @@ public function testSelectConditions($condition)
public function providerSelectConditions()
{
- return [
+ $data = [
[
['parent' => 0]
],
@@ -94,7 +94,154 @@ public function providerSelectConditions()
],
[
['date_time' => '2018-03-24 00:00:00']
- ]
+ ],
+ [
+ [
+ 'integer' => 1999,
+ 'OR:date_time:=' => '2018-03-24 00:00:00'
+ ]
+ ],
];
+
+ switch (self::$properties['xpdo_driver']) {
+ case 'mysql':
+ $data = array_merge($data, [
+ [
+ [
+ '`integer` - 1' => 1998,
+ ]
+ ],
+ [
+ [
+ 'enum' => 'T',
+ [
+ 'OR:`integer` - 1:=' => 1998,
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'LOG(`integer`)' => 7.6009,
+ [
+ 'OR:text:=' => 'text',
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'date_time' => null,
+ 'OR:LOG(`integer`):=' => 7.6009,
+ 'OR:float:=' => 3.14159,
+ ]
+ ],
+ ]);
+ break;
+ case 'sqlite':
+ $data = array_merge($data, [
+ [
+ [
+ '`integer` - 1' => 1998,
+ ]
+ ],
+ [
+ [
+ 'enum' => 'T',
+ [
+ 'OR:`integer` - 1:=' => 1998,
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'LOG(`integer`)' => 7.6009,
+ [
+ 'OR:text:=' => 'text',
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'date_time' => null,
+ 'OR:LOG(`integer`):=' => 7.6009,
+ 'OR:float:=' => 3.14159,
+ ]
+ ],
+ ]);
+ break;
+ case 'pgsql':
+ $data = array_merge($data, [
+ [
+ [
+ '"integer" - 1' => 1998,
+ ]
+ ],
+ [
+ [
+ 'enum' => 'T',
+ [
+ 'OR:"integer" - 1:=' => 1998,
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'LOG("integer")' => 7.6009,
+ [
+ 'OR:text:=' => 'text',
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'date_time' => null,
+ 'OR:LOG("integer"):=' => 7.6009,
+ 'OR:float:=' => 3.14159,
+ ]
+ ],
+ ]);
+ break;
+ case 'sqlsrv':
+ $data = array_merge($data, [
+ [
+ [
+ '[integer] - 1' => 1998,
+ ]
+ ],
+ [
+ [
+ 'enum' => 'T',
+ [
+ 'OR:[integer] - 1:=' => 1998,
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'LOG([integer])' => 7.6009,
+ [
+ 'OR:text:=' => 'text',
+ 'date_time' => '2018-03-24 00:00:00'
+ ]
+ ]
+ ],
+ [
+ [
+ 'date_time' => null,
+ 'OR:LOG([integer]):=' => 7.6009,
+ 'OR:float:=' => 3.14159,
+ ]
+ ],
+ ]);
+ break;
+ }
+
+ return $data;
}
}