Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion packages/database/src/Builder/QueryBuilders/BuildsQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,49 @@

namespace Tempest\Database\Builder\QueryBuilders;

use Tempest\Database\Builder\ModelInspector;
use Tempest\Database\Query;

/**
* @template TModel
*/
interface BuildsQuery
{
/**
* The current bindings for this query builder.
*
* @return array<mixed>
*/
public array $bindings {
get;
}

/**
* The model inspector for this query builder.
*/
public ModelInspector $model {
get;
}

/**
* Creates a {@see Query} instance with the specified optional bindings.
*
* ### Example
* ```php
* $builder->build(id: $id);
* ```
*/
public function build(mixed ...$bindings): Query;

/** @return self<TModel> */
/**
* Registers the specified bindings for this query.
*
* ### Example
* ```php
* $builder->bind(id: $id);
* ```
*
* @return self<TModel>
*/
public function bind(mixed ...$bindings): self;
}
43 changes: 29 additions & 14 deletions packages/database/src/Builder/QueryBuilders/CountQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Tempest\Database\OnDatabase;
use Tempest\Database\Query;
use Tempest\Database\QueryStatements\CountStatement;
use Tempest\Database\QueryStatements\HasWhereStatements;
use Tempest\Support\Arr\ImmutableArray;
use Tempest\Support\Conditions\HasConditions;
use Tempest\Support\Str\ImmutableString;

Expand All @@ -18,17 +18,22 @@
/**
* @template TModel of object
* @implements \Tempest\Database\Builder\QueryBuilders\BuildsQuery<TModel>
* @implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereStatements<TModel>
* @use \Tempest\Database\Builder\QueryBuilders\HasWhereQueryBuilderMethods<TModel>
*/
final class CountQueryBuilder implements BuildsQuery
final class CountQueryBuilder implements BuildsQuery, SupportsWhereStatements
{
use HasConditions, OnDatabase, HasWhereQueryBuilderMethods, TransformsQueryBuilder;

private CountStatement $count;

private array $bindings = [];
public ModelInspector $model;

private ModelInspector $model;
public array $bindings = [];

public ImmutableArray $wheres {
get => $this->count->where;
}

/**
* @param class-string<TModel>|string|TModel $model
Expand All @@ -43,6 +48,26 @@ public function __construct(string|object $model, ?string $column = null)
);
}

/**
* Creates an instance from another query builder, inheriting conditions and bindings.
*
* @template TSourceModel of object
* @param (BuildsQuery<TSourceModel>&SupportsWhereStatements<TSourceModel>) $source
* @param string|null $column
* @return CountQueryBuilder<TSourceModel>
*/
public static function fromQueryBuilder(BuildsQuery&SupportsWhereStatements $source, ?string $column = null): CountQueryBuilder
{
$builder = new self($source->model->model, $column);
$builder->bind(...$source->bindings);

foreach ($source->wheres as $where) {
$builder->wheres[] = $where;
}

return $builder;
}

/**
* Executes the count query and returns the number of matching records.
*/
Expand Down Expand Up @@ -99,14 +124,4 @@ public function build(mixed ...$bindings): Query
{
return new Query($this->count, [...$this->bindings, ...$bindings])->onDatabase($this->onDatabase);
}

private function getStatementForWhere(): HasWhereStatements
{
return $this->count;
}

private function getModel(): ModelInspector
{
return $this->model;
}
}
42 changes: 28 additions & 14 deletions packages/database/src/Builder/QueryBuilders/DeleteQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Tempest\Database\OnDatabase;
use Tempest\Database\Query;
use Tempest\Database\QueryStatements\DeleteStatement;
use Tempest\Database\QueryStatements\HasWhereStatements;
use Tempest\Support\Arr\ImmutableArray;
use Tempest\Support\Conditions\HasConditions;
use Tempest\Support\Str\ImmutableString;

