Skip to content

Commit

Permalink
temp: initial draft of arg substitution
Browse files Browse the repository at this point in the history
  • Loading branch information
mecha committed Jul 15, 2024
1 parent fd4d5a7 commit e2bb6e1
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 20 deletions.
6 changes: 6 additions & 0 deletions inc/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use RebelCode\Atlas\Expression\ColumnTerm;
use RebelCode\Atlas\Expression\ExprInterface;
use RebelCode\Atlas\Expression\Term;
use RebelCode\Atlas\Expression\VarExpr as ArgExpr;

/**
* Creates a new column term.
Expand Down Expand Up @@ -218,3 +219,8 @@ function andAll(iterable $exprs): ?ExprInterface
return $result;
}
}

function arg(string $name): ArgExpr
{
return new ArgExpr($name);
}
25 changes: 22 additions & 3 deletions src/DatabaseAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,33 @@
/** An adapter for a database connection that allows queries created by Atlas to be executed. */
interface DatabaseAdapter
{
/**
* Gets the placeholder to use for a value before preparing the query.
*
* @param string $name The name of the value.
* @param mixed $value The value.
* @return string The placeholder string, such as '?'.
*/
public function getPlaceholder(string $name, $value): string;

/**
* Replaces ??{var}?? placeholders in the SQL with '?' and collects the
* values in the order they appear in the string.
*
* @param string $sql The SQL string
* @param list<mixed> $values The values
* @return array{0:string,1:list<mixed>} The SQL and var values.
*/
public function prepare(string $sql, array $values): array;

/**
* Executes a query that returns results.
*
* @param string $sql The SQL for the query to execute.
* @return array<string,mixed>[] A list of rows, where each row is a map of column names to values.
* @throws DatabaseException If an error occurred while executing the query.
*/
public function queryResults(string $sql): array;
public function queryResults(string $sql, array $args = []): array;

/**
* Executes a query and returns the number of affected rows.
Expand All @@ -23,7 +42,7 @@ public function queryResults(string $sql): array;
* @return int The number of affected rows.
* @throws DatabaseException If an error occurred while executing the query.
*/
public function queryNumRows(string $sql): int;
public function queryNumRows(string $sql, array $args = []): int;

/**
* Executes a query and returns whether the query was successful.
Expand All @@ -32,7 +51,7 @@ public function queryNumRows(string $sql): int;
* @return bool True if the query was successful, false otherwise.
* @throws DatabaseException If an error occurred while executing the query.
*/
public function query(string $sql): bool;
public function query(string $sql, array $args = []): bool;

/**
* Gets the value generated for an AUTO_INCREMENT column by the last query.
Expand Down
23 changes: 23 additions & 0 deletions src/Expression/VarExpr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace RebelCode\Atlas\Expression;

class VarExpr extends BaseExpr
{
protected string $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function getName(): string
{
return $this->name;
}

protected function toBaseString(): string
{
return '??{' . $this->name . '}??';
}
}
36 changes: 35 additions & 1 deletion src/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,42 @@ abstract public function toSql(): string;
/**
* Executes the query.
*
* @param array<string,mixed> $args A map of values to interpolate. The keys
* correspond to {@link VarExpr} names.
* @return mixed The query result.
* @throws DatabaseException If an error occurred while executing the query.
*/
abstract public function exec();
abstract public function exec(array $args = []);

/**
* Replaces ??{var}?? placeholders in the SQL with '?' and collects the
* values in the order they appear in the string.
*
* @param string $sql The SQL string
* @param array $args An array of args, mapping each arg name to its value.
* @return array{0:string,1:list<mixed>} The SQL and var values.
*/
protected function templateVars(string $sql, array $args)
{
if (count($args) === 0) {
return [$sql, []];
}

$vars = [];
$result = preg_replace_callback(
'/(\?\?\{([\w\d_]+)\}\?\?)/im',
function (array $match) use ($args, &$vars) {
$name = trim($match[2] ?? '');
if (empty($name) && !array_key_exists($name, $args)) {
return $match[1];
}
$value = $args[$name];
$vars[] = $value;
return $this->adapter->getPlaceholder($name, $value);
},
$sql
);

return [$result, $vars];
}
}
4 changes: 2 additions & 2 deletions src/Query/CompoundQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ public function toSql(): string
}

