1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-04 21:28:02 +02:00

- REWRITTEN DibiTranslar

- allows modifiers inside SQL -> modifiers become placeholders
- new modifier %ex - expand array
- new modifiers %or and %and
- changed interface IDibiVariable and implementation DibiVariable
This commit is contained in:
David Grudl
2008-01-18 07:35:45 +00:00
parent f6b781f12d
commit 18e02de80c
7 changed files with 177 additions and 197 deletions

View File

@@ -78,7 +78,7 @@ class dibi
// special // special
FIELD_COUNTER = 'C', // counter or autoincrement, is integer FIELD_COUNTER = 'C', // counter or autoincrement, is integer
IDENTIFIER = 'I', IDENTIFIER = 'n',
// dibi version // dibi version
VERSION = '0.9 (Revision: $WCREV$, Date: $WCDATE$)'; VERSION = '0.9 (Revision: $WCREV$, Date: $WCDATE$)';
@@ -246,8 +246,7 @@ class dibi
*/ */
public static function query($args) public static function query($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
return self::getConnection()->query($args); return self::getConnection()->query($args);
} }
@@ -274,8 +273,7 @@ class dibi
*/ */
public static function test($args) public static function test($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
return self::getConnection()->test($args); return self::getConnection()->test($args);
} }
@@ -290,8 +288,7 @@ class dibi
*/ */
public static function fetch($args) public static function fetch($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
return self::getConnection()->query($args)->fetch(); return self::getConnection()->query($args)->fetch();
} }
@@ -306,8 +303,7 @@ class dibi
*/ */
public static function fetchAll($args) public static function fetchAll($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
return self::getConnection()->query($args)->fetchAll(); return self::getConnection()->query($args)->fetchAll();
} }
@@ -322,8 +318,7 @@ class dibi
*/ */
public static function fetchSingle($args) public static function fetchSingle($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
return self::getConnection()->query($args)->fetchSingle(); return self::getConnection()->query($args)->fetchSingle();
} }
@@ -446,7 +441,7 @@ class dibi
public static function date($date = NULL) public static function date($date = NULL)
{ {
$var = self::datetime($date); $var = self::datetime($date);
$var->type = dibi::FIELD_DATE; $var->modifier = dibi::FIELD_DATE;
return $var; return $var;
} }
@@ -578,7 +573,7 @@ class dibi
$sql = preg_replace("#\n{2,}#", "\n", $sql); $sql = preg_replace("#\n{2,}#", "\n", $sql);
// syntax highlight // syntax highlight
$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(])($keywords2)(?=[\\s,)])#i", array('dibi', 'highlightCallback'), $sql); $sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#i", array('dibi', 'highlightCallback'), $sql);
$sql = trim($sql); $sql = trim($sql);
echo '<pre class="dump">', $sql, "</pre>\n"; echo '<pre class="dump">', $sql, "</pre>\n";
} }

View File

