1
0
mirror of https://github.com/dg/dibi.git synced 2025-07-31 19:30:30 +02:00

drivers: escape*() methods moved to Engine [WIP]

This commit is contained in:
David Grudl
2024-09-03 18:10:42 +02:00
parent 9814e8d3e2
commit ffa630885d
21 changed files with 478 additions and 624 deletions

View File

@@ -42,6 +42,7 @@ class Connection
/** @var string[] resultset formats */
private array $formats;
private ?Drivers\Connection $driver = null;
private Drivers\Engine $engine;
private ?Translator $translator = null;
/** @var array<string, callable(object): Expression | null> */
@@ -670,6 +671,15 @@ class Connection
}
public function getDatabaseEngine(): Drivers\Engine
{
if (!$this->driver) { // TODO
$this->connect();
}
return $this->engine ??= $this->driver->getReflector();
}
/**
* Gets a information about the current database.
*/
@@ -679,7 +689,7 @@ class Connection
$this->connect();
}
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
return new Reflection\Database($this->getDatabaseEngine(), $this->config['database'] ?? null);
}

View File

@@ -35,7 +35,7 @@ class DataSource implements IDataSource
public function __construct(string $sql, Connection $connection)
{
$this->sql = strpbrk($sql, " \t\r\n") === false
? $connection->getDriver()->escapeIdentifier($sql) // table name
? $connection->getDatabaseEngine()->escapeIdentifier($sql) // table name
: '(' . $sql . ') t'; // SQL command
$this->connection = $connection;
}

View File

@@ -74,24 +74,4 @@ interface Connection
function escapeText(string $value): string;
function escapeBinary(string $value): string;
function escapeIdentifier(string $value): string;
function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string;
function escapeDateTime(\DateTimeInterface $value): string;
function escapeDateInterval(\DateInterval $value): string;
/**
* Encodes string for use in a LIKE statement.
*/
function escapeLike(string $value, int $pos): string;
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
function applyLimit(string &$sql, ?int $limit, ?int $offset): void;
}

View File

