Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[12.x] Query builder PDO fetch modes #54443

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
19 changes: 11 additions & 8 deletions src/Illuminate/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,11 +391,12 @@ public function selectFromWriteConnection($query, $bindings = [])
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @param array $fetchArgs
* @return array
*/
public function select($query, $bindings = [], $useReadPdo = true)
public function select($query, $bindings = [], $useReadPdo = true, array $fetchArgs = [])
{
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo, $fetchArgs) {
if ($this->pretending()) {
return [];
}
Expand All @@ -411,7 +412,7 @@ public function select($query, $bindings = [], $useReadPdo = true)

$statement->execute();

return $statement->fetchAll();
return $statement->fetchAll(...$fetchArgs);
});
}

Expand All @@ -421,11 +422,12 @@ public function select($query, $bindings = [], $useReadPdo = true)
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @param array $fetchArgs
* @return array
*/
public function selectResultSets($query, $bindings = [], $useReadPdo = true)
public function selectResultSets($query, $bindings = [], $useReadPdo = true, array $fetchArgs = [])
{
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo, $fetchArgs) {
if ($this->pretending()) {
return [];
}
Expand All @@ -441,7 +443,7 @@ public function selectResultSets($query, $bindings = [], $useReadPdo = true)
$sets = [];

do {
$sets[] = $statement->fetchAll();
$sets[] = $statement->fetchAll(...$fetchArgs);
} while ($statement->nextRowset());

return $sets;
Expand All @@ -454,9 +456,10 @@ public function selectResultSets($query, $bindings = [], $useReadPdo = true)
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @param array $fetchArgs
* @return \Generator<int, \stdClass>
*/
public function cursor($query, $bindings = [], $useReadPdo = true)
public function cursor($query, $bindings = [], $useReadPdo = true, array $fetchArgs = [])
{
$statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
Expand All @@ -481,7 +484,7 @@ public function cursor($query, $bindings = [], $useReadPdo = true)
return $statement;
});

