From 18e02de80c3aff585e0d35c309c13be9c91fe863 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 18 Jan 2008 07:35:45 +0000 Subject: [PATCH] - 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 --- dibi/dibi.php | 21 +-- dibi/libs/DibiConnection.php | 6 +- dibi/libs/DibiDataSource.php | 5 +- dibi/libs/DibiTranslator.php | 294 +++++++++++++++++++---------------- dibi/libs/DibiVariable.php | 10 +- dibi/libs/interfaces.php | 4 +- examples/log.sql | 34 ---- 7 files changed, 177 insertions(+), 197 deletions(-) delete mode 100644 examples/log.sql diff --git a/dibi/dibi.php b/dibi/dibi.php index 85b01c70..9da70991 100644 --- a/dibi/dibi.php +++ b/dibi/dibi.php @@ -78,7 +78,7 @@ class dibi // special FIELD_COUNTER = 'C', // counter or autoincrement, is integer - IDENTIFIER = 'I', + IDENTIFIER = 'n', // dibi version VERSION = '0.9 (Revision: $WCREV$, Date: $WCDATE$)'; @@ -246,8 +246,7 @@ class dibi */ public static function query($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); return self::getConnection()->query($args); } @@ -274,8 +273,7 @@ class dibi */ public static function test($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); return self::getConnection()->test($args); } @@ -290,8 +288,7 @@ class dibi */ public static function fetch($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); return self::getConnection()->query($args)->fetch(); } @@ -306,8 +303,7 @@ class dibi */ public static function fetchAll($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); return self::getConnection()->query($args)->fetchAll(); } @@ -322,8 +318,7 @@ class dibi */ public static function fetchSingle($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); return self::getConnection()->query($args)->fetchSingle(); } @@ -446,7 +441,7 @@ class dibi public static function date($date = NULL) { $var = self::datetime($date); - $var->type = dibi::FIELD_DATE; + $var->modifier = dibi::FIELD_DATE; return $var; } @@ -578,7 +573,7 @@ class dibi $sql = preg_replace("#\n{2,}#", "\n", $sql); // 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); echo '
', $sql, "
\n"; } diff --git a/dibi/libs/DibiConnection.php b/dibi/libs/DibiConnection.php index e6087483..38b65ef0 100644 --- a/dibi/libs/DibiConnection.php +++ b/dibi/libs/DibiConnection.php @@ -206,8 +206,7 @@ class DibiConnection extends NObject */ final public function query($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); $this->connect(); $trans = new DibiTranslator($this->driver); if ($trans->translate($args)) { @@ -227,8 +226,7 @@ class DibiConnection extends NObject */ final public function test($args) { - if (!is_array($args)) $args = func_get_args(); - + $args = func_get_args(); $trans = new DibiTranslator($this->driver); $ok = $trans->translate($args); dibi::dump($trans->sql); diff --git a/dibi/libs/DibiDataSource.php b/dibi/libs/DibiDataSource.php index 5fff71a3..4dc8ba2a 100644 --- a/dibi/libs/DibiDataSource.php +++ b/dibi/libs/DibiDataSource.php @@ -69,7 +69,7 @@ class DibiDataSource extends NObject implements IDataSource { return $this->connection->query(' SELECT %n', ($cols === NULL ? '*' : $cols), ' - FROM %sql', $this->sql, ' + FROM', $this->sql, ' %ofs %lmt', $offset, $limit ); } @@ -83,8 +83,7 @@ class DibiDataSource extends NObject implements IDataSource { if ($this->count === NULL) { $this->count = $this->connection->query(' - SELECT COUNT(*) - FROM %sql', $this->sql + SELECT COUNT(*) FROM', $this->sql )->fetchSingle(); } return $this->count; diff --git a/dibi/libs/DibiTranslator.php b/dibi/libs/DibiTranslator.php index e16b6229..955d2ede 100644 --- a/dibi/libs/DibiTranslator.php +++ b/dibi/libs/DibiTranslator.php @@ -32,14 +32,14 @@ final class DibiTranslator extends NObject /** @var string */ public $sql; - /** @var string NOT USED YET */ - public $mask; - /** @var IDibiDriver */ private $driver; - /** @var string last modifier */ - private $modifier; + /** @var int */ + private $cursor; + + /** @var array */ + private $args; /** @var bool */ private $hasError; @@ -54,10 +54,10 @@ final class DibiTranslator extends NObject private $ifLevelStart; /** @var int */ - public $limit; + private $limit; /** @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 * @@ -81,8 +91,11 @@ final class DibiTranslator extends NObject $this->hasError = FALSE; $commandIns = NULL; $lastArr = NULL; - $mod = & $this->modifier; // shortcut - $mod = FALSE; + // shortcuts + $cursor = & $this->cursor; + $cursor = 0; + $this->args = array_values($args); + $args = & $this->args; // conditional sql $this->ifLevel = $this->ifLevelStart = 0; @@ -90,58 +103,73 @@ final class DibiTranslator extends NObject $comment = FALSE; // iterate - $sql = $mask = array(); - $i = 0; - foreach ($args as $arg) + $sql = array(); + while ($cursor < count($args)) { - $i++; - - // %if was opened - if ($mod === 'if') { - $mod = FALSE; - $this->ifLevel++; - if (!$comment && !$arg) { - // open comment - $sql[] = "\0"; - $this->ifLevelStart = $this->ifLevel; - $comment = TRUE; - } - continue; - } + $arg = $args[$cursor]; + $cursor++; // simple string means SQL - if (is_string($arg) && (!$mod || $mod === 'sql')) { - $mod = FALSE; - // will generate new mod - /*$mask[] =*/ $sql[] = $this->formatValue($arg, 'sql'); + if (is_string($arg)) { + // speed-up - is regexp required? + $toSkip = strcspn($arg, '`[\'"%'); + + 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; } - // associative array without modifier - autoselect between SET or VALUES & LIST - if (!$mod && is_array($arg) && is_string(key($arg))) { - if ($commandIns === NULL) { - $commandIns = strtoupper(substr(ltrim($args[0]), 0, 6)); - $commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC'; - $mod = $commandIns ? 'v' : 'a'; - } else { - $mod = $commandIns ? 'l' : 'a'; - if ($lastArr === $i - 1) /*$mask[] =*/ $sql[] = ','; + if ($comment) continue; + + if (is_array($arg)) { + if (is_string(key($arg))) { + // associative array -> autoselect between SET or VALUES & LIST + if ($commandIns === NULL) { + $commandIns = strtoupper(substr(ltrim($args[0]), 0, 6)); + $commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC'; + $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 - //$mask[] = '?'; - if (!$comment) { - $sql[] = $this->formatValue($arg, $mod); - } - $mod = FALSE; - } // foreach + $sql[] = $this->formatValue($arg, FALSE); + } // while + if ($comment) $sql[] = "\0"; - /*$this->mask = implode(' ', $mask);*/ - $this->sql = implode(' ', $sql); // remove comments @@ -164,52 +192,53 @@ final class DibiTranslator extends NObject * @param string * @return string */ - private function formatValue($value, $modifier) + public function formatValue($value, $modifier) { // array processing (with or without modifier) if (is_array($value)) { $vx = $kx = array(); + $separator = ', '; 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) { - // split into identifier & modifier - $pair = explode('%', $k, 2); - - // generate array + $pair = explode('%', $k, 2); // split into identifier & modifier $vx[] = $this->delimite($pair[0]) . '=' . $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE); } - return implode(', ', $vx); + return implode($separator, $vx); - case 'l': // LIST - $kx = NULL; - // break intentionally omitted - case 'v': // VALUES + case 'l': // LIST val, val, ... foreach ($value as $k => $v) { - // split into identifier & modifier - $pair = explode('%', $k, 2); - - // generate arrays - if ($kx !== NULL) { - $kx[] = $this->delimite($pair[0]); - } + $pair = explode('%', $k, 2); // split into identifier & modifier $vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : FALSE); } + return '(' . implode(', ', $vx) . ')'; - if ($kx === NULL) { - return '(' . implode(', ', $vx) . ')'; - } else { - return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; + + case 'v': // (key, key, ...) VALUES (val, val, ...) + foreach ($value as $k => $v) { + $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: foreach ($value as $v) { $vx[] = $this->formatValue($v, $modifier); } - return implode(', ', $vx); } } @@ -222,7 +251,7 @@ final class DibiTranslator extends NObject } if ($value instanceof IDibiVariable) { - return $value->toSql($this->driver, $modifier); + return $value->toSql($this, $modifier); } if (!is_scalar($value)) { // array is already processed @@ -266,59 +295,30 @@ final class DibiTranslator extends NObject case 'sql':// preserve as SQL $value = (string) $value; - // speed-up - is regexp required? - $toSkip = strcspn($value, '`[\'"%'); - + $toSkip = strcspn($value, '`[\'"'); if (strlen($value) === $toSkip) { // needn't be translated return $value; - } - - // note: only this can change $this->modifier - 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', + } else { + return substr($value, 0, $toSkip) + . preg_replace_callback('/(?=`|\[|\'|")(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"(\'|"))/s', array($this, 'cb'), 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 'v': $this->hasError = TRUE; return '**Unexpected type ' . gettype($value) . '**'; - case 'if': - $this->hasError = TRUE; - return "**The %$modifier is not allowed here**"; - default: $this->hasError = TRUE; - return "**Unknown modifier %$modifier**"; + return "**Unknown or invalid modifier %$modifier**"; } } - // without modifier procession if (is_string($value)) return $this->driver->format($value, dibi::FIELD_TEXT); @@ -333,7 +333,7 @@ final class DibiTranslator extends NObject return 'NULL'; if ($value instanceof IDibiVariable) - return $value->toSql($this->driver, NULL); + return $value->toSql($this, NULL); $this->hasError = TRUE; 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 * @return string */ @@ -355,17 +354,40 @@ final class DibiTranslator extends NObject // [4] => string // [5] => " // [6] => string - // [7] => %else | %end - // [8] => right modifier - // [9] => lone-quote + // [7] => lone-quote + // [8] => modifier (when called from self::translate()) - if (!empty($matches[7])) { // %end | %else - if (!$this->ifLevel) { + if (!empty($matches[8])) { // modifier + $mod = $matches[8]; + $cursor = & $this->cursor; + + if ($cursor >= count($this->args) && $mod !== 'else' && $mod !== 'end') { $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--; if ($this->ifLevelStart === $this->ifLevel + 1) { // close comment @@ -374,28 +396,29 @@ final class DibiTranslator extends NObject return "\0"; } return ''; - } - // 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 === 'ex') { // array expansion + array_splice($this->args, $cursor, 1, $this->args[$cursor]); + return ''; - if (!empty($matches[8])) { // modifier - $this->modifier = $matches[8]; - return ''; + } elseif ($mod === 'lmt') { // apply limit + if ($this->args[$cursor] !== NULL) $this->limit = (int) $this->args[$cursor]; + $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 ($matches[1]) // SQL identifiers: `ident` return $this->delimite($matches[1]); @@ -408,8 +431,7 @@ final class DibiTranslator extends NObject if ($matches[5]) // SQL strings: "..." return $this->driver->format( str_replace('""', '"', $matches[6]), dibi::FIELD_TEXT); - - if ($matches[9]) { // string quote + if ($matches[7]) { // string quote $this->hasError = TRUE; return '**Alone quote**'; } diff --git a/dibi/libs/DibiVariable.php b/dibi/libs/DibiVariable.php index 962a1980..8b918a0d 100644 --- a/dibi/libs/DibiVariable.php +++ b/dibi/libs/DibiVariable.php @@ -29,20 +29,20 @@ class DibiVariable extends NObject implements IDibiVariable public $value; /** @var string */ - public $type; + public $modifier; - public function __construct($value, $type) + public function __construct($value, $modifier) { $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); } } \ No newline at end of file diff --git a/dibi/libs/interfaces.php b/dibi/libs/interfaces.php index be1aaab0..703f7def 100644 --- a/dibi/libs/interfaces.php +++ b/dibi/libs/interfaces.php @@ -28,11 +28,11 @@ interface IDibiVariable /** * Format for SQL * - * @param object destination IDibiDriver + * @param object DibiTranslator * @param string optional modifier * @return string SQL code */ - public function toSql(IDibiDriver $driver, $modifier); + public function toSql(DibiTranslator $translator, $modifier); } diff --git a/examples/log.sql b/examples/log.sql deleted file mode 100644 index c4143ead..00000000 --- a/examples/log.sql +++ /dev/null @@ -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 -