@@ -15,6 +15,26 @@ namespace Dibi\Drivers;
*/
interface Engine
{
function escapeIdentifier(string $value): string;
function escapeBool(bool $value): string;
function escapeDate(\DateTimeInterface $value): string;
function escapeDateTime(\DateTimeInterface $value): string;
function escapeDateInterval(\DateInterval $value): string;
/**
* Encodes string for use in a LIKE statement.
*/
function escapeLike(string $value, int $pos): string;
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
function applyLimit(string &$sql, ?int $limit, ?int $offset): void;
/**
* Returns list of tables.
* @return array of {name [, (bool) view ]}

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
@@ -24,6 +25,58 @@ class FirebirdEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->driver->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit > 0 || $offset > 0) {
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
}
}
/**
* Returns list of tables.
*/

View File

@@ -26,6 +26,66 @@ class MySQLEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
return '`' . str_replace('`', '``', $value) . '`';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
}
return $value->format("'%r%H:%I:%S.%f'");
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
/**
* Returns list of tables.
*/
@@ -49,7 +109,7 @@ class MySQLEngine implements Engine
*/
public function getColumns(string $table): array
{
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->driver->escapeIdentifier($table)}");
$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->escapeIdentifier($table)}");
$columns = [];
while ($row = $res->fetch(true)) {
$type = explode('(', $row['Type']);
@@ -74,7 +134,7 @@ class MySQLEngine implements Engine
*/
public function getIndexes(string $table): array
{
$res = $this->driver->query("SHOW INDEX FROM {$this->driver->escapeIdentifier($table)}");
$res = $this->driver->query("SHOW INDEX FROM {$this->escapeIdentifier($table)}");
$indexes = [];
while ($row = $res->fetch(true)) {
$indexes[$row['Key_name']]['name'] = $row['Key_name'];

View File

@@ -25,6 +25,63 @@ class ODBCEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format('#m/d/Y#');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#'); // TODO
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
}
}
/**
* Returns list of tables.
*/

View File

@@ -25,6 +25,72 @@ class OracleEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $this->nativeDate // TODO
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $value->format('U');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $this->nativeDate // TODO
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
: $value->format('U');
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
}
/**
* Returns list of tables.
*/

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
@@ -24,6 +25,68 @@ class PostgreSQLEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? 'TRUE' : 'FALSE';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$bs = $this->driver->escapeText('\\'); // standard_conforming_strings = on/off
$value = $this->driver->escapeText($value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
}
/**
* Returns list of tables.
*/
@@ -64,7 +127,7 @@ class PostgreSQLEngine implements Engine
*/
public function getColumns(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT *
@@ -124,7 +187,7 @@ class PostgreSQLEngine implements Engine
*/
public function getIndexes(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT
a.attnum AS ordinal_position,
@@ -174,7 +237,7 @@ class PostgreSQLEngine implements Engine
*/
public function getForeignKeys(string $table): array
{
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$_table = $this->driver->escapeText($this->escapeIdentifier($table));
$res = $this->driver->query("
SELECT

View File

@@ -25,6 +25,65 @@ class SQLServerEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
return '[' . str_replace(']', ']]', $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
}
/**
* Returns list of tables.
*/

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi\Drivers\Engines;
use Dibi;
use Dibi\Drivers\Connection;
use Dibi\Drivers\Engine;
@@ -24,6 +25,61 @@ class SQLiteEngine implements Engine
}
public function escapeIdentifier(string $value): string
{
return '[' . strtr($value, '[]', ' ') . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format($this->fmtDate); // TODO
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->fmtDateTime); // TODO
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->driver->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
/**
* Returns list of tables.
*/
@@ -49,7 +105,7 @@ class SQLiteEngine implements Engine
*/
public function getColumns(string $table): array
{
$res = $this->driver->query("PRAGMA table_info({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA table_info({$this->escapeIdentifier($table)})");
$columns = [];
while ($row = $res->fetch(true)) {
$column = $row['name'];
@@ -76,7 +132,7 @@ class SQLiteEngine implements Engine
*/
public function getIndexes(string $table): array
{
$res = $this->driver->query("PRAGMA index_list({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA index_list({$this->escapeIdentifier($table)})");
$indexes = [];
while ($row = $res->fetch(true)) {
$indexes[$row['name']]['name'] = $row['name'];
@@ -84,7 +140,7 @@ class SQLiteEngine implements Engine
}
foreach ($indexes as $index => $values) {
$res = $this->driver->query("PRAGMA index_info({$this->driver->escapeIdentifier($index)})");
$res = $this->driver->query("PRAGMA index_info({$this->escapeIdentifier($index)})");
while ($row = $res->fetch(true)) {
$indexes[$index]['columns'][$row['seqno']] = $row['name'];
}
@@ -127,7 +183,7 @@ class SQLiteEngine implements Engine
*/
public function getForeignKeys(string $table): array
{
$res = $this->driver->query("PRAGMA foreign_key_list({$this->driver->escapeIdentifier($table)})");
$res = $this->driver->query("PRAGMA foreign_key_list({$this->escapeIdentifier($table)})");
$keys = [];
while ($row = $res->fetch(true)) {
$keys[$row['id']]['name'] = $row['id']; // foreign key name

View File

@@ -233,56 +233,4 @@ class Connection implements Drivers\Connection
{
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return "'" . substr($value->format('Y-m-d H:i:s.u'), 0, -2) . "'";
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->escapeText($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit > 0 || $offset > 0) {
// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . $limit : '') . ($offset > 0 ? ' SKIP ' . $offset : '') . ' * FROM (' . $sql . ')';
}
}
}

View File

@@ -297,64 +297,4 @@ class Connection implements Drivers\Connection
{
return "_binary'" . $this->connection->escape_string($value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '`' . str_replace('`', '``', $value) . '`';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.');
}
return $value->format("'%r%H:%I:%S.%f'");
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
}

View File

@@ -223,70 +223,4 @@ class Connection implements Drivers\Connection
{
return "'" . str_replace("'", "''", $value) . "'"; // TODO: not tested
}
public function escapeIdentifier(string $value): string
{
// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $this->nativeDate
? "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')"
: $value->format('U');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $this->nativeDate
? "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')"
: $value->format('U');
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
}
}

View File

@@ -209,61 +209,4 @@ class Connection implements Drivers\Connection
{
return "'" . str_replace("'", "''", $value) . "'";
}
public function escapeIdentifier(string $value): string
{
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format('#m/d/Y#');
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->microseconds ? '#m/d/Y H:i:s.u#' : '#m/d/Y H:i:s#');
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
}
}
}

View File

@@ -220,165 +220,4 @@ class Connection implements Drivers\Connection
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
}
public function escapeIdentifier(string $value): string
{
return match ($this->driverName) {
'mysql' => '`' . str_replace('`', '``', $value) . '`',
'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
default => $value,
};
}
public function escapeBool(bool $value): string
{
if ($this->driverName === 'pgsql') {
return $value ? 'TRUE' : 'FALSE';
} else {
return $value ? '1' : '0';
}
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return match ($this->driverName) {
'odbc' => $value->format('#m/d/Y H:i:s.u#'),
'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
default => $value->format("'Y-m-d H:i:s.u'"),
};
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
switch ($this->driverName) {
case 'mysql':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'oci':
$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
$value = str_replace("'", "''", $value);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'pgsql':
$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
case 'sqlite':
$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
case 'odbc':
case 'mssql':
case 'dblib':
case 'sqlsrv':
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
default:
throw new Dibi\NotImplementedException;
}
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
switch ($this->driverName) {
case 'mysql':
if ($limit !== null || $offset) {
// see http://dev.mysql.com/doc/refman/5.0/en/select.html
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : '');
}
break;
case 'pgsql':
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
break;
case 'sqlite':
if ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : '');
}
break;
case 'oci':
if ($offset) {
// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
. ($limit !== null ? 'WHERE ROWNUM <= ' . ($offset + $limit) : '')
. ') WHERE "__rnum" > ' . $offset;
} elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
}
break;
case 'mssql':
case 'sqlsrv':
case 'dblib':
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
if ($limit !== null) {
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
break;
case 'odbc':
if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
} elseif ($limit !== null) {
$sql = 'SELECT TOP ' . $limit . ' * FROM (' . $sql . ') t';
break;
}
// break omitted
default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
}
}
}

