Skip to content

Commit

Permalink
Make SQL Expression & Query classes abstract (#1048)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Aug 14, 2022
1 parent d09466d commit d8cd47e
Show file tree
Hide file tree
Showing 37 changed files with 248 additions and 245 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ DSQL tries to do things differently:
DSQL Is Simple and Powerful

``` php
$query = new Atk4\Data\Persistence\Sql\Query();
$query = $connection->dsql();
$query->table('employees')
->where('birth_date', '1961-05-02')
->field('count(*)');
Expand All @@ -641,7 +641,7 @@ If the basic query is not fun, how about more complex one?

``` php
// establish a query looking for a maximum salary
$salary = new Atk4\Data\Persistence\Sql\Query(['connection' => $pdo]);
$salary = $connection->dsql();

// create few expression objects
$eMs = $salary->expr('max(salary)');
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
"require": {
"php": ">=7.4 <8.3",
"atk4/core": "dev-develop",
"doctrine/dbal": "^3.3",
"doctrine/dbal": "~3.3.0",
"mvorisek/atk4-hintable": "~1.9.0"
},
"require-release": {
"php": ">=7.4 <8.3",
"atk4/core": "~3.2.0",
"doctrine/dbal": "^3.3",
"doctrine/dbal": "~3.3.0",
"mvorisek/atk4-hintable": "~1.9.0"
},
"require-dev": {
Expand Down
2 changes: 1 addition & 1 deletion docs/persistence/csv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ You can take a model that is loaded from other persistence and save
it into CSV like this. The next example demonstrates a basic functionality
of SQL database export to CSV file::

$db = new Persistence\Sql($pdo);
$db = new Persistence\Sql($connection);
$csv = new Persistence\Csv('dump.csv');

$m = new Model_User($db);
Expand Down
15 changes: 9 additions & 6 deletions docs/persistence/sql/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ Using DSQL without Connection
You can use :php:class:`Query` and :php:class:`Expression` without connection
at all. Simply create expression::

$expr = new Expression('show tables like []', ['foo%']);
$expr = new Mysql\Expression('show tables like []', ['foo%']);

or query::

$query = (new Query())->table('user')->where('id', 1);
$query = (new Mysql\Query())->table('user')->where('id', 1);

When it's time to execute you can specify your PDO manually::
When it's time to execute you can specify your Connection manually::

$rows = $expr->getRows($pdo);
$rows = $expr->getRows($connection);
foreach ($rows as $row) {
echo json_encode($row) . "\n";
}

With queries you might need to select mode first::

$stmt = $query->mode('delete')->executeStatement($pdo);
$stmt = $query->mode('delete')->executeStatement($connection);

The :php:meth:`Expresssion::execute` is a convenient way to prepare query,
bind all parameters and get `Doctrine\DBAL\Result`, but if you wish to do it manually,
Expand All @@ -43,7 +43,6 @@ If you use DSQL inside another framework, it's possible that there is already
a PDO object which you can use. In Laravel you can optimize some of your queries
by switching to DSQL::

$pdo = DB::connection()->getPdo();
$c = new Connection(['connection' => $pdo]);

$userIds = $c->dsql()->table('expired_users')->field('user_id');
Expand Down Expand Up @@ -86,6 +85,9 @@ Let's say you want to add support for new SQL vendor::

class Query_MyVendor extends Atk4\Data\Persistence\Sql\Query
{
protected string $identifierEscapeChar = '"';
protected string $expressionClass = Expression_MyVendor::class;

// truncate is done differently by this vendor
protected $template_truncate = 'delete [from] [table]';

Expand Down Expand Up @@ -157,6 +159,7 @@ query "LOAD DATA INFILE":
So to implement our task, you might need a class like this::

use \Atk4\Data\Persistence\Sql\Exception;

class QueryMysqlCustom extends \Atk4\Data\Persistence\Sql\Mysql\Query
{
protected $template_load_data = 'load data local infile [file] into table [table]';
Expand Down
49 changes: 16 additions & 33 deletions docs/persistence/sql/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ Creating Expression

::

use Atk4\Data\Persistence\Sql\Expression;

$expr = new Expression('NOW()');
$expr = $connection->expr('NOW()');

You can also use :php:meth:`expr()` method to create expression, in which case
you do not have to define "use" block::
Expand All @@ -94,7 +92,7 @@ you do not have to define "use" block::
You can specify some of the expression properties through first argument of the
constructor::

$expr = new Expression(['NOW()', 'connection' => $pdo]);
$expr = $connection->expr(['template' => 'NOW()']);

:ref:`Scroll down <properties>` for full list of properties.

Expand All @@ -113,14 +111,14 @@ square brackets:
Arguments can be specified immediately through an array as a second argument
into constructor or you can specify arguments later::

$expr = new Expression(
$expr = $connection->expr(
'coalesce([name], [surname])',
['name' => $name, 'surname' => $surname]
);

// is the same as

$expr = new Expression('coalesce([name], [surname])');
$expr = $connection->expr('coalesce([name], [surname])');
$expr['name'] = $name;
$expr['surname'] = $surname;

Expand All @@ -129,8 +127,8 @@ Nested expressions

Expressions can be nested several times::

$age = new Expression('coalesce([age], [default_age])');
$age['age'] = new Expression("year(now()) - year(birth_date)");
$age = $connection->expr('coalesce([age], [default_age])');
$age['age'] = $connection->expr("year(now()) - year(birth_date)");
$age['default_age'] = 18;

$query->table('user')->field($age, 'calculated_age');
Expand Down Expand Up @@ -162,18 +160,12 @@ your expression. Before you do, however, you need to have :php:attr:`$connection
property set. (See `Connecting to Database` on more details). In short the
following code will connect your expression with the database::

$expr = new Expression('connection' => $pdo);
$expr = $connection->expr();

If you are looking to use connection :php:class:`Query` class, you may want to
consider using a proper vendor-specific subclass::

$query = new \Atk4\Data\Persistence\Sql\Mysql\Query('connection' => $pdo);


If your expression already exist and you wish to associate it with connection
you can simply change the value of :php:attr:`$connection` property::

$expr->connection = $pdo;
$query = new \Atk4\Data\Persistence\Sql\Mysql\Query('connection' => $connection);

Finally, you can pass connection class into :php:meth:`executeQuery` directly.

Expand All @@ -182,7 +174,7 @@ Finally, you can pass connection class into :php:meth:`executeQuery` directly.
Executes expression using current database connection or the one you
specify as the argument::

$stmt = $expr->executeQuery($pdo);
$stmt = $expr->executeQuery($connection);

returns `Doctrine\DBAL\Result`.

Expand All @@ -205,10 +197,7 @@ Finally, you can pass connection class into :php:meth:`executeQuery` directly.
Executes expression and return whole result-set in form of array of hashes::

$data = new Expression([
'connection' => $pdo,
'template' => 'show databases',
])->getRows();
$data = $connection->expr('show databases')->getRows();
echo json_encode($data);

The output would be
Expand All @@ -226,10 +215,7 @@ Finally, you can pass connection class into :php:meth:`executeQuery` directly.
Executes expression and returns first row of data from result-set as a hash::

$data = new Expression([
'connection' => $pdo,
'template' => 'SELECT @@global.time_zone, @@session.time_zone',
])->getRow()
$data = $connection->expr('SELECT @@global.time_zone, @@session.time_zone')->getRow()

echo json_encode($data);

Expand All @@ -244,10 +230,7 @@ Finally, you can pass connection class into :php:meth:`executeQuery` directly.
Executes expression and return first value of first row of data from
result-set::

$time = new Expression([
'connection' => $pdo,
'template' => 'now()',
])->getOne();
$time = $connection->expr('NOW()')->getOne();

Magic an Debug Methods
======================
Expand Down Expand Up @@ -337,13 +320,13 @@ Other Properties
.. php:attr:: template
Template which is used when rendering.
You can set this with either `new Expression('show tables')`
or `new Expression(['show tables'])`
or `new Expression(['template' => 'show tables'])`.
You can set this with either `$connection->expr('show tables')`
or `$connection->expr(['show tables'])`
or `$connection->expr(['template' => 'show tables'])`.

.. php:attr:: connection
PDO connection object or any other DB connection object.
DB connection object.

.. php:attr:: paramBase
Expand Down
2 changes: 1 addition & 1 deletion docs/persistence/sql/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ DSQL by example
===============
The simplest way to explain DSQL is by example::

$query = new Atk4\Data\Persistence\Sql\Query();
$query = $connection->dsql();
$query->table('employees')
->where('birth_date', '1961-05-02')
->field('count(*)');
Expand Down
6 changes: 3 additions & 3 deletions docs/persistence/sql/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ You can also mix and match with expressions and strings::
$q->where([['name', 'like', '%john%'], 'surname is null']);
// .. WHERE `name` like '%john%' AND `surname` is null

$q->where([['name', 'like', '%john%'], new Expression('surname is null')]);
$q->where([['name', 'like', '%john%'], $q->expr('surname is null')]);
// .. WHERE `name` like '%john%' AND surname is null

There is a more flexible way to use OR arguments:
Expand Down Expand Up @@ -439,7 +439,7 @@ Few examples::

$q->group('gender')->group('age');

$q->group(new Expression('year(date)'));
$q->group($q->expr('year(date)'));

Method can be executed several times on the same Query object.

Expand Down Expand Up @@ -553,7 +553,7 @@ Joining on expression
For a more complex join conditions, you can pass second argument as expression::

$q->table('user', 'u');
$q->join('address a', new Expression('a.name like u.pattern'));
$q->join('address a', $q->expr('a.name like u.pattern'));


Use WITH cursors
Expand Down
6 changes: 2 additions & 4 deletions docs/persistence/sql/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ Getting Started
We will start by looking at the :php:class:`Query` building, because you do
not need a database to create a query::

use Atk4\Data\Persistence\Sql\Query;

$query = new Query(['connection' => $pdo]);
$query = $connection->dsql();

Once you have a query object, you can add parameters by calling some of it's
methods::
Expand Down Expand Up @@ -68,7 +66,7 @@ The next example might be a bit too complex for you, but still read through and
try to understand what each section does to your base query::

// Establish a query looking for a maximum salary
$salary = new Query(['connection' => $pdo]);
$salary = $connection->dsql();

// Create few expression objects
$eMaxSalary = $salary->expr('max(salary)');
Expand Down
4 changes: 2 additions & 2 deletions docs/sql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ SQL Field
SQL Fields can be used inside other SQL expressions::

$q = new \Atk4\Data\Persistence\Sql\Expression('[age] + [birth_year]', [
$q = $connection->expr('[age] + [birth_year]', [
'age' => $m->getField('age'),
'birth_year' => $m->getField('birth_year'),
]);
Expand Down Expand Up @@ -203,7 +203,7 @@ There is, however, one difference. Expression class requires all named arguments
to be specified. Use of Model::expr() allows you to specify field names and those
field expressions will be automatically substituted. Here is long / short format::

$q = new \Atk4\Data\Persistence\Sql\Expression('[age] + [birth_year]', [
$q = $connection->expr('[age] + [birth_year]', [
'age' => $m->getField('age'),
'birth_year' => $m->getField('birth_year'),
]);
Expand Down
30 changes: 15 additions & 15 deletions src/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ class Model implements \IteratorAggregate
/** @var array<string, true> */
private static $_modelOnlyProperties;

/** @var string|array The class used by addField() method. */
/** @var array The seed used by addField() method. */
protected $_defaultSeedAddField = [Field::class];

/** @var string|array The class used by addExpression() method. */
/** @var array The seed used by addExpression() method. */
protected $_defaultSeedAddExpression = [CallbackField::class];

/** @var array<string, Field> */
protected $fields = [];
protected array $fields = [];

/**
* Contains name of table, session key, collection or file where this
Expand All @@ -138,25 +138,23 @@ class Model implements \IteratorAggregate
/** @var Model\Scope\RootScope */
private $scope;

/** @var array */
public $limit = [null, 0];
/** @var array{0: int|null, 1: int} */
public array $limit = [null, 0];

/** @var array */
public $order = [];
/** @var array<int, array{0: string|Persistence\Sql\Expressionable, 1: 'asc'|'desc'}> */
public array $order = [];

/** @var array<string, array{'model': Model, 'recursive': bool}> */
public $cteModels = [];
public array $cteModels = [];

/**
* Currently loaded record data. This record is associative array
* that contain field => data pairs. It may contain data for un-defined
* fields only if $onlyFields mode is false.
*
* Avoid accessing $data directly, use set() / get() instead.
*
* @var array
*/
private $data = [];
private array $data = [];

/**
* After loading an active record from DataSet it will be stored in
Expand Down Expand Up @@ -531,7 +529,7 @@ public function validate(string $intent = null): array
}

/** @var array<string, array> */
protected $fieldSeedByType = [];
protected array $fieldSeedByType = [];

/**
* Given a field seed, return a field object.
Expand Down Expand Up @@ -1259,12 +1257,14 @@ private function _load(bool $fromReload, bool $fromTryLoad, $id)
return $res;
}

$dataRef = &$this->getDataRef();
$dataRef = $this->getPersistence()->{$fromTryLoad ? 'tryLoad' : 'load'}($this->getModel(), $this->remapIdLoadToPersistence($id));
if ($dataRef === null) {
$data = $this->getPersistence()->{$fromTryLoad ? 'tryLoad' : 'load'}($this->getModel(), $this->remapIdLoadToPersistence($id));
if ($data === null) {
return null; // $fromTryLoad is always true here
}

$dataRef = &$this->getDataRef();
$dataRef = $data;

if ($this->id_field) {
$this->setId($this->getId());
}
Expand Down
3 changes: 2 additions & 1 deletion src/Model/Scope/Condition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Atk4\Data\Model;
use Atk4\Data\Persistence\Sql\Expression;
use Atk4\Data\Persistence\Sql\Expressionable;
use Atk4\Data\Persistence\Sql\Sqlite\Expression as SqliteExpression;

class Condition extends AbstractScope
{
Expand Down Expand Up @@ -355,7 +356,7 @@ protected function valueToWords(Model $model, $value): string
}

if ($value instanceof Expressionable) {
return 'expression \'' . $value->getDsqlExpression(new Expression())->getDebugQuery() . '\'';
return 'expression \'' . $value->getDsqlExpression(new SqliteExpression())->getDebugQuery() . '\'';
}

return 'object ' . print_r($value, true);
Expand Down

0 comments on commit d8cd47e

Please sign in to comment.