Expand All @@ -15,17 +15,22 @@
/**
* @template TModel of object
* @implements \Tempest\Database\Builder\QueryBuilders\BuildsQuery<TModel>
* @implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereStatements<TModel>
* @use \Tempest\Database\Builder\QueryBuilders\HasWhereQueryBuilderMethods<TModel>
*/
final class DeleteQueryBuilder implements BuildsQuery
final class DeleteQueryBuilder implements BuildsQuery, SupportsWhereStatements
{
use HasConditions, OnDatabase, HasWhereQueryBuilderMethods, TransformsQueryBuilder;

private DeleteStatement $delete;

private array $bindings = [];
public array $bindings = [];

private ModelInspector $model;
public ModelInspector $model;

public ImmutableArray $wheres {
get => $this->delete->where;
}

/**
* @param class-string<TModel>|string|TModel $model
Expand All @@ -36,6 +41,25 @@ public function __construct(string|object $model)
$this->delete = new DeleteStatement($this->model->getTableDefinition());
}

/**
* Creates an instance from another query builder, inheriting conditions and bindings.
*
* @template TSourceModel of object
* @param (BuildsQuery<TSourceModel>&SupportsWhereStatements<TSourceModel>) $source
* @return DeleteQueryBuilder<TSourceModel>
*/
public static function fromQueryBuilder(BuildsQuery&SupportsWhereStatements $source): DeleteQueryBuilder
{
$builder = new self($source->model->model);
$builder->bind(...$source->bindings);

foreach ($source->wheres as $where) {
$builder->wheres[] = $where;
}

return $builder;
}

/**
* Executes the delete query, removing matching records from the database.
*/
Expand Down Expand Up @@ -96,14 +120,4 @@ public function build(mixed ...$bindings): Query

return new Query($this->delete, [...$this->bindings, ...$bindings])->onDatabase($this->onDatabase);
}

private function getStatementForWhere(): HasWhereStatements
{
return $this->delete;
}

