diff --git a/src/Connection.php b/src/Connection.php index 41cdb87f..c231246c 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -280,7 +280,7 @@ public function cursorWithOptions(string $query, array $bindings, array $options /** * @inheritDoc */ - public function statement($query, $bindings = []): bool + public function statement($query, $bindings = [], array $types = []): bool { // is SELECT query if (0 === stripos(ltrim($query), 'select')) { @@ -291,7 +291,7 @@ public function statement($query, $bindings = []): bool if (0 === stripos(ltrim($query), 'insert') || 0 === stripos(ltrim($query), 'update') || 0 === stripos(ltrim($query), 'delete')) { - return $this->affectingStatement($query, $bindings) !== null; + return $this->affectingStatement($query, $bindings, $types) !== null; } // is DDL Query @@ -301,11 +301,27 @@ public function statement($query, $bindings = []): bool /** * @inheritDoc */ - public function affectingStatement($query, $bindings = []): int + public function insert($query, $bindings = [], array $types = []): bool + { + return $this->statement($query, $bindings, $types); + } + + /** + * @inheritDoc + */ + public function update($query, $bindings = [], array $types = []): int + { + return $this->affectingStatement($query, $bindings, $types); + } + + /** + * @inheritDoc + */ + public function affectingStatement($query, $bindings = [], array $types = []): int { /** @var Closure(): int $runQueryCall */ - $runQueryCall = function () use ($query, $bindings) { - return $this->run($query, $bindings, function ($query, $bindings) { + $runQueryCall = function () use ($query, $bindings, $types) { + return $this->run($query, $bindings, function ($query, $bindings) use ($types) { if ($this->pretending()) { return 0; } @@ -316,7 +332,10 @@ public function affectingStatement($query, $bindings = []): int throw new RuntimeException('Tried to run update outside of transaction! Affecting statements must be done inside a transaction'); } - $rowCount = $transaction->executeUpdate($query, ['parameters' => $this->prepareBindings($bindings)]); + $rowCount = $transaction->executeUpdate($query, [ + 'parameters' => $this->prepareBindings($bindings), + 'types' => $types, + ]); $this->recordsHaveBeenModified($rowCount > 0); diff --git a/src/Eloquent/Model.php b/src/Eloquent/Model.php index 5cc79542..d25450ec 100644 --- a/src/Eloquent/Model.php +++ b/src/Eloquent/Model.php @@ -17,6 +17,7 @@ namespace Colopl\Spanner\Eloquent; +use Colopl\Spanner\Query\Builder; use Illuminate\Database\Eloquent\Model as BaseModel; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Str; @@ -39,6 +40,24 @@ class Model extends BaseModel */ public $incrementing = false; + /** + * @var string[] + */ + protected $types = []; + + /** + * @inheritDoc + */ + public function newModelQuery() + { + /** @var Builder */ + $baseQueryBuilder = $this->newBaseQueryBuilder(); + + return $this->newEloquentBuilder( + $baseQueryBuilder->setTypes($this->types) + )->setModel($this); + } + /** * @param BaseModel|Relation $query * @param mixed $value diff --git a/src/Query/Builder.php b/src/Query/Builder.php index b7488832..f887f000 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -34,12 +34,95 @@ class Builder extends BaseBuilder */ public $connection; + /** + * @var string[] + */ + protected $types = []; + + /** + * @param string[] $types + * @return $this + */ + public function setTypes($types) + { + $this->types = $types; + return $this; + } + /** * @inheritDoc */ public function insert(array $values) { - return parent::insert($this->prepareInsertForDml($values)); + $values = $this->prepareInsertForDml($values); + + if (empty($values)) + return true; + + if (! is_array(reset($values))) { + $values = [$values]; + } else { + foreach ($values as $key => $value) { + /** @var array $value */ + ksort($value); + $values[$key] = $value; + } + } + + $sql = $this->grammar->compileInsert($this, $values); + + // Detect spanner types from values and create a binding compatible array of types + $types = []; + $i = 0; + foreach ($values as $key => $value) { + /** @var array $value */ + foreach ($value as $k => $v) { + $type = $this->checkForType($i, $k, $v, $sql); + if(count($type)) $types[$type[0]] = $type[1]; + $i++; + } + } + + $this->applyBeforeQueryCallbacks(); + + return $this->connection->insert($sql, $this->cleanBindings(Arr::flatten($values, 1)), $types); + } + + public function update(array $values) + { + $this->applyBeforeQueryCallbacks(); + + $sql = $this->grammar->compileUpdate($this, $values); + + // Detect spanner types from values and create a binding compatible array of types + $types = []; + $i = 0; + foreach ($values as $key => $value) { + $type = $this->checkForType($i, $key, $value, $sql); + if(count($type)) $types[$type[0]] = $type[1]; + $i++; + } + + return $this->connection->update($sql, $this->cleanBindings( + $this->grammar->prepareBindingsForUpdate($this->bindings, $values) + ), $types); + } + + /** + * @return string[] + */ + public function checkForType(int $i, string|int $key, mixed $value, string $sql = '') + { + if(!array_key_exists($key, $this->types)) return []; + if($value == null) return []; + + if (is_array($value)) { + /** @var array $value */ + if(!count($value)) return []; + } + if (is_string($value) && Parameterizer::hasLikeWildcard($sql, $value)) return []; + + return ["p$i", $this->types[$key]]; } /** diff --git a/src/Query/Parameterizer.php b/src/Query/Parameterizer.php index 7b4e6957..ed293c2a 100644 --- a/src/Query/Parameterizer.php +++ b/src/Query/Parameterizer.php @@ -72,7 +72,7 @@ public function parameterizeQuery(string $query, array $bindings): array * @param string $value * @return bool */ - private static function hasLikeWildcard(string $query, string $value) + public static function hasLikeWildcard(string $query, string $value) { return Str::contains(strtolower($query), 'like') && Str::contains($value, ['%', '_'])