From c2a88028c6b194ace5cef9392b94053206716dd9 Mon Sep 17 00:00:00 2001 From: Yurun Date: Fri, 1 Sep 2023 14:15:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=20whereBrackets=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E6=94=B6?= =?UTF-8?q?=E9=9B=86=E5=99=A8=20(#580)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增强 whereBrackets,支持查询条件收集器 * 更新文档 * 更新文档 * 优化代码,完善测试用例 * 修复测试 --- doc/base/version/2.0-2.1.md | 6 + doc/components/db/index.md | 19 +- .../pgsql/tests/Unit/Db/Pdo/QueryCurdTest.php | 2 +- .../tests/Unit/Db/Swoole/QueryCurdTest.php | 2 +- .../Tests/Db/Swoole/QueryCurdTest.php | 2 +- .../Query/Interfaces/IBaseWhereCollector.php | 190 ++++++++++++++++ src/Db/Query/Interfaces/IQuery.php | 184 +--------------- src/Db/Query/Interfaces/IWhereCollector.php | 13 ++ src/Db/Query/Query.php | 197 +---------------- src/Db/Query/Traits/TWhereCollector.php | 207 ++++++++++++++++++ src/Db/Query/Where/WhereBrackets.php | 32 ++- src/Db/Query/WhereCollector.php | 98 +++++++++ .../Tests/Db/Mysqli/QueryCurdTest.php | 2 +- .../Component/Tests/Db/Pdo/QueryCurdTest.php | 2 +- .../Component/Tests/Db/QueryCurdBaseTest.php | 152 +++++++++++++ 15 files changed, 718 insertions(+), 390 deletions(-) create mode 100644 src/Db/Query/Interfaces/IBaseWhereCollector.php create mode 100644 src/Db/Query/Interfaces/IWhereCollector.php create mode 100644 src/Db/Query/Traits/TWhereCollector.php create mode 100644 src/Db/Query/WhereCollector.php diff --git a/doc/base/version/2.0-2.1.md b/doc/base/version/2.0-2.1.md index 0770116d4a..5403f42732 100644 --- a/doc/base/version/2.0-2.1.md +++ b/doc/base/version/2.0-2.1.md @@ -18,6 +18,12 @@ v2.0 是一个非常成功的 LTS 版本,进行了底层重构,增加了强 ## 新功能 +### v2.1.53 + +**发布日期:** `2023-09-01` + +* 增强 `whereBrackets`,支持查询条件收集器 ([#580](https://github.com/imiphp/imi/pull/580)) ([文档](https://doc.imiphp.com/v2.1/components/db/index.html#whereBrackets)) + ### v2.1.52 **发布日期:** `2023-08-18` diff --git a/doc/components/db/index.md b/doc/components/db/index.md index 4720ed91ef..a55d8e9b88 100644 --- a/doc/components/db/index.md +++ b/doc/components/db/index.md @@ -453,26 +453,37 @@ Db::query()->orWhereRaw('id >= :value', [':value' => 1]); #### whereBrackets ```php +// 查询条件收集器:where (age < 14 or age > 60) +Db::query()->where('id', '=', 1)->whereBrackets(function(\Imi\Db\Query\Interfaces\IQuery $query, \Imi\Db\Query\Interfaces\IWhereCollector $where) { + // 注意:使用第 2 个参数 $where,而不是 $query + $where->where('age', '<', 14)->orWhere('age', '>', 60); + // 不要有返回值 +}, 'or'); + // where id = 1 or (age < 14) Db::query()->where('id', '=', 1)->whereBrackets(function(\Imi\Db\Query\Interfaces\IQuery $query) { - // 直接返回字符串 + // 返回条件字符串 return 'age < 14'; }, 'or'); + // 支持使用 sql 语句: where id = 1 or (age > 10 and age < 14) Db::query()->where('id', '=', 1)->whereBrackets(function(\Imi\Db\Query\Interfaces\IQuery $query) { - // 直接返回字符串 + // 返回 Where 系列数组 return [ \Imi\Db\Query\Where\Where::raw('age > 10'), new \Imi\Db\Query\Where\Where('age', '<', 14), ]; }, 'or'); + // where id = 1 or (age < 14) Db::query()->where('id', '=', 1)->whereBrackets(function(\Imi\Db\Query\Interfaces\IQuery $query) { - // 直接返回字符串 + // 返回 Where 系列对象 return new \Imi\Db\Query\Where\Where('age', '<', 14); }, 'or'); + +// OR 条件 Db::query()->where('id', '=', 1)->orWhereBrackets(function(\Imi\Db\Query\Interfaces\IQuery $query) { - // 直接返回字符串 + // 返回 Where 系列对象 return new \Imi\Db\Query\Where\Where('age', '<', 14); }); ``` diff --git a/src/Components/pgsql/tests/Unit/Db/Pdo/QueryCurdTest.php b/src/Components/pgsql/tests/Unit/Db/Pdo/QueryCurdTest.php index c6253c982a..13823af293 100644 --- a/src/Components/pgsql/tests/Unit/Db/Pdo/QueryCurdTest.php +++ b/src/Components/pgsql/tests/Unit/Db/Pdo/QueryCurdTest.php @@ -26,7 +26,7 @@ class QueryCurdTest extends QueryCurdBaseTest * * @var string */ - protected $expectedTestWhereExSql = 'select * from "tb_article" where ("id" = :p1 and ("id" in (:p2) ) )'; + protected $expectedTestWhereExSql = 'select * from "tb_article" where ("id" = :p1 and ("id" in (:p2)))'; /** * 测试 JSON 查询的 SQL. diff --git a/src/Components/pgsql/tests/Unit/Db/Swoole/QueryCurdTest.php b/src/Components/pgsql/tests/Unit/Db/Swoole/QueryCurdTest.php index b75bdd7f01..a26ab5841a 100644 --- a/src/Components/pgsql/tests/Unit/Db/Swoole/QueryCurdTest.php +++ b/src/Components/pgsql/tests/Unit/Db/Swoole/QueryCurdTest.php @@ -26,7 +26,7 @@ class QueryCurdTest extends QueryCurdBaseTest * * @var string */ - protected $expectedTestWhereExSql = 'select * from "tb_article" where ("id" = :p1 and ("id" in (:p2) ) )'; + protected $expectedTestWhereExSql = 'select * from "tb_article" where ("id" = :p1 and ("id" in (:p2)))'; /** * 测试 JSON 查询的 SQL. diff --git a/src/Components/swoole/tests/unit/Component/Tests/Db/Swoole/QueryCurdTest.php b/src/Components/swoole/tests/unit/Component/Tests/Db/Swoole/QueryCurdTest.php index aba0af42f7..116ed30666 100644 --- a/src/Components/swoole/tests/unit/Component/Tests/Db/Swoole/QueryCurdTest.php +++ b/src/Components/swoole/tests/unit/Component/Tests/Db/Swoole/QueryCurdTest.php @@ -23,7 +23,7 @@ class QueryCurdTest extends QueryCurdBaseTest * * @var string */ - protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2) ) )'; + protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2)))'; /** * 测试 JSON 查询的 SQL. diff --git a/src/Db/Query/Interfaces/IBaseWhereCollector.php b/src/Db/Query/Interfaces/IBaseWhereCollector.php new file mode 100644 index 0000000000..c601e2888d --- /dev/null +++ b/src/Db/Query/Interfaces/IBaseWhereCollector.php @@ -0,0 +1,190 @@ +、<、like等. + * + * @param mixed $value + * + * @return static + */ + public function where(string $fieldName, string $operation, $value, string $logicalOperator = 'and'): self; + + /** + * 设置 where 条件,用原生语句. + * + * @return static + */ + public function whereRaw(string $raw, string $logicalOperator = 'and', array $binds = []): self; + + /** + * 设置 where 条件,传入回调,回调中的条件加括号. + * + * @return static + */ + public function whereBrackets(callable $callback, string $logicalOperator = 'and'): self; + + /** + * 设置 where 条件,使用 IBaseWhere 结构. + * + * @return static + */ + public function whereStruct(IBaseWhere $where, string $logicalOperator = 'and'): self; + + /** + * 设置 where 条件,支持语法如下:. + * + * [ + * 'id' => 1, + * 'or' => [ + * 'id' => 2, + * ], + * 'title' => ['like', '%test%'], + * 'age' => ['>', 18], + * 'age' => ['between', 19, 29] + * ] + * + * SQL: id = 1 or (id = 2) and title like '%test%' and age > 18 and age between 19 and 29 + * + * @return static + */ + public function whereEx(array $condition, string $logicalOperator = 'and'): self; + + /** + * where between $begin end $end. + * + * @param mixed $begin + * @param mixed $end + * + * @return static + */ + public function whereBetween(string $fieldName, $begin, $end, string $logicalOperator = 'and'): self; + + /** + * or where between $begin end $end. + * + * @param mixed $begin + * @param mixed $end + * + * @return static + */ + public function orWhereBetween(string $fieldName, $begin, $end): self; + + /** + * where not between $begin end $end. + * + * @param mixed $begin + * @param mixed $end + * + * @return static + */ + public function whereNotBetween(string $fieldName, $begin, $end, string $logicalOperator = 'and'): self; + + /** + * or where not between $begin end $end. + * + * @param mixed $begin + * @param mixed $end + * + * @return static + */ + public function orWhereNotBetween(string $fieldName, $begin, $end): self; + + /** + * 设置 where or 条件. + * + * @param mixed $value + * + * @return static + */ + public function orWhere(string $fieldName, string $operation, $value): self; + + /** + * 设置 where or 条件,用原生语句. + * + * @return static + */ + public function orWhereRaw(string $where, array $binds = []): self; + + /** + * 设置 where or 条件,传入回调,回调中的条件加括号. + * + * @return static + */ + public function orWhereBrackets(callable $callback): self; + + /** + * 设置 where or 条件,使用 IBaseWhere 结构. + * + * @return static + */ + public function orWhereStruct(IBaseWhere $where): self; + + /** + * 设置 where or 条件,支持语法参考 whereEx 方法. + * + * @return static + */ + public function orWhereEx(array $condition): self; + + /** + * where field in (list). + * + * @return static + */ + public function whereIn(string $fieldName, array $list, string $logicalOperator = 'and'): self; + + /** + * or where field in (list). + * + * @return static + */ + public function orWhereIn(string $fieldName, array $list): self; + + /** + * where field not in (list). + * + * @return static + */ + public function whereNotIn(string $fieldName, array $list, string $logicalOperator = 'and'): self; + + /** + * or where field not in (list). + * + * @return static + */ + public function orWhereNotIn(string $fieldName, array $list): self; + + /** + * where field is null. + * + * @return static + */ + public function whereIsNull(string $fieldName, string $logicalOperator = 'and'): self; + + /** + * or where field is null. + * + * @return static + */ + public function orWhereIsNull(string $fieldName): self; + + /** + * where field is not null. + * + * @return static + */ + public function whereIsNotNull(string $fieldName, string $logicalOperator = 'and'): self; + + /** + * or where field is not null. + * + * @return static + */ + public function orWhereIsNotNull(string $fieldName): self; +} diff --git a/src/Db/Query/Interfaces/IQuery.php b/src/Db/Query/Interfaces/IQuery.php index 0ca2a6108d..f00441b205 100644 --- a/src/Db/Query/Interfaces/IQuery.php +++ b/src/Db/Query/Interfaces/IQuery.php @@ -13,7 +13,7 @@ /** * 查询器接口. */ -interface IQuery +interface IQuery extends IBaseWhereCollector { /** * 获取所有操作的记录. @@ -112,188 +112,6 @@ public function field(string ...$fields): self; */ public function fieldRaw(string $raw, ?string $alias = null, array $binds = []): self; - /** - * 设置 where 条件,一般用于 =、>、<、like等. - * - * @param mixed $value - * - * @return static - */ - public function where(string $fieldName, string $operation, $value, string $logicalOperator = 'and'): self; - - /** - * 设置 where 条件,用原生语句. - * - * @return static - */ - public function whereRaw(string $raw, string $logicalOperator = 'and', array $binds = []): self; - - /** - * 设置 where 条件,传入回调,回调中的条件加括号. - * - * @return static - */ - public function whereBrackets(callable $callback, string $logicalOperator = 'and'): self; - - /** - * 设置 where 条件,使用 IBaseWhere 结构. - * - * @return static - */ - public function whereStruct(IBaseWhere $where, string $logicalOperator = 'and'): self; - - /** - * 设置 where 条件,支持语法如下:. - * - * [ - * 'id' => 1, - * 'or' => [ - * 'id' => 2, - * ], - * 'title' => ['like', '%test%'], - * 'age' => ['>', 18], - * 'age' => ['between', 19, 29] - * ] - * - * SQL: id = 1 or (id = 2) and title like '%test%' and age > 18 and age between 19 and 29 - * - * @return static - */ - public function whereEx(array $condition, string $logicalOperator = 'and'): self; - - /** - * where between $begin end $end. - * - * @param mixed $begin - * @param mixed $end - * - * @return static - */ - public function whereBetween(string $fieldName, $begin, $end, string $logicalOperator = 'and'): self; - - /** - * or where between $begin end $end. - * - * @param mixed $begin - * @param mixed $end - * - * @return static - */ - public function orWhereBetween(string $fieldName, $begin, $end): self; - - /** - * where not between $begin end $end. - * - * @param mixed $begin - * @param mixed $end - * - * @return static - */ - public function whereNotBetween(string $fieldName, $begin, $end, string $logicalOperator = 'and'): self; - - /** - * or where not between $begin end $end. - * - * @param mixed $begin - * @param mixed $end - * - * @return static - */ - public function orWhereNotBetween(string $fieldName, $begin, $end): self; - - /** - * 设置 where or 条件. - * - * @param mixed $value - * - * @return static - */ - public function orWhere(string $fieldName, string $operation, $value): self; - - /** - * 设置 where or 条件,用原生语句. - * - * @return static - */ - public function orWhereRaw(string $where, array $binds = []): self; - - /** - * 设置 where or 条件,传入回调,回调中的条件加括号. - * - * @return static - */ - public function orWhereBrackets(callable $callback): self; - - /** - * 设置 where or 条件,使用 IBaseWhere 结构. - * - * @return static - */ - public function orWhereStruct(IBaseWhere $where): self; - - /** - * 设置 where or 条件,支持语法参考 whereEx 方法. - * - * @return static - */ - public function orWhereEx(array $condition): self; - - /** - * where field in (list). - * - * @return static - */ - public function whereIn(string $fieldName, array $list, string $logicalOperator = 'and'): self; - - /** - * or where field in (list). - * - * @return static - */ - public function orWhereIn(string $fieldName, array $list): self; - - /** - * where field not in (list). - * - * @return static - */ - public function whereNotIn(string $fieldName, array $list, string $logicalOperator = 'and'): self; - - /** - * or where field not in (list). - * - * @return static - */ - public function orWhereNotIn(string $fieldName, array $list): self; - - /** - * where field is null. - * - * @return static - */ - public function whereIsNull(string $fieldName, string $logicalOperator = 'and'): self; - - /** - * or where field is null. - * - * @return static - */ - public function orWhereIsNull(string $fieldName): self; - - /** - * where field is not null. - * - * @return static - */ - public function whereIsNotNull(string $fieldName, string $logicalOperator = 'and'): self; - - /** - * or where field is not null. - * - * @return static - */ - public function orWhereIsNotNull(string $fieldName): self; - /** * join. * diff --git a/src/Db/Query/Interfaces/IWhereCollector.php b/src/Db/Query/Interfaces/IWhereCollector.php new file mode 100644 index 0000000000..452efc7ef6 --- /dev/null +++ b/src/Db/Query/Interfaces/IWhereCollector.php @@ -0,0 +1,13 @@ +whereBrackets(fn () => $this->parseWhereEx($condition), $logicalOperator); - } - - protected function parseWhereEx(array $condition): array - { - $result = []; - foreach ($condition as $key => $value) - { - if (null === LogicalOperator::getText(strtolower($key))) - { - // 条件 k => v - if (\is_array($value)) - { - $operator = strtolower($value[0] ?? ''); - switch ($operator) - { - case 'between': - if (!isset($value[2])) - { - throw new \RuntimeException('Between must have 3 params'); - } - $result[] = new Where($key, 'between', [$value[1], $value[2]]); - break; - case 'not between': - if (!isset($value[2])) - { - throw new \RuntimeException('Not between must have 3 params'); - } - $result[] = new Where($key, 'not between', [$value[1], $value[2]]); - break; - case 'in': - if (!isset($value[1])) - { - throw new \RuntimeException('In must have 3 params'); - } - $result[] = new Where($key, 'in', $value[1]); - break; - case 'not in': - if (!isset($value[1])) - { - throw new \RuntimeException('Not in must have 3 params'); - } - $result[] = new Where($key, 'not in', $value[1]); - break; - default: - $result[] = new Where($key, $operator, $value[1]); - break; - } - } - else - { - $result[] = new Where($key, '=', $value); - } - } - else - { - // 逻辑运算符 - $result[] = new WhereBrackets(fn () => $this->parseWhereEx($value), $key); - } - } - - return $result; - } - - /** - * {@inheritDoc} - */ - public function whereBetween(string $fieldName, $begin, $end, string $logicalOperator = LogicalOperator::AND): self - { - return $this->where($fieldName, 'between', [$begin, $end], $logicalOperator); - } - - /** - * {@inheritDoc} - */ - public function orWhereBetween(string $fieldName, $begin, $end): self - { - return $this->where($fieldName, 'between', [$begin, $end], LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function whereNotBetween(string $fieldName, $begin, $end, string $logicalOperator = LogicalOperator::AND): self - { - return $this->where($fieldName, 'not between', [$begin, $end], $logicalOperator); - } - - /** - * {@inheritDoc} - */ - public function orWhereNotBetween(string $fieldName, $begin, $end): self - { - return $this->where($fieldName, 'not between', [$begin, $end], LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function orWhere(string $fieldName, string $operation, $value): self - { - return $this->where($fieldName, $operation, $value, LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function orWhereRaw(string $where, array $binds = []): self - { - return $this->whereRaw($where, LogicalOperator::OR, $binds); - } - - /** - * {@inheritDoc} - */ - public function orWhereBrackets(callable $callback): self - { - return $this->whereBrackets($callback, LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function orWhereStruct(IBaseWhere $where): self - { - return $this->whereStruct($where, LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function orWhereEx(array $condition): self - { - return $this->whereEx($condition, LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function whereIn(string $fieldName, array $list, string $logicalOperator = LogicalOperator::AND): self - { - return $this->where($fieldName, 'in', $list, $logicalOperator); - } - - /** - * {@inheritDoc} - */ - public function orWhereIn(string $fieldName, array $list): self - { - return $this->where($fieldName, 'in', $list, LogicalOperator::OR); - } - - /** - * {@inheritDoc} - */ - public function whereNotIn(string $fieldName, array $list, string $logicalOperator = LogicalOperator::AND): self - { - return $this->where($fieldName, 'not in', $list, $logicalOperator); - } - - /** - * {@inheritDoc} - */ - public function orWhereNotIn(string $fieldName, array $list): self - { - return $this->where($fieldName, 'not in', $list, LogicalOperator::OR); - } - /** * {@inheritDoc} */ @@ -555,14 +380,6 @@ public function whereIsNull(string $fieldName, string $logicalOperator = Logical return $this->whereRaw($this->fieldQuote($fieldName) . ' is null', $logicalOperator); } - /** - * {@inheritDoc} - */ - public function orWhereIsNull(string $fieldName): self - { - return $this->whereIsNull($fieldName, LogicalOperator::OR); - } - /** * {@inheritDoc} */ @@ -571,14 +388,6 @@ public function whereIsNotNull(string $fieldName, string $logicalOperator = Logi return $this->whereRaw($this->fieldQuote($fieldName) . ' is not null', $logicalOperator); } - /** - * {@inheritDoc} - */ - public function orWhereIsNotNull(string $fieldName): self - { - return $this->whereIsNotNull($fieldName, LogicalOperator::OR); - } - /** * {@inheritDoc} */ diff --git a/src/Db/Query/Traits/TWhereCollector.php b/src/Db/Query/Traits/TWhereCollector.php new file mode 100644 index 0000000000..ef58622a1b --- /dev/null +++ b/src/Db/Query/Traits/TWhereCollector.php @@ -0,0 +1,207 @@ +whereBrackets(fn () => $this->parseWhereEx($condition), $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function whereBetween(string $fieldName, $begin, $end, string $logicalOperator = LogicalOperator::AND): self + { + return $this->where($fieldName, 'between', [$begin, $end], $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function orWhereBetween(string $fieldName, $begin, $end): self + { + return $this->where($fieldName, 'between', [$begin, $end], LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function whereNotBetween(string $fieldName, $begin, $end, string $logicalOperator = LogicalOperator::AND): self + { + return $this->where($fieldName, 'not between', [$begin, $end], $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function orWhereNotBetween(string $fieldName, $begin, $end): self + { + return $this->where($fieldName, 'not between', [$begin, $end], LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhere(string $fieldName, string $operation, $value): self + { + return $this->where($fieldName, $operation, $value, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhereRaw(string $where, array $binds = []): self + { + return $this->whereRaw($where, LogicalOperator::OR, $binds); + } + + /** + * {@inheritDoc} + */ + public function orWhereBrackets(callable $callback): self + { + return $this->whereBrackets($callback, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhereStruct(IBaseWhere $where): self + { + return $this->whereStruct($where, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhereEx(array $condition): self + { + return $this->whereEx($condition, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function whereIn(string $fieldName, array $list, string $logicalOperator = LogicalOperator::AND): self + { + return $this->where($fieldName, 'in', $list, $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function orWhereIn(string $fieldName, array $list): self + { + return $this->where($fieldName, 'in', $list, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function whereNotIn(string $fieldName, array $list, string $logicalOperator = LogicalOperator::AND): self + { + return $this->where($fieldName, 'not in', $list, $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function orWhereNotIn(string $fieldName, array $list): self + { + return $this->where($fieldName, 'not in', $list, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhereIsNull(string $fieldName): self + { + return $this->whereIsNull($fieldName, LogicalOperator::OR); + } + + /** + * {@inheritDoc} + */ + public function orWhereIsNotNull(string $fieldName): self + { + return $this->whereIsNotNull($fieldName, LogicalOperator::OR); + } + + protected function parseWhereEx(array $condition): array + { + $result = []; + foreach ($condition as $key => $value) + { + if (null === LogicalOperator::getText(strtolower($key))) + { + // 条件 k => v + if (\is_array($value)) + { + $operator = strtolower($value[0] ?? ''); + switch ($operator) + { + case 'between': + if (!isset($value[2])) + { + throw new \RuntimeException('Between must have 3 params'); + } + $result[] = new Where($key, 'between', [$value[1], $value[2]]); + break; + case 'not between': + if (!isset($value[2])) + { + throw new \RuntimeException('Not between must have 3 params'); + } + $result[] = new Where($key, 'not between', [$value[1], $value[2]]); + break; + case 'in': + if (!isset($value[1])) + { + throw new \RuntimeException('In must have 3 params'); + } + $result[] = new Where($key, 'in', $value[1]); + break; + case 'not in': + if (!isset($value[1])) + { + throw new \RuntimeException('Not in must have 3 params'); + } + $result[] = new Where($key, 'not in', $value[1]); + break; + default: + $result[] = new Where($key, $operator, $value[1]); + break; + } + } + else + { + $result[] = new Where($key, '=', $value); + } + } + else + { + // 逻辑运算符 + $result[] = new WhereBrackets(fn () => $this->parseWhereEx($value), $key); + } + } + + return $result; + } +} diff --git a/src/Db/Query/Where/WhereBrackets.php b/src/Db/Query/Where/WhereBrackets.php index 8a8392763b..0832d292e0 100644 --- a/src/Db/Query/Where/WhereBrackets.php +++ b/src/Db/Query/Where/WhereBrackets.php @@ -9,6 +9,7 @@ use Imi\Db\Query\Interfaces\IQuery; use Imi\Db\Query\Interfaces\IWhereBrackets; use Imi\Db\Query\Traits\TRaw; +use Imi\Db\Query\WhereCollector; class WhereBrackets extends BaseWhere implements IWhereBrackets { @@ -53,7 +54,8 @@ public function toStringWithoutLogic(IQuery $query): string return $this->rawSQL; } $binds = &$this->binds; - $callResult = ($this->callback)($query); + $whereCollector = new WhereCollector($query); + $callResult = ($this->callback)($query, $whereCollector); if (\is_array($callResult)) { if (empty($callResult)) @@ -68,17 +70,21 @@ public function toStringWithoutLogic(IQuery $query): string { if (0 === $i) { - $result .= $callResultItem->toStringWithoutLogic($query) . ' '; + $result .= $callResultItem->toStringWithoutLogic($query); } else { - $result .= $callResultItem->getLogicalOperator() . ' ' . $callResultItem->toStringWithoutLogic($query) . ' '; + $result .= ' ' . $callResultItem->getLogicalOperator() . ' ' . $callResultItem->toStringWithoutLogic($query); } $binds = array_merge($binds, $callResultItem->getBinds()); } else { - $result .= $callResultItem . ' '; + if ($i > 0) + { + $result .= ' '; + } + $result .= $callResultItem; } } @@ -91,6 +97,24 @@ public function toStringWithoutLogic(IQuery $query): string return '(' . $result . ')'; } + elseif (null === $callResult) + { + $result = '('; + foreach ($whereCollector->getWhere() as $i => $where) + { + if (0 === $i) + { + $result .= $where->toStringWithoutLogic($query); + } + else + { + $result .= ' ' . $where->getLogicalOperator() . ' ' . $where->toStringWithoutLogic($query); + } + $binds = array_merge($binds, $where->getBinds()); + } + + return $result . ')'; + } else { return '(' . $callResult . ')'; diff --git a/src/Db/Query/WhereCollector.php b/src/Db/Query/WhereCollector.php new file mode 100644 index 0000000000..8412f922cd --- /dev/null +++ b/src/Db/Query/WhereCollector.php @@ -0,0 +1,98 @@ +query = $query; + } + + /** + * @return IBaseWhere[] + */ + public function getWhere(): array + { + return $this->where; + } + + /** + * {@inheritDoc} + */ + public function where(string $fieldName, string $operation, $value, string $logicalOperator = LogicalOperator::AND): self + { + $this->where[] = new Where($fieldName, $operation, $value, $logicalOperator); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function whereRaw(string $raw, string $logicalOperator = LogicalOperator::AND, array $binds = []): self + { + $where = new Where(); + $where->useRaw(); + $where->setRawSQL($raw, $binds); + $where->setLogicalOperator($logicalOperator); + $this->where[] = $where; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function whereBrackets(callable $callback, string $logicalOperator = LogicalOperator::AND): self + { + $this->where[] = new WhereBrackets($callback, $logicalOperator); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function whereStruct(IBaseWhere $where, string $logicalOperator = LogicalOperator::AND): self + { + $this->where[] = $where; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function whereIsNull(string $fieldName, string $logicalOperator = LogicalOperator::AND): self + { + return $this->whereRaw($this->query->fieldQuote($fieldName) . ' is null', $logicalOperator); + } + + /** + * {@inheritDoc} + */ + public function whereIsNotNull(string $fieldName, string $logicalOperator = LogicalOperator::AND): self + { + return $this->whereRaw($this->query->fieldQuote($fieldName) . ' is not null', $logicalOperator); + } +} diff --git a/tests/unit/Component/Tests/Db/Mysqli/QueryCurdTest.php b/tests/unit/Component/Tests/Db/Mysqli/QueryCurdTest.php index f7eec90603..a387f6a987 100644 --- a/tests/unit/Component/Tests/Db/Mysqli/QueryCurdTest.php +++ b/tests/unit/Component/Tests/Db/Mysqli/QueryCurdTest.php @@ -23,7 +23,7 @@ class QueryCurdTest extends QueryCurdBaseTest * * @var string */ - protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2) ) )'; + protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2)))'; /** * 测试 JSON 查询的 SQL. diff --git a/tests/unit/Component/Tests/Db/Pdo/QueryCurdTest.php b/tests/unit/Component/Tests/Db/Pdo/QueryCurdTest.php index c64f9ed89d..e54ae153a7 100644 --- a/tests/unit/Component/Tests/Db/Pdo/QueryCurdTest.php +++ b/tests/unit/Component/Tests/Db/Pdo/QueryCurdTest.php @@ -23,7 +23,7 @@ class QueryCurdTest extends QueryCurdBaseTest * * @var string */ - protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2) ) )'; + protected $expectedTestWhereExSql = 'select * from `tb_article` where (`id` = :p1 and (`id` in (:p2)))'; /** * 测试 JSON 查询的 SQL. diff --git a/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php b/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php index 3be958e0e2..881247efe2 100644 --- a/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php +++ b/tests/unit/Component/Tests/Db/QueryCurdBaseTest.php @@ -5,11 +5,14 @@ namespace Imi\Test\Component\Tests\Db; use Imi\Db\Db; +use Imi\Db\Mysql\Consts\LogicalOperator; use Imi\Db\Mysql\Query\FullText\MysqlFullTextOptions; use Imi\Db\Mysql\Query\FullText\SearchModifier; use Imi\Db\Mysql\Query\Lock\MysqlLock; use Imi\Db\Mysql\Query\Pagination\BigTablePagination; use Imi\Db\Query\Database; +use Imi\Db\Query\Interfaces\IQuery; +use Imi\Db\Query\Interfaces\IWhereCollector; use Imi\Db\Query\Raw; use Imi\Db\Query\Where\Where; use Imi\Test\BaseTest; @@ -431,6 +434,32 @@ public function testDelete(): void Assert::assertNull($record); } + public function testWhere(): void + { + $query = Db::query($this->poolName); + $sql = $query->from($this->tableArticle)->where('where', '=', 1) + ->orWhere('orWhere', '=', 2) + ->whereBetween('whereBetween', 1, 2) + ->orWhereBetween('orWhereBetween', 1, 2) + ->whereNotBetween('whereNotBetween', 1, 2) + ->orWhereNotBetween('orWhereNotBetween', 1, 2) + ->whereIn('whereIn', [1, 2]) + ->orWhereIn('orWhereIn', [1, 2]) + ->whereNotIn('whereNotIn', [1, 2]) + ->orWhereNotIn('orWhereNotIn', [1, 2]) + ->whereIsNull('whereIsNull') + ->orWhereIsNull('orWhereIsNull') + ->whereIsNotNull('whereIsNotNull') + ->orWhereIsNotNull('orWhereIsNotNull') + ->whereRaw('whereRaw = 1') + ->orWhereRaw('orWhereRaw = 2') + ->whereStruct(new Where('whereStruct', '=', 1)) + ->buildSelectSql(); + Assert::assertEquals(<<fullTableArticle}` where `where` = :p1 or `orWhere` = :p2 and `whereBetween` between :p3 and :p4 or `orWhereBetween` between :p5 and :p6 and `whereNotBetween` not between :p7 and :p8 or `orWhereNotBetween` not between :p9 and :pa and `whereIn` in (:pb,:pc) or `orWhereIn` in (:pd,:pe) and `whereNotIn` not in (:pf,:p10) or `orWhereNotIn` not in (:p11,:p12) and `whereIsNull` is null or `orWhereIsNull` is null and `whereIsNotNull` is not null or `orWhereIsNotNull` is not null and whereRaw = 1 or orWhereRaw = 2 and `whereStruct` = :p13 + SQL, $sql); + } + /** * @depends testInsert */ @@ -459,6 +488,129 @@ public function testWhereEx(array $args): void Assert::assertEquals('select * from `' . $this->fullTableArticle . '`', Db::query($this->poolName)->from($this->tableArticle)->whereEx([])->select()->getSql()); } + /** + * @depends testInsert + */ + public function testWhereBrackets(array $args): void + { + ['ids' => $ids] = $args; + $id = $ids[1]; + + // 字符串 + $query = Db::query($this->poolName); + $result = $query->from($this->tableArticle)->whereBrackets(static fn () => 'id=' . $id)->select(); + $record = $result->get(); + Assert::assertEquals([ + 'id' => $id, + 'title' => 'title-insert', + 'content' => 'content-insert', + 'time' => '2019-06-21 00:00:00', + 'member_id' => 0, + ], $record); + Assert::assertEquals('select * from `tb_article` where (id=2)', $result->getSql()); + + // Where 数组 + $query = Db::query($this->poolName); + $result = $query->from($this->tableArticle)->whereBrackets(static fn () => [ + new Where('id', '=', $id), + (static function () { + $where = new Where(); + $where->setRawSQL('1=2'); + $where->setLogicalOperator(LogicalOperator::OR); + $where->useRaw(); + + return $where; + })(), + ])->select(); + $record = $result->get(); + Assert::assertEquals([ + 'id' => $id, + 'title' => 'title-insert', + 'content' => 'content-insert', + 'time' => '2019-06-21 00:00:00', + 'member_id' => 0, + ], $record); + Assert::assertEquals('select * from `tb_article` where (`id` = :p1 or 1=2)', $result->getSql()); + $query = Db::query($this->poolName); + $result = $query->from($this->tableArticle)->whereBrackets(static fn () => [ + new Where('id', '=', $id), + 'or 1=2', + ])->select(); + $record = $result->get(); + Assert::assertEquals([ + 'id' => $id, + 'title' => 'title-insert', + 'content' => 'content-insert', + 'time' => '2019-06-21 00:00:00', + 'member_id' => 0, + ], $record); + Assert::assertEquals('select * from `tb_article` where (`id` = :p1 or 1=2)', $result->getSql()); + + // Where 对象 + $query = Db::query($this->poolName); + $result = $query->from($this->tableArticle)->whereBrackets(static fn () => new Where('id', '=', $id))->select(); + $record = $result->get(); + Assert::assertEquals([ + 'id' => $id, + 'title' => 'title-insert', + 'content' => 'content-insert', + 'time' => '2019-06-21 00:00:00', + 'member_id' => 0, + ], $record); + Assert::assertEquals('select * from `tb_article` where (`id` = :p1)', $result->getSql()); + + // Where 收集器 + $query = Db::query($this->poolName); + $result = $query->from($this->tableArticle)->whereBrackets(static function (IQuery $query, IWhereCollector $where) use ($id) { + $where->where('id', '=', $id)->orWhereRaw('1=2'); + })->select(); + $record = $result->get(); + Assert::assertEquals([ + 'id' => $id, + 'title' => 'title-insert', + 'content' => 'content-insert', + 'time' => '2019-06-21 00:00:00', + 'member_id' => 0, + ], $record); + Assert::assertEquals('select * from `tb_article` where (`id` = :p1 or 1=2)', $result->getSql()); + + $query = Db::query($this->poolName); + $sql = $query->from($this->tableArticle)->whereBrackets(static function (IQuery $query, IWhereCollector $where) { + $where->where('where', '=', 1) + ->orWhere('orWhere', '=', 2) + ->whereBetween('whereBetween', 1, 2) + ->orWhereBetween('orWhereBetween', 1, 2) + ->whereNotBetween('whereNotBetween', 1, 2) + ->orWhereNotBetween('orWhereNotBetween', 1, 2) + ->whereIn('whereIn', [1, 2]) + ->orWhereIn('orWhereIn', [1, 2]) + ->whereNotIn('whereNotIn', [1, 2]) + ->orWhereNotIn('orWhereNotIn', [1, 2]) + ->whereIsNull('whereIsNull') + ->orWhereIsNull('orWhereIsNull') + ->whereIsNotNull('whereIsNotNull') + ->orWhereIsNotNull('orWhereIsNotNull') + ->whereRaw('whereRaw = 1') + ->orWhereRaw('orWhereRaw = 2') + ->whereStruct(new Where('whereStruct', '=', 1)) + ->whereEx([ + 'whereEx' => 1, + 'and' => [ + 'whereEx' => ['in', [1]], + ], + ]) + ->orWhereEx([ + 'orWhereEx' => 1, + 'or' => [ + 'orWhereEx' => ['in', [1]], + ], + ]); + })->buildSelectSql(); + Assert::assertEquals(<<fullTableArticle}` where (`where` = :p1 or `orWhere` = :p2 and `whereBetween` between :p3 and :p4 or `orWhereBetween` between :p5 and :p6 and `whereNotBetween` not between :p7 and :p8 or `orWhereNotBetween` not between :p9 and :pa and `whereIn` in (:pb,:pc) or `orWhereIn` in (:pd,:pe) and `whereNotIn` not in (:pf,:p10) or `orWhereNotIn` not in (:p11,:p12) and `whereIsNull` is null or `orWhereIsNull` is null and `whereIsNotNull` is not null or `orWhereIsNotNull` is not null and whereRaw = 1 or orWhereRaw = 2 and `whereStruct` = :p13 and (`whereEx` = :p14 and (`whereEx` in (:p15))) or (`orWhereEx` = :p16 or (`orWhereEx` in (:p17)))) + SQL, $sql); + } + /** * @depends testInsert */