View File

@@ -272,66 +272,4 @@ class Connection implements Drivers\Connection
return "'" . pg_escape_bytea($this->connection, $value) . "'";
}
public function escapeIdentifier(string $value): string
{
// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
return '"' . str_replace('"', '""', $value) . '"';
}
public function escapeBool(bool $value): string
{
return $value ? 'TRUE' : 'FALSE';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d H:i:s.u'");
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
$value = pg_escape_string($this->connection, $value);
$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
}
if ($limit !== null) {
$sql .= ' LIMIT ' . $limit;
}
if ($offset) {
$sql .= ' OFFSET ' . $offset;
}
}
}

View File

@@ -204,63 +204,4 @@ class Connection implements Drivers\Connection
{
return '0x' . bin2hex($value);
}
public function escapeIdentifier(string $value): string
{
// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
return '[' . str_replace(']', ']]', $value) . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format("'Y-m-d'");
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'");
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
} elseif ($offset) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
}
}
}

View File

@@ -208,61 +208,6 @@ class Connection implements Drivers\Connection
}
public function escapeIdentifier(string $value): string
{
return '[' . strtr($value, '[]', ' ') . ']';
}
public function escapeBool(bool $value): string
{
return $value ? '1' : '0';
}
public function escapeDate(\DateTimeInterface $value): string
{
return $value->format($this->fmtDate);
}
public function escapeDateTime(\DateTimeInterface $value): string
{
return $value->format($this->fmtDateTime);
}
public function escapeDateInterval(\DateInterval $value): string
{
throw new Dibi\NotImplementedException;
}
/**
* Encodes string for use in a LIKE statement.
*/
public function escapeLike(string $value, int $pos): string
{
$value = addcslashes($this->connection->escapeString($value), '%_\\');
return ($pos & 1 ? "'%" : "'") . $value . ($pos & 2 ? "%'" : "'") . " ESCAPE '\\'";
}
/**
* Injects LIMIT/OFFSET to the SQL query.
*/
public function applyLimit(string &$sql, ?int $limit, ?int $offset): void
{
if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.');
} elseif ($limit !== null || $offset) {
$sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : '');
}
}
/********************* user defined functions ****************d*g**/

View File

