From 0ca9b95aed33637cc36374bbf3e33dc5a001f099 Mon Sep 17 00:00:00 2001 From: Josh McRae Date: Tue, 3 Dec 2024 08:53:04 +1000 Subject: [PATCH] [ENHANCEMENT] - Allow additional conditions on joins (#28) * Implemented NOT and XOR operations * Implemented tests for supported operators * Fixed NOT operator * Adjusted tests * Finalized tests * Fixed table name escaping * Fixed further tests * Implemented join conditionals * Added support for IN conditions * Fixed bound params * Updated README.md --- README.md | 24 ++++++++++++++++++++- lib/PicoDb/Table.php | 42 ++++++++++++++++++++++++++++++------- tests/MysqlTableTest.php | 24 +++++++++++++++++++++ tests/PostgresTableTest.php | 23 ++++++++++++++++++++ tests/SqliteTableTest.php | 23 ++++++++++++++++++++ 5 files changed, 127 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f5ae702..50e0f4a 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ Returns true if a record exists otherwise false. $db->table('mytable')->eq('column1', 12)->exists(); ``` -### Left joins +### Joins ```php // SELECT * FROM mytable LEFT JOIN my_other_table AS t1 ON t1.id=mytable.foreign_key @@ -346,6 +346,28 @@ or $db->table('mytable')->join('my_other_table', 'id', 'foreign_key')->findAll(); ``` +or + +```php +// SELECT * FROM mytable LEFT JOIN my_other_table AS t1 ON t1.id=mytable.foreign_key +$db->table('mytable')->inner('my_other_table', 't1', 'id', 'mytable', 'foreign_key')->findAll(); +``` + +Additional equality conditions can be added to a left or inner join: + +```php +// SELECT * FROM mytable LEFT JOIN my_other_table AS t1 ON t1.id=mytable.foreign_key and t1.status="active" +$db->table('mytable')->left('my_other_table', 't1', 'id', 'mytable', 'foreign_key', ['status' => 'active'])->findAll(); +``` + +or + +```php +// SELECT * FROM mytable LEFT JOIN my_other_table AS t1 ON t1.id=mytable.foreign_key and t1.status IN ("archived", "disabled") +$db->table('mytable')->left('my_other_table', 't1', 'id', 'mytable', 'foreign_key', ['status' => ['archived', 'disabled']])->findAll(); +``` + + ### Equals condition ```php diff --git a/lib/PicoDb/Table.php b/lib/PicoDb/Table.php index 9f6406d..0b24969 100644 --- a/lib/PicoDb/Table.php +++ b/lib/PicoDb/Table.php @@ -559,16 +559,29 @@ public function join($table, $foreign_column, $local_column, $local_table = '', * @param string $column1 * @param string $table2 * @param string $column2 + * @param array $conditions * @return $this */ - public function left($table1, $alias1, $column1, $table2, $column2) - { + public function left($table1, $alias1, $column1, $table2, $column2, $conditions = []) + { + $where = ''; + foreach ($conditions as $column => $value) { + if (is_array($value)) { + $where .= ' AND ' . $this->db->escapeIdentifier($alias1) . '.' . $this->db->escapeIdentifier($column) . ' IN (' . implode(',', array_fill(0, count($value), '?')) . ')'; + $this->joinValues = array_merge($this->joinValues, $value); + } else { + $where .= ' AND ' . $this->db->escapeIdentifier($alias1) . '.' . $this->db->escapeIdentifier($column) . ' = ?'; + $this->joinValues[] = $value; + } + } + $this->joins[] = sprintf( - 'LEFT JOIN %s AS %s ON %s=%s', + 'LEFT JOIN %s AS %s ON %s=%s%s', $this->db->escapeIdentifier($table1), $this->db->escapeIdentifier($alias1), $this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1), - $this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2) + $this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2), + $where ); return $this; @@ -583,16 +596,29 @@ public function left($table1, $alias1, $column1, $table2, $column2) * @param string $column1 * @param string $table2 * @param string $column2 + * @param array $conditions * @return $this */ - public function inner($table1, $alias1, $column1, $table2, $column2) - { + public function inner($table1, $alias1, $column1, $table2, $column2, array $conditions = []) + { + $where = ''; + foreach ($conditions as $column => $value) { + if (is_array($value)) { + $where .= ' AND ' . $this->db->escapeIdentifier($alias1) . '.' . $this->db->escapeIdentifier($column) . ' IN (' . implode(',', array_fill(0, count($value), '?')) . ')'; + $this->joinValues = array_merge($this->joinValues, $value); + } else { + $where .= ' AND ' . $this->db->escapeIdentifier($alias1) . '.' . $this->db->escapeIdentifier($column) . ' = ?'; + $this->joinValues[] = $value; + } + } + $this->joins[] = sprintf( - 'JOIN %s AS %s ON %s=%s', + 'JOIN %s AS %s ON %s=%s%s', $this->db->escapeIdentifier($table1), $this->db->escapeIdentifier($alias1), $this->db->escapeIdentifier($alias1).'.'.$this->db->escapeIdentifier($column1), - $this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2) + $this->db->escapeIdentifier($table2).'.'.$this->db->escapeIdentifier($column2), + $where ); return $this; diff --git a/tests/MysqlTableTest.php b/tests/MysqlTableTest.php index 1dc5223..e4fb461 100644 --- a/tests/MysqlTableTest.php +++ b/tests/MysqlTableTest.php @@ -508,6 +508,30 @@ public function testLeftJoin() ); } + public function testLeftJoinConditions() + { + $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (a INTEGER NOT NULL, foreign_key INTEGER NOT NULL)')); + $this->assertNotFalse($this->db->execute('CREATE TABLE test2 (id INTEGER NOT NULL, b INTEGER NOT NULL)')); + + $this->assertTrue($this->db->table('test2')->insert(array('id' => 42, 'b' => 2))); + $this->assertTrue($this->db->table('test1')->insert(array('a' => 18, 'foreign_key' => 42))); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 18])->findOne() + ); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => [18, 19]])->findOne() + ); + + + $this->assertNull( + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 19])->findOne() + ); + } + public function testJoinSubquery() { $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (id INTEGER NOT NULL, a INTEGER NOT NULL)')); diff --git a/tests/PostgresTableTest.php b/tests/PostgresTableTest.php index 0bc34d8..653afa5 100644 --- a/tests/PostgresTableTest.php +++ b/tests/PostgresTableTest.php @@ -499,6 +499,29 @@ public function testLeftJoin() ); } + public function testLeftJoinConditions() + { + $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (a INTEGER NOT NULL, foreign_key INTEGER NOT NULL)')); + $this->assertNotFalse($this->db->execute('CREATE TABLE test2 (id INTEGER NOT NULL, b INTEGER NOT NULL)')); + + $this->assertTrue($this->db->table('test2')->insert(array('id' => 42, 'b' => 2))); + $this->assertTrue($this->db->table('test1')->insert(array('a' => 18, 'foreign_key' => 42))); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 18])->findOne() + ); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => [18, 19]])->findOne() + ); + + $this->assertNull( + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 19])->findOne() + ); + } + public function testJoinSubquery() { $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (id INTEGER NOT NULL, a INTEGER NOT NULL)')); diff --git a/tests/SqliteTableTest.php b/tests/SqliteTableTest.php index 444228a..2d711ce 100644 --- a/tests/SqliteTableTest.php +++ b/tests/SqliteTableTest.php @@ -587,6 +587,29 @@ public function testLeftJoin() ); } + public function testLeftJoinConditions() + { + $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (a INTEGER NOT NULL, foreign_key INTEGER NOT NULL)')); + $this->assertNotFalse($this->db->execute('CREATE TABLE test2 (id INTEGER NOT NULL, b INTEGER NOT NULL)')); + + $this->assertTrue($this->db->table('test2')->insert(array('id' => 42, 'b' => 2))); + $this->assertTrue($this->db->table('test1')->insert(array('a' => 18, 'foreign_key' => 42))); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 18])->findOne() + ); + + $this->assertEquals( + array('a' => 18, 'b' => 2), + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => [18, 19]])->findOne() + ); + + $this->assertNull( + $this->db->table('test2')->columns('a', 'b')->eq('a', 18)->left('test1', 't1', 'foreign_key', 'test2', 'id', ['a' => 19])->findOne() + ); + } + public function testJoinSubquery() { $this->assertNotFalse($this->db->execute('CREATE TABLE test1 (id INTEGER NOT NULL, a INTEGER NOT NULL)'));