/** @inheritDoc */
public function exec()
public function exec(array $args = [])
{
$results = [];
foreach ($this->queries as $query) {
$results[] = $query->exec();
$results[] = $query->exec($args);
}

return $results;
Expand Down
6 changes: 4 additions & 2 deletions src/Query/CreateIndexQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ public function toSql(): string
*
* @return bool True if the index was created successfully, false otherwise.
*/
public function exec(): bool
public function exec(array $args = []): bool
{
return $this->getAdapter()->query($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->query($sql, $values);
}
}
6 changes: 4 additions & 2 deletions src/Query/CreateTableQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ protected function compileSchema(Schema $schema): string
*
* @return bool True if the query was executed successfully, false otherwise.
*/
public function exec(): bool
public function exec(array $args = []): bool
{
return $this->getAdapter()->query($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->query($sql, $values);
}
}
6 changes: 4 additions & 2 deletions src/Query/DeleteQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ public function toSql(): string
* @return int The number of rows affected by the query.
* @throws DatabaseException If an error occurred while executing the query.
*/
public function exec(): int
public function exec(array $args = []): int
{
return $this->getAdapter()->queryNumRows($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->queryNumRows($sql, $values);
}
}
6 changes: 4 additions & 2 deletions src/Query/DropTableQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public function toSql(): string
*
* @return bool True if the table was dropped, false if not.
*/
public function exec(): bool
public function exec(array $args = []): bool
{
return $this->getAdapter()->query($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->query($sql, $values);
}
}
6 changes: 4 additions & 2 deletions src/Query/InsertQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ protected function compileInsertValues(): string
*
* @return int|null The last inserted ID, or null if no rows were inserted.
*/
public function exec(): ?int
public function exec(array $args = []): ?int
{
[$sql, $values] = $this->templateVars($this->toSql(), $args);

$adapter = $this->getAdapter();
$numRows = $adapter->queryNumRows($this->toSql());
$numRows = $adapter->queryNumRows($sql, $values);

if ($numRows > 0) {
return $adapter->getInsertId();
Expand Down
6 changes: 4 additions & 2 deletions src/Query/SelectQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,10 @@ public function compileSource(): string
*
* @return array<string,mixed>[] A list of rows, where each row is a map of column names to values.
*/
public function exec(): array
public function exec(array $args = []): array
{
return $this->getAdapter()->queryResults($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->queryResults($sql, $values);
}
}
6 changes: 4 additions & 2 deletions src/Query/UpdateQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ public function toSql(): string
* @return int The number of rows affected by the query.
* @throws DatabaseException If an error occurred while executing the query.
*/
public function exec(): int
public function exec(array $args = []): int
{
return $this->getAdapter()->queryNumRows($this->toSql());
[$sql, $values] = $this->templateVars($this->toSql(), $args);

return $this->getAdapter()->queryNumRows($sql, $values);
}
}
32 changes: 32 additions & 0 deletions tests/Query/SelectQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

namespace RebelCode\Atlas\Test\Query;

use PHPUnit\Framework\MockObject\MockClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use RebelCode\Atlas\DatabaseAdapter;
use RebelCode\Atlas\DataSource;
use RebelCode\Atlas\Expression\ColumnTerm;
use RebelCode\Atlas\Expression\ExprInterface;
use RebelCode\Atlas\Expression\VarExpr;
use RebelCode\Atlas\Group;
use RebelCode\Atlas\Join;
use RebelCode\Atlas\Order;
use RebelCode\Atlas\Query;
use RebelCode\Atlas\Query\SelectQuery;
use RebelCode\Atlas\Test\Helpers\ReflectionHelper;

use function RebelCode\Atlas\andAll;
use function RebelCode\Atlas\arg;
use function RebelCode\Atlas\col;
use function RebelCode\Atlas\table;

Expand Down Expand Up @@ -467,4 +472,31 @@ public function testNotExists()

$this->assertEquals("NOT EXISTS(SELECT * FROM `test` WHERE (`role` = 'admin'))", $exists->toSql());
}

public function testWithVars()
{
$args = ['foo' => 'VALUE1', 'bar' => 'VALUE2'];

$adapter = $this->createMock(DatabaseAdapter::class);
$adapter->expects($this->exactly(2))
->method('getPlaceholder')
->withConsecutive(['bar', 'VALUE2'], ['foo', 'VALUE1'])
->willReturnOnConsecutiveCalls('<bar>', '<foo>');

$adapter->expects($this->once())
->method('queryResults')
->with(
'SELECT * FROM `test` WHERE ((`name` = <bar>) AND (`age` = <foo>))',
['VALUE2', 'VALUE1']
);

$query = (new SelectQuery($adapter))
->from(table('test'))
->where(andAll([
col('name')->eq(arg('bar')),
col('age')->eq(arg('foo')),
]));

$query->exec($args);
}
}

0 comments on commit e2bb6e1

Please sign in to comment.