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; } }