@@ -206,8 +206,7 @@ class DibiConnection extends NObject
*/ */
final public function query($args) final public function query($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
$this->connect(); $this->connect();
$trans = new DibiTranslator($this->driver); $trans = new DibiTranslator($this->driver);
if ($trans->translate($args)) { if ($trans->translate($args)) {
@@ -227,8 +226,7 @@ class DibiConnection extends NObject
*/ */
final public function test($args) final public function test($args)
{ {
if (!is_array($args)) $args = func_get_args(); $args = func_get_args();
$trans = new DibiTranslator($this->driver); $trans = new DibiTranslator($this->driver);
$ok = $trans->translate($args); $ok = $trans->translate($args);
dibi::dump($trans->sql); dibi::dump($trans->sql);

View File

@@ -69,7 +69,7 @@ class DibiDataSource extends NObject implements IDataSource
{ {
return $this->connection->query(' return $this->connection->query('
SELECT %n', ($cols === NULL ? '*' : $cols), ' SELECT %n', ($cols === NULL ? '*' : $cols), '
FROM %sql', $this->sql, ' FROM', $this->sql, '
%ofs %lmt', $offset, $limit %ofs %lmt', $offset, $limit
); );
} }
@@ -83,8 +83,7 @@ class DibiDataSource extends NObject implements IDataSource
{ {
if ($this->count === NULL) { if ($this->count === NULL) {
$this->count = $this->connection->query(' $this->count = $this->connection->query('
SELECT COUNT(*) SELECT COUNT(*) FROM', $this->sql
FROM %sql', $this->sql
)->fetchSingle(); )->fetchSingle();
} }
return $this->count; return $this->count;

View File

@@ -32,14 +32,14 @@ final class DibiTranslator extends NObject
/** @var string */ /** @var string */
public $sql; public $sql;
/** @var string NOT USED YET */
public $mask;
/** @var IDibiDriver */ /** @var IDibiDriver */
private $driver; private $driver;
/** @var string last modifier */ /** @var int */
private $modifier; private $cursor;
/** @var array */
private $args;
/** @var bool */ /** @var bool */
private $hasError; private $hasError;
@@ -54,10 +54,10 @@ final class DibiTranslator extends NObject
private $ifLevelStart; private $ifLevelStart;
/** @var int */ /** @var int */
public $limit; private $limit;
/** @var int */ /** @var int */
public $offset; private $offset;
@@ -68,6 +68,16 @@ final class DibiTranslator extends NObject
/**
* return IDibiDriver
*/
public function getDriver()
{
return $this->driver;
}
/** /**
* Generates SQL * Generates SQL
* *
@@ -81,8 +91,11 @@ final class DibiTranslator extends NObject
$this->hasError = FALSE; $this->hasError = FALSE;
$commandIns = NULL; $commandIns = NULL;
$lastArr = NULL; $lastArr = NULL;
$mod = & $this->modifier; // shortcut // shortcuts
$mod = FALSE; $cursor = & $this->cursor;
$cursor = 0;
$this->args = array_values($args);
$args = & $this->args;
// conditional sql // conditional sql
$this->ifLevel = $this->ifLevelStart = 0; $this->ifLevel = $this->ifLevelStart = 0;
@@ -90,58 +103,73 @@ final class DibiTranslator extends NObject
$comment = FALSE; $comment = FALSE;
// iterate // iterate
$sql = $mask = array(); $sql = array();
$i = 0; while ($cursor < count($args))
foreach ($args as $arg)
{ {
$i++; $arg = $args[$cursor];
$cursor++;
// %if was opened
if ($mod === 'if') {
$mod = FALSE;
$this->ifLevel++;
if (!$comment && !$arg) {
// open comment
$sql[] = "\0";
$this->ifLevelStart = $this->ifLevel;
$comment = TRUE;
}
continue;
}
// simple string means SQL // simple string means SQL
if (is_string($arg) && (!$mod || $mod === 'sql')) { if (is_string($arg)) {
$mod = FALSE; // speed-up - is regexp required?
// will generate new mod $toSkip = strcspn($arg, '`[\'"%');
/*$mask[] =*/ $sql[] = $this->formatValue($arg, 'sql');
if (strlen($arg) === $toSkip) { // needn't be translated
$sql[] = $arg;
} else {
$sql[] = substr($arg, 0, $toSkip)
/*
. preg_replace_callback('/
(?=`|\[|\'|"|%) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(\')((?:\'\'|[^\'])*)\'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string"
(\'|") ## 7) lone-quote
%([a-zA-Z]{1,4})(?![a-zA-Z])|## 8) modifier
)/xs',
*/ // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback('/(?=`|\[|\'|"|%)(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|%([a-zA-Z]{1,4})(?![a-zA-Z]))/s',
array($this, 'cb'),
substr($arg, $toSkip)
);
}
continue; continue;
} }
// associative array without modifier - autoselect between SET or VALUES & LIST if ($comment) continue;
if (!$mod && is_array($arg) && is_string(key($arg))) {
if ($commandIns === NULL) { if (is_array($arg)) {
$commandIns = strtoupper(substr(ltrim($args[0]), 0, 6)); if (is_string(key($arg))) {
$commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC'; // associative array -> autoselect between SET or VALUES & LIST
$mod = $commandIns ? 'v' : 'a'; if ($commandIns === NULL) {
} else { $commandIns = strtoupper(substr(ltrim($args[0]), 0, 6));
$mod = $commandIns ? 'l' : 'a'; $commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC';
if ($lastArr === $i - 1) /*$mask[] =*/ $sql[] = ','; $sql[] = $this->formatValue($arg, $commandIns ? 'v' : 'a');
} else {
if ($lastArr === $cursor - 1) $sql[] = ',';
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
}
$lastArr = $cursor;
continue;
} elseif ($cursor === 1) {
// implicit array expansion
$cursor = 0;
array_splice($args, 0, 1, $arg);
continue;
} }
$lastArr = $i;
} }
// default processing // default processing
//$mask[] = '?'; $sql[] = $this->formatValue($arg, FALSE);
if (!$comment) { } // while
$sql[] = $this->formatValue($arg, $mod);
}
$mod = FALSE;
} // foreach
if ($comment) $sql[] = "\0"; if ($comment) $sql[] = "\0";
/*$this->mask = implode(' ', $mask);*/
$this->sql = implode(' ', $sql); $this->sql = implode(' ', $sql);
// remove comments // remove comments
@@ -164,52 +192,53 @@ final class DibiTranslator extends NObject
* @param string * @param string
* @return string * @return string
*/ */
private function formatValue($value, $modifier) public function formatValue($value, $modifier)
{ {
// array processing (with or without modifier) // array processing (with or without modifier)
if (is_array($value)) { if (is_array($value)) {
$vx = $kx = array(); $vx = $kx = array();
$separator = ', ';
switch ($modifier) { switch ($modifier) {
case 'a': // SET (assoc) case 'and':
case 'or':
$separator = ' ' . strtoupper($modifier) . ' ';
if (!is_string(key($value))) {
foreach ($value as $v) {
$vx[] = $this->formatValue($v, 'sql');
}
return implode($separator, $vx);
}
// break intentionally omitted
case 'a': // SET key=val, key=val, ...
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
// split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
$pair = explode('%', $k, 2);
// generate array
$vx[] = $this->delimite($pair[0]) . '=' $vx[] = $this->delimite($pair[0]) . '='
. $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE); . $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE);
} }
return implode(', ', $vx); return implode($separator, $vx);
case 'l': // LIST case 'l': // LIST val, val, ...
$kx = NULL;
// break intentionally omitted
case 'v': // VALUES
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
// split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
$pair = explode('%', $k, 2);
// generate arrays
if ($kx !== NULL) {
$kx[] = $this->delimite($pair[0]);
}
$vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE); $vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE);
} }
return '(' . implode(', ', $vx) . ')';
if ($kx === NULL) {
return '(' . implode(', ', $vx) . ')'; case 'v': // (key, key, ...) VALUES (val, val, ...)
} else { foreach ($value as $k => $v) {
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; $pair = explode('%', $k, 2); // split into identifier & modifier
$kx[] = $this->delimite($pair[0]);
$vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE);
} }
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
default: default:
foreach ($value as $v) { foreach ($value as $v) {
$vx[] = $this->formatValue($v, $modifier); $vx[] = $this->formatValue($v, $modifier);
} }
return implode(', ', $vx); return implode(', ', $vx);
} }
} }
@@ -222,7 +251,7 @@ final class DibiTranslator extends NObject
} }
if ($value instanceof IDibiVariable) { if ($value instanceof IDibiVariable) {
return $value->toSql($this->driver, $modifier); return $value->toSql($this, $modifier);
} }
if (!is_scalar($value)) { // array is already processed if (!is_scalar($value)) { // array is already processed
@@ -266,59 +295,30 @@ final class DibiTranslator extends NObject
case 'sql':// preserve as SQL case 'sql':// preserve as SQL
$value = (string) $value; $value = (string) $value;
// speed-up - is regexp required? // speed-up - is regexp required?
$toSkip = strcspn($value, '`[\'"%'); $toSkip = strcspn($value, '`[\'"');
if (strlen($value) === $toSkip) { // needn't be translated if (strlen($value) === $toSkip) { // needn't be translated
return $value; return $value;
} } else {
return substr($value, 0, $toSkip)
// note: only this can change $this->modifier . preg_replace_callback('/(?=`|\[|\'|")(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"(\'|"))/s',
return substr($value, 0, $toSkip)
/*
. preg_replace_callback('/
(?=`|\[|\'|"|%) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(\')((?:\'\'|[^\'])*)\'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string"
%(else|end)| ## 7) conditional SQL
%([a-zA-Z]{1,3})$| ## 8) right modifier
(\'|") ## 9) lone-quote
)/xs',
*/
. preg_replace_callback('/(?=`|\[|\'|"|%)(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|%(else|end)|%([a-zA-Z]{1,3})$|(\'|"))/s',
array($this, 'cb'), array($this, 'cb'),
substr($value, $toSkip) substr($value, $toSkip)
); );
}
case 'lmt': // apply limit
if ($value !== NULL) $this->limit = (int) $value;
return '';
case 'ofs': // apply offset
if ($value !== NULL) $this->offset = (int) $value;
return '';
case 'a': case 'a':
case 'v': case 'v':
$this->hasError = TRUE; $this->hasError = TRUE;
return '**Unexpected type ' . gettype($value) . '**'; return '**Unexpected type ' . gettype($value) . '**';
case 'if':
$this->hasError = TRUE;
return "**The %$modifier is not allowed here**";
default: default:
$this->hasError = TRUE; $this->hasError = TRUE;
return "**Unknown modifier %$modifier**"; return "**Unknown or invalid modifier %$modifier**";
} }
} }
// without modifier procession // without modifier procession
if (is_string($value)) if (is_string($value))
return $this->driver->format($value, dibi::FIELD_TEXT); return $this->driver->format($value, dibi::FIELD_TEXT);
@@ -333,7 +333,7 @@ final class DibiTranslator extends NObject
return 'NULL'; return 'NULL';
if ($value instanceof IDibiVariable) if ($value instanceof IDibiVariable)
return $value->toSql($this->driver, NULL); return $value->toSql($this, NULL);
$this->hasError = TRUE; $this->hasError = TRUE;
return '**Unexpected ' . gettype($value) . '**'; return '**Unexpected ' . gettype($value) . '**';
@@ -342,8 +342,7 @@ final class DibiTranslator extends NObject
/** /**
* PREG callback for @see self::formatValue() * PREG callback from translate() or formatValue()
*
* @param array * @param array
* @return string * @return string
*/ */
@@ -355,17 +354,40 @@ final class DibiTranslator extends NObject
// [4] => string // [4] => string
// [5] => " // [5] => "
// [6] => string // [6] => string
// [7] => %else | %end // [7] => lone-quote
// [8] => right modifier // [8] => modifier (when called from self::translate())
// [9] => lone-quote
if (!empty($matches[7])) { // %end | %else if (!empty($matches[8])) { // modifier
if (!$this->ifLevel) { $mod = $matches[8];
$cursor = & $this->cursor;
if ($cursor >= count($this->args) && $mod !== 'else' && $mod !== 'end') {
$this->hasError = TRUE; $this->hasError = TRUE;
return "**Unexpected condition $matches[7]**"; return "**Extra modifier %$mod**";
} }
if ($matches[7] === 'end') { if ($mod === 'if') {
$this->ifLevel++;
if (!$this->comment && !$this->args[$cursor]) {
// open comment
$this->ifLevelStart = $this->ifLevel;
$this->comment = TRUE;
}
$cursor++;
return "\0";
} elseif ($mod === 'else') {
if ($this->ifLevelStart === $this->ifLevel) {
$this->ifLevelStart = 0;
$this->comment = FALSE;
return "\0";
} elseif (!$this->comment) {
$this->ifLevelStart = $this->ifLevel;
$this->comment = TRUE;
return "\0";
}
} elseif ($mod === 'end') {
$this->ifLevel--; $this->ifLevel--;
if ($this->ifLevelStart === $this->ifLevel + 1) { if ($this->ifLevelStart === $this->ifLevel + 1) {
// close comment // close comment
@@ -374,28 +396,29 @@ final class DibiTranslator extends NObject
return "\0"; return "\0";
} }
return ''; return '';
}
// else } elseif ($mod === 'ex') { // array expansion
if ($this->ifLevelStart === $this->ifLevel) { array_splice($this->args, $cursor, 1, $this->args[$cursor]);
$this->ifLevelStart = 0; return '';
$this->comment = FALSE;
return "\0";
} elseif (!$this->comment) {
$this->ifLevelStart = $this->ifLevel;
$this->comment = TRUE;
return "\0";
}
}
if (!empty($matches[8])) { // modifier } elseif ($mod === 'lmt') { // apply limit
$this->modifier = $matches[8]; if ($this->args[$cursor] !== NULL) $this->limit = (int) $this->args[$cursor];
return ''; $cursor++;
return '';
} elseif ($mod === 'ofs') { // apply offset
if ($this->args[$cursor] !== NULL) $this->offset = (int) $this->args[$cursor];
$cursor++;
return '';
} else { // default processing
$cursor++;
return $this->formatValue($this->args[$cursor - 1], $mod);
}
} }
if ($this->comment) return ''; if ($this->comment) return '';
if ($matches[1]) // SQL identifiers: `ident` if ($matches[1]) // SQL identifiers: `ident`
return $this->delimite($matches[1]); return $this->delimite($matches[1]);
@@ -408,8 +431,7 @@ final class DibiTranslator extends NObject
if ($matches[5]) // SQL strings: "..." if ($matches[5]) // SQL strings: "..."
return $this->driver->format( str_replace('""', '"', $matches[6]), dibi::FIELD_TEXT); return $this->driver->format( str_replace('""', '"', $matches[6]), dibi::FIELD_TEXT);
if ($matches[7]) { // string quote
if ($matches[9]) { // string quote
$this->hasError = TRUE; $this->hasError = TRUE;
return '**Alone quote**'; return '**Alone quote**';
} }

