From cc9da4fd7d561479cb6909012e7fa248dcb5d726 Mon Sep 17 00:00:00 2001 From: Ville Virta Date: Tue, 22 May 2018 20:54:04 +0200 Subject: [PATCH] Ability to check exists through tables with "through" key. Checking exists changed to join, since it's insanely much faster (from tens or hundreds of seconds to milliseconds). Checking not exists is as before. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Through works with unlimited number of pivot tables. Example: 'join2through' => array(             'from_table'      => 'master2',             'from_col'        => 'm2_col',             'to_table'        => 'subtable2',             'to_col'          => 's2_col',             'to_value_column' => 's2_value',             'not_exists'      => true,             'through' => array(             'from_table'      => 'subtable2',             'from_col'        => 's2_col',             'to_table'        => 'subtable3',             'to_col'          => 's3_col',             'to_value_column' => 's3_value',             )             ), Keep adding "through" if there's more tables in between. --- .../JoinSupportingQueryBuilderParser.php | 99 ++++++++++++++----- .../JoinSupportingQueryBuilderParserTest.php | 28 ++++++ 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/src/QueryBuilderParser/JoinSupportingQueryBuilderParser.php b/src/QueryBuilderParser/JoinSupportingQueryBuilderParser.php index 9d15d36..4664d77 100644 --- a/src/QueryBuilderParser/JoinSupportingQueryBuilderParser.php +++ b/src/QueryBuilderParser/JoinSupportingQueryBuilderParser.php @@ -95,28 +95,51 @@ private function buildSubclauseQuery($query, $rule, $value, $condition) $not = array_key_exists('not_exists', $subclause) && $subclause['not_exists']; - // Create a where exists clause to join to the other table, and find results matching the criteria - $query = $query->whereExists( - /** - * @param Builder $query - */ - function(Builder $query) use ($subclause) { - - $q = $query->selectRaw(1) - ->from($subclause['to_table']) - ->whereRaw($subclause['to_table'].'.'.$subclause['to_col'] - .' = ' - .$subclause['from_table'].'.'.$subclause['from_col']); - - if (array_key_exists('to_clause', $subclause)) { - $q->where($subclause['to_clause']); - } - - $this->buildSubclauseInnerQuery($subclause, $q); - }, - $condition, - $not - ); + if ( $not ) { + // Create a where exists clause to join to the other table, and find results matching the criteria + $query = $query->whereExists( + /** + * @param Builder $query + */ + function(Builder $query) use ($subclause, $not, $condition) { + $q = $query->selectRaw(1) + ->from($subclause['to_table']) + ->whereRaw($subclause['to_table'].'.'.$subclause['to_col'] + .' = ' + .$subclause['from_table'].'.'.$subclause['from_col']); + if ( ! isset( $subclause["through"] ) ) { + if ( array_key_exists( 'to_clause', $subclause ) ) { + $q->where( $subclause['to_clause'] ); + } + $this->buildSubclauseInnerQuery( $subclause, $q ); + } else { + $this->buildSubclauseThroughQuery( $subclause, $not, $condition, $q ); + } + + }, + $condition, + $not + ); + } else { + // Create a join clause to join to the other table, and find results matching the criteria + + $query = $query->join( $subclause["to_table"], $subclause['to_table'] . '.' . $subclause['to_col'], '=', $subclause['from_table'] . '.' . $subclause['from_col'] ); + $first_from_col = $subclause['from_col']; + $first_from = $subclause['from_table']; + //Loop through the 'through' key to access through multiple tables + while ( isset( $subclause['through'] ) ) { + $subclause["through"]["require_array"] = $subclause["require_array"]; + $subclause["through"]["operator"] = $subclause["operator"]; + $subclause["through"]["value"] = $subclause["value"]; + $subclause = $subclause["through"]; + $query = $query->join( $subclause["to_table"], $subclause['to_table'] . '.' . $subclause['to_col'], '=', $subclause['from_table'] . '.' . $subclause['from_col'] ); + } + if (array_key_exists('to_clause', $subclause)) { + $query->where($subclause['to_clause']); + } + $this->buildSubclauseInnerQuery( $subclause, $query ); + $query->groupBy( $first_from.".".$first_from_col ); + } return $query; } @@ -206,4 +229,36 @@ private function buildSubclauseWithNull($subclause, Builder $query, $isNotNull = return $query->whereNull($subclause['to_value_column']); } + private function buildSubclauseThroughQuery($subclause, $not, $condition, Builder $q) { + $subclause["through"]["require_array"] = $subclause["require_array"]; + $subclause["through"]["operator"] = $subclause["operator"]; + $subclause["through"]["value"] = $subclause["value"]; + $subclause = $subclause["through"]; + + $q->whereExists( + function ( \Illuminate\Database\Query\Builder $query ) use ( $subclause, $not, $condition, $q ) { + + $q = $query->selectRaw( 1 ) + ->from( $subclause['to_table'] ) + ->whereRaw( $subclause['to_table'] . '.' . $subclause['to_col'] + . ' = ' + . $subclause['from_table'] . '.' . $subclause['from_col'] ); + + + + if ( ! isset( $subclause["through"] ) ) { + if ( array_key_exists( 'to_clause', $subclause ) ) { + $q->where( $subclause['to_clause'] ); + } + $this->buildSubclauseInnerQuery( $subclause, $q ); + } else { + $this->buildSubclauseThroughQuery( $subclause, $not, $condition, $q ); + } + }, + $condition, + $not + ); + return $q; + } + } diff --git a/tests/JoinSupportingQueryBuilderParserTest.php b/tests/JoinSupportingQueryBuilderParserTest.php index 6acfd77..a23d0cd 100644 --- a/tests/JoinSupportingQueryBuilderParserTest.php +++ b/tests/JoinSupportingQueryBuilderParserTest.php @@ -37,6 +37,21 @@ private function getJoinFields() 'to_value_column' => 's_value', 'to_clause' => array('othercol' => 'value'), ), + 'join2through' => array( + 'from_table' => 'master2', + 'from_col' => 'm2_col', + 'to_table' => 'subtable2', + 'to_col' => 's2_col', + 'to_value_column' => 's2_value', + 'not_exists' => true, + 'through' => array( + 'from_table' => 'subtable2', + 'from_col' => 's2_col', + 'to_table' => 'subtable3', + 'to_col' => 's3_col', + 'to_value_column' => 's3_value', + ) + ), ); } @@ -328,4 +343,17 @@ public function testDateNotBetween() $this->assertEquals(22, $bindings[0]->day); $this->assertEquals(28, $bindings[1]->day); } + + public function testJoinNotExistsInThrough() + { + $json = '{"condition":"AND","rules":[{"id":"join2through","field":"join2through","type":"text","input":"select","operator":"in","value":["a","b"]}]}'; + + $builder = $this->createQueryBuilder(); + + $parser = $this->getParserUnderTest(); + $parser->parse($json, $builder); + + $this->assertEquals('select * where not exists (select 1 from `subtable2` where subtable2.s2_col = master2.m2_col and not exists (select 1 from `subtable3` where subtable3.s3_col = subtable2.s2_col and `s3_value` in (?, ?)))', + $builder->toSql()); + } }