Skip to content

Commit

Permalink
Merge pull request #55 from atk4/feature/fix-related-entity-aliases
Browse files Browse the repository at this point in the history
Fix aliases on related entities.
  • Loading branch information
romaninsh committed Jul 21, 2016
2 parents be22ea9 + d46cc53 commit 74f298f
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 17 deletions.
67 changes: 67 additions & 0 deletions docs/relations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Relations

.. php:class:: Model
.. php:method:: ref($link, $defailts = []);
Models can relate one to another. The logic of traversing relations, however, is
slightly different to the traditional ORM implementation, because in Agile Data
traversing also imposes :ref:`conditions`
Expand Down Expand Up @@ -41,6 +43,10 @@ order will look like this::
select id from user where is_vip = 1
) limit 1

Argument $defaults will be passed to the new model that will be used for
relation. This will not work if you have specified relation as existing
model that has a persistence set.

Persistence
-----------

Expand Down Expand Up @@ -321,3 +327,64 @@ will ecapsulate sub-queries resulting in a query like this::
(select user_id from order where id=1 ))


Relation Aliases
================

When related entity relies on the same table it is possible to run into problem when SQL is
confused about which table to use.

.. code-block:: sql
select name, (select name from item where item.parent_id = item.id) parent_name from item
To avoid this problem Agile Data will automatically alias tables in sub-queries. Here is how
it works::

$item->hasMany('parent_item_id', new Model_Item())
->addField('parent', 'name');

When generating expression for 'parent', the sub-query will use alias ``pi`` consisting of
first letters in 'parent_item_id'. (except _id). You can actually specify a custom table alias
if you want::

$item->hasMany('parent_item_id', [new Model_Item(), 'table_alias'=>'mypi'])
->addField('parent', 'name');

Additionally you can pass table_alias as second argument into ref() or refLink(). This can
help you in creating a recursive models that relate to itself. Here is example::

class Model_Item3 extends \atk4\data\Model {
public $table='item';
function init() {
parent::init();

$m = new Model_Item3();

$this->addField('name');
$this->addField('age');
$i2 = $this->join('item2.item_id');
$i2->hasOne('parent_item_id', [$m, 'table_alias'=>'parent'])
->addTitle();

$this->hasMany('Child', [$m, 'their_field'=>'parent_item_id', 'table_alias'=>'child'])
->addField('child_age',['aggregate'=>'sum', 'field'=>'age']);
}
}

Loading model like that can produce a pretty sophisticated query

.. code-block:: sql
select
`pp`.`id`,`pp`.`name`,`pp`.`age`,`pp_i`.`parent_item_id`,
(select `parent`.`name`
from `item` `parent`
left join `item2` as `parent_i` on `parent_i`.`item_id` = `parent`.`id`
where `parent`.`id` = `pp_i`.`parent_item_id`
) `parent_item`,
(select sum(`child`.`age`) from `item` `child`
left join `item2` as `child_i` on `child_i`.`item_id` = `child`.`id`
where `child_i`.`parent_item_id` = `pp`.`id`
) `child_age`,`pp`.`id` `_i`
from `item` `pp`left join `item2` as `pp_i` on `pp_i`.`item_id` = `pp`.`id`
33 changes: 26 additions & 7 deletions src/Field_Many.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class Field_Many
}
use \atk4\core\InitializerTrait;

/**
* Use this alias for related entity by default.
*/
protected $table_alias;

/**
* What should we pass into owner->ref() to get
Expand Down Expand Up @@ -68,19 +72,33 @@ public function init()

protected function getModel($defaults = [])
{
if (!isset($defaults['table_alias'])) {
if (!$this->table_alias) {
$this->table_alias = $this->link;
$this->table_alias = preg_replace('/_id/', '', $this->table_alias);
$this->table_alias = preg_replace('/([a-zA-Z])[a-zA-Z]*[^a-zA-Z]*/', '\1', $this->table_alias);
}
$defaults['table_alias'] = $this->table_alias;
}

if (is_object($this->model) && $this->model instanceof \Closure) {
$c = $this->model;

return $c($this->owner, $this, $defaults);
$c = $c($this->owner, $this);
if (!$c->persistence && $this->owner->persistence) {
$c = $this->owner->persistence->add($c, $defaults);
}

return $c;
}