View File

@@ -29,20 +29,20 @@ class DibiVariable extends NObject implements IDibiVariable
public $value; public $value;
/** @var string */ /** @var string */
public $type; public $modifier;
public function __construct($value, $type) public function __construct($value, $modifier)
{ {
$this->value = $value; $this->value = $value;
$this->type = $type; $this->modifier = $modifier;
} }
public function toSql(IDibiDriver $driver, $modifier) public function toSql(DibiTranslator $translator, $modifier)
{ {
return $driver->format($this->value, $this->type); return $translator->formatValue($this->value, $this->modifier);
} }
} }

View File

@@ -28,11 +28,11 @@ interface IDibiVariable
/** /**
* Format for SQL * Format for SQL
* *
* @param object destination IDibiDriver * @param object DibiTranslator
* @param string optional modifier * @param string optional modifier
* @return string SQL code * @return string SQL code
*/ */
public function toSql(IDibiDriver $driver, $modifier); public function toSql(DibiTranslator $translator, $modifier);
} }

View File

@@ -1,34 +0,0 @@
OK: SELECT * FROM [customers] WHERE [customer_id] = 1;
-- rows: 1
-- takes: 0.395 ms
-- driver: sqlite
-- 2007-11-15 01:19:00
OK: SELECT * FROM [customers] WHERE [customer_id] < 5;
-- rows: 4
-- takes: 0.437 ms
-- driver: sqlite
-- 2007-11-15 01:19:00
ERROR: [1] near "FROM": syntax error
-- SQL: SELECT FROM [customers] WHERE [customer_id] < 38
-- driver: ;
-- 2007-11-15 01:19:00
OK: SELECT * FROM [customers] WHERE [customer_id] = 1;
-- rows: 1
-- takes: 0.319 ms
-- driver: sqlite
-- 2008-01-18 03:57:19
OK: SELECT * FROM [customers] WHERE [customer_id] < 5;
-- rows: 4
-- takes: 0.384 ms
-- driver: sqlite
-- 2008-01-18 03:57:19
ERROR: [2] sqlite_query(): near "FROM": syntax error
-- SQL: SELECT FROM [customers] WHERE [customer_id] < 38
-- driver: ;
-- 2008-01-18 03:57:19