Skip to content

Commit

Permalink
Merge branch 'feature/subqueries' of https://github.com/sebdesign/fra…
Browse files Browse the repository at this point in the history
…mework into sebdesign-feature/subqueries
  • Loading branch information
taylorotwell committed Oct 18, 2019
2 parents 19c2a10 + 6d06919 commit ab4910f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
43 changes: 23 additions & 20 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,7 @@ public function select($columns = ['*'])
$columns = is_array($columns) ? $columns : func_get_args();

foreach ($columns as $as => $column) {
if (is_string($as) && (
$column instanceof self ||
$column instanceof EloquentBuilder ||
$column instanceof Closure
)) {
if (is_string($as) && $this->isQueryable($column)) {
$this->selectSub($column, $as);
} else {
$this->columns[] = $column;
Expand Down Expand Up @@ -335,6 +331,8 @@ protected function createSub($query)
*
* @param mixed $query
* @return array
*
* @throws \InvalidArgumentException
*/
protected function parseSub($query)
{
Expand All @@ -343,10 +341,25 @@ protected function parseSub($query)
} elseif (is_string($query)) {
return [$query, []];
} else {
throw new InvalidArgumentException;
throw new InvalidArgumentException(
'The subquery must be an instance of Closure or Builder, or a string.'
);
}
}

/**
* Determine if the value is a query builder instance or a closure.
*
* @param mixed $value
* @return bool
*/
protected function isQueryable($value)
{
return $value instanceof self ||
$value instanceof EloquentBuilder ||
$value instanceof Closure;
}

/**
* Add a new select column to the query.
*
Expand All @@ -358,11 +371,7 @@ public function addSelect($column)
$columns = is_array($column) ? $column : func_get_args();

foreach ($columns as $as => $column) {
if (is_string($as) && (
$column instanceof self ||
$column instanceof EloquentBuilder ||
$column instanceof Closure
)) {
if (is_string($as) && $this->isQueryable($column)) {
if (is_null($this->columns)) {
$this->select($this->from.'.*');
}
Expand Down Expand Up @@ -403,9 +412,7 @@ public function distinct()
*/
public function from($table, $as = null)
{
if ($table instanceof self ||
$table instanceof EloquentBuilder ||
$table instanceof Closure) {
if ($this->isQueryable($table)) {
return $this->fromSub($table, $as);
}

Expand Down Expand Up @@ -890,9 +897,7 @@ public function whereIn($column, $values, $boolean = 'and', $not = false)
// If the value is a query builder instance we will assume the developer wants to
// look for any values that exists within this given query. So we will add the
// query accordingly so that this query is properly executed when it is run.
if ($values instanceof self ||
$values instanceof EloquentBuilder ||
$values instanceof Closure) {
if ($this->isQueryable($values)) {
[$query, $bindings] = $this->createSub($values);

$values = [new Expression($query)];
Expand Down Expand Up @@ -1806,9 +1811,7 @@ public function orHavingRaw($sql, array $bindings = [])
*/
public function orderBy($column, $direction = 'asc')
{
if ($column instanceof self ||
$column instanceof EloquentBuilder ||
$column instanceof Closure) {
if ($this->isQueryable($column)) {
[$query, $bindings] = $this->createSub($column);

$column = new Expression('('.$query.')');
Expand Down
31 changes: 31 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,10 @@ public function testJoinSub()
$expected .= 'inner join (select * from "contacts" where "name" = ?) as "sub2" on "users"."id" = "sub2"."user_id"';
$this->assertEquals($expected, $builder->toSql());
$this->assertEquals(['foo', 1, 'bar'], $builder->getRawBindings()['join']);

$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->from('users')->joinSub(['foo'], 'sub', 'users.id', '=', 'sub.id');
}

public function testJoinSubWithPrefix()
Expand All @@ -1673,13 +1677,21 @@ public function testLeftJoinSub()
$builder = $this->getBuilder();
$builder->from('users')->leftJoinSub($this->getBuilder()->from('contacts'), 'sub', 'users.id', '=', 'sub.id');
$this->assertSame('select * from "users" left join (select * from "contacts") as "sub" on "users"."id" = "sub"."id"', $builder->toSql());

$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->from('users')->leftJoinSub(['foo'], 'sub', 'users.id', '=', 'sub.id');
}

public function testRightJoinSub()
{
$builder = $this->getBuilder();
$builder->from('users')->rightJoinSub($this->getBuilder()->from('contacts'), 'sub', 'users.id', '=', 'sub.id');
$this->assertSame('select * from "users" right join (select * from "contacts") as "sub" on "users"."id" = "sub"."id"', $builder->toSql());

$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->from('users')->rightJoinSub(['foo'], 'sub', 'users.id', '=', 'sub.id');
}

public function testRawExpressionsInSelect()
Expand Down Expand Up @@ -1917,6 +1929,13 @@ function (Builder $query) {
$this->assertEquals(1, $result);
}

public function testInsertUsingInvalidSubquery()
{
$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->from('table1')->insertUsing(['foo'], ['bar']);
}

public function testInsertOrIgnoreMethod()
{
$this->expectException(RuntimeException::class);
Expand Down Expand Up @@ -2869,6 +2888,10 @@ public function testSubSelect()
$builder->selectSub($subBuilder, 'sub');
$this->assertEquals($expectedSql, $builder->toSql());
$this->assertEquals($expectedBindings, $builder->getBindings());

$this->expectException(InvalidArgumentException::class);
$builder = $this->getPostgresBuilder();
$builder->selectSub(['foo'], 'sub');
}

public function testSqlServerWhereDate()
Expand Down Expand Up @@ -3421,6 +3444,10 @@ public function testFromSub()
}, 'sessions')->where('bar', '<', '10');
$this->assertSame('select * from (select max(last_seen_at) as last_seen_at from "user_sessions" where "foo" = ?) as "sessions" where "bar" < ?', $builder->toSql());
$this->assertEquals(['1', '10'], $builder->getBindings());

$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->fromSub(['invalid'], 'sessions')->where('bar', '<', '10');
}

public function testFromSubWithPrefix()
Expand All @@ -3441,6 +3468,10 @@ public function testFromSubWithoutBindings()
$query->select(new Raw('max(last_seen_at) as last_seen_at'))->from('user_sessions');
}, 'sessions');
$this->assertSame('select * from (select max(last_seen_at) as last_seen_at from "user_sessions") as "sessions"', $builder->toSql());

$this->expectException(InvalidArgumentException::class);
$builder = $this->getBuilder();
$builder->fromSub(['invalid'], 'sessions');
}

public function testFromRaw()
Expand Down

0 comments on commit ab4910f

Please sign in to comment.