if (is_object($this->model)) {
$m = clone $this->model;
if ($defaults) {
$m->setDefaults($defaults);
if ($this->model->persistence || !$this->owner->persistence) {
return clone $this->model;
}
$c = clone $this->model;

return $m;
return $this->owner->persistence->add($c, $defaults);
}

// last effort - try to add model
Expand Down Expand Up @@ -160,9 +178,10 @@ public function addField($n, $defaults = [])
}

$field = isset($defaults['field']) ? $defaults['field'] : $n;
$action = $this->refLink()->action('fx', [$defaults['aggregate'], $field]);

return $this->owner->addExpression($n, $action);
return $this->owner->addExpression($n, function () use ($defaults, $field) {
return $this->refLink()->action('fx', [$defaults['aggregate'], $field]);
});
}

public function addFields($fields = [])
Expand Down
26 changes: 24 additions & 2 deletions src/Field_One.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class Field_One
}
use \atk4\core\TrackableTrait;

/**
* Use this alias for related entity by default.
*/
protected $table_alias;

/**
* What should we pass into owner->ref() to get
Expand Down Expand Up @@ -72,14 +76,32 @@ public function init()

public function getModel($defaults = [])
{
if (!isset($defaults['table_alias'])) {
if (!$this->table_alias) {
$this->table_alias = $this->link;
$this->table_alias = preg_replace('/_id/', '', $this->table_alias);
$this->table_alias = preg_replace('/([a-zA-Z])[a-zA-Z]*[^a-zA-Z]*/', '\1', $this->table_alias);
}
$defaults['table_alias'] = $this->table_alias;
}
if (is_object($this->model) && $this->model instanceof \Closure) {
$c = $this->model;

return $c($this->owner, $this);
$c = $c($this->owner, $this);
if (!$c->persistence) {
$c = $this->owner->persistence->add($c, $defaults);
}

return $c;
}

if (is_object($this->model)) {
return $this->model;
$c = clone $this->model;
if (!$this->model->persistence && $this->owner->persistence) {
$this->owner->persistence->add($c, $defaults);
}

return $c;
}

