diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index c9f2c2f57430..310a92c01837 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -40,6 +40,7 @@ class Builder { */ protected $bindings = array( 'select' => [], + 'from' => [], 'join' => [], 'where' => [], 'having' => [], @@ -301,8 +302,15 @@ public function distinct() * @param string $table * @return $this */ - public function from($table) + public function from($table, $as = null) { + if ($table instanceof Builder) + { + $this->setBindings($table->getBindings(), 'from'); + + $table = new Expression('('.$table->toSql().') as '.$this->grammar->wrap($as ?: 'sub')); + } + $this->from = $table; return $this; @@ -888,7 +896,7 @@ public function orWhereNotNull($column) { return $this->whereNotNull($column, 'or'); } - + /** * Add a "where date" statement to the query. * @@ -902,7 +910,7 @@ public function whereDate($column, $operator, $value, $boolean = 'and') { return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean); } - + /** * Add a "where day" statement to the query. * @@ -1645,18 +1653,7 @@ public function avg($column) */ public function aggregate($function, $columns = array('*')) { - $this->aggregate = compact('function', 'columns'); - - $previousColumns = $this->columns; - - $results = $this->get($columns); - - // Once we have executed the query, we will reset the aggregate property so - // that more select queries can be executed against the database without - // the aggregate value getting in the way when the grammar builds it. - $this->aggregate = null; - - $this->columns = $previousColumns; + $results = $this->aggregateQuery($function, $columns)->get(); if (isset($results[0])) { @@ -1666,6 +1663,36 @@ public function aggregate($function, $columns = array('*')) } } + /** + * Create a query to compute an aggregate value. + * + * @param string $function + * @param array $columns + * @return mixed + */ + protected function aggregateQuery($function, $columns = array('*')) + { + $previousColumns = $this->columns; + + // If the developer specified a column to calculate the aggregate for, we will + // make sure it is also selected from within the subquery otherwise the data + // would not be available when the aggregate is actually being calculated. + if ($columns != array('*')) + { + $this->addSelect($columns); + + $this->columns = array_unique($this->columns); + } + + $query = $this->newQuery()->from($this); + + $query->aggregate = compact('function', 'columns'); + + $this->columns = $previousColumns; + + return $query; + } + /** * Insert a new record into the database. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 4da440e4384d..bf9f7b682d19 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -41,6 +41,7 @@ public function testBasicTableWrappingProtectsQuotationMarks() $this->assertEquals('select * from "some""table"', $builder->toSql()); } + public function testAliasWrappingAsWholeConstant() { $builder = $this->getBuilder(); @@ -48,6 +49,7 @@ public function testAliasWrappingAsWholeConstant() $this->assertEquals('select "x"."y" as "foo.bar" from "baz"', $builder->toSql()); } + public function testAddingSelects() { $builder = $this->getBuilder(); @@ -73,7 +75,6 @@ public function testBasicSelectDistinct() } - public function testBasicAlias() { $builder = $this->getBuilder(); @@ -624,6 +625,7 @@ public function testComplexJoin() $this->assertEquals(array('foo', 'bar'), $builder->getBindings()); } + public function testJoinWhereNull() { $builder = $this->getBuilder(); @@ -634,6 +636,7 @@ public function testJoinWhereNull() $this->assertEquals('select * from "users" inner join "contacts" on "users"."id" = "contacts"."id" and "contacts"."deleted_at" is null', $builder->toSql()); } + public function testRawExpressionsInSelect() { $builder = $this->getBuilder(); @@ -721,31 +724,31 @@ public function testPluckMethodReturnsSingleColumn() public function testAggregateFunctions() { $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from (select * from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); $results = $builder->from('users')->count(); $this->assertEquals(1, $results); $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from "users" limit 1', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from (select * from "users" limit 1) as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); $results = $builder->from('users')->exists(); $this->assertTrue($results); $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select max("id") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select max("id") as aggregate from (select "id" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); $results = $builder->from('users')->max('id'); $this->assertEquals(1, $results); $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select min("id") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select min("id") as aggregate from (select "id" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); $results = $builder->from('users')->min('id'); $this->assertEquals(1, $results); $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from (select "id" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); $results = $builder->from('users')->sum('id'); $this->assertEquals(1, $results); @@ -755,8 +758,8 @@ public function testAggregateFunctions() public function testAggregateResetFollowedByGet() { $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); - $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 2))); + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from (select "column1", "column2" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select sum("id") as aggregate from (select "column1", "column2", "id" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 2))); $builder->getConnection()->shouldReceive('select')->once()->with('select "column1", "column2" from "users"', array())->andReturn(array(array('column1' => 'foo', 'column2' => 'bar'))); $builder->getProcessor()->shouldReceive('processSelect')->andReturnUsing(function($builder, $results) { return $results; }); $builder->from('users')->select('column1', 'column2'); @@ -772,13 +775,13 @@ public function testAggregateResetFollowedByGet() public function testAggregateResetFollowedBySelectGet() { $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count("column1") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); - $builder->getConnection()->shouldReceive('select')->once()->with('select "column2", "column3" from "users"', array())->andReturn(array(array('column2' => 'foo', 'column3' => 'bar'))); + $builder->getConnection()->shouldReceive('select')->once()->with('select count("column1") as aggregate from (select "column2", "column3", "column1" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select "column2", "column3", "column1" from "users"', array())->andReturn(array(array('column2' => 'foo', 'column3' => 'bar'))); $builder->getProcessor()->shouldReceive('processSelect')->andReturnUsing(function($builder, $results) { return $results; }); - $builder->from('users'); + $builder->from('users')->select('column2', 'column3', 'column1'); $count = $builder->count('column1'); $this->assertEquals(1, $count); - $result = $builder->select('column2', 'column3')->get(); + $result = $builder->get(); $this->assertEquals(array(array('column2' => 'foo', 'column3' => 'bar')), $result); } @@ -786,7 +789,7 @@ public function testAggregateResetFollowedBySelectGet() public function testAggregateResetFollowedByGetWithColumns() { $builder = $this->getBuilder(); - $builder->getConnection()->shouldReceive('select')->once()->with('select count("column1") as aggregate from "users"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getConnection()->shouldReceive('select')->once()->with('select count("column1") as aggregate from (select "column1" from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); $builder->getConnection()->shouldReceive('select')->once()->with('select "column2", "column3" from "users"', array())->andReturn(array(array('column2' => 'foo', 'column3' => 'bar'))); $builder->getProcessor()->shouldReceive('processSelect')->andReturnUsing(function($builder, $results) { return $results; }); $builder->from('users'); @@ -797,6 +800,16 @@ public function testAggregateResetFollowedByGetWithColumns() } + public function testAggregateWithDistict() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from (select distinct * from "users") as "sub"', array())->andReturn(array(array('aggregate' => 1))); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function($builder, $results) { return $results; }); + $results = $builder->from('users')->distinct()->count(); + $this->assertEquals(1, $results); + } + + public function testInsertMethod() { $builder = $this->getBuilder(); @@ -1184,6 +1197,21 @@ public function testSubSelect() } + public function testSelectFromQuery() + { + $expectedSql = 'select "foo", "bar" from (select "baz" from "two" where "subkey" = ?) as "sub" where "key" = ?'; + $expectedBindings = ['subval', 'val']; + + $table = $this->getPostgresBuilder(); + $table->from('two')->select('baz')->where('subkey', '=', 'subval'); + + $builder = $this->getPostgresBuilder(); + $builder->from($table, 'sub')->select(['foo', 'bar'])->where('key', '=', 'val'); + $this->assertEquals($expectedSql, $builder->toSql()); + $this->assertEquals($expectedBindings, $builder->getBindings()); + } + + protected function getBuilder() { $grammar = new Illuminate\Database\Query\Grammars\Grammar;