diff --git a/src/Queries/Delete.php b/src/Queries/Delete.php index e51fc04..4faa142 100644 --- a/src/Queries/Delete.php +++ b/src/Queries/Delete.php @@ -8,15 +8,17 @@ use Puzzle\QueryBuilder\Traits\EscaperAware; use Puzzle\QueryBuilder\Snippet; use Puzzle\QueryBuilder\Queries\Snippets\Builders; +use Puzzle\QueryBuilder\QueryPartAware; -class Delete implements Query +class Delete implements Query, QueryPartAware { use EscaperAware, Builders\Join, Builders\Where, Builders\OrderBy, - Builders\Limit; + Builders\Limit, + Builders\QueryPart; private $from; @@ -37,6 +39,10 @@ public function __construct($table = null, ?string $alias = null) public function toString(): string { + $snippets = $this->joins; + $snippets[] = $this->from; + $this->ensureNeededTablesArePresent($snippets); + $queryParts = array( 'DELETE', $this->buildFrom(), diff --git a/src/Queries/Select.php b/src/Queries/Select.php index 3f1e13a..66aeecb 100644 --- a/src/Queries/Select.php +++ b/src/Queries/Select.php @@ -10,8 +10,9 @@ use Puzzle\QueryBuilder\Snippet; use Puzzle\QueryBuilder\Queries\Snippets\Builders; use Puzzle\QueryBuilder\Queries\Snippets\Having; +use Puzzle\QueryBuilder\QueryPartAware; -class Select implements Query +class Select implements Query, QueryPartAware { use EscaperAware, @@ -19,7 +20,8 @@ class Select implements Query Builders\Where, Builders\GroupBy, Builders\OrderBy, - Builders\Limit; + Builders\Limit, + Builders\QueryPart; private $select, @@ -42,6 +44,10 @@ public function __construct($columns = []) public function toString(): string { + $snippets = $this->joins; + $snippets[] = $this->from; + $this->ensureNeededTablesArePresent($snippets); + $queryParts = array( $this->buildSelect(), $this->buildFrom(), diff --git a/src/Queries/Snippets/Builders/QueryPart.php b/src/Queries/Snippets/Builders/QueryPart.php new file mode 100644 index 0000000..42ca4e4 --- /dev/null +++ b/src/Queries/Snippets/Builders/QueryPart.php @@ -0,0 +1,60 @@ +build($this); + + return $this; + } + + public function needTable($tableName): Query + { + if(! in_array($tableName, $this->neededTableNames)) + { + $this->neededTableNames[] = $tableName; + } + + return $this; + } + + public function ensureNeededTablesArePresent(array $snippets): void + { + foreach($this->neededTableNames as $tableName) + { + if(! $this->isAtLeastOneSnippetHasNeededTable($tableName, $snippets)) + { + throw new \LogicException("One of query parts you used needs $tableName table"); + } + } + } + + private function isAtLeastOneSnippetHasNeededTable($tableName, array $snippets): bool + { + foreach($snippets as $snippet) + { + if(! $snippet instanceof NeedTableAware) + { + throw new \LogicException('Snippet has not expected NeedTableAware type'); + } + + if($snippet->hasNeededTable($tableName)) + { + return true; + } + } + + return false; + } +} diff --git a/src/Queries/Snippets/From.php b/src/Queries/Snippets/From.php index f806d9b..8d30a76 100644 --- a/src/Queries/Snippets/From.php +++ b/src/Queries/Snippets/From.php @@ -6,7 +6,7 @@ use Puzzle\QueryBuilder\Snippet; -class From implements Snippet +class From implements Snippet, NeedTableAware { private $tableName; @@ -28,4 +28,14 @@ public function toString(): string { return sprintf('FROM %s', $this->tableName->toString()); } + + public function hasNeededTable(string $tableName): bool + { + if($this->tableName->getName() === $tableName || $this->tableName->getAlias() === $tableName) + { + return true; + } + + return false; + } } diff --git a/src/Queries/Snippets/Joins/AbstractJoin.php b/src/Queries/Snippets/Joins/AbstractJoin.php index ed860cd..a2d289a 100644 --- a/src/Queries/Snippets/Joins/AbstractJoin.php +++ b/src/Queries/Snippets/Joins/AbstractJoin.php @@ -7,8 +7,9 @@ use Puzzle\QueryBuilder\Snippet; use Puzzle\QueryBuilder\Queries\Snippets\Join; use Puzzle\QueryBuilder\Queries\Snippets; +use Puzzle\QueryBuilder\Queries\Snippets\NeedTableAware; -abstract class AbstractJoin implements Join, Snippet +abstract class AbstractJoin implements Join, Snippet, NeedTableAware { private $table, @@ -55,6 +56,15 @@ public function toString(): string return $joinQueryPart; } + public function hasNeededTable(string $tableName): bool + { + if($this->table->getName() === $tableName || $this->table->getAlias() === $tableName) + { + return true; + } + return false; + } + abstract protected function getJoinDeclaration(): string; private function buildUsingConditionClause(): string diff --git a/src/Queries/Snippets/NeedTableAware.php b/src/Queries/Snippets/NeedTableAware.php new file mode 100644 index 0000000..081227a --- /dev/null +++ b/src/Queries/Snippets/NeedTableAware.php @@ -0,0 +1,10 @@ +tableName, $this->alias); } + + public function getName(): string + { + return $this->tableName; + } + + public function getAlias(): string + { + return $this->alias; + } } diff --git a/src/Queries/Snippets/Update.php b/src/Queries/Snippets/Update.php index ed771db..3a972bb 100644 --- a/src/Queries/Snippets/Update.php +++ b/src/Queries/Snippets/Update.php @@ -6,7 +6,7 @@ use Puzzle\QueryBuilder\Snippet; -class Update implements Snippet +class Update implements Snippet, NeedTableAware { private $tables; @@ -51,4 +51,17 @@ public function toString(): string return sprintf('UPDATE %s', $tablesString); } + + public function hasNeededTable(string $tableName): bool + { + foreach($this->tables as $table) + { + if($table->getName() === $tableName || $table->getAlias() === $tableName) + { + return true; + } + } + + return false; + } } diff --git a/src/Queries/Update.php b/src/Queries/Update.php index 4a80fea..3564bc3 100644 --- a/src/Queries/Update.php +++ b/src/Queries/Update.php @@ -7,15 +7,17 @@ use Puzzle\QueryBuilder\Query; use Puzzle\QueryBuilder\Traits\EscaperAware; use Puzzle\QueryBuilder\Queries\Snippets\Builders; +use Puzzle\QueryBuilder\QueryPartAware; -class Update implements Query +class Update implements Query, QueryPartAware { use EscaperAware, Builders\Join, Builders\Where, Builders\OrderBy, - Builders\Limit; + Builders\Limit, + Builders\QueryPart; private $updatePart, @@ -39,6 +41,10 @@ public function __construct($table = null, ?string $alias = null) public function toString(): string { + $snippets = $this->joins; + $snippets[] = $this->updatePart; + $this->ensureNeededTablesArePresent($snippets); + $queryParts = array( $this->buildUpdate(), $this->buildJoin(), diff --git a/src/QueryPart.php b/src/QueryPart.php new file mode 100644 index 0000000..0ac11da --- /dev/null +++ b/src/QueryPart.php @@ -0,0 +1,10 @@ +add(new Conditions\Equal(new Types\TString('color'), 'white')) + ->add(new Conditions\Lower(new Types\TInt('age'), 1)); + + $query->where($conditionSet); + } +} + +class IsPony implements QueryPart +{ + public function build(QueryPartAware $query): void + { + $query + ->needTable('creatures') + ->where(new Conditions\Equal(new Types\TString('type'), 'pony')); + } +} + +class QueryPartTest extends TestCase +{ + protected + $escaper; + + protected function setUp() + { + $this->escaper = new AlwaysQuoteEscaper(); + } + + public function testSelectQueryPart() + { + $query = (new Queries\Select())->setEscaper($this->escaper); + $query + ->select([ 'name', 'color' ]) + ->from('creatures') + ->add(new IsPony()) + ->add(new IsCute()) + ->where(new Conditions\Equal(new Types\TString('owner'), 'Paul')) + ->having(new Conditions\Greater(new Types\TInt('rank'), 42)); + + $this->assertSame("SELECT name, color FROM creatures WHERE type = 'pony' AND color = 'white' AND age < 1 AND owner = 'Paul' HAVING rank > 42", $query->toString()); + } + + public function testUpdateWithQueryPart() + { + $query = (new Queries\Update())->setEscaper($this->escaper); + + $query + ->update('poney') + ->set(array('owner' => 'John')) + ->where(new Conditions\In(new Types\TString('author'), array('julian', 'claude'))) + ->add(new IsCute()); + + $this->assertSame("UPDATE poney SET owner = 'John' WHERE author IN ('julian', 'claude') AND color = 'white' AND age < 1", $query->toString($this->escaper)); + } + + public function testDeleteWithQueryPart() + { + $query = (new Queries\Delete('burger', 'b'))->setEscaper($this->escaper); + + $query + ->where(new Conditions\Equal(new Types\TString('owner'), 'Claude')) + ->add(new IsCute()); + + $this->assertSame("DELETE FROM burger AS b WHERE owner = 'Claude' AND color = 'white' AND age < 1", $query->toString($this->escaper)); + } + + public function testNeededTableIsPresentInFromSnippet() + { + $query = (new Queries\Select())->setEscaper($this->escaper); + $query + ->select([ 'name', 'color' ]) + ->from('creatures') + ->add(new IsPony()); + + $query->toString(); + + $this->assertTrue(true); + } + + public function testNeededTableIsPresentInJoinSnippet() + { + $query = (new Queries\Select())->setEscaper($this->escaper); + $query + ->select([ 'name', 'color' ]) + ->from('burger') + ->innerJoin('creatures')->on('id_burger', 'id_creature') + ->add(new IsPony()); + + $query->toString(); + + $this->assertTrue(true); + } + + public function testNeededTableWithAliases() + { + $query = (new Queries\Select())->setEscaper($this->escaper); + $query + ->needTable('b') + ->needTable('p') + ->select([ 'name', 'color' ]) + ->from('burger', 'b') + ->leftJoin('pony', 'p')->on('id_burger', 'id_pony'); + + $query->toString(); + + $this->assertTrue(true); + } + + /** + * @expectedException \LogicException + */ + public function testMissingNeededTable() + { + $query = (new Queries\Select())->setEscaper($this->escaper); + $query + ->select([ 'name', 'color' ]) + ->from('burger') + ->add(new IsPony()); + + $query->toString(); + } + + public function testUpdateWithNeededTable() + { + $query = (new Queries\Update())->setEscaper($this->escaper); + + $query + ->update('creatures') + ->set(array('owner' => 'John')) + ->add(new IsPony()); + + $query->toString(); + + $this->assertTrue(true); + } + + /** + * @expectedException \LogicException + */ + public function testUpdateWithMissingNeededTable() + { + $query = (new Queries\Update())->setEscaper($this->escaper); + + $query + ->update('animals') + ->set(array('owner' => 'John')) + ->add(new IsPony()); + + $query->toString(); + } + + public function testDeleteWithNeededTable() + { + $query = (new Queries\Delete('creatures'))->setEscaper($this->escaper); + + $query + ->where(new Conditions\Equal(new Types\TString('owner'), 'Claude')) + ->add(new IsPony()); + + $query->toString(); + + $this->assertTrue(true); + } + + /** + * @expectedException \LogicException + */ + public function testDeleteWithMissingNeededTable() + { + $query = (new Queries\Delete('burger'))->setEscaper($this->escaper); + + $query + ->where(new Conditions\Equal(new Types\TString('owner'), 'Claude')) + ->add(new IsPony()); + + $query->toString(); + } +}