while ($record = $statement->fetch()) {
while ($record = $statement->fetch(...$fetchArgs)) {
yield $record;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Illuminate/Database/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,21 @@ public function scalar($query, $bindings = [], $useReadPdo = true);
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @param array $fetchArgs
* @return array
*/
public function select($query, $bindings = [], $useReadPdo = true);
public function select($query, $bindings = [], $useReadPdo = true, array $fetchArgs = []);

/**
* Run a select statement against the database and returns a generator.
*
* @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @param array $fetchArgs
* @return \Generator
*/
public function cursor($query, $bindings = [], $useReadPdo = true);
public function cursor($query, $bindings = [], $useReadPdo = true, array $fetchArgs = []);

/**
* Run an insert statement against the database.
Expand Down
162 changes: 39 additions & 123 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use LogicException;
use PDO;
use RuntimeException;
use UnitEnum;

Expand Down Expand Up @@ -248,6 +249,13 @@ class Builder implements BuilderContract
*/
public $useWritePdo = false;

/**
* Custom arguments for the PDOStatement::fetchAll/fetch functions.
*
* @var array
*/
public array $fetchArgs = [];

/**
* Create a new query builder instance.
*
Expand Down Expand Up @@ -3114,9 +3122,14 @@ public function soleValue($column)
*/
public function get($columns = ['*'])
{
$items = new Collection($this->onceWithColumns(Arr::wrap($columns), function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
$original = $this->columns;

$this->columns ??= Arr::wrap($columns);

$items = new Collection($this->processor->processSelect($this, $this->runSelect()));

// Revert to original columns so future queries can use the previous selection.
$this->columns = $original;

return $this->applyAfterQueryCallbacks(
isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
Expand All @@ -3125,13 +3138,12 @@ public function get($columns = ['*'])

/**
* Run the query as a "select" statement against the connection.
*
* @return array
*/
protected function runSelect()
{
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
$this->toSql(), $this->getBindings(), ! $this->useWritePdo, $this->fetchArgs
);
}

Expand Down Expand Up @@ -3375,110 +3387,27 @@ protected function enforceOrderBy()
* Get a collection instance containing the values of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @param string|null $key
* @param \Illuminate\Contracts\Database\Query\Expression|string|null $key
* @return \Illuminate\Support\Collection<array-key, mixed>
*/
public function pluck($column, $key = null)
{
// First, we will need to select the results of the query accounting for the
// given columns / key. Once we have the results, we will be able to take
// the results and get the exact data that was requested for the query.
$queryResult = $this->onceWithColumns(
is_null($key) ? [$column] : [$column, $key],
function () {
return $this->processor->processSelect(
$this, $this->runSelect()
);
}
);

if (empty($queryResult)) {
return new Collection;
}

// If the columns are qualified with a table or have an alias, we cannot use
// those directly in the "pluck" operations since the results from the DB
// are only keyed by the column itself. We'll strip the table out here.
$column = $this->stripTableForPluck($column);

$key = $this->stripTableForPluck($key);

return $this->applyAfterQueryCallbacks(
is_array($queryResult[0])
? $this->pluckFromArrayColumn($queryResult, $column, $key)
: $this->pluckFromObjectColumn($queryResult, $column, $key)
);
}

/**
* Strip off the table name or alias from a column identifier.
*
* @param string $column
* @return string|null
*/
protected function stripTableForPluck($column)
{
if (is_null($column)) {
return $column;
}

$columnString = $column instanceof ExpressionContract
? $this->grammar->getValue($column)
: $column;

$separator = str_contains(strtolower($columnString), ' as ') ? ' as ' : '\.';

return last(preg_split('~'.$separator.'~i', $columnString));
}

/**
* Retrieve column values from rows represented as objects.
*
* @param array $queryResult
* @param string $column
* @param string $key
* @return \Illuminate\Support\Collection
*/
protected function pluckFromObjectColumn($queryResult, $column, $key)
{
$results = [];
$original = [$this->columns, $this->fetchArgs];

if (is_null($key)) {
foreach ($queryResult as $row) {
$results[] = $row->$column;
}
if(is_null($key)) {
$this->columns = [$column];
$this->fetchArgs = [PDO::FETCH_COLUMN];
} else {
foreach ($queryResult as $row) {
$results[$row->$key] = $row->$column;
}
$this->columns = [$key, $column];
$this->fetchArgs = [PDO::FETCH_KEY_PAIR];
}

return new Collection($results);
}

/**
* Retrieve column values from rows represented as arrays.
*
* @param array $queryResult
* @param string $column
* @param string $key
* @return \Illuminate\Support\Collection
*/
protected function pluckFromArrayColumn($queryResult, $column, $key)
{
$results = [];
$queryResult = $this->processor->processSelect($this, $this->runSelect());

if (is_null($key)) {
foreach ($queryResult as $row) {
$results[] = $row[$column];
}
} else {
foreach ($queryResult as $row) {
$results[$row[$key]] = $row[$column];
}
}
// Revert to original values so future queries can use the previous selection.
[$this->columns, $this->fetchArgs] = $original;

return new Collection($results);
return $this->applyAfterQueryCallbacks(new Collection($queryResult));
}

/**
Expand Down Expand Up @@ -3686,30 +3615,6 @@ protected function setAggregate($function, $columns)
return $this;
}

/**
* Execute the given callback while selecting the given columns.
*
* After running the callback, the columns are reset to the original value.
*
* @param array $columns
* @param callable $callback
* @return mixed
*/
protected function onceWithColumns($columns, $callback)
{
$original = $this->columns;

if (is_null($original)) {
$this->columns = $columns;
}

$result = $callback();

$this->columns = $original;

return $result;
}

/**
* Insert new records into the database.
*
Expand Down Expand Up @@ -4338,6 +4243,17 @@ public function useWritePdo()
return $this;
}

/**
* Set arguments for the PDOStatement::fetchAll/fetch functions.
* @param mixed ...$fetchArgs
* @return $this
*/
public function fetchArgs(...$fetchArgs): static
{
$this->fetchArgs = $fetchArgs;
return $this;
}

/**
* Determine if the value is a query builder instance or a Closure.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function testCreateOrFirstMethodAssociatesExistingRelated(): void

$source->getConnection()
->expects('select')
->with('select * from "related_table" where ("attr" = ?) limit 1', ['foo'], true)
->with('select * from "related_table" where ("attr" = ?) limit 1', ['foo'], true, [])
->andReturn([[
'id' => 456,
'attr' => 'foo',
Expand Down Expand Up @@ -128,6 +128,7 @@ public function testFirstOrCreateMethodRetrievesExistingRelatedAlreadyAssociated
'select "related_table".*, "pivot_table"."source_id" as "pivot_source_id", "pivot_table"."related_id" as "pivot_related_id" from "related_table" inner join "pivot_table" on "related_table"."id" = "pivot_table"."related_id" where "pivot_table"."source_id" = ? and ("attr" = ?) limit 1',
[123, 'foo'],
true,
[],
)
->andReturn([[
'id' => 456,
Expand Down Expand Up @@ -176,7 +177,7 @@ public function testCreateOrFirstMethodRetrievesExistingRelatedAssociatedJustNow

$source->getConnection()
->expects('select')
->with('select * from "related_table" where ("attr" = ?) limit 1', ['foo'], true)
->with('select * from "related_table" where ("attr" = ?) limit 1', ['foo'], true, [])
->andReturn([[
'id' => 456,
'attr' => 'foo',
Expand All @@ -199,6 +200,7 @@ public function testCreateOrFirstMethodRetrievesExistingRelatedAssociatedJustNow
'select "related_table".*, "pivot_table"."source_id" as "pivot_source_id", "pivot_table"."related_id" as "pivot_related_id" from "related_table" inner join "pivot_table" on "related_table"."id" = "pivot_table"."related_id" where "pivot_table"."source_id" = ? and ("attr" = ?) limit 1',
[123, 'foo'],
false,
[],
)
->andReturn([[
'id' => 456,
Expand Down Expand Up @@ -243,6 +245,7 @@ public function testFirstOrCreateMethodRetrievesExistingRelatedAndAssociatesIt()
'select "related_table".*, "pivot_table"."source_id" as "pivot_source_id", "pivot_table"."related_id" as "pivot_related_id" from "related_table" inner join "pivot_table" on "related_table"."id" = "pivot_table"."related_id" where "pivot_table"."source_id" = ? and ("attr" = ?) limit 1',
[123, 'foo'],
true,
[],
)
->andReturn([]);

Expand All @@ -252,6 +255,7 @@ public function testFirstOrCreateMethodRetrievesExistingRelatedAndAssociatesIt()
'select * from "related_table" where ("attr" = ?) limit 1',
['foo'],
true,
[],
)
->andReturn([[
'id' => 456,
Expand Down Expand Up @@ -326,6 +330,7 @@ protected function newBelongsToMany(Builder $query, Model $parent, $table, $fore
'select "related_table".*, "pivot_table"."source_id" as "pivot_source_id", "pivot_table"."related_id" as "pivot_related_id" from "related_table" inner join "pivot_table" on "related_table"."id" = "pivot_table"."related_id" where "pivot_table"."source_id" = ? and ("attr" = ?) limit 1',
[123, 'foo'],
true,
[],
)
->andReturn([]);

Expand All @@ -335,6 +340,7 @@ protected function newBelongsToMany(Builder $query, Model $parent, $table, $fore
'select * from "related_table" where ("attr" = ?) limit 1',
['foo'],
true,
[],
)
->andReturn([]);

Expand Down
Loading