laravel-performance/app/Database/Query/Grammars/PostgresGrammar.php

181 lines
5.4 KiB
PHP

<?php
namespace App\Database\Query\Grammars;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\PostgresGrammar as BasePostgresGrammar;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class PostgresGrammar extends BasePostgresGrammar
{
public function getTableOrAlias(Builder $query)
{
return last(preg_split('/\s+as\s+/i', $query->from));
}
public function getTableName(Builder $query)
{
return current(preg_split('/\s+as\s+/i', $query->from));
}
/**
* @param array $items
*
* Ex:
* update transactions as t
* set
* date = c.date,
* value = c.value
* from ( values
* (1, '2023-01-05 00:00:00'::timestamp, 23.44),
* (2, '2023-01-03 00:00:00'::timestamp, 23.44)
* ) as c (id, date, value)
* where t.id = c.id";
* @throws \Exception
*/
public function compileMassUpdateWithFrom(Builder $query, array $items, string $uniqueBy = 'id')
{
if (count($items) === 0) {
throw new \Exception('values is empty');
}
$table = $this->wrapTable($query->from);
$tableName = $this->getTableName($query);
$tableOrAlias = $this->getTableOrAlias($query);
$tableAuxName = 'mu';
$properties = array_keys($items[0]);
$columnTypes = $this->getColumnTypes($tableName, $properties);
$columnsSql = [
// $this->wrap('type') . ' = ("mu"."id"::int + "t"."type" + 2)::int',
// $this->wrap('status') . ' = (case "mu"."id"::int when 1 then \'paid\' end)::text',
];
foreach ($properties as $property) {
if ($property !== $uniqueBy) {
$columnsSql[] = $this->wrap($property) . ' = ' . $this->wrap("$tableAuxName.$property") . '::' . $columnTypes[$property];
}
}
$columns = implode(', ', $columnsSql);
$parameters = [];
foreach ($items as $item) {
if (!isset($item[$uniqueBy])) {
throw new \Exception('column \'' . $uniqueBy . '\' not found');
}
$parametersItem = [];
foreach ($properties as $property) {
$parametersItem[] = $this->parameter($item[$property]);
}
$parameters[] = '(' . implode(', ', $parametersItem) . ')';
}
$parameters = implode(', ', $parameters);
$parametersColumns = implode(', ', $properties);
$from = " from (values $parameters) as $tableAuxName ($parametersColumns)";
$query->where("$tableOrAlias.$uniqueBy", '=', $query->raw($this->wrap("$tableAuxName.$uniqueBy") . '::' . $columnTypes[$uniqueBy]));
$where = $this->compileWheres($query);
return "update {$table} set {$columns}{$from} {$where}";
}
public function prepareBindingsForMassUpdateWithFrom(array $bindings, array $items)
{
$properties = array_keys($items[0]);
$values = [];
foreach ($items as $item) {
foreach ($properties as $property) {
$values[] = $item[$property];
}
}
$values = collect($values)
->map(function ($value, $column) {
return is_array($value) || ($this->isJsonSelector($column) && !$this->isExpression($value))
? json_encode($value)
: $value;
})
->all();
$cleanBindings = Arr::except($bindings, 'select');
return array_values(
array_merge($values, Arr::flatten($cleanBindings))
);
}
private function getColumnTypes(string $tableName, array $columns): array
{
$key = vsprintf('column;types;%s;%s', [
$tableName,
implode(';', $columns)
]);
return Cache::store('array')->rememberForever($key, function () use ($tableName, $columns) {
$schemaColumns = DB::table('information_schema.columns')
->select([
'column_name',
'udt_name',
])
->where('table_name', '=', $tableName)
->whereIn('column_name', $columns)
->get();
$types = [];
foreach ($schemaColumns as $schemaColumn) {
$types[$schemaColumn->column_name] = $schemaColumn->udt_name;
}
return $types;
});
}
public function compileJoinFrom(Builder $query, array $items, $tableAlias)
{
if (count($items) === 0) {
throw new \Exception('items is empty');
}
$values = [];
$properties = array_keys($items[0]);
foreach ($items as $item) {
$valuesItem = [];
foreach ($properties as $property) {
$valuesItem[] = $this->parameter($item[$property]);
}
$values[] = '(' . implode(', ', $valuesItem) . ')';
}
$values = implode(', ', $values);
$properties = implode(', ', $properties);
return "(values $values) as $tableAlias ($properties)";
}
public function prepareBindingsJoinFrom(array $items)
{
$values = [];
$properties = array_keys($items[0]);
foreach ($items as $item) {
foreach ($properties as $property) {
$values[] = $item[$property];
}
}
return $values;
}
}