@@ -17,8 +17,9 @@ use function array_filter, array_keys, array_splice, array_values, count, explod
*/
final class Translator
{
private Connection $connection;
private readonly Connection $connection;
private readonly Drivers\Connection $driver;
private readonly Drivers\Engine $engine;
private int $cursor = 0;
private array $args;
@@ -36,6 +37,7 @@ final class Translator
{
$this->connection = $connection;
$this->driver = $connection->getDriver();
$this->engine = $connection->getDatabaseEngine();
$this->identifiers = new HashMap($this->delimite(...));
}
@@ -144,7 +146,7 @@ final class Translator
// apply limit
if ($this->limit !== null || $this->offset !== null) {
$this->driver->applyLimit($sql, $this->limit, $this->offset);
$this->engine->applyLimit($sql, $this->limit, $this->offset);
}
return $sql;
@@ -209,7 +211,7 @@ final class Translator
case 'n': // key, key, ... identifier names
foreach ($value as $k => $v) {
if (is_string($k)) {
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->driver->escapeIdentifier($v));
$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->engine->escapeIdentifier($v));
} else {
$pair = explode('%', $v, 2); // split into identifier & modifier
$vx[] = $this->identifiers->{$pair[0]};
@@ -346,7 +348,7 @@ final class Translator
case 's': // string
return $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
: $this->engine->escapeText((string) $value);
case 'bin':// binary
return $value === null
@@ -356,7 +358,7 @@ final class Translator
case 'b': // boolean
return $value === null
? 'NULL'
: $this->driver->escapeBool((bool) $value);
: $this->engine->escapeBool((bool) $value);
case 'sN': // string or null
case 'sn':
@@ -406,15 +408,15 @@ final class Translator
}
return $modifier === 'd'
? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value);
? $this->engine->escapeDate($value)
: $this->engine->escapeDateTime($value);
case 'by':
case 'n': // composed identifier name
return $this->identifiers->$value;
case 'N': // identifier name
return $this->driver->escapeIdentifier($value);
return $this->engine->escapeIdentifier($value);
case 'ex':
case 'sql': // preserve as dibi-SQL (TODO: leave only %ex)
@@ -450,16 +452,16 @@ final class Translator
return (string) $value;
case 'like~': // LIKE string%
return $this->driver->escapeLike($value, 2);
return $this->engine->escapeLike($value, 2);
case '~like': // LIKE %string
return $this->driver->escapeLike($value, 1);
return $this->engine->escapeLike($value, 1);
case '~like~': // LIKE %string%
return $this->driver->escapeLike($value, 3);
return $this->engine->escapeLike($value, 3);
case 'like': // LIKE string
return $this->driver->escapeLike($value, 0);
return $this->engine->escapeLike($value, 0);
case 'and':
case 'or':
@@ -485,16 +487,16 @@ final class Translator
return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
} elseif (is_bool($value)) {
return $this->driver->escapeBool($value);
return $this->engine->escapeBool($value);
} elseif ($value === null) {
return 'NULL';
} elseif ($value instanceof \DateTimeInterface) {
return $this->driver->escapeDateTime($value);
return $this->engine->escapeDateTime($value);
} elseif ($value instanceof \DateInterval) {
return $this->driver->escapeDateInterval($value);
return $this->engine->escapeDateInterval($value);
} elseif ($value instanceof Literal) {
return (string) $value;
@@ -653,7 +655,7 @@ final class Translator
$parts = explode('.', $value);
foreach ($parts as &$v) {
if ($v !== '*') {
$v = $this->driver->escapeIdentifier($v);
$v = $this->engine->escapeIdentifier($v);
}
}

View File

@@ -49,7 +49,7 @@ test('DateTime', function () use ($conn) {
// Without object translator, DateTime child is translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('?', $stamp),
);
@@ -67,15 +67,15 @@ test('DateTime', function () use ($conn) {
// With modifier, it is still translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('%dt', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->getDatabaseEngine()->escapeDateTime($stamp),
$conn->translate('%t', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDate($stamp),
$conn->getDatabaseEngine()->escapeDate($stamp),
$conn->translate('%d', $stamp),
);
@@ -83,7 +83,7 @@ test('DateTime', function () use ($conn) {
// DateTimeImmutable as a Time parent is not affected and still translated by driver
$dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
$conn->getDriver()->escapeDateTime($dt),
$conn->getDatabaseEngine()->escapeDateTime($dt),
$conn->translate('?', $dt),
);