diff --git a/src/System/Database/MyQuery/Delete.php b/src/System/Database/MyQuery/Delete.php index 683e7c2d..92253952 100644 --- a/src/System/Database/MyQuery/Delete.php +++ b/src/System/Database/MyQuery/Delete.php @@ -5,6 +5,7 @@ namespace System\Database\MyQuery; use System\Database\MyPDO; +use System\Database\MyQuery\Join\AbstractJoin; use System\Database\MyQuery\Traits\ConditionTrait; use System\Database\MyQuery\Traits\SubQueryTrait; @@ -13,6 +14,8 @@ class Delete extends Execute use ConditionTrait; use SubQueryTrait; + protected ?string $alias = null; + public function __construct(string $table_name, MyPDO $PDO) { $this->_table = $table_name; @@ -24,12 +27,59 @@ public function __toString() return $this->builder(); } + /** + * Set alias for the table. + * If using an alias, conditions with binding values will be ignored, + * except when using subqueries, clause in join also will be generate as alias. + */ + public function alias(string $alias): self + { + $this->alias = $alias; + + return $this; + } + + /** + * Join statment: + * - inner join + * - left join + * - right join + * - full join. + */ + public function join(AbstractJoin $ref_table): self + { + $table = $this->alias ?? $this->_table; + $ref_table->table($table); + + $this->_join[] = $ref_table->stringJoin(); + $binds = (fn () => $this->{'sub_query'})->call($ref_table); + + if (null !== $binds) { + $this->_binds = array_merge($this->_binds, $binds->getBind()); + } + + return $this; + } + + private function getJoin(): string + { + return 0 === count($this->_join) + ? '' + : implode(' ', $this->_join) + ; + } + protected function builder(): string { - $where = $this->getWhere(); + $build = []; + + $build['join'] = $this->getJoin(); + $build['where'] = $this->getWhere(); - $this->_query = "DELETE FROM {$this->_table} {$where}"; + $query_parts = implode(' ', array_filter($build, fn ($item) => $item !== '')); - return $this->_query; + return $this->_query = null === $this->alias + ? "DELETE FROM {$this->_table} {$query_parts}" + : "DELETE {$this->alias} FROM {$this->_table} AS {$this->alias} {$query_parts}"; } } diff --git a/src/System/Database/MyQuery/Query.php b/src/System/Database/MyQuery/Query.php index ff2ea582..34bfb6e6 100644 --- a/src/System/Database/MyQuery/Query.php +++ b/src/System/Database/MyQuery/Query.php @@ -181,8 +181,11 @@ protected function splitFilters(array $filters): string foreach ($filters['filters'] as $fieldName => $fieldValue) { $value = $fieldValue['value']; $comparation = $fieldValue['comparation']; + $column = str_contains($fieldName, '.') ? $fieldName : "{$table_name}.{$fieldName}"; + $bind = $fieldValue['bind']; + if ($value !== '') { - $query[] = "({$table_name}.{$fieldName} {$comparation} :{$fieldName})"; + $query[] = "({$column} {$comparation} :{$bind})"; } } diff --git a/src/System/Database/MyQuery/Traits/ConditionTrait.php b/src/System/Database/MyQuery/Traits/ConditionTrait.php index 85b2fdc6..f77aa113 100644 --- a/src/System/Database/MyQuery/Traits/ConditionTrait.php +++ b/src/System/Database/MyQuery/Traits/ConditionTrait.php @@ -127,10 +127,12 @@ public function in(string $column_name, $value) */ public function compare(string $bind, string $comparation, $value, bool $bindValue = false) { - $this->_binds[] = Bind::set($bind, $value); + $escape_bind = str_replace('.', '__', $bind); + $this->_binds[] = Bind::set($escape_bind, $value); $this->_filters[$bind] = [ 'value' => $value, 'comparation' => $comparation, + 'bind' => $escape_bind, $bindValue, ]; diff --git a/src/System/Database/MyQuery/Update.php b/src/System/Database/MyQuery/Update.php index 603746d2..9282dca4 100644 --- a/src/System/Database/MyQuery/Update.php +++ b/src/System/Database/MyQuery/Update.php @@ -5,6 +5,7 @@ namespace System\Database\MyQuery; use System\Database\MyPDO; +use System\Database\MyQuery\Join\AbstractJoin; use System\Database\MyQuery\Traits\ConditionTrait; use System\Database\MyQuery\Traits\SubQueryTrait; @@ -55,10 +56,38 @@ public function value(string $bind, $value) return $this; } - protected function builder(): string + /** + * Join statment: + * - inner join + * - left join + * - right join + * - full join. + */ + public function join(AbstractJoin $ref_table): self + { + // overide master table + $ref_table->table($this->_table); + + $this->_join[] = $ref_table->stringJoin(); + $binds = (fn () => $this->{'sub_query'})->call($ref_table); + + if (null !== $binds) { + $this->_binds = array_merge($this->_binds, $binds->getBind()); + } + + return $this; + } + + private function getJoin(): string { - $where = $this->getWhere(); + return 0 === count($this->_join) + ? '' + : implode(' ', $this->_join) + ; + } + protected function builder(): string + { $setter = []; foreach ($this->_binds as $bind) { if ($bind->hasColumName()) { @@ -66,11 +95,13 @@ protected function builder(): string } } - // $binds = array_filter($setter); - $set_string = implode(', ', $setter); + $build = []; + $build['join'] = $this->getJoin(); + $build[] = 'SET ' . implode(', ', $setter); + $build['where'] = $this->getWhere(); - $this->_query = "UPDATE $this->_table SET $set_string $where"; + $query_parts = implode(' ', array_filter($build, fn ($item) => $item !== '')); - return $this->_query; + return $this->_query = "UPDATE {$this->_table} {$query_parts}"; } } diff --git a/tests/DataBase/Query/JoinTest.php b/tests/DataBase/Query/JoinTest.php index 26f55254..2fc96230 100644 --- a/tests/DataBase/Query/JoinTest.php +++ b/tests/DataBase/Query/JoinTest.php @@ -176,4 +176,46 @@ public function itCanGenerateInnerJoinWithSubQuery() $join->queryBind() ); } + + /** @test */ + public function itCanGenerateInnerJoinInDeleteClausa() + { + $join = MyQuery::from('base_table', $this->PDO) + ->delete() + ->alias('bt') + ->join(InnerJoin::ref('join_table', 'base_id', 'join_id')) + ->equal('join_table.a', 1) + ; + + $this->assertEquals( + 'DELETE bt FROM base_table AS bt INNER JOIN join_table ON bt.base_id = join_table.join_id WHERE ( (join_table.a = :join_table__a) )', + $join->__toString() + ); + + $this->assertEquals( + 'DELETE bt FROM base_table AS bt INNER JOIN join_table ON bt.base_id = join_table.join_id WHERE ( (join_table.a = 1) )', + $join->queryBind() + ); + } + + /** @test */ + public function itCanGenerateInnerJoinInUpdateClausa() + { + $update = MyQuery::from('test', $this->PDO) + ->update() + ->value('a', 'b') + ->join(InnerJoin::ref('join_table', 'base_id', 'join_id')) + ->equal('test.column_1', 100) + ; + + $this->assertEquals( + 'UPDATE test INNER JOIN join_table ON test.base_id = join_table.join_id SET a = :bind_a WHERE ( (test.column_1 = :test__column_1) )', + $update->__toString() + ); + + $this->assertEquals( + 'UPDATE test INNER JOIN join_table ON test.base_id = join_table.join_id SET a = \'b\' WHERE ( (test.column_1 = 100) )', + $update->queryBind() + ); + } } diff --git a/tests/DataBase/RealDatabase/JoinTest.php b/tests/DataBase/RealDatabase/JoinTest.php new file mode 100644 index 00000000..71d444b6 --- /dev/null +++ b/tests/DataBase/RealDatabase/JoinTest.php @@ -0,0 +1,184 @@ +createConnection(); + } + + protected function tearDown(): void + { + $this->dropConnection(); + } + + // schema + + private function createUsersSchema(): bool + { + return $this + ->pdo + ->query('CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) UNIQUE NOT NULL, + role_id INT NOT NULL + )') + ->execute(); + } + + private function createRolesSchema(): bool + { + return $this + ->pdo + ->query('CREATE TABLE roles ( + id INT AUTO_INCREMENT PRIMARY KEY, + role_name VARCHAR(100) NOT NULL UNIQUE + )') + ->execute(); + } + + private function createLogsSchema(): bool + { + return $this + ->pdo + ->query('CREATE TABLE logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + action VARCHAR(255) NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + )') + ->execute(); + } + + // factory + + private function factory() + { + $this->pdo + ->query('INSERT INTO roles (role_name) VALUES + ("Admin"), + ("Editor"), + ("Subscriber")') + ->execute(); + + $this->pdo + ->query('INSERT INTO users (name, email, role_id) VALUES + (\'Alice\', \'alice@example.com\', 1), + (\'Bob\', \'bob@example.com\', 2), + (\'Charlie\', \'charlie@example.com\', 3);') + ->execute(); + + $this->pdo + ->query('INSERT INTO logs (user_id, action) VALUES + (1, \'Logged In\'), + (2, \'Logged In\'), + (1, \'Deactivated\'), + (3, \'Logged Out\');') + ->execute(); + } + + // test + + /** + * @test + * + * @group database + */ + public function itCanJoinInSelectQuery() + { + $this->createUsersSchema(); + $this->createRolesSchema(); + $this->createLogsSchema(); + $this->factory(); + + $users = MyQuery::from('users', $this->pdo) + ->select(['users.name', 'roles.role_name']) + ->join(InnerJoin::ref('roles', 'role_id', 'id')) + ->get(); + + $this->assertEquals('Alice', $users[0]['name']); + $this->assertEquals('Admin', $users[0]['role_name']); + } + + /** + * @test + * + * @group database + */ + public function itCanJoinInUpdateQuery() + { + $this->createUsersSchema(); + $this->createRolesSchema(); + $this->createLogsSchema(); + $this->factory(); + + MyQuery::from('users', $this->pdo) + ->update() + ->value('name', 'Eve') + ->join(InnerJoin::ref('roles', 'role_id', 'id')) + ->equal('roles.role_name', 'Admin') + ->execute(); + + $users = $this->pdo->query(' + SELECT + users.name, roles.role_name + FROM users + INNER JOIN roles ON + users.role_id = roles.id + ') + ->resultset(); + + $this->assertEquals('Eve', $users[0]['name']); + $this->assertEquals('Admin', $users[0]['role_name']); + } + + /** + * @test + * + * @group database + */ + public function itCanJoinInDeleteQuery(): void + { + $this->createUsersSchema(); + $this->createRolesSchema(); + $this->createLogsSchema(); + $this->factory(); + + // Delete related logs first + MyQuery::from('logs', $this->pdo) + ->delete() + ->alias('l') + ->join(InnerJoin::ref('users', 'user_id', 'id')) + ->equal('users.role_id', 1) // Assuming role_id 1 is for 'Admin' + ->execute(); + + MyQuery::from('users', $this->pdo) + ->delete() + ->alias('u') + ->join(InnerJoin::ref('roles', 'role_id', 'id')) + ->equal('roles.role_name', 'Admin') + ->execute(); + + $users = $this->pdo->query(' + SELECT + users.name, roles.role_name + FROM users + INNER JOIN roles ON + users.role_id = roles.id + ') + ->resultset(); + + $this->assertEquals('Bob', $users[0]['name']); + $this->assertEquals('Editor', $users[0]['role_name']); + } +}