// last effort - try to add model
Expand Down
4 changes: 2 additions & 2 deletions src/Field_SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public function getDSQLExpression($expression)
if (isset($this->owner->persistence_data['use_table_prefixes'])) {
if ($this->actual) {
return $expression->expr('{}.{}', [
$this->join ? $this->join->short_name
$this->join ? (isset($this->join->foreign_alias) ? $this->join->foreign_alias : $this->join->short_name)
: ($this->owner->table_alias ?: $this->owner->table),
$this->actual,
]);
} else {
return $expression->expr('{}.{}', [
$this->join ? $this->join->short_name
$this->join ? (isset($this->join->foreign_alias) ? $this->join->foreign_alias : $this->join->short_name)
: (isset($this->owner->table_alias) ? $this->owner->table_alias : $this->owner->table),
$this->short_name,
]);
Expand Down
2 changes: 1 addition & 1 deletion src/Field_SQL_One.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function refLink()
public function addTitle()
{
$field = str_replace('_id', '', $this->link);
$this->owner->addExpression($field, function ($m) use ($their_field) {
$this->owner->addExpression($field, function ($m) {
$mm = $m->refLink($this->link);

return $mm->action('field', [$mm->title_field]);
Expand Down
12 changes: 8 additions & 4 deletions src/Join_SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class Join_SQL extends Join implements \atk4\dsql\Expressionable
{
protected $foreign_alias;
public $foreign_alias;
/**
* A short symbolic name that will be used as an alias for the joined table.
*/
Expand Down Expand Up @@ -65,7 +65,7 @@ public function init()

// Our short name will be unique
if (!$this->foreign_alias) {
$this->foreign_alias = $this->short_name;
$this->foreign_alias = (isset($this->owner->table_alias) ? $this->owner->table_alias : '').$this->short_name;
}

$this->dsql->table($this->foreign_table, $this->foreign_alias);
Expand Down Expand Up @@ -114,12 +114,16 @@ public function initSelectQuery($model, $query)
),
(
isset($this->owner->table_alias) ?
$this->owner->table_alias :
($this->owner->table_alias.'.'.$this->master_field) :
($this->owner->table).'.'.$this->master_field)
);

if ($this->reverse) {
$query->field([$this->short_name => ($this->join ?: ($this->owner->table.'.'.$this->master_field))]);
$query->field([$this->short_name => ($this->join ?:
(
(isset($this->owner->table_alias) ? $this->owner->table_alias : $this->owner->table)
.'.'.$this->master_field)
)]);
} else {
$query->field([$this->short_name => $this->foreign_alias.'.'.$this->foreign_field]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence_SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ public function delete(Model $m, $id)
{
$delete = $this->action($m, 'delete');
$delete->reset('where'); // because it could have join there..
$delete->where($m->getElement($m->id_field), $id);
$delete->where($m->id_field, $id);
$m->hook('beforeDeleteQuery', [$delete]);
try {
$delete->execute();
Expand Down
117 changes: 117 additions & 0 deletions tests/RandomTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,51 @@ public function init()
$this->addField('ask');
}
}
class Model_Item extends \atk4\data\Model
{
public $table = 'item';

public function init()
{
parent::init();
$this->addField('name');
$this->hasOne('parent_item_id', '\atk4\data\tests\Item')
->addTitle();
}
}
class Model_Item2 extends \atk4\data\Model
{
public $table = 'item';

public function init()
{
parent::init();
$this->addField('name');
$i2 = $this->join('item2.item_id');
$i2->hasOne('parent_item_id', new self())
->addTitle();
}
}
class Model_Item3 extends \atk4\data\Model
{
public $table = 'item';

public function init()
{
parent::init();

$m = new self();

$this->addField('name');
$this->addField('age');
$i2 = $this->join('item2.item_id');
$i2->hasOne('parent_item_id', [$m, 'table_alias' => 'parent'])
->addTitle();

$this->hasMany('Child', [$m, 'their_field' => 'parent_item_id', 'table_alias' => 'child'])
->addField('child_age', ['aggregate' => 'sum', 'field' => 'age']);
}
}



Expand Down Expand Up @@ -128,4 +173,76 @@ public function testBasic()
$m->action('select')->render()
);
}

public function testSameTable()
{
$db = new Persistence_SQL($this->db->connection);
$a = [
'item' => [
1 => ['id' => 1, 'name' => 'John', 'parent_item_id' => '1'],
2 => ['id' => 2, 'name' => 'Sue', 'parent_item_id' => '1'],
3 => ['id' => 3, 'name' => 'Smith', 'parent_item_id' => '2'],
], ];
$this->setDB($a);

$m = new Model_Item($db, 'item');

$this->assertEquals(
['id' => '3', 'name' => 'Smith', 'parent_item_id' => '2', 'parent_item' => 'Sue'],
$m->load(3)->get()
);
}

public function testSameTable2()
{
$db = new Persistence_SQL($this->db->connection);
$a = [
'item' => [
1 => ['id' => 1, 'name' => 'John'],
2 => ['id' => 2, 'name' => 'Sue'],
3 => ['id' => 3, 'name' => 'Smith'],
],
'item2' => [
1 => ['id' => 1, 'item_id' => 1, 'parent_item_id' => '1'],
2 => ['id' => 2, 'item_id' => 2, 'parent_item_id' => '1'],
3 => ['id' => 3, 'item_id' => 3, 'parent_item_id' => '2'],
],
];
$this->setDB($a);

$m = new Model_Item2($db, 'item');

$this->assertEquals(
['id' => '3', 'name' => 'Smith', 'parent_item_id' => '2', 'parent_item' => 'Sue'],
$m->load(3)->get()
);
}

public function testSameTable3()
{
$db = new Persistence_SQL($this->db->connection);
$a = [
'item' => [
1 => ['id' => 1, 'name' => 'John', 'age' => 18],
2 => ['id' => 2, 'name' => 'Sue', 'age' => 20],
3 => ['id' => 3, 'name' => 'Smith', 'age' => 24],
],
'item2' => [
1 => ['id' => 1, 'item_id' => 1, 'parent_item_id' => '1'],
2 => ['id' => 2, 'item_id' => 2, 'parent_item_id' => '1'],
3 => ['id' => 3, 'item_id' => 3, 'parent_item_id' => '2'],
],
];
$this->setDB($a);

$m = new Model_Item3($db, 'item');

$this->assertEquals(
['id' => '2', 'name' => 'Sue', 'parent_item_id' => '1', 'parent_item' => 'John', 'age' => '20', 'child_age' => 24],
$m->load(2)->get()
);

$this->assertEquals(1, $m->load(2)->ref('Child', ['table_alias' => 'pp'])->action('count')->getOne());
$this->assertEquals('John', $m->load(2)->ref('parent_item_id', ['table_alias' => 'pp'])->get('name'));
}
}

0 comments on commit 74f298f

Please sign in to comment.