Skip to content

Commit

Permalink
[+]: add named parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
voku committed Apr 28, 2018
1 parent 6571ee1 commit c57bb1f
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 85 deletions.
192 changes: 118 additions & 74 deletions src/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ class Query
private $params = [];

private $escapeChars = array(
"\x00" => "\\0",
"\r" => "\\r",
"\n" => "\\n",
"\t" => "\\t",
//"\b" => "\\b",
//"\x1a" => "\\Z",
"'" => "\'",
'"' => '\"',
"\\" => "\\\\",
//"%" => "\\%",
//"_" => "\\_",
);
"\x00" => "\\0",
"\r" => "\\r",
"\n" => "\\n",
"\t" => "\\t",
//"\b" => "\\b",
//"\x1a" => "\\Z",
"'" => "\'",
'"' => '\"',
"\\" => "\\\\",
//"%" => "\\%",
//"_" => "\\_",
);

public function __construct($sql)
{
Expand All @@ -32,35 +32,35 @@ public function __construct($sql)
/**
* Binding params for the query, multiple arguments support.
*
* @param mixed $param
* @param mixed $param
* @return \React\MySQL\Query
*/
public function bindParams()
{
$this->builtSql = null;
$this->params = func_get_args();
$this->params = func_get_args();

return $this;
}

public function bindParamsFromArray(array $params)
{
$this->builtSql = null;
$this->params = $params;
$this->params = $params;

return $this;
}

/**
* Binding params for the query, multiple arguments support.
*
* @param mixed $param
* @param mixed $param
* @return \React\MySQL\Query
* @deprecated
* @deprecated
*/
public function params()
{
$this->params = func_get_args();
$this->params = func_get_args();
$this->builtSql = null;

return $this;
Expand All @@ -72,15 +72,15 @@ public function escape($str)
}

/**
* @param mixed $value
* @param mixed $value
* @return string
*/
protected function resolveValueForSql($value)
{
$type = gettype($value);
switch ($type) {
case 'boolean':
$value = (int) $value;
$value = (int)$value;
break;
case 'double':
case 'integer':
Expand Down Expand Up @@ -109,73 +109,117 @@ protected function buildSql()
{
$sql = $this->sql;

$offset = strpos($sql, '?');
foreach ($this->params as $param) {
$replacement = $this->resolveValueForSql($param);
$sql = substr_replace($sql, $replacement, $offset, 1);
$offset = strpos($sql, '?', $offset + strlen($replacement));
}
if ($offset !== false) {
throw new \LogicException('Params not enouth to build sql');
if (\count($this->params) > 0) {
$parseQueryParams = $this->_parseQueryParams($sql, $this->params);
$parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
$sql = $parseQueryParamsByName['sql'];
}

return $sql;
/*
$names = array();
$inName = false;
$currName = '';
$currIdx = 0;
$sql = $this->sql;
$len = strlen($sql);
$i = 0;
do {
$c = $sql[$i];
if ($c === '?') {
$names[$i] = $c;
} elseif ($c === ':') {
$currName .= $c;
$currIdx = $i;
$inName = true;
} elseif ($c === ' ') {
$inName = false;
if ($currName) {
$names[$currIdx] = $currName;
$currName = '';
}
} else {
if ($inName) {
$currName .= $c;
}

/**
* @param string $sql
* @param array $params
*
* @return array <p>with the keys -> 'sql', 'params'</p>
*/
private function _parseQueryParams($sql, array $params = [])
{
$offset = \strpos($sql, '?');

// is there anything to parse?
if (
$offset === false
||
\count($params) === 0
) {
return ['sql' => $sql, 'params' => $params];
}

foreach ($params as $key => $param) {
// use this only for not named parameters
if (!is_int($key)) {
continue;
}
if (is_array($param) && count($param) > 0) {
foreach ($param as $paramInnerKey => $paramInnerValue) {
if (!is_int($paramInnerKey)) {
continue 2;
}
}
}
} while (++ $i < $len);

if ($inName) {
$names[$currIdx] = $currName;
if ($offset === false) {
continue;
}

$replacement = $this->resolveValueForSql($param);
unset($params[$key]);
$sql = \substr_replace($sql, $replacement, $offset, 1);
$offset = \strpos($sql, '?', $offset + \strlen((string)$replacement));
}

$namedMarks = $unnamedMarks = array();
foreach ($this->params as $arg) {
if (is_array($arg)) {
$namedMarks += $arg;
} else {
$unnamedMarks[] = $arg;
}
return ['sql' => $sql, 'params' => $params];
}

/**
* Returns the SQL by replacing :placeholders with SQL-escaped values.
*
* @param mixed $sql <p>The SQL string.</p>
* @param array $params <p>An array of key-value bindings.</p>
*
* @return array <p>with the keys -> 'sql', 'params'</p>
*/
public function _parseQueryParamsByName($sql, array $params = [])
{
// is there anything to parse?
if (
\strpos($sql, ':') === false
||
\count($params) === 0
) {
return ['sql' => $sql, 'params' => $params];
}

$offset = 0;
foreach ($names as $idx => $value) {
if ($value === '?') {
$replacement = array_shift($unnamedMarks);
} else {
$replacement = $namedMarks[$value];
foreach ($params as $paramsInner) {

$offset = null;
$replacement = null;
foreach ($paramsInner as $name => $param) {
// use this only for named parameters
if (is_int($name)) {
continue;
}

// add ":" if needed
if (\strpos($name, ':') !== 0) {
$nameTmp = ':' . $name;
} else {
$nameTmp = $name;
}

if ($offset === null) {
$offset = \strpos($sql, $nameTmp);
} else {
$offset = \strpos($sql, $nameTmp, $offset + \strlen((string)$replacement));
}

if ($offset === false) {
continue;
}

$replacement = $this->resolveValueForSql($param);

$sql = \substr_replace($sql, $replacement, $offset, \strlen($nameTmp));
}

if ($offset === false) {
unset($params[$name]);
}
list($arg, $len) = $this->getEscapedStringAndLen($replacement);
$sql = substr_replace($sql, $arg, $idx + $offset, strlen($value));
$offset += $len - strlen($value);
}

return $sql;
*/
return ['sql' => $sql, 'params' => $params];
}

/**
Expand Down
37 changes: 26 additions & 11 deletions tests/QueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,70 @@ class QueryTest extends \PHPUnit_Framework_TestCase
public function testBindParams()
{
$query = new Query('select * from test where id = ? and name = ?');
$sql = $query->bindParams(100, 'test')->getSql();
$sql = $query->bindParams(100, 'test')->getSql();
$this->assertEquals("select * from test where id = 100 and name = 'test'", $sql);

$query = new Query('select * from test where id in (?) and name = ?');
$sql = $query->bindParams([1, 2], 'test')->getSql();
$sql = $query->bindParams([1, 2], 'test')->getSql();
$this->assertEquals("select * from test where id in (1,2) and name = 'test'", $sql);
/*

$query = new Query('select * from test where id in (?) and name = ?');
$sql = $query->bindParams([1, 2], 'Iñtërnâtiônàlizætiøn')->getSql();
$this->assertEquals("select * from test where id in (1,2) and name = 'Iñtërnâtiônàlizætiøn'", $sql);

$query = new Query('select * from test where id = :id and name = :name');
$sql = $query->params(array(':id' => 100, ':name' => 'test'))->getSql();
$sql = $query->bindParams([':id' => 100, ':name' => 'test'])->getSql();
$this->assertEquals("select * from test where id = 100 and name = 'test'", $sql);

$query = new Query('select * from test where id = :id and name = ?');
$sql = $query->params('test', array(':id' => 100))->getSql();
$sql = $query->bindParams('test', [':id' => 100])->getSql();
$this->assertEquals("select * from test where id = 100 and name = 'test'", $sql);
*/

$query = new Query('select * from test where name = ? or id = :id');
$sql = $query->bindParams('test', [':id' => 100])->getSql();
$this->assertEquals("select * from test where name = 'test' or id = 100", $sql);

$query = new Query('select * from test where name = ? or id = :id');
$sql = $query->bindParams('test', ['id' => 100])->getSql();
$this->assertEquals("select * from test where name = 'test' or id = 100", $sql);

$query = new Query('select * from test where name = :name or id IN (?)');
$sql = $query->bindParams([':name' => 'Iñtërnâtiônàlizætiøn'], [1, 2])->getSql();
$this->assertEquals("select * from test where name = 'Iñtërnâtiônàlizætiøn' or id IN (1,2)", $sql);
}

public function testGetSqlReturnsQuestionMarkReplacedWhenBound()
{
$query = new Query('select ?');
$sql = $query->bindParams('hello')->getSql();
$sql = $query->bindParams('hello')->getSql();
$this->assertEquals("select 'hello'", $sql);
}

public function testGetSqlReturnsQuestionMarkReplacedWhenBoundFromLastCall()
{
$query = new Query('select ?');
$sql = $query->bindParams('foo')->bindParams('bar')->getSql();
$sql = $query->bindParams('foo')->bindParams('bar')->getSql();
$this->assertEquals("select 'bar'", $sql);
}

public function testGetSqlReturnsQuestionMarkReplacedWithNullValueWhenBound()
{
$query = new Query('select ?');
$sql = $query->bindParams(null)->getSql();
$sql = $query->bindParams(null)->getSql();
$this->assertEquals("select NULL", $sql);
}

public function testGetSqlReturnsQuestionMarkReplacedFromBoundWhenBound()
{
$query = new Query('select CONCAT(?, ?)');
$sql = $query->bindParams('hello??', 'world??')->getSql();
$sql = $query->bindParams('hello??', 'world??')->getSql();
$this->assertEquals("select CONCAT('hello??', 'world??')", $sql);
}

public function testGetSqlReturnsQuestionMarksAsIsWhenNotBound()
{
$query = new Query('select "hello?"');
$sql = $query->getSql();
$sql = $query->getSql();
$this->assertEquals("select \"hello?\"", $sql);
}

Expand Down

0 comments on commit c57bb1f

Please sign in to comment.