private function getModel(): ModelInspector
{
return $this->model;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

/**
* @template TModel of object
* @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereConditions
*
* Shared methods for building WHERE conditions and convenience WHERE methods.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,21 @@
namespace Tempest\Database\Builder\QueryBuilders;

use Closure;
use Tempest\Database\Builder\ModelInspector;
use Tempest\Database\Builder\WhereOperator;
use Tempest\Database\QueryStatements\HasWhereStatements;
use Tempest\Database\QueryStatements\WhereStatement;
use Tempest\Support\Str;

use function Tempest\Support\str;

/**
* @template TModel of object
* @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\BuildsQuery
* @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereConditions
* @use \Tempest\Database\Builder\QueryBuilders\HasConvenientWhereMethods<TModel>
*/
trait HasWhereQueryBuilderMethods
{
use HasConvenientWhereMethods;

abstract private function getModel(): ModelInspector;

abstract private function getStatementForWhere(): HasWhereStatements;

/**
* Adds a SQL `WHERE` condition to the query. If the `$statement` looks like raw SQL, the method will assume it is and call `whereRaw`. Otherwise, `whereField` will be called.
*
Expand Down Expand Up @@ -51,14 +45,14 @@ public function where(string $statement, mixed ...$bindings): self
public function whereField(string $field, mixed $value, string|WhereOperator $operator = WhereOperator::EQUALS): self
{
$operator = WhereOperator::fromOperator($operator);
$fieldDefinition = $this->getModel()->getFieldDefinition($field);
$fieldDefinition = $this->model->getFieldDefinition($field);
$condition = $this->buildCondition((string) $fieldDefinition, $operator, $value);

if ($this->getStatementForWhere()->where->isNotEmpty()) {
if ($this->wheres->isNotEmpty()) {
return $this->andWhere($field, $value, $operator);
}

$this->getStatementForWhere()->where[] = new WhereStatement($condition['sql']);
$this->wheres[] = new WhereStatement($condition['sql']);
$this->bind(...$condition['bindings']);

return $this;
Expand All @@ -72,10 +66,10 @@ public function whereField(string $field, mixed $value, string|WhereOperator $op
public function andWhere(string $field, mixed $value, WhereOperator $operator = WhereOperator::EQUALS): self
{
$operator = WhereOperator::fromOperator($operator);
$fieldDefinition = $this->getModel()->getFieldDefinition($field);
$fieldDefinition = $this->model->getFieldDefinition($field);
$condition = $this->buildCondition((string) $fieldDefinition, $operator, $value);

$this->getStatementForWhere()->where[] = new WhereStatement("AND {$condition['sql']}");
$this->wheres[] = new WhereStatement("AND {$condition['sql']}");
$this->bind(...$condition['bindings']);

return $this;
Expand All @@ -89,10 +83,10 @@ public function andWhere(string $field, mixed $value, WhereOperator $operator =
public function orWhere(string $field, mixed $value, WhereOperator $operator = WhereOperator::EQUALS): self
{
$operator = WhereOperator::fromOperator($operator);
$fieldDefinition = $this->getModel()->getFieldDefinition($field);
$fieldDefinition = $this->model->getFieldDefinition($field);
$condition = $this->buildCondition((string) $fieldDefinition, $operator, $value);

$this->getStatementForWhere()->where[] = new WhereStatement("OR {$condition['sql']}");
$this->wheres[] = new WhereStatement("OR {$condition['sql']}");
$this->bind(...$condition['bindings']);

return $this;
Expand All @@ -105,11 +99,11 @@ public function orWhere(string $field, mixed $value, WhereOperator $operator = W
*/
public function whereRaw(string $statement, mixed ...$bindings): self
{
if ($this->getStatementForWhere()->where->isNotEmpty() && ! str($statement)->trim()->startsWith(['AND', 'OR'])) {
if ($this->wheres->isNotEmpty() && ! str($statement)->trim()->startsWith(['AND', 'OR'])) {
return $this->andWhereRaw($statement, ...$bindings);
}

$this->getStatementForWhere()->where[] = new WhereStatement($statement);
$this->wheres[] = new WhereStatement($statement);
$this->bind(...$bindings);

return $this;
Expand All @@ -122,7 +116,7 @@ public function whereRaw(string $statement, mixed ...$bindings): self
*/
public function andWhereRaw(string $rawCondition, mixed ...$bindings): self
{
$this->getStatementForWhere()->where[] = new WhereStatement("AND {$rawCondition}");
$this->wheres[] = new WhereStatement("AND {$rawCondition}");
$this->bind(...$bindings);

return $this;
Expand All @@ -135,7 +129,7 @@ public function andWhereRaw(string $rawCondition, mixed ...$bindings): self
*/
public function orWhereRaw(string $rawCondition, mixed ...$bindings): self
{
$this->getStatementForWhere()->where[] = new WhereStatement("OR {$rawCondition}");
$this->wheres[] = new WhereStatement("OR {$rawCondition}");
$this->bind(...$bindings);

return $this;
Expand All @@ -149,12 +143,12 @@ public function orWhereRaw(string $rawCondition, mixed ...$bindings): self
*/
public function whereGroup(Closure $callback): self
{
$groupBuilder = new WhereGroupBuilder($this->getModel());
$groupBuilder = new WhereGroupBuilder($this->model);
$callback($groupBuilder);
$group = $groupBuilder->build();

if (! $group->conditions->isEmpty()) {
$this->getStatementForWhere()->where[] = $group;
$this->wheres[] = $group;
$this->bind(...$groupBuilder->getBindings());
}

Expand All @@ -169,8 +163,8 @@ public function whereGroup(Closure $callback): self
*/
public function andWhereGroup(Closure $callback): self
{
if ($this->getStatementForWhere()->where->isNotEmpty()) {
$this->getStatementForWhere()->where[] = new WhereStatement('AND');
if ($this->wheres->isNotEmpty()) {
$this->wheres[] = new WhereStatement('AND');
}

return $this->whereGroup($callback);
Expand All @@ -184,8 +178,8 @@ public function andWhereGroup(Closure $callback): self
*/
public function orWhereGroup(Closure $callback): self
{
if ($this->getStatementForWhere()->where->isNotEmpty()) {
$this->getStatementForWhere()->where[] = new WhereStatement('OR');
if ($this->wheres->isNotEmpty()) {
$this->wheres[] = new WhereStatement('OR');
}

return $this->whereGroup($callback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ final class InsertQueryBuilder implements BuildsQuery

private array $after = [];

private array $bindings = [];
public array $bindings = [];

private ModelInspector $model;
public ModelInspector $model;

/**
* @param class-string<TModel>|string|TModel $model
Expand Down
Loading