From 3093adc26aa25c133fcb223c47fb1244f67e958d Mon Sep 17 00:00:00 2001 From: Yurun Date: Tue, 7 May 2024 12:58:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8E=E7=BD=AE=20Where=20?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=20(#695)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/base/version/2.0-2.1.md | 36 +++++++++++++++++++ doc/components/db/index.md | 14 ++++++++ .../src/Db/Query/Builder/DeleteBuilder.php | 2 +- .../src/Db/Query/Builder/SelectBuilder.php | 2 +- .../src/Db/Query/Builder/UpdateBuilder.php | 2 +- src/Db/Mysql/Query/Builder/DeleteBuilder.php | 2 +- src/Db/Mysql/Query/Builder/SelectBuilder.php | 2 +- src/Db/Mysql/Query/Builder/UpdateBuilder.php | 2 +- src/Db/Query/Builder/BaseBuilder.php | 30 +++++++++------- .../Query/Interfaces/IBaseWhereCollector.php | 14 ++++++++ src/Db/Query/Query.php | 10 ++++++ src/Db/Query/QueryOption.php | 7 ++++ src/Db/Query/Traits/TWhereCollector.php | 8 +++++ src/Db/Query/Where/WhereBrackets.php | 22 +++++++----- src/Db/Query/WhereCollector.php | 23 ++++++++++++ .../SoftDelete/Annotation/SoftDelete.php | 7 ++-- src/Model/SoftDelete/Traits/TSoftDelete.php | 23 ++++++------ .../Component/Tests/Db/QueryCurdBaseTest.php | 18 ++++++++++ 18 files changed, 184 insertions(+), 40 deletions(-) diff --git a/doc/base/version/2.0-2.1.md b/doc/base/version/2.0-2.1.md index 5403f42732..043a85e3ed 100644 --- a/doc/base/version/2.0-2.1.md +++ b/doc/base/version/2.0-2.1.md @@ -18,6 +18,42 @@ v2.0 是一个非常成功的 LTS 版本,进行了底层重构,增加了强 ## 新功能 +### v2.1.63 + +**发布日期:** `2024-00-00` + +* 支持后置 Where 条件 + +### v2.1.61 + +**发布日期:** `2024-02-19` + +* 支持在模型中使用枚举类型的属性值 ([#674](https://github.com/imiphp/imi/pull/580)) + +* 支持在控制器方法中使用枚举类型的属性值 ([#675](https://github.com/imiphp/imi/pull/580)) + +* 支持枚举和联合类型的控制器方法参数 ([#676](https://github.com/imiphp/imi/pull/580)) + +### v2.1.60 + +**发布日期:** `2024-01-19` + +* 支持 YurunHttp 5.0 ([#672](https://github.com/imiphp/imi/pull/672)) + +### v2.1.58 + +**发布日期:** `2023-11-20` + +* PHP 原生枚举深度支持 ([#646](https://github.com/imiphp/imi/pull/646)) + +### v2.1.56 + +**发布日期:** `2023-10-14` + +* 新增获取应用请求地址方法 ([#587](https://github.com/imiphp/imi/pull/587)) + +* 支持获取限流桶内可用数量 ([#588](https://github.com/imiphp/imi/pull/588)) + ### v2.1.53 **发布日期:** `2023-09-01` diff --git a/doc/components/db/index.md b/doc/components/db/index.md index a55d8e9b88..33f5e1e320 100644 --- a/doc/components/db/index.md +++ b/doc/components/db/index.md @@ -496,6 +496,20 @@ Db::query()->whereStruct(new \Imi\Db\Query\Where\Where('age', '<', 14), 'or'); Db::query()->orWhereStruct(new \Imi\Db\Query\Where\Where('age', '<', 14)); ``` +#### postWhere + +用法类似 `whereBrackets`,后置 Where 条件 + +```php +// where `a` = 1 and (`b` = 2 or `c` = 3) and (`d` != 4) +Db::query()->postWhere(static function (\Imi\Db\Query\Interfaces\IQuery $query, \Imi\Db\Query\Interfaces\IWhereCollector $where) { + $whereCollector->where('b', '=', 2) + ->orWhere('c', '=', 3); + }) + ->postWhere(static fn () => new Where('d', '!=', 4, 'or')) + ->where('a', '=', 1); +``` + #### 全文搜索(fullText) imi 查询构建器提供的全文搜索功能,不仅支持全文搜索匹配,还支持返回匹配分数和排序等功能。 diff --git a/src/Components/pgsql/src/Db/Query/Builder/DeleteBuilder.php b/src/Components/pgsql/src/Db/Query/Builder/DeleteBuilder.php index 3d007986e7..1c2a44baa8 100644 --- a/src/Components/pgsql/src/Db/Query/Builder/DeleteBuilder.php +++ b/src/Components/pgsql/src/Db/Query/Builder/DeleteBuilder.php @@ -16,7 +16,7 @@ public function build(...$args): string $option = $query->getOption(); $sql = 'delete from ' . $option->table->toString($query) - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseOrder($option->order) ; $query->bindValues($this->params); diff --git a/src/Components/pgsql/src/Db/Query/Builder/SelectBuilder.php b/src/Components/pgsql/src/Db/Query/Builder/SelectBuilder.php index 04581214ee..e8755202fc 100644 --- a/src/Components/pgsql/src/Db/Query/Builder/SelectBuilder.php +++ b/src/Components/pgsql/src/Db/Query/Builder/SelectBuilder.php @@ -19,7 +19,7 @@ public function build(...$args): string . ' from ' . $option->table->toString($query) . $this->parseJoin($option->join) - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseGroup($option->group) . $this->parseHaving($option->having) . $this->parseOrder($option->order) diff --git a/src/Components/pgsql/src/Db/Query/Builder/UpdateBuilder.php b/src/Components/pgsql/src/Db/Query/Builder/UpdateBuilder.php index b404f0af78..dc7aeb1bb1 100644 --- a/src/Components/pgsql/src/Db/Query/Builder/UpdateBuilder.php +++ b/src/Components/pgsql/src/Db/Query/Builder/UpdateBuilder.php @@ -96,7 +96,7 @@ public function build(...$args): string } $sql = 'update ' . $option->table->toString($query) . ' set ' . implode(',', $setStrs) . $jsonSets - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseOrder($option->order); $query->bindValues($params); diff --git a/src/Db/Mysql/Query/Builder/DeleteBuilder.php b/src/Db/Mysql/Query/Builder/DeleteBuilder.php index 5f5b855790..2bc6eaf928 100644 --- a/src/Db/Mysql/Query/Builder/DeleteBuilder.php +++ b/src/Db/Mysql/Query/Builder/DeleteBuilder.php @@ -17,7 +17,7 @@ public function build(...$args): string $sql = 'delete from ' . $option->table->toString($query) . (($option->partition && '' !== ($partition = $option->partition->toString($query))) ? (' PARTITION(' . $partition . ')') : '') - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseOrder($option->order) . $this->parseLimit($option->offset, $option->limit) ; diff --git a/src/Db/Mysql/Query/Builder/SelectBuilder.php b/src/Db/Mysql/Query/Builder/SelectBuilder.php index 4dc8328939..1693176d83 100644 --- a/src/Db/Mysql/Query/Builder/SelectBuilder.php +++ b/src/Db/Mysql/Query/Builder/SelectBuilder.php @@ -24,7 +24,7 @@ public function build(...$args): string . $option->table->toString($query) . (($option->partition && '' !== ($partition = $option->partition->toString($query))) ? (' PARTITION(' . $partition . ')') : '') . $this->parseJoin($option->join) - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseGroup($option->group) . $this->parseHaving($option->having) . $this->parseOrder($option->order) diff --git a/src/Db/Mysql/Query/Builder/UpdateBuilder.php b/src/Db/Mysql/Query/Builder/UpdateBuilder.php index 29e7aa7e9f..b8ef5930de 100644 --- a/src/Db/Mysql/Query/Builder/UpdateBuilder.php +++ b/src/Db/Mysql/Query/Builder/UpdateBuilder.php @@ -95,7 +95,7 @@ public function build(...$args): string . (($option->partition && '' !== ($partition = $option->partition->toString($query))) ? (' PARTITION(' . $partition . ')') : '') . ' set ' . implode(',', $setStrs) . $jsonSets - . $this->parseWhere($option->where) + . $this->parseWhere($option->where, $option->postWhere) . $this->parseOrder($option->order) . $this->parseLimit($option->offset, $option->limit); diff --git a/src/Db/Query/Builder/BaseBuilder.php b/src/Db/Query/Builder/BaseBuilder.php index 2538517b8c..43fc2a1f35 100644 --- a/src/Db/Query/Builder/BaseBuilder.php +++ b/src/Db/Query/Builder/BaseBuilder.php @@ -94,29 +94,33 @@ protected function parseJoin(array $join): string * where. * * @param \Imi\Db\Query\Interfaces\IBaseWhere[] $where + * @param \Imi\Db\Query\Interfaces\IBaseWhere[] $postWhere */ - protected function parseWhere(array $where): string + protected function parseWhere(array $where, array $postWhere): string { - if (!$where) + if (!$where && !$postWhere) { return ''; } $result = []; $params = &$this->params; $query = $this->query; - foreach ($where as $item) + foreach ([$where, $postWhere] as $whereList) { - $sql = $item->toStringWithoutLogic($query); - if ('' === $sql) + foreach ($whereList as $item) { - continue; - } - $result[] = $item->getLogicalOperator(); - $result[] = $sql; - $binds = $item->getBinds(); - if ($binds) - { - $params = array_merge($params, $binds); + $sql = $item->toStringWithoutLogic($query); + if ('' === $sql) + { + continue; + } + $result[] = $item->getLogicalOperator(); + $result[] = $sql; + $binds = $item->getBinds(); + if ($binds) + { + $params = array_merge($params, $binds); + } } } unset($result[0]); diff --git a/src/Db/Query/Interfaces/IBaseWhereCollector.php b/src/Db/Query/Interfaces/IBaseWhereCollector.php index c601e2888d..29bf7b1336 100644 --- a/src/Db/Query/Interfaces/IBaseWhereCollector.php +++ b/src/Db/Query/Interfaces/IBaseWhereCollector.php @@ -187,4 +187,18 @@ public function whereIsNotNull(string $fieldName, string $logicalOperator = 'and * @return static */ public function orWhereIsNotNull(string $fieldName): self; + + /** + * 后置 Where,and 条件. + * + * @return static + */ + public function postWhere(callable $callback, string $logicalOperator = 'and'): self; + + /** + * 后置 Where,or 条件. + * + * @return static + */ + public function orPostWhere(callable $callback): self; } diff --git a/src/Db/Query/Query.php b/src/Db/Query/Query.php index f43eccdb3c..7cff2c27e7 100644 --- a/src/Db/Query/Query.php +++ b/src/Db/Query/Query.php @@ -388,6 +388,16 @@ public function whereIsNotNull(string $fieldName, string $logicalOperator = Logi return $this->whereRaw($this->fieldQuote($fieldName) . ' is not null', $logicalOperator); } + /** + * {@inheritDoc} + */ + public function postWhere(callable $callback, string $logicalOperator = LogicalOperator::AND): self + { + $this->option->postWhere[] = new WhereBrackets($callback, $logicalOperator); + + return $this; + } + /** * {@inheritDoc} */ diff --git a/src/Db/Query/QueryOption.php b/src/Db/Query/QueryOption.php index c905c04a85..baf38a5f42 100644 --- a/src/Db/Query/QueryOption.php +++ b/src/Db/Query/QueryOption.php @@ -31,6 +31,13 @@ class QueryOption */ public array $where = []; + /** + * 后置 where 条件. + * + * @var \Imi\Db\Query\Interfaces\IBaseWhere[] + */ + public array $postWhere = []; + /** * join. * diff --git a/src/Db/Query/Traits/TWhereCollector.php b/src/Db/Query/Traits/TWhereCollector.php index ef58622a1b..0bf972c4b2 100644 --- a/src/Db/Query/Traits/TWhereCollector.php +++ b/src/Db/Query/Traits/TWhereCollector.php @@ -204,4 +204,12 @@ protected function parseWhereEx(array $condition): array return $result; } + + /** + * {@inheritDoc} + */ + public function orPostWhere(callable $callback): self + { + return $this->postWhere($callback, LogicalOperator::OR); + } } diff --git a/src/Db/Query/Where/WhereBrackets.php b/src/Db/Query/Where/WhereBrackets.php index 0832d292e0..544e0bea33 100644 --- a/src/Db/Query/Where/WhereBrackets.php +++ b/src/Db/Query/Where/WhereBrackets.php @@ -100,17 +100,23 @@ public function toStringWithoutLogic(IQuery $query): string elseif (null === $callResult) { $result = '('; - foreach ($whereCollector->getWhere() as $i => $where) + foreach ([ + $whereCollector->getWhere(), + $whereCollector->getPostWhere(), + ] as $wheres) { - if (0 === $i) + foreach ($wheres as $i => $where) { - $result .= $where->toStringWithoutLogic($query); - } - else - { - $result .= ' ' . $where->getLogicalOperator() . ' ' . $where->toStringWithoutLogic($query); + if (0 === $i) + { + $result .= $where->toStringWithoutLogic($query); + } + else + { + $result .= ' ' . $where->getLogicalOperator() . ' ' . $where->toStringWithoutLogic($query); + } + $binds = array_merge($binds, $where->getBinds()); } - $binds = array_merge($binds, $where->getBinds()); } return $result . ')'; diff --git a/src/Db/Query/WhereCollector.php b/src/Db/Query/WhereCollector.php index 8412f922cd..0120fc907b 100644 --- a/src/Db/Query/WhereCollector.php +++ b/src/Db/Query/WhereCollector.php @@ -21,6 +21,11 @@ class WhereCollector implements IWhereCollector */ protected array $where = []; + /** + * @var IBaseWhere[] + */ + protected array $postWhere = []; + protected IQuery $query; public function __construct(IQuery $query) @@ -36,6 +41,14 @@ public function getWhere(): array return $this->where; } + /** + * @return IBaseWhere[] + */ + public function getPostWhere(): array + { + return $this->postWhere; + } + /** * {@inheritDoc} */ @@ -95,4 +108,14 @@ public function whereIsNotNull(string $fieldName, string $logicalOperator = Logi { return $this->whereRaw($this->query->fieldQuote($fieldName) . ' is not null', $logicalOperator); } + + /** + * @return static + */ + public function postWhere(callable $callback, string $logicalOperator = 'and'): self + { + $this->postWhere[] = new WhereBrackets($callback, $logicalOperator); + + return $this; + } } diff --git a/src/Model/SoftDelete/Annotation/SoftDelete.php b/src/Model/SoftDelete/Annotation/SoftDelete.php index 2cc0a97b11..50d78f0f97 100644 --- a/src/Model/SoftDelete/Annotation/SoftDelete.php +++ b/src/Model/SoftDelete/Annotation/SoftDelete.php @@ -14,8 +14,9 @@ * * @Target("CLASS") * - * @property string $field 软删除字段名 - * @property mixed $default 软删除字段的默认值,代表非删除状态 + * @property string $field 软删除字段名 + * @property mixed $default 软删除字段的默认值,代表非删除状态 + * @property bool $postWhere 软删除字段查询时是否为后置条件,一般用于索引优化可以设为 true,默认为 false */ #[\Attribute(\Attribute::TARGET_CLASS)] class SoftDelete extends Base @@ -28,7 +29,7 @@ class SoftDelete extends Base /** * @param mixed $default */ - public function __construct(?array $__data = null, string $field = '', $default = 0) + public function __construct(?array $__data = null, string $field = '', $default = 0, bool $postWhere = false) { parent::__construct(...\func_get_args()); if ('' === $this->field) diff --git a/src/Model/SoftDelete/Traits/TSoftDelete.php b/src/Model/SoftDelete/Traits/TSoftDelete.php index 86e429e6b9..2dcebf771b 100644 --- a/src/Model/SoftDelete/Traits/TSoftDelete.php +++ b/src/Model/SoftDelete/Traits/TSoftDelete.php @@ -8,7 +8,7 @@ use Imi\Bean\BeanFactory; use Imi\Db\Query\Interfaces\IQuery; use Imi\Db\Query\Interfaces\IResult; -use Imi\Db\Query\Where\Where; +use Imi\Db\Query\Interfaces\IWhereCollector; use Imi\Event\Event; use Imi\Model\Contract\IModelQuery; use Imi\Model\Event\ModelEvents; @@ -63,9 +63,10 @@ public static function query(?string $poolName = null, ?int $queryType = null, ? { /** @var IModelQuery $query */ $query = parent::query($poolName, $queryType, $queryClass, $alias); + $softDeleteAnnotation = self::__getSoftDeleteAnnotation(); + $whereMethod = $softDeleteAnnotation->postWhere ? 'postWhere' : 'whereBrackets'; - return $query->whereBrackets(static function (IQuery $query) { - $softDeleteAnnotation = self::__getSoftDeleteAnnotation(); + return $query->{$whereMethod}(static function (IQuery $query, IWhereCollector $whereCollector) use ($softDeleteAnnotation) { $table = $query->getOption()->table; if (null === ($alias = $table->getAlias())) { @@ -87,12 +88,7 @@ public static function query(?string $poolName = null, ?int $queryType = null, ? { $fieldTableName = $alias; } - if (null === $softDeleteAnnotation->default) - { - return new Where($fieldTableName . '.' . $softDeleteAnnotation->field, 'is', null); - } - - return new Where($fieldTableName . '.' . $softDeleteAnnotation->field, '=', $softDeleteAnnotation->default); + $whereCollector->where($fieldTableName . '.' . $softDeleteAnnotation->field, null === $softDeleteAnnotation->default ? 'is' : '=', $softDeleteAnnotation->default); }); } @@ -215,7 +211,14 @@ public static function findDeleted(...$ids): ?Model $query->where($name, '=', $ids[$i]); } } - $query->where($softDeleteAnnotation->field, '!=', $softDeleteAnnotation->default); + if ($softDeleteAnnotation->postWhere) + { + $query->postWhere(static fn (IQuery $query, IWhereCollector $whereCollector) => $whereCollector->where($softDeleteAnnotation->field, '!=', $softDeleteAnnotation->default)); + } + else + { + $query->where($softDeleteAnnotation->field, '!=', $softDeleteAnnotation->default); + } } // 查找前 diff --git a/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php b/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php index b66880e66e..9180e23483 100644 --- a/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php +++ b/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php @@ -874,4 +874,22 @@ public function testWhereNull(): void $this->assertEquals('select * from `test` where `a` is NULL', $query->buildSelectSql()); $this->assertEquals([], $query->getBinds()); } + + public function testPostWhere(): void + { + $query = Db::query()->from('test') + ->postWhere(static function (IQuery $query, IWhereCollector $whereCollector) { + $whereCollector->where('b', '=', 2) + ->orWhere('c', '=', 3); + }) + ->postWhere(static fn () => new Where('d', '!=', 4, 'or')) + ->where('a', '=', 1); + $this->assertEquals('select * from `test` where `a` = :p1 and (`b` = :p2 or `c` = :p3) and (`d` != :p4)', $query->buildSelectSql()); + $this->assertEquals([ + ':p1' => 1, + ':p2' => 2, + ':p3' => 3, + ':p4' => 4, + ], $query->getBinds()); + } }