From 4d1b4583f89f01446d1a87066d3e5ce496c0b701 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 12 Nov 2024 21:56:03 +0800 Subject: [PATCH 01/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/WhereQuery.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index 19d044df..454a427b 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -48,6 +48,15 @@ public function where($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); + if (is_array($field) && is_array($field[0])) { + $where = function($query) use($field) { + foreach($field as $item) { + $query->where($item[0], $item[1], $item[2]); + } + }; + $logic = is_string($op) && 'or' == strtolower($op) ? 'OR' : 'AND'; + return $this->parseWhereExp($logic, $where, null, null); + } return $this->parseWhereExp('AND', $field, $op, $condition, $param); } @@ -91,6 +100,16 @@ public function whereOr($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); + if (is_array($field) && is_array($field[0])) { + $where = function($query) use($field) { + foreach($field as $item) { + $query->whereOr($item[0], $item[1], $item[2]); + } + }; + $logic = is_string($op) && 'or' == strtolower($op) ? 'OR' : 'AND'; + return $this->parseWhereExp($logic, $where, null, null); + } + return $this->parseWhereExp('OR', $field, $op, $condition, $param); } From 202bbd7656523e609932de11021e3579143a5199 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 13 Nov 2024 11:33:25 +0800 Subject: [PATCH 02/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/WhereQuery.php | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index 454a427b..5a3a18a3 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -48,14 +48,10 @@ public function where($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field) && is_array($field[0])) { - $where = function($query) use($field) { - foreach($field as $item) { - $query->where($item[0], $item[1], $item[2]); - } - }; - $logic = is_string($op) && 'or' == strtolower($op) ? 'OR' : 'AND'; - return $this->parseWhereExp($logic, $where, null, null); + if (is_array($field)) { + return $this->where(function ($query) use ($param, $condition, $op, $field) { + return $query->parseWhereExp('AND', $field, $op, $condition, $param); + }); } return $this->parseWhereExp('AND', $field, $op, $condition, $param); } @@ -100,14 +96,10 @@ public function whereOr($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field) && is_array($field[0])) { - $where = function($query) use($field) { - foreach($field as $item) { - $query->whereOr($item[0], $item[1], $item[2]); - } - }; - $logic = is_string($op) && 'or' == strtolower($op) ? 'OR' : 'AND'; - return $this->parseWhereExp($logic, $where, null, null); + if (is_array($field)) { + return $this->where(function ($query) use ($param, $condition, $op, $field) { + return $query->parseWhereExp('OR', $field, $op, $condition, $param); + }); } return $this->parseWhereExp('OR', $field, $op, $condition, $param); From d3881d99d27218b5d2da9cd4e9e8b5f765117600 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Sat, 23 Nov 2024 17:39:59 +0800 Subject: [PATCH 03/53] =?UTF-8?q?=E6=94=B9=E8=BF=9BgetSuffix=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Model.php | 2 +- src/db/concern/WhereQuery.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Model.php b/src/Model.php index 487e6984..1b88ef67 100644 --- a/src/Model.php +++ b/src/Model.php @@ -392,7 +392,7 @@ public function setSuffix(string $suffix) */ public function getSuffix(): string { - return $this->suffix ?: ''; + return $this->suffix ?? ''; } /** diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index 5a3a18a3..abc898fd 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -119,6 +119,11 @@ public function whereXor($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); + if (is_array($field)) { + return $this->where(function ($query) use ($param, $condition, $op, $field) { + return $query->parseWhereExp('XOR', $field, $op, $condition, $param); + }); + } return $this->parseWhereExp('XOR', $field, $op, $condition, $param); } From e85379e18cb0ed3e215491fa43eb61f5d6886b89 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 27 Nov 2024 19:48:20 +0800 Subject: [PATCH 04/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3withJoin=E5=85=B3?= =?UTF-8?q?=E8=81=94=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/relation/OneToOne.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/relation/OneToOne.php b/src/model/relation/OneToOne.php index bfa3cd29..8424c784 100644 --- a/src/model/relation/OneToOne.php +++ b/src/model/relation/OneToOne.php @@ -92,7 +92,7 @@ public function eagerly(Query $query, string $relation, $field = true, string $j // 预载入封装 $joinTable = $this->query->getTable(); - $joinAlias = $relation; + $joinAlias = Str::snake($relation); $joinType = $joinType ?: $this->joinType; $query->via($joinAlias); @@ -127,7 +127,7 @@ public function eagerly(Query $query, string $relation, $field = true, string $j } $query->join([$joinTable => $joinAlias], $joinOn, $joinType) - ->tableField($field, $joinTable, $joinAlias, $relation . '__'); + ->tableField($field, $joinTable, $joinAlias, $joinAlias . '__'); } /** @@ -275,8 +275,8 @@ protected function match(string $model, string $relation, Model $result): void foreach ($result->getData() as $key => $val) { if (str_contains($key, '__')) { [$name, $attr] = explode('__', $key, 2); - if ($name == $relation) { - $list[$name][$attr] = $val; + if ($name == Str::snake($relation)) { + $list[$relation][$attr] = $val; unset($result->$key); } } From 51f0c35f76c1d178e37d8e11e13ec3cb28790ebc Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 2 Dec 2024 15:00:54 +0800 Subject: [PATCH 05/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Where=E7=B1=BB=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/Where.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/Where.php b/src/db/Where.php index bce9f045..af0783da 100644 --- a/src/db/Where.php +++ b/src/db/Where.php @@ -168,7 +168,7 @@ public function offsetUnset(mixed $name): void $this->__unset($name); } - public function offsetGet(mixed $name) + public function offsetGet(mixed $name): mixed { return $this->__get($name); } From d878b90ead979baaf22839e870b788d92d3a2009 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 4 Dec 2024 11:48:45 +0800 Subject: [PATCH 06/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3suffix=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/ModelRelationQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/db/concern/ModelRelationQuery.php b/src/db/concern/ModelRelationQuery.php index 9f145bc1..f83b857b 100644 --- a/src/db/concern/ModelRelationQuery.php +++ b/src/db/concern/ModelRelationQuery.php @@ -669,6 +669,10 @@ protected function resultToModel(array &$result): void $this->options ); + if ($this->suffix) { + $result->setSuffix($this->suffix); + } + // 模型数据处理 foreach ($this->options['filter'] as $filter) { call_user_func_array($filter, [$result, $this->options]); From 15255d63b24e54ec4c35e36e5fd7aacb884ab5c4 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Dec 2024 11:20:22 +0800 Subject: [PATCH 07/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3where=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/WhereQuery.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index abc898fd..c390f3ed 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -37,6 +37,8 @@ public function where($field, $op = null, $condition = null) } elseif (true === $field || 1 === $field) { $this->options['where']['AND'][] = true; + return $this; + } elseif (empty($field)) { return $this; } @@ -96,7 +98,7 @@ public function whereOr($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field)) { + if (is_array($field) && !empty($field)) { return $this->where(function ($query) use ($param, $condition, $op, $field) { return $query->parseWhereExp('OR', $field, $op, $condition, $param); }); @@ -119,7 +121,7 @@ public function whereXor($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field)) { + if (is_array($field) && !empty($field)) { return $this->where(function ($query) use ($param, $condition, $op, $field) { return $query->parseWhereExp('XOR', $field, $op, $condition, $param); }); From d6e23e6d6d090b95f7b80b4e57d5394d2c220a70 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 5 Dec 2024 15:09:52 +0800 Subject: [PATCH 08/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=88=86=E9=A1=B5toarr?= =?UTF-8?q?ay=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Paginator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Paginator.php b/src/Paginator.php index 35317921..124de5f7 100644 --- a/src/Paginator.php +++ b/src/Paginator.php @@ -531,6 +531,7 @@ public function toArray(): array 'current_page' => $this->currentPage(), 'last_page' => $this->lastPage, 'data' => $this->items->toArray(), + 'has_more' => $this->hasMore, ]; } From b75d534d8fa01122a7197f65c868b19f52a69fbb Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 10 Dec 2024 21:38:40 +0800 Subject: [PATCH 09/53] =?UTF-8?q?=E6=94=B9=E8=BF=9Bwhen=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/WhereQuery.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index c390f3ed..d408fdd3 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -634,18 +634,18 @@ public function when($condition, Closure | array $query, Closure | array | null // 根据条件决定执行哪个查询 if ($condition) { - $this->executeQuery($query); + $this->executeQuery($query, $condition); } elseif ($otherwise) { - $this->executeQuery($otherwise); + $this->executeQuery($otherwise, $condition); } return $this; } - protected function executeQuery(Closure | array $query): void + protected function executeQuery(Closure | array $query, $condition): void { if ($query instanceof Closure) { - $query($this); + $query($this, $condition); } elseif (is_array($query)) { $this->where($query); } From 5667109f9a4e8124a42719de4bdd9793f81e1696 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Thu, 12 Dec 2024 16:32:37 +0800 Subject: [PATCH 10/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model.php b/src/Model.php index 1b88ef67..fbe6256f 100644 --- a/src/Model.php +++ b/src/Model.php @@ -23,7 +23,7 @@ /** * Class Model. * - * @mixin Query + * @mixin \think\db\Query * * @method static void onAfterRead(Model $model) after_read事件定义 * @method static mixed onBeforeInsert(Model $model) before_insert事件定义 From 674af0cf55185711b6b632f50ca8da936d4dc4e5 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Thu, 12 Dec 2024 16:44:36 +0800 Subject: [PATCH 11/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/BaseQuery.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/db/BaseQuery.php b/src/db/BaseQuery.php index b653e7b3..30410e47 100644 --- a/src/db/BaseQuery.php +++ b/src/db/BaseQuery.php @@ -277,7 +277,7 @@ public function getConfig(string $name = '') /** * 得到当前或者指定名称的数据表. - * @param bool $alias 是否返回数据表别名 + * @param bool $alias 是否返回数据表别名 * * @return string|array|Raw */ @@ -1411,11 +1411,7 @@ public function delete($data = null): int * * @param array $data 主键数据 * - * @throws Exception - * @throws ModelNotFoundException - * @throws DataNotFoundException - * - * @return Collection|array|static[] + * @return \think\model\Collection|\think\Collection */ public function select(array $data = []): Collection { From cfa4fa75ee70d75d96b22089103b3fcbc1f65d46 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Thu, 12 Dec 2024 16:50:46 +0800 Subject: [PATCH 12/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/BaseQuery.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/BaseQuery.php b/src/db/BaseQuery.php index 30410e47..b5d35c98 100644 --- a/src/db/BaseQuery.php +++ b/src/db/BaseQuery.php @@ -1442,13 +1442,13 @@ public function select(array $data = []): Collection * 查找单条记录. * * @param mixed $data 主键数据 - * @param Closure $closure 闭包数据 + * @param ?Closure $closure 闭包数据 * * @throws Exception * @throws ModelNotFoundException * @throws DataNotFoundException * - * @return mixed + * @return static|\think\Model|array|null */ public function find($data = null, ?Closure $closure = null) { From 1a970ac7504184b9d5befb7bddc39603e8e1a7da Mon Sep 17 00:00:00 2001 From: thinkphp Date: Fri, 13 Dec 2024 15:17:03 +0800 Subject: [PATCH 13/53] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmodel=E7=B1=BB=E5=AF=B9?= =?UTF-8?q?=E8=B0=83=E7=94=A8withAttr=E7=9A=84=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Model.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Model.php b/src/Model.php index 1b88ef67..b95c67f8 100644 --- a/src/Model.php +++ b/src/Model.php @@ -1171,6 +1171,10 @@ public function __call($method, $args) return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args); } + if ($this->exists && strtolower($method) == 'withattr') { + return call_user_func_array([$this, 'withFieldAttr'], $args); + } + return call_user_func_array([$this->db(), $method], $args); } From 5fc62141b0aeebfdb6095310b62d9e2328d53939 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Dec 2024 09:22:34 +0800 Subject: [PATCH 14/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/concern/Attribute.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/concern/Attribute.php b/src/model/concern/Attribute.php index dd12f04f..a70f56f5 100644 --- a/src/model/concern/Attribute.php +++ b/src/model/concern/Attribute.php @@ -534,9 +534,9 @@ public function getAttr(string $name) { try { $relation = false; - if (isset($this->mapping[$name])) { - // 检查字段映射 - $name = $this->mapping[$name]; + $key = array_search($name, $this->mapping); + if (is_string($key)) { + $name = $key; } $value = $this->getData($name); } catch (InvalidArgumentException $e) { From 8d305da35664a64e4ab2a7faaaf6ed301c482651 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Dec 2024 09:30:21 +0800 Subject: [PATCH 15/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/concern/Attribute.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/model/concern/Attribute.php b/src/model/concern/Attribute.php index a70f56f5..0f2a1f1b 100644 --- a/src/model/concern/Attribute.php +++ b/src/model/concern/Attribute.php @@ -430,10 +430,7 @@ public function setAttrs(array $data): void public function setAttr(string $name, $value, array $data = []): void { if ($this->mapping) { - $key = array_search($name, $this->mapping); - if (is_string($key)) { - $name = $key; - } + $name = array_search($name, $this->mapping) ?: $name; } $name = $this->getRealFieldName($name); @@ -534,9 +531,8 @@ public function getAttr(string $name) { try { $relation = false; - $key = array_search($name, $this->mapping); - if (is_string($key)) { - $name = $key; + if ($this->mapping) { + $name = array_search($name, $this->mapping) ?: $name; } $value = $this->getData($name); } catch (InvalidArgumentException $e) { From a96c1274dd8572ff7d6d82639b904a19ade684e6 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Dec 2024 11:47:15 +0800 Subject: [PATCH 16/53] =?UTF-8?q?Db=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AD=97=E6=AE=B5=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/ModelRelationQuery.php | 2 +- src/db/concern/ResultOperation.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/db/concern/ModelRelationQuery.php b/src/db/concern/ModelRelationQuery.php index f83b857b..26dd178d 100644 --- a/src/db/concern/ModelRelationQuery.php +++ b/src/db/concern/ModelRelationQuery.php @@ -714,7 +714,7 @@ protected function resultToModel(array &$result): void } foreach (['hidden', 'visible', 'append'] as $name) { - if (isset($this->options[$name])) { + if (!empty($this->options[$name])) { [$value, $merge] = $this->options[$name]; $result->$name($value, $merge); } diff --git a/src/db/concern/ResultOperation.php b/src/db/concern/ResultOperation.php index c0df62d7..0dea8057 100644 --- a/src/db/concern/ResultOperation.php +++ b/src/db/concern/ResultOperation.php @@ -105,6 +105,16 @@ protected function result(array &$result): void if (!empty($this->options['with_attr'])) { $this->getResultAttr($result, $this->options['with_attr']); } + + // 检查字段映射 + if (!empty($this->options['mapping'])) { + foreach ($this->options['mapping'] as $name => $alias) { + if (isset($result[$name])) { + $result[$alias] = $result[$name]; + unset($result[$name]); + } + } + } } /** From 4556263e1d7884e33a57099a41a6615a417053c9 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 18 Dec 2024 19:11:57 +0800 Subject: [PATCH 17/53] =?UTF-8?q?=E6=94=B9=E8=BF=9Bwhere=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/concern/WhereQuery.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index d408fdd3..13cbc2db 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -50,7 +50,7 @@ public function where($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field)) { + if (is_array($field) && !empty($field) && array_is_list($field)) { return $this->where(function ($query) use ($param, $condition, $op, $field) { return $query->parseWhereExp('AND', $field, $op, $condition, $param); }); @@ -98,7 +98,7 @@ public function whereOr($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field) && !empty($field)) { + if (is_array($field) && !empty($field) && array_is_list($field)) { return $this->where(function ($query) use ($param, $condition, $op, $field) { return $query->parseWhereExp('OR', $field, $op, $condition, $param); }); @@ -121,7 +121,7 @@ public function whereXor($field, $op = null, $condition = null) $param = func_get_args(); array_shift($param); - if (is_array($field) && !empty($field)) { + if (is_array($field) && !empty($field) && array_is_list($field)) { return $this->where(function ($query) use ($param, $condition, $op, $field) { return $query->parseWhereExp('XOR', $field, $op, $condition, $param); }); From 6e0ea679f7448ff9c8906606462505597681e22e Mon Sep 17 00:00:00 2001 From: thinkphp Date: Thu, 19 Dec 2024 09:52:44 +0800 Subject: [PATCH 18/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=A0=87=E8=AF=86=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/Query.php | 24 ++++++++++++++++++++++-- src/db/concern/WhereQuery.php | 21 ++++++++++++--------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/db/Query.php b/src/db/Query.php index c8a05721..bf5f0ca6 100644 --- a/src/db/Query.php +++ b/src/db/Query.php @@ -13,7 +13,9 @@ namespace think\db; +use Closure; use PDOStatement; +use ReflectionFunction; use think\db\exception\DbException as Exception; /** @@ -518,11 +520,29 @@ protected function getLazyFieldCacheKey(string $field, $id = null): string public function getQueryGuid($data = null): string { if (null === $data) { - $data = $this->options; + $data = $this->options; + $data['table'] = $this->getConfig('database') . $this->getTable(); unset($data['scope'], $data['default_model']); + foreach (['AND', 'OR', 'XOR'] as $logic) { + if (isset($data['where'][$logic])) { + foreach ($data['where'][$logic] as $key => $val) { + if ($val instanceof Closure) { + $reflection = new ReflectionFunction($val); + $properties = $reflection->getStaticVariables(); + if (empty($properties)) { + $name = $reflection->getName() . $reflection->getStartLine() . '-' . $reflection->getEndLine(); + } else { + $name = var_export($properties, true); + } + $data['Closure'][] = $name; + unset($data['where'][$logic][$key]); + } + } + } + } } - return md5($this->getConfig('database') . serialize(var_export($data, true)) . serialize($this->getBind(false))); + return md5(serialize(var_export($data, true)) . serialize($this->getBind(false))); } /** diff --git a/src/db/concern/WhereQuery.php b/src/db/concern/WhereQuery.php index 13cbc2db..3eaae5b0 100644 --- a/src/db/concern/WhereQuery.php +++ b/src/db/concern/WhereQuery.php @@ -47,15 +47,16 @@ public function where($field, $op = null, $condition = null) $this->options['key'] = is_null($condition) ? $op : $condition; } + $logic = 'AND'; $param = func_get_args(); array_shift($param); if (is_array($field) && !empty($field) && array_is_list($field)) { - return $this->where(function ($query) use ($param, $condition, $op, $field) { - return $query->parseWhereExp('AND', $field, $op, $condition, $param); + return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) { + return $query->parseWhereExp($logic, $field, $op, $condition, $param); }); } - return $this->parseWhereExp('AND', $field, $op, $condition, $param); + return $this->parseWhereExp($logic, $field, $op, $condition, $param); } /** @@ -95,16 +96,17 @@ protected function parseQueryWhere(BaseQuery $query): void */ public function whereOr($field, $op = null, $condition = null) { + $logic = 'OR'; $param = func_get_args(); array_shift($param); if (is_array($field) && !empty($field) && array_is_list($field)) { - return $this->where(function ($query) use ($param, $condition, $op, $field) { - return $query->parseWhereExp('OR', $field, $op, $condition, $param); + return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) { + return $query->parseWhereExp($logic, $field, $op, $condition, $param); }); } - return $this->parseWhereExp('OR', $field, $op, $condition, $param); + return $this->parseWhereExp($logic, $field, $op, $condition, $param); } /** @@ -118,15 +120,16 @@ public function whereOr($field, $op = null, $condition = null) */ public function whereXor($field, $op = null, $condition = null) { + $logic = 'XOR'; $param = func_get_args(); array_shift($param); if (is_array($field) && !empty($field) && array_is_list($field)) { - return $this->where(function ($query) use ($param, $condition, $op, $field) { - return $query->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this->where(function ($query) use ($param, $condition, $op, $field, $logic) { + return $query->parseWhereExp($logic, $field, $op, $condition, $param); }); } - return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this->parseWhereExp($logic, $field, $op, $condition, $param); } /** From 47804c47c7a8b2bccfa694448bc769d3edac0290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E6=B4=9B=E6=B4=9B?= <1031601644@qq.com> Date: Wed, 25 Dec 2024 16:59:23 +0800 Subject: [PATCH 19/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwhere=E9=97=AD=E5=8C=85?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E4=B8=AD=E7=9A=84=E5=9B=9E=E8=B0=83=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1$query=E4=B8=A2=E5=A4=B1alias=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/BaseQuery.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/db/BaseQuery.php b/src/db/BaseQuery.php index b5d35c98..bca05b5a 100644 --- a/src/db/BaseQuery.php +++ b/src/db/BaseQuery.php @@ -177,6 +177,10 @@ public function newQuery(): BaseQuery $query->lazyFields($this->options['lazy_fields']); } + if (isset($this->options['alias'])) { + $query->alias($this->options['alias']); + } + return $query; } From 6a38636caac7e75f0b8700ed8e8b3fdef5ff73e0 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Mon, 13 Jan 2025 11:44:33 +0800 Subject: [PATCH 20/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3Raw=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E8=B5=8B=E5=80=BC=E5=90=8E=E4=BA=8C=E6=AC=A1=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E6=89=A7=E8=A1=8C=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/concern/Attribute.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/model/concern/Attribute.php b/src/model/concern/Attribute.php index 0f2a1f1b..105d2840 100644 --- a/src/model/concern/Attribute.php +++ b/src/model/concern/Attribute.php @@ -374,6 +374,10 @@ public function getChangedData(): array return 1; } + if ($b instanceof Raw) { + return 0; + } + return is_object($a) || $a != $b ? 1 : 0; }); From 715e55da149fe32a12d68ef10e5b00e70bd3dbec Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 14 Jan 2025 14:03:33 +0800 Subject: [PATCH 21/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=A0=87=E8=AF=86=E8=87=AA=E5=8A=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/BaseQuery.php | 2 +- src/db/Connection.php | 2 +- src/db/Query.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/db/BaseQuery.php b/src/db/BaseQuery.php index bca05b5a..4ad54a68 100644 --- a/src/db/BaseQuery.php +++ b/src/db/BaseQuery.php @@ -1006,7 +1006,7 @@ public function cache($key = true, $expire = null, $tag = null) $key = true; } - $this->options['cache'] = [$key, $expire, $tag ?: $this->getTable()]; + $this->options['cache'] = [$key, $expire, $tag ?: var_export($this->getTable(), true)]; return $this; } diff --git a/src/db/Connection.php b/src/db/Connection.php index 8a418f59..37a7a002 100644 --- a/src/db/Connection.php +++ b/src/db/Connection.php @@ -320,7 +320,7 @@ protected function cacheData(CacheItem $cacheItem) protected function getCacheKey(BaseQuery $query, string $method = ''): string { if (!empty($query->getOptions('key')) && empty($method)) { - $key = 'think_' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key'); + $key = 'think_' . $this->getConfig('database') . '.' . var_export($query->getTable(), true) . '|' . $query->getOptions('key'); } else { $key = $query->getQueryGuid(); } diff --git a/src/db/Query.php b/src/db/Query.php index bf5f0ca6..854ace4c 100644 --- a/src/db/Query.php +++ b/src/db/Query.php @@ -521,7 +521,7 @@ public function getQueryGuid($data = null): string { if (null === $data) { $data = $this->options; - $data['table'] = $this->getConfig('database') . $this->getTable(); + $data['table'] = $this->getConfig('database') . var_export($this->getTable(), true); unset($data['scope'], $data['default_model']); foreach (['AND', 'OR', 'XOR'] as $logic) { if (isset($data['where'][$logic])) { From 3fca9df9c920d646518cda2f76b9fa3d7aec21fe Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 15 Jan 2025 17:52:14 +0800 Subject: [PATCH 22/53] =?UTF-8?q?=E6=94=B9=E8=BF=9Bmongo=E7=9A=84order?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/Mongo.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/db/Mongo.php b/src/db/Mongo.php index 18e97ff5..edc2af32 100644 --- a/src/db/Mongo.php +++ b/src/db/Mongo.php @@ -486,7 +486,9 @@ public function limit(int $offset, ?int $length = null) public function order($field, string $order = '') { if (is_array($field)) { - $this->options['sort'] = $field; + $this->options['sort'] = array_map(function ($val) { + return 'asc' == strtolower($val) ? 1 : -1; + }, $field); } else { $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1; } From ae9f1df17583812060d8071b559c755eeccf6b40 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 1 Apr 2025 14:09:05 +0800 Subject: [PATCH 23/53] =?UTF-8?q?=E6=94=B9=E8=BF=9B=20actions=20=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=8E=A7=E5=88=B6=E6=8F=90=E5=8D=87=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 5 ++++- .github/workflows/tests.yml | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index d4be6643..2b22254e 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -2,6 +2,9 @@ name: codecov on: [push, pull_request] +permissions: + contents: read + jobs: tests: runs-on: ubuntu-latest @@ -37,7 +40,7 @@ jobs: - name: Get composer cache directory id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache vendor uses: actions/cache@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f1c6340..bea3c2b8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,6 +2,9 @@ name: tests on: [push, pull_request] +permissions: + contents: read + jobs: phpunit: runs-on: ubuntu-latest From 52668c1b5dd828d6ade19e17b62d9b309aa2b9c2 Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 12 May 2025 11:55:04 +0800 Subject: [PATCH 24/53] =?UTF-8?q?=E5=AE=8C=E5=96=84pg=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E8=8E=B7=E5=8F=96=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 023a886f..665c4188 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -60,7 +60,7 @@ public function getFields(string $tableName): array { [$tableName] = explode(' ', $tableName); - $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra",fields_comment as "comment" from table_msg(\'' . $tableName . '\');'; $pdo = $this->getPDOStatement($sql); $result = $pdo->fetchAll(PDO::FETCH_ASSOC); $info = []; @@ -76,6 +76,7 @@ public function getFields(string $tableName): array 'default' => $val['default'], 'primary' => !empty($val['key']), 'autoinc' => str_starts_with((string) $val['extra'], 'nextval('), + 'comment' => $val['comment'], ]; } } From 3cfb585fc500b6b41012320d69656917d329fac2 Mon Sep 17 00:00:00 2001 From: auooru Date: Sun, 18 May 2025 23:53:26 +0800 Subject: [PATCH 25/53] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20pgsql=20=E5=8D=95?= =?UTF-8?q?=E6=B5=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit.xml | 9 ++ tests/bootstrap.php | 24 +++++ tests/functions.php | 73 ++++++++++++++ tests/orm/{DbTest.php => BaseDbTest.php} | 121 ++++++++++++----------- tests/orm/MysqlDbTest.php | 9 ++ tests/orm/PgsqlDbTest.php | 22 +++++ 6 files changed, 199 insertions(+), 59 deletions(-) rename tests/orm/{DbTest.php => BaseDbTest.php} (53%) create mode 100644 tests/orm/MysqlDbTest.php create mode 100644 tests/orm/PgsqlDbTest.php diff --git a/phpunit.xml b/phpunit.xml index bb93b3c9..87f92af5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,6 +8,10 @@ convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerErrors="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" processIsolation="false" stopOnError="false" stopOnFailure="false" @@ -30,5 +34,10 @@ + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cce8e7d3..3d11e185 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -55,5 +55,29 @@ // 数据库调试模式 'debug' => false, ], + 'pgsql' => [ + // 数据库类型 + 'type' => 'pgsql', + // 主机地址 + 'hostname' => getenv('TESTS_DB_PGSQL_HOSTNAME'), + // 主机端口 + 'hostport' => getenv('TESTS_DB_PGSQL_HOSTPORT'), + // 数据库名 + 'database' => getenv('TESTS_DB_PGSQL_DATABASE'), + // 用户名 + 'username' => getenv('TESTS_DB_PGSQL_USERNAME'), + // 密码 + 'password' => getenv('TESTS_DB_PGSQL_PASSWORD'), + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'test_', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + // 数据库调试模式 + 'debug' => false, + ], ], ]); diff --git a/tests/functions.php b/tests/functions.php index 35bc7c71..f0dc2be7 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -54,3 +54,76 @@ function mysql_kill_connection(string $name, $cid) { Db::connect($name)->execute("KILL {$cid}"); } + +global $pg_func_installed; +$pg_func_installed = []; + +function pg_server_version(string $name = 'pgsql'): string +{ + $pdo = Db::connect($name)->connect(); + $version = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION); + + return explode(' ', $version)[0]; +} + +function pg_install_func(string $name = 'pgsql'): void +{ + global $pg_func_installed; + + if ($pg_func_installed[$name] ?? false) { + return; + } + + /** @var \PDO $pdo */ + $pdo = Db::connect($name)->connect(); + + $file_path = version_compare(pg_server_version($name), '12.0', '>=') + ? __DIR__ . '/../src/db/connector/pgsql12.sql' + : __DIR__ . '/../src/db/connector/pgsql.sql'; + + $content = file_get_contents($file_path); + $statements = preg_split('/;\s*(?=CREATE|COMMENT|DROP)/i', $content); + + $pdo->beginTransaction(); + try { + foreach ($statements as $stmt) { + $stmt = trim($stmt); + if (!empty($stmt)) { + $pdo->exec($stmt); + } + } + $pdo->commit(); + } catch (\Throwable $exception) { + $pdo->rollBack(); + throw $exception; + } + + $pg_func_installed[$name] = true; +} + +function pg_reset_function(string $name = 'pgsql'): void +{ + global $pg_func_installed; + + /** @var \PDO $pdo */ + $pdo = Db::connect($name)->connect(); + + $statements = [ + "DROP FUNCTION IF EXISTS public.table_msg(a_table_name varchar)", + "DROP FUNCTION IF EXISTS public.table_msg(a_schema_name varchar, a_table_name varchar)", + "DROP FUNCTION IF EXISTS pgsql_type(a_type varchar)", + "DROP TYPE IF EXISTS public.tablestruct CASCADE", + ]; + $pdo->beginTransaction(); + try { + foreach ($statements as $statement) { + $pdo->exec($statement); + } + $pdo->commit(); + } catch (\Throwable $exception) { + $pdo->rollBack(); + throw $exception; + } + + $pg_func_installed[$name] = false; +} diff --git a/tests/orm/DbTest.php b/tests/orm/BaseDbTest.php similarity index 53% rename from tests/orm/DbTest.php rename to tests/orm/BaseDbTest.php index e5fcdcb5..c3863c74 100644 --- a/tests/orm/DbTest.php +++ b/tests/orm/BaseDbTest.php @@ -4,6 +4,7 @@ namespace tests\orm; +use think\db\ConnectionInterface; use function array_column; use function array_keys; use function array_unique; @@ -17,102 +18,101 @@ use think\Exception as ThinkException; use think\facade\Db; -class DbTest extends Base +abstract class BaseDbTest extends Base { - protected static $testUserData; + public ConnectionInterface $db; + protected string $dbName; - public static function setUpBeforeClass(): void + protected function provideTestData(): array { - Db::execute('DROP TABLE IF EXISTS `test_user`;'); - Db::execute( - <<<'SQL' -CREATE TABLE `test_user` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `type` tinyint(4) NOT NULL DEFAULT '0', - `username` varchar(32) NOT NULL, - `nickname` varchar(32) NOT NULL, - `password` varchar(64) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -SQL - ); - } - - public function setUp(): void - { - Db::execute('TRUNCATE TABLE `test_user`;'); - self::$testUserData = [ - ['id' => 1, 'type' => 3, 'username' => 'qweqwe', 'nickname' => 'asdasd', 'password' => '123123'], - ['id' => 2, 'type' => 2, 'username' => 'rtyrty', 'nickname' => 'fghfgh', 'password' => '456456'], + return [ + ['id' => 1, 'type' => 3, 'username' => 'qw"e"qwe', 'nickname' => 'asdasd', 'password' => '123123'], + ['id' => 2, 'type' => 2, 'username' => 'rt"yrty', 'nickname' => 'fghfgh', 'password' => '456456'], ['id' => 3, 'type' => 1, 'username' => 'uiouio', 'nickname' => 'jkljkl', 'password' => '789789'], ['id' => 5, 'type' => 2, 'username' => 'qazqaz', 'nickname' => 'wsxwsx', 'password' => '098098'], ['id' => 7, 'type' => 2, 'username' => 'rfvrfv', 'nickname' => 'tgbtgb', 'password' => '765765'], ]; - Db::table('test_user')->insertAll(self::$testUserData); } - public function testColumn() + public function setUp(): void { - $users = self::$testUserData; + $this->db = Db::connect($this->dbName); + } + public function testInitUsers(): array + { + $this->db->execute('TRUNCATE TABLE test_user;'); + + $userData = $this->provideTestData(); + $this->db->table('test_user')->insertAll($userData); + + return $userData; + } + + /** + * @depends testInitUsers + */ + public function testColumn(array $users) + { // 获取全部列 - $result = Db::table('test_user')->column('*', 'id'); + $result = $this->db->table('test_user')->column('*', 'id'); $this->assertCount(5, $result); $this->assertEquals($users, array_values($result)); $this->assertEquals(array_column($users, 'id'), array_keys($result)); // 获取某一个字段 - $result = Db::table('test_user')->column('username'); + $result = $this->db->table('test_user')->column('username'); $this->assertEquals(array_column($users, 'username'), $result); // 获取某字段唯一 - $result = Db::table('test_user')->column('DISTINCT type'); + $result = $this->db->table('test_user')->order('type', 'desc')->column('DISTINCT type'); $expected = array_unique(array_column($users, 'type')); $this->assertEquals($expected, $result); // 字段别名 - $result = Db::table('test_user')->column('username as name2'); + $result = $this->db->table('test_user')->column('username as name2'); $expected = array_column($users, 'username'); $this->assertEquals($expected, $result); // 表别名 - $result = Db::table('test_user')->alias('test2')->column('test2.username'); + $result = $this->db->table('test_user')->alias('test2')->column('test2.username'); $expected = array_column($users, 'username'); $this->assertEquals($expected, $result); // 获取若干列 - $result = Db::table('test_user')->column('username,nickname', 'id'); + $result = $this->db->table('test_user')->column('username,nickname', 'id'); $expected = array_column_ex($users, ['username', 'nickname', 'id'], 'id'); $this->assertEquals($expected, $result); // 获取若干列不指定key时不报错 - $result = Db::table('test_user')->column('username,nickname,id'); + $result = $this->db->table('test_user')->column('username,nickname,id'); $expected = array_column_ex($users, ['username', 'nickname', 'id']); $this->assertEquals($expected, $result); // 数组方式获取 - $result = Db::table('test_user')->column(['username', 'nickname', 'type'], 'id'); + $result = $this->db->table('test_user')->column(['username', 'nickname', 'type'], 'id'); $expected = array_column_ex($users, ['username', 'nickname', 'type', 'id'], 'id'); $this->assertEquals($expected, $result); // 数组方式获取(单字段) - $result = Db::table('test_user')->column(['type'], 'id'); + $result = $this->db->table('test_user')->column(['type'], 'id'); $expected = array_column($users, 'type', 'id'); $this->assertEquals($expected, $result); // 数组方式获取(重命名字段) - $result = Db::table('test_user')->column(['username' => 'my_name', 'nickname'], 'id'); + $result = $this->db->table('test_user')->column(['username' => 'my_name', 'nickname'], 'id'); $expected = array_column_ex($users, ['username' => 'my_name', 'nickname', 'id'], 'id'); array_value_sort($result); array_value_sort($expected); $this->assertEquals($expected, $result); // 数组方式获取(定义表达式) - $result = Db::table('test_user') + $result = $this->db->table('test_user') ->column([ 'username' => 'my_name', 'nickname', - new Raw('`type`+1000 as type2'), + new Raw('type+1000 as type2'), ], 'id'); $expected = array_column_ex( $users, @@ -131,46 +131,49 @@ public function testColumn() $this->assertEquals($expected, $result); } - public function testWhereIn() + /** + * @depends testInitUsers + */ + public function testWhereIn(array $users) { $sqlLogs = []; Db::listen(function ($sql) use (&$sqlLogs) { $sqlLogs[] = $sql; }); - $expected = Collection::make(self::$testUserData)->whereIn('type', [1, 3])->values()->toArray(); - $result = Db::table('test_user')->whereIn('type', [1, 3])->column('*'); + $expected = Collection::make($users)->whereIn('type', [1, 3])->values()->toArray(); + $result = $this->db->table('test_user')->whereIn('type', [1, 3])->column('*'); $this->assertEquals($expected, $result); - $expected = Collection::make(self::$testUserData)->whereIn('type', [1])->values()->toArray(); - $result = Db::table('test_user')->whereIn('type', [1])->column('*'); + $expected = Collection::make($users)->whereIn('type', [1])->values()->toArray(); + $result = $this->db->table('test_user')->whereIn('type', [1])->column('*'); $this->assertEquals($expected, $result); - $expected = Collection::make(self::$testUserData)->whereIn('type', [1, ''])->values()->toArray(); - $result = Db::table('test_user')->whereIn('type', [1, ''])->column('*'); + $expected = Collection::make($users)->whereIn('type', [1, ''])->values()->toArray(); + $result = $this->db->table('test_user')->whereIn('type', [1, ''])->column('*'); $this->assertEquals($expected, $result); - $result = Db::table('test_user')->whereIn('type', [])->column('*'); + $result = $this->db->table('test_user')->whereIn('type', [])->column('*'); $this->assertEquals([], $result); - $expected = Collection::make(self::$testUserData)->whereNotIn('type', [1, 3])->values()->toArray(); - $result = Db::table('test_user')->whereNotIn('type', [1, 3])->column('*'); + $expected = Collection::make($users)->whereNotIn('type', [1, 3])->values()->toArray(); + $result = $this->db->table('test_user')->whereNotIn('type', [1, 3])->column('*'); $this->assertEquals($expected, $result); - $expected = Collection::make(self::$testUserData)->values()->toArray(); - $result = Db::table('test_user')->whereNotIn('type', [])->column('*'); + $expected = Collection::make($users)->values()->toArray(); + $result = $this->db->table('test_user')->whereNotIn('type', [])->column('*'); $this->assertEquals($expected, $result); - // 合并多余空格 - $sqlLogs = array_map(static fn ($str) => preg_replace('#\s{2,}#', ' ', $str), $sqlLogs); + // 合并多余空格,替换 "`" 是为了同时兼容 pg 与 mysql + $sqlLogs = array_map(static fn ($str) => preg_replace(['#\s{2,}#', '~`~'], [' ', ''], $str), $sqlLogs); $this->assertEquals([ - 'SELECT * FROM `test_user` WHERE `type` IN (1,3)', - 'SELECT * FROM `test_user` WHERE `type` = 1', - 'SELECT * FROM `test_user` WHERE `type` IN (1,0)', - 'SELECT * FROM `test_user` WHERE 0 = 1', - 'SELECT * FROM `test_user` WHERE `type` NOT IN (1,3)', - 'SELECT * FROM `test_user` WHERE 1 = 1', + 'SELECT * FROM test_user WHERE type IN (1,3)', + 'SELECT * FROM test_user WHERE type = 1', + 'SELECT * FROM test_user WHERE type IN (1,0)', + 'SELECT * FROM test_user WHERE 0 = 1', + 'SELECT * FROM test_user WHERE type NOT IN (1,3)', + 'SELECT * FROM test_user WHERE 1 = 1', ], $sqlLogs); } @@ -179,7 +182,7 @@ public function testException() $this->expectException(DbException::class); try { - Db::query('wrong syntax'); + $this->db->query('wrong syntax'); } catch (DbException $exception) { $this->assertInstanceOf(ThinkException::class, $exception); diff --git a/tests/orm/MysqlDbTest.php b/tests/orm/MysqlDbTest.php new file mode 100644 index 00000000..2b2478fd --- /dev/null +++ b/tests/orm/MysqlDbTest.php @@ -0,0 +1,9 @@ +db->execute('TRUNCATE TABLE "test_user";'); + + // 当前驱动批量插入不兼容,会产生类型错误 + $userData = $this->provideTestData(); + foreach ($userData as $datum) { + $this->db->table('test_user')->insert($datum); + } + + return $userData; + } +} From 73adb88423b868d00e6ecdc26446e3304b67df24 Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 12:10:25 +0800 Subject: [PATCH 26/53] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=20pgsql=20=E5=8D=95?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/bootstrap.php | 20 ++ tests/functions.php | 36 ++++ tests/orm/BaseDbTransactionTest.php | 275 +++++++++++++++++++++++++++ tests/orm/DbTransactionTest.php | 240 ----------------------- tests/orm/ModelOneToOneTest.php | 1 + tests/orm/MysqlDbTransactionTest.php | 9 + tests/orm/PgsqlDbTransactionTest.php | 9 + 7 files changed, 350 insertions(+), 240 deletions(-) create mode 100644 tests/orm/BaseDbTransactionTest.php delete mode 100644 tests/orm/DbTransactionTest.php create mode 100644 tests/orm/MysqlDbTransactionTest.php create mode 100644 tests/orm/PgsqlDbTransactionTest.php diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3d11e185..c0387bc7 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -79,5 +79,25 @@ // 数据库调试模式 'debug' => false, ], + 'pgsql_manage' => [ + // 数据库类型 + 'type' => 'pgsql', + // 主机地址 + 'hostname' => getenv('TESTS_DB_PGSQL_HOSTNAME'), + // 主机端口 + 'hostport' => getenv('TESTS_DB_PGSQL_HOSTPORT'), + // 数据库名 + 'database' => getenv('TESTS_DB_PGSQL_DATABASE'), + // 用户名 + 'username' => getenv('TESTS_DB_PGSQL_USERNAME'), + // 密码 + 'password' => getenv('TESTS_DB_PGSQL_PASSWORD'), + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'test_', + // 数据库调试模式 + 'debug' => false, + ], ], ]); diff --git a/tests/functions.php b/tests/functions.php index f0dc2be7..a6b09a07 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -50,11 +50,47 @@ function query_mysql_connection_id(ConnectionInterface $connect): int return (int) $cid; } +function query_pgsql_connection_id(ConnectionInterface $connect): int +{ + $cid = $connect->query('SELECT pg_backend_pid() as cid')[0]['cid']; + + return (int) $cid; +} + +function query_connection_id(ConnectionInterface $connect): int +{ + if ($connect->getConfig('type') === 'mysql') { + return query_mysql_connection_id($connect); + } elseif ($connect->getConfig('type') === 'pgsql') { + return query_pgsql_connection_id($connect); + } else { + throw new \RuntimeException('Unsupported database type'); + } +} + + function mysql_kill_connection(string $name, $cid) { Db::connect($name)->execute("KILL {$cid}"); } +function pgsql_kill_connection(string $name, $cid) +{ + Db::connect($name)->execute("SELECT pg_terminate_backend({$cid})"); +} + +function kill_connection(string $name, $cid): void +{ + $connect = Db::connect($name); + if ($connect->getConfig('type') === 'mysql') { + mysql_kill_connection($name, $cid); + } elseif ($connect->getConfig('type') === 'pgsql') { + pgsql_kill_connection($name, $cid); + } else { + throw new \RuntimeException('Unsupported database type'); + } +} + global $pg_func_installed; $pg_func_installed = []; diff --git a/tests/orm/BaseDbTransactionTest.php b/tests/orm/BaseDbTransactionTest.php new file mode 100644 index 00000000..0fa28b31 --- /dev/null +++ b/tests/orm/BaseDbTransactionTest.php @@ -0,0 +1,275 @@ + 1, 'type' => 9, 'username' => '1-9-a'], + ['id' => 2, 'type' => 8, 'username' => '2-8-a'], + ['id' => 3, 'type' => 7, 'username' => '3-7-a'], + ]; + } + + public function setUp(): void + { + Db::listen(function ($sql, $time) { + echo "SQL: $sql [$time ms]\n"; + }); + $this->db = Db::connect($this->dbName, true); + $this->db->execute('TRUNCATE TABLE test_tran_a;'); + } + + protected function reconnect(): ConnectionInterface + { + return $this->db = Db::connect($this->dbName, true); + } + + protected static function insertAll(ConnectionInterface $db, string $table, array $data): void + { + if ($db instanceof Pgsql) { + foreach ($data as $datum) { + $db->table($table)->insert($datum); + } + } else { + $db->table($table)->insertAll($data); + } + } + + public function testTransaction() + { + $this->db->query('SELECT 1;'); + $this->db->table('test_tran_a')->startTrans(); + self::insertAll($this->db, 'test_tran_a', $this->provideTestData()); + $this->db->table('test_tran_a')->rollback(); + + $this->assertEmpty($this->db->table('test_tran_a')->column('*')); + + $this->db->execute('TRUNCATE TABLE test_tran_a;'); + $this->db->table('test_tran_a')->startTrans(); + self::insertAll($this->db, 'test_tran_a', $this->provideTestData()); + $this->db->table('test_tran_a')->commit(); + $this->assertEquals($this->provideTestData(), $this->db->table('test_tran_a')->column('*')); + $this->db->table('test_tran_a')->startTrans(); + $this->db->table('test_tran_a')->where('id', '=', 2)->update([ + 'username' => '2-8-b', + ]); + $this->db->table('test_tran_a')->commit(); + $this->assertEquals( + '2-8-b', + $this->db->table('test_tran_a')->where('id', '=', 2)->value('username') + ); + } + + public function testBreakReconnect() + { + // 初始化配置 + $oldConfig = Db::getConfig(); + $config = $oldConfig; + $config['connections'][$this->dbName]['break_reconnect'] = true; + $config['connections'][$this->dbName]['break_match_str'] = [ + 'query execution was interrupted', + 'no connection to the server', + ]; + Db::setConfig($config); + + $this->reconnect(); + try { + // 初始化数据 + self::insertAll($this->db, 'test_tran_a', $this->provideTestData()); + + $cid = query_connection_id($this->db); + kill_connection($this->dbName . '_manage', $cid); + // 触发重连 + $this->db->table('test_tran_a')->where('id', '=', 2)->value('username'); + + $newCid = query_connection_id($this->db); + $this->assertNotEquals($cid, $newCid); + $cid = $newCid; + + // 事务前重连 + kill_connection($this->dbName . '_manage', $cid); + $this->db->table('test_tran_a')->startTrans(); + $this->db->table('test_tran_a')->where('id', '=', 2)->update([ + 'username' => '2-8-b', + ]); + $this->db->table('test_tran_a')->commit(); + $newCid = query_connection_id($this->db); + $this->assertNotEquals($cid, $newCid); + $cid = $newCid; + $this->assertEquals( + '2-8-b', + $this->db->table('test_tran_a')->where('id', '=', 2)->value('username') + ); + + // 事务中不能重连 + try { + $this->db->table('test_tran_a')->startTrans(); + $this->db->table('test_tran_a')->where('id', '=', 2)->update([ + 'username' => '2-8-c', + ]); + kill_connection($this->dbName . '_manage', $cid); + $this->db->table('test_tran_a')->where('id', '=', 3)->update([ + 'username' => '3-7-b', + ]); + $this->db->table('test_tran_a')->commit(); + } catch (Throwable|Exception $exception) { + try { + $this->db->table('test_tran_a')->rollback(); + } catch (Exception $rollbackException) { + // Ignore exception + $this->proxyAssertMatchesRegularExpression( + $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $rollbackException->getMessage() + ); + } + // Ignore exception + $this->proxyAssertMatchesRegularExpression( + $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $exception->getMessage() + ); + } + // 预期应该没有发生任何更改 + $this->assertEquals( + '2-8-b', + $this->db->table('test_tran_a')->where('id', '=', 2)->value('username') + ); + $this->assertEquals( + '3-7-a', + $this->db->table('test_tran_a')->where('id', '=', 3)->value('username') + ); + } finally { + Db::setConfig($oldConfig); + $this->reconnect(); + } + } + + public function testTransactionSavepoint() + { + // 初始化数据 + $oldConnect = $this->db; + $oldConnect->query('select 1;'); + $newConnect = $this->reconnect(); + $newConnect->query('select 1;'); + self::assertNotEquals(spl_object_id($oldConnect), spl_object_id($newConnect)); + + self::insertAll($newConnect, 'test_tran_a', $this->provideTestData()); + + try { + $this->db->table('test_tran_a')->transaction(function () use ($newConnect, $oldConnect) { + // tran 1 + $newConnect->table('test_tran_a')->startTrans(); + $oldConnect->table('test_tran_a')->where('id', '=', 2)->update([ + 'username' => '2-8-c', + ]); + $newConnect->table('test_tran_a')->commit(); + // tran 2 + $newConnect->table('test_tran_a')->startTrans(); + $oldConnect->table('test_tran_a')->where('id', '=', 3)->update([ + 'username' => '3-7-b', + ]); + $newConnect->table('test_tran_a')->commit(); + }); + } finally { + $oldConnect->close(); + } + + // 预期变化 + $this->assertEquals( + '2-8-c', + $newConnect->table('test_tran_a')->where('id', '=', 2)->value('username') + ); + $this->assertEquals( + '3-7-b', + $newConnect->table('test_tran_a')->where('id', '=', 3)->value('username') + ); + } + + public function testTransactionSavepointBreakReconnect() + { + // 初始化配置 + $oldConfig = Db::getConfig(); + $config = $oldConfig; + $config['connections'][$this->dbName]['break_reconnect'] = true; + $config['connections'][$this->dbName]['break_match_str'] = [ + 'query execution was interrupted', + 'no connection to the server', + ]; + Db::setConfig($config); + // 初始化数据 + $oldConnect = $this->db; + $oldConnect->query('select 1;'); + $newConnect = $this->reconnect(); + $newConnect->query('select 1;'); + self::assertNotEquals(spl_object_id($oldConnect->getPdo()), spl_object_id($newConnect->getPdo())); + self::insertAll($newConnect, 'test_tran_a', $this->provideTestData()); + + // 事务中不能重连 + try { + // tran 0 + $newConnect->table('test_tran_a')->startTrans(); + $cid = query_connection_id($newConnect); + // tran 1 + $oldConnect->table('test_tran_a')->startTrans(); + $newConnect->table('test_tran_a')->where('id', '=', 2)->update([ + 'username' => '2-8-c', + ]); + $oldConnect->table('test_tran_a')->commit(); + // kill + kill_connection($this->dbName . '_manage', $cid); + // tran 2 + $oldConnect->table('test_tran_a')->startTrans(); + $newConnect->table('test_tran_a')->where('id', '=', 3)->update([ + 'username' => '3-7-b', + ]); + $oldConnect->table('test_tran_a')->commit(); + // tran 0 + $newConnect->table('test_tran_a')->commit(); + } catch (Throwable|Exception $exception) { + try { + $newConnect->table('test_tran_a')->rollback(); + } catch (Exception $rollbackException) { + // Ignore exception + $this->proxyAssertMatchesRegularExpression( + $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $rollbackException->getMessage() + ); + } + // Ignore exception + $this->proxyAssertMatchesRegularExpression( + $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $exception->getMessage() + ); + } finally { + $oldConnect->close(); + } + // 预期应该没有发生任何更改 + $this->assertEquals( + '2-8-a', + $this->db->table('test_tran_a')->where('id', '=', 2)->value('username') + ); + $this->assertEquals( + '3-7-a', + $this->db->table('test_tran_a')->where('id', '=', 3)->value('username') + ); + } +} diff --git a/tests/orm/DbTransactionTest.php b/tests/orm/DbTransactionTest.php deleted file mode 100644 index 2f6177cd..00000000 --- a/tests/orm/DbTransactionTest.php +++ /dev/null @@ -1,240 +0,0 @@ - 1, 'type' => 9, 'username' => '1-9-a'], - ['id' => 2, 'type' => 8, 'username' => '2-8-a'], - ['id' => 3, 'type' => 7, 'username' => '3-7-a'], - ]; - } - - public function testTransaction() - { - $testData = self::$testData; - $connect = Db::connect(); - - $connect->table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->insertAll($testData); - $connect->table('test_tran_a')->rollback(); - - $this->assertEmpty($connect->table('test_tran_a')->column('*')); - - $connect->execute('TRUNCATE TABLE `test_tran_a`;'); - $connect->table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->insertAll($testData); - $connect->table('test_tran_a')->commit(); - $this->assertEquals($testData, $connect->table('test_tran_a')->column('*')); - $connect->table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 2)->update([ - 'username' => '2-8-b', - ]); - $connect->table('test_tran_a')->commit(); - $this->assertEquals( - '2-8-b', - $connect->table('test_tran_a')->where('id', '=', 2)->value('username') - ); - } - - public function testBreakReconnect() - { - $testData = self::$testData; - // 初始化配置 - $config = Db::getConfig(); - $config['connections']['mysql']['break_reconnect'] = true; - $config['connections']['mysql']['break_match_str'] = [ - 'query execution was interrupted', - ]; - Db::setConfig($config); - // 初始化数据 - $connect = Db::connect(null, true); - $connect->table('test_tran_a')->insertAll($testData); - - $cid = query_mysql_connection_id($connect); - mysql_kill_connection('mysql_manage', $cid); - // 触发重连 - $connect->table('test_tran_a')->where('id', '=', 2)->value('username'); - - $newCid = query_mysql_connection_id($connect); - $this->assertNotEquals($cid, $newCid); - $cid = $newCid; - - // 事务前重连 - mysql_kill_connection('mysql_manage', $cid); - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 2)->update([ - 'username' => '2-8-b', - ]); - Db::table('test_tran_a')->commit(); - $newCid = query_mysql_connection_id($connect); - $this->assertNotEquals($cid, $newCid); - $cid = $newCid; - $this->assertEquals( - '2-8-b', - Db::table('test_tran_a')->where('id', '=', 2)->value('username') - ); - - // 事务中不能重连 - try { - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 2)->update([ - 'username' => '2-8-c', - ]); - mysql_kill_connection('mysql_manage', $cid); - $connect->table('test_tran_a')->where('id', '=', 3)->update([ - 'username' => '3-7-b', - ]); - Db::table('test_tran_a')->commit(); - } catch (Throwable|Exception $exception) { - try { - Db::table('test_tran_a')->rollback(); - } catch (Exception $rollbackException) { - // Ignore exception - $this->proxyAssertMatchesRegularExpression( - '~(server has gone away)~', - $rollbackException->getMessage() - ); - } - // Ignore exception - $this->proxyAssertMatchesRegularExpression( - '~(server has gone away)~', - $exception->getMessage() - ); - } - // 预期应该没有发生任何更改 - $this->assertEquals( - '2-8-b', - Db::table('test_tran_a')->where('id', '=', 2)->value('username') - ); - $this->assertEquals( - '3-7-a', - Db::table('test_tran_a')->where('id', '=', 3)->value('username') - ); - } - - public function testTransactionSavepoint() - { - $testData = self::$testData; - // 初始化数据 - $connect = Db::connect(null, true); - $connect->table('test_tran_a')->insertAll($testData); - - Db::table('test_tran_a')->transaction(function () use ($connect) { - $cid = query_mysql_connection_id($connect); - // tran 1 - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 2)->update([ - 'username' => '2-8-c', - ]); - Db::table('test_tran_a')->commit(); - // tran 2 - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 3)->update([ - 'username' => '3-7-b', - ]); - Db::table('test_tran_a')->commit(); - }); - - // 预期变化 - $this->assertEquals( - '2-8-c', - Db::table('test_tran_a')->where('id', '=', 2)->value('username') - ); - $this->assertEquals( - '3-7-b', - Db::table('test_tran_a')->where('id', '=', 3)->value('username') - ); - } - - public function testTransactionSavepointBreakReconnect() - { - $testData = self::$testData; - // 初始化配置 - $config = Db::getConfig(); - $config['connections']['mysql']['break_reconnect'] = true; - $config['connections']['mysql']['break_match_str'] = [ - 'query execution was interrupted', - ]; - Db::setConfig($config); - // 初始化数据 - $connect = Db::connect(null, true); - $connect->table('test_tran_a')->insertAll($testData); - - // 事务中不能重连 - try { - // tran 0 - Db::table('test_tran_a')->startTrans(); - $cid = query_mysql_connection_id($connect); - // tran 1 - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 2)->update([ - 'username' => '2-8-c', - ]); - Db::table('test_tran_a')->commit(); - // kill - mysql_kill_connection('mysql_manage', $cid); - // tran 2 - Db::table('test_tran_a')->startTrans(); - $connect->table('test_tran_a')->where('id', '=', 3)->update([ - 'username' => '3-7-b', - ]); - Db::table('test_tran_a')->commit(); - // tran 0 - Db::table('test_tran_a')->commit(); - } catch (Throwable|Exception $exception) { - try { - Db::table('test_tran_a')->rollback(); - } catch (Exception $rollbackException) { - // Ignore exception - $this->proxyAssertMatchesRegularExpression( - '~(server has gone away)~', - $rollbackException->getMessage() - ); - } - // Ignore exception - $this->proxyAssertMatchesRegularExpression( - '~(server has gone away)~', - $exception->getMessage() - ); - } - // 预期应该没有发生任何更改 - $this->assertEquals( - '2-8-a', - Db::table('test_tran_a')->where('id', '=', 2)->value('username') - ); - $this->assertEquals( - '3-7-a', - Db::table('test_tran_a')->where('id', '=', 3)->value('username') - ); - } -} diff --git a/tests/orm/ModelOneToOneTest.php b/tests/orm/ModelOneToOneTest.php index 5d4be715..bc4664b0 100644 --- a/tests/orm/ModelOneToOneTest.php +++ b/tests/orm/ModelOneToOneTest.php @@ -15,6 +15,7 @@ class ModelOneToOneTest extends TestCase { public static function setUpBeforeClass(): void { + self::markTestSkipped('冲突需要更改兼容性'); $sqlList = [ 'DROP TABLE IF EXISTS `test_user`;', 'CREATE TABLE `test_user` ( diff --git a/tests/orm/MysqlDbTransactionTest.php b/tests/orm/MysqlDbTransactionTest.php new file mode 100644 index 00000000..c3b5d4ee --- /dev/null +++ b/tests/orm/MysqlDbTransactionTest.php @@ -0,0 +1,9 @@ + Date: Mon, 19 May 2025 12:12:01 +0800 Subject: [PATCH 27/53] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 3 +- .gitignore | 3 +- composer.json | 36 +++++++++++++- phinx.php | 32 ++++++++++++ .../migrations/20250517151353_test_user.php | 49 +++++++++++++++++++ tests/db/migrations/20250518164819_tran.php | 44 +++++++++++++++++ vendor-bin/phinx/composer.json | 5 ++ 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 phinx.php create mode 100644 tests/db/migrations/20250517151353_test_user.php create mode 100644 tests/db/migrations/20250518164819_tran.php create mode 100644 vendor-bin/phinx/composer.json diff --git a/.gitattributes b/.gitattributes index 36c9cd12..162760ef 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ /.github export-ignore /tests export-ignore -/phpunit.xml export-ignore \ No newline at end of file +/phpunit.xml export-ignore +/vendor-bin export-ignore \ No newline at end of file diff --git a/.gitignore b/.gitignore index 36e8429f..4bce57ca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ composer.lock Thumbs.db /.idea /.vscode -/.settings \ No newline at end of file +/.settings +/vendor-bin/**/vendor/ \ No newline at end of file diff --git a/composer.json b/composer.json index 16ee6048..6b891534 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "topthink/think-helper":"^3.1" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8", "phpunit/phpunit": "^9.6|^10" }, "autoload": { @@ -40,6 +41,39 @@ "ext-mongodb": "provide mongodb support" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "bamarni/composer-bin-plugin": true + } + }, + "scripts": { + "post-install-cmd": [ + "@composer bin phinx install --ansi" + ], + "phinx-update": [ + "@composer bin phinx update --ansi" + ], + "db-migrate": [ + "./vendor/bin/phinx migrate -e mysql", + "./vendor/bin/phinx migrate -e pgsql" + ], + "db-rollback": [ + "./vendor/bin/phinx rollback -f -e mysql", + "./vendor/bin/phinx rollback -f -e pgsql" + ], + "db-status": [ + "./vendor/bin/phinx status -e mysql", + "./vendor/bin/phinx status -e pgsql" + ], + "db-rebuild": [ + "@composer run db-rollback", + "@composer run db-migrate" + ] + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } } } diff --git a/phinx.php b/phinx.php new file mode 100644 index 00000000..df3c07fd --- /dev/null +++ b/phinx.php @@ -0,0 +1,32 @@ + [ + 'migrations' => '%%PHINX_CONFIG_DIR%%/tests/db/migrations', + 'seeds' => '%%PHINX_CONFIG_DIR%%/tests/db/seeds' + ], + 'environments' => [ + 'default_migration_table' => 'phinxlog', + 'default_environment' => '', + 'mysql' => [ + 'adapter' => 'mysql', + 'host' => getenv('TESTS_DB_MYSQL_HOSTNAME') ?: 'localhost', + 'name' => getenv('TESTS_DB_MYSQL_HOSTPORT') ?: 'tp_orm_test', + 'user' => getenv('TESTS_DB_MYSQL_DATABASE') ?: 'homestead', + 'pass' => getenv('TESTS_DB_MYSQL_USERNAME') ?: 'secret', + 'port' => getenv('TESTS_DB_MYSQL_PASSWORD') ?: '3306', + 'charset' => 'utf8', + ], + 'pgsql' => [ + 'adapter' => 'pgsql', + 'host' => getenv('TESTS_DB_PGSQL_HOSTNAME') ?: 'localhost', + 'name' => getenv('TESTS_DB_PGSQL_HOSTPORT') ?: 'tp_orm_test', + 'user' => getenv('TESTS_DB_PGSQL_DATABASE') ?: 'homestead', + 'pass' => getenv('TESTS_DB_PGSQL_USERNAME') ?: 'secret', + 'port' => getenv('TESTS_DB_PGSQL_PASSWORD') ?: '5432', + 'charset' => 'utf8', + ] + ], + 'version_order' => 'creation' +]; diff --git a/tests/db/migrations/20250517151353_test_user.php b/tests/db/migrations/20250517151353_test_user.php new file mode 100644 index 00000000..7e25e990 --- /dev/null +++ b/tests/db/migrations/20250517151353_test_user.php @@ -0,0 +1,49 @@ +table('test_user', ['id' => false, 'primary_key' => ['id']]) + ->addColumn('id', 'integer', [ + 'identity' => true, + 'signed' => false, + 'null' => false, + ]) + ->addColumn('type', 'integer', [ + 'limit' => 4, // 对应 MySQL 的 TINYINT(4) + 'default' => 0, + 'null' => false, + 'signed' => false, // 转换为 PostgreSQL 的 SMALLINT + ]) + ->addColumn('username', 'string', [ + 'limit' => 32, + 'null' => false, + ]) + ->addColumn('nickname', 'string', [ + 'limit' => 32, + 'null' => false, + ]) + ->addColumn('password', 'string', [ + 'limit' => 64, + 'null' => false, + ]) + ->create(); + } +} diff --git a/tests/db/migrations/20250518164819_tran.php b/tests/db/migrations/20250518164819_tran.php new file mode 100644 index 00000000..2876139d --- /dev/null +++ b/tests/db/migrations/20250518164819_tran.php @@ -0,0 +1,44 @@ +table('test_tran_a', [ + 'id' => false, + 'primary_key' => ['id'], + ]) + ->addColumn('id', 'integer', [ + 'signed' => false, + 'identity' => true, + 'null' => false, + ]) + ->addColumn('type', 'integer', [ + 'limit' => 2, // MySQL:TINYINT(4),PG:SMALLINT + 'default' => 0, + 'signed' => false, + 'null' => false, + ]) + ->addColumn('username', 'string', [ + 'limit' => 32, + 'null' => false, + ]) + ->create(); + } +} diff --git a/vendor-bin/phinx/composer.json b/vendor-bin/phinx/composer.json new file mode 100644 index 00000000..6e7c5ff0 --- /dev/null +++ b/vendor-bin/phinx/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "robmorgan/phinx": "^0.14" + } +} From 23925618ad548647e1817a74e58358737f05b011 Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 12:12:34 +0800 Subject: [PATCH 28/53] =?UTF-8?q?ci=20=E7=8E=AF=E5=A2=83=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20pg=20=E6=9C=8D=E5=8A=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 18 ++++++++++++++++++ .github/workflows/tests.yml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2b22254e..b939ef13 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,6 +17,19 @@ jobs: ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres:15 + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: password + POSTGRES_DB: testing + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U root" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout @@ -61,6 +74,11 @@ jobs: TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing + TESTS_DB_PGSQL_HOST: 127.0.0.1 + TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_USERNAME: root + TESTS_DB_PGSQL_PASSWORD: password + TESTS_DB_PGSQL_DATABASE: testing - name: Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bea3c2b8..5af009f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,6 +29,19 @@ jobs: ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres:15 + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: password + POSTGRES_DB: testing + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U root" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout @@ -73,3 +86,8 @@ jobs: TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing + TESTS_DB_PGSQL_HOST: 127.0.0.1 + TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_USERNAME: root + TESTS_DB_PGSQL_PASSWORD: password + TESTS_DB_PGSQL_DATABASE: testing From 4564c3394368c0decf97eadadcc96b566d27b69a Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 12:16:46 +0800 Subject: [PATCH 29/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 16 ++++++++++++++++ .github/workflows/tests.yml | 16 ++++++++++++++++ phinx.php | 16 ++++++++-------- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index b939ef13..26040022 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -66,6 +66,22 @@ jobs: - name: Install dependencies (composer.lock) run: composer install --prefer-dist --no-progress --no-suggest + - name: Run test suite + run: | + composer bin phinx install + composer run db-migrate + env: + TESTS_DB_MYSQL_HOST: 127.0.0.1 + TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_USERNAME: root + TESTS_DB_MYSQL_PASSWORD: password + TESTS_DB_MYSQL_DATABASE: testing + TESTS_DB_PGSQL_HOST: 127.0.0.1 + TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_USERNAME: root + TESTS_DB_PGSQL_PASSWORD: password + TESTS_DB_PGSQL_DATABASE: testing + - name: Run test suite run: composer exec -- phpunit --coverage-clover=coverage.xml -v env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5af009f8..d8b95b61 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,6 +78,22 @@ jobs: - name: Install dependencies (composer.lock) run: composer install --prefer-dist --no-progress --no-suggest + - name: Run test suite + run: | + composer bin phinx install + composer run db-migrate + env: + TESTS_DB_MYSQL_HOST: 127.0.0.1 + TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_USERNAME: root + TESTS_DB_MYSQL_PASSWORD: password + TESTS_DB_MYSQL_DATABASE: testing + TESTS_DB_PGSQL_HOST: 127.0.0.1 + TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_USERNAME: root + TESTS_DB_PGSQL_PASSWORD: password + TESTS_DB_PGSQL_DATABASE: testing + - name: Run test suite run: composer exec -- phpunit env: diff --git a/phinx.php b/phinx.php index df3c07fd..f72f4934 100644 --- a/phinx.php +++ b/phinx.php @@ -12,19 +12,19 @@ 'mysql' => [ 'adapter' => 'mysql', 'host' => getenv('TESTS_DB_MYSQL_HOSTNAME') ?: 'localhost', - 'name' => getenv('TESTS_DB_MYSQL_HOSTPORT') ?: 'tp_orm_test', - 'user' => getenv('TESTS_DB_MYSQL_DATABASE') ?: 'homestead', - 'pass' => getenv('TESTS_DB_MYSQL_USERNAME') ?: 'secret', - 'port' => getenv('TESTS_DB_MYSQL_PASSWORD') ?: '3306', + 'name' => getenv('TESTS_DB_MYSQL_DATABASE') ?: 'tp_orm_test', + 'user' => getenv('TESTS_DB_MYSQL_USERNAME') ?: 'homestead', + 'pass' => getenv('TESTS_DB_MYSQL_PASSWORD') ?: 'secret', + 'port' => getenv('TESTS_DB_MYSQL_HOSTPORT') ?: '3306', 'charset' => 'utf8', ], 'pgsql' => [ 'adapter' => 'pgsql', 'host' => getenv('TESTS_DB_PGSQL_HOSTNAME') ?: 'localhost', - 'name' => getenv('TESTS_DB_PGSQL_HOSTPORT') ?: 'tp_orm_test', - 'user' => getenv('TESTS_DB_PGSQL_DATABASE') ?: 'homestead', - 'pass' => getenv('TESTS_DB_PGSQL_USERNAME') ?: 'secret', - 'port' => getenv('TESTS_DB_PGSQL_PASSWORD') ?: '5432', + 'name' => getenv('TESTS_DB_PGSQL_DATABASE') ?: 'tp_orm_test', + 'user' => getenv('TESTS_DB_PGSQL_USERNAME') ?: 'homestead', + 'pass' => getenv('TESTS_DB_PGSQL_PASSWORD') ?: 'secret', + 'port' => getenv('TESTS_DB_PGSQL_HOSTPORT') ?: '5432', 'charset' => 'utf8', ] ], From 4719e04603568975f8970f6f4d193988843ac15a Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 12:41:58 +0800 Subject: [PATCH 30/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 18 +++++++++--------- .github/workflows/tests.yml | 19 ++++++++++--------- phinx.php | 3 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 26040022..034e5fc2 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -66,18 +66,18 @@ jobs: - name: Install dependencies (composer.lock) run: composer install --prefer-dist --no-progress --no-suggest - - name: Run test suite + - name: Run DB Migrate run: | composer bin phinx install composer run db-migrate env: - TESTS_DB_MYSQL_HOST: 127.0.0.1 - TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_MYSQL_HOSTPORT: 3306 TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing - TESTS_DB_PGSQL_HOST: 127.0.0.1 - TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_PGSQL_HOSTPORT: 5432 TESTS_DB_PGSQL_USERNAME: root TESTS_DB_PGSQL_PASSWORD: password TESTS_DB_PGSQL_DATABASE: testing @@ -85,13 +85,13 @@ jobs: - name: Run test suite run: composer exec -- phpunit --coverage-clover=coverage.xml -v env: - TESTS_DB_MYSQL_HOST: 127.0.0.1 - TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_MYSQL_HOSTPORT: 3306 TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing - TESTS_DB_PGSQL_HOST: 127.0.0.1 - TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_PGSQL_HOSTPORT: 5432 TESTS_DB_PGSQL_USERNAME: root TESTS_DB_PGSQL_PASSWORD: password TESTS_DB_PGSQL_DATABASE: testing diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8b95b61..4cf5df45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,18 +78,19 @@ jobs: - name: Install dependencies (composer.lock) run: composer install --prefer-dist --no-progress --no-suggest - - name: Run test suite + - name: Run DB Migrate + continue-on-error: true run: | composer bin phinx install composer run db-migrate env: - TESTS_DB_MYSQL_HOST: 127.0.0.1 - TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_MYSQL_HOSTPORT: 3306 TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing - TESTS_DB_PGSQL_HOST: 127.0.0.1 - TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_PGSQL_HOSTPORT: 5432 TESTS_DB_PGSQL_USERNAME: root TESTS_DB_PGSQL_PASSWORD: password TESTS_DB_PGSQL_DATABASE: testing @@ -97,13 +98,13 @@ jobs: - name: Run test suite run: composer exec -- phpunit env: - TESTS_DB_MYSQL_HOST: 127.0.0.1 - TESTS_DB_MYSQL_PORT: 3306 + TESTS_DB_MYSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_MYSQL_HOSTPORT: 3306 TESTS_DB_MYSQL_USERNAME: root TESTS_DB_MYSQL_PASSWORD: password TESTS_DB_MYSQL_DATABASE: testing - TESTS_DB_PGSQL_HOST: 127.0.0.1 - TESTS_DB_PGSQL_PORT: 5432 + TESTS_DB_PGSQL_HOSTNAME: 127.0.0.1 + TESTS_DB_PGSQL_HOSTPORT: 5432 TESTS_DB_PGSQL_USERNAME: root TESTS_DB_PGSQL_PASSWORD: password TESTS_DB_PGSQL_DATABASE: testing diff --git a/phinx.php b/phinx.php index f72f4934..ff111ade 100644 --- a/phinx.php +++ b/phinx.php @@ -1,7 +1,6 @@ [ 'migrations' => '%%PHINX_CONFIG_DIR%%/tests/db/migrations', 'seeds' => '%%PHINX_CONFIG_DIR%%/tests/db/seeds' From 8c046f7b9f7ff3700bee73e3a8e7ec1bf80cbc8f Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:12:10 +0800 Subject: [PATCH 31/53] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 3 +-- tests/functions.php | 10 +++++++--- tests/orm/BaseDbTransactionTest.php | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4cf5df45..ef70cc0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -79,7 +79,6 @@ jobs: run: composer install --prefer-dist --no-progress --no-suggest - name: Run DB Migrate - continue-on-error: true run: | composer bin phinx install composer run db-migrate @@ -96,7 +95,7 @@ jobs: TESTS_DB_PGSQL_DATABASE: testing - name: Run test suite - run: composer exec -- phpunit + run: composer exec -- phpunit --testdox env: TESTS_DB_MYSQL_HOSTNAME: 127.0.0.1 TESTS_DB_MYSQL_HOSTPORT: 3306 diff --git a/tests/functions.php b/tests/functions.php index a6b09a07..682c12c3 100644 --- a/tests/functions.php +++ b/tests/functions.php @@ -94,12 +94,12 @@ function kill_connection(string $name, $cid): void global $pg_func_installed; $pg_func_installed = []; -function pg_server_version(string $name = 'pgsql'): string +function pg_server_version(string $name = 'pgsql', bool $raw = false): string { $pdo = Db::connect($name)->connect(); $version = $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION); - return explode(' ', $version)[0]; + return $raw ? $version : explode(' ', $version)[0]; } function pg_install_func(string $name = 'pgsql'): void @@ -113,10 +113,14 @@ function pg_install_func(string $name = 'pgsql'): void /** @var \PDO $pdo */ $pdo = Db::connect($name)->connect(); - $file_path = version_compare(pg_server_version($name), '12.0', '>=') + $rawVersion = pg_server_version($name, true); + $version = pg_server_version($name); + $file_path = version_compare($version, '12.0', '>=') ? __DIR__ . '/../src/db/connector/pgsql12.sql' : __DIR__ . '/../src/db/connector/pgsql.sql'; + echo PHP_EOL, "> Installing PostgreSQL({$rawVersion}) functions from {$file_path}", PHP_EOL; + $content = file_get_contents($file_path); $statements = preg_split('/;\s*(?=CREATE|COMMENT|DROP)/i', $content); diff --git a/tests/orm/BaseDbTransactionTest.php b/tests/orm/BaseDbTransactionTest.php index 0fa28b31..494b8243 100644 --- a/tests/orm/BaseDbTransactionTest.php +++ b/tests/orm/BaseDbTransactionTest.php @@ -32,9 +32,9 @@ protected function provideTestData(): array public function setUp(): void { - Db::listen(function ($sql, $time) { - echo "SQL: $sql [$time ms]\n"; - }); + // Db::listen(function ($sql, $time) { + // echo "SQL: $sql [$time ms]\n"; + // }); $this->db = Db::connect($this->dbName, true); $this->db->execute('TRUNCATE TABLE test_tran_a;'); } From 341e6155474f03a8fa6c5fb202f37386f606c3ae Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:20:06 +0800 Subject: [PATCH 32/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dpg=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/orm/BaseDbTest.php | 7 +++++++ tests/orm/BaseDbTransactionTest.php | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/tests/orm/BaseDbTest.php b/tests/orm/BaseDbTest.php index c3863c74..567780fe 100644 --- a/tests/orm/BaseDbTest.php +++ b/tests/orm/BaseDbTest.php @@ -17,6 +17,8 @@ use think\db\Raw; use think\Exception as ThinkException; use think\facade\Db; +use function tests\pg_install_func; +use function tests\pg_reset_function; abstract class BaseDbTest extends Base { @@ -37,6 +39,11 @@ protected function provideTestData(): array public function setUp(): void { $this->db = Db::connect($this->dbName); + + if ($this->dbName === 'pgsql') { + pg_reset_function(); + pg_install_func(); + } } public function testInitUsers(): array diff --git a/tests/orm/BaseDbTransactionTest.php b/tests/orm/BaseDbTransactionTest.php index 494b8243..3a7afef1 100644 --- a/tests/orm/BaseDbTransactionTest.php +++ b/tests/orm/BaseDbTransactionTest.php @@ -10,6 +10,8 @@ use think\db\connector\Pgsql; use function tests\kill_connection; use function tests\mysql_kill_connection; +use function tests\pg_install_func; +use function tests\pg_reset_function; use function tests\query_connection_id; use function tests\query_mysql_connection_id; use think\facade\Db; @@ -37,6 +39,11 @@ public function setUp(): void // }); $this->db = Db::connect($this->dbName, true); $this->db->execute('TRUNCATE TABLE test_tran_a;'); + + if ($this->dbName === 'pgsql') { + pg_reset_function(); + pg_install_func(); + } } protected function reconnect(): ConnectionInterface From f392e9c7c38fff721d006e60c07a008fa017126d Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:24:46 +0800 Subject: [PATCH 33/53] =?UTF-8?q?=E5=B0=9D=E8=AF=95=20pg=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 665c4188..63dcca56 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -12,6 +12,7 @@ namespace think\db\connector; use PDO; +use think\db\BaseQuery; use think\db\PDOConnection; /** @@ -109,4 +110,11 @@ protected function supportSavepoint(): bool { return true; } + + public function getLastInsID(BaseQuery $query, ?string $sequence = null) + { + $insertId = $this->linkID->lastInsertId($sequence); + + return $this->autoInsIDType($query, $insertId); + } } From 81536c5ffe6360dd7c188937ce61bffa28e3d086 Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:29:02 +0800 Subject: [PATCH 34/53] =?UTF-8?q?=E5=B0=9D=E8=AF=95=20pg=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 63dcca56..1f941c58 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -111,6 +111,43 @@ protected function supportSavepoint(): bool return true; } + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql); + + if ($result) { + // todo 应该改造为使用 returning 返回完全解决该问题 + $sequence = $options['sequence'] ?? null; + $lastInsId = $this->getLastInsID($query, $sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getAutoInc(); + if ($pk && is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID && $lastInsId) { + return $lastInsId; + } + } + + return $result; + } + public function getLastInsID(BaseQuery $query, ?string $sequence = null) { $insertId = $this->linkID->lastInsertId($sequence); From 601587b5fbd95c815213cfbe49082ce1c107c390 Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:32:44 +0800 Subject: [PATCH 35/53] =?UTF-8?q?=E5=85=88=E9=99=8D=E7=BA=A7=E5=90=8E?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 034e5fc2..2ef92d62 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -18,7 +18,7 @@ jobs: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: - image: postgres:15 + image: postgres:13 env: POSTGRES_USER: root POSTGRES_PASSWORD: password diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ef70cc0e..51460a86 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,7 @@ jobs: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: - image: postgres:15 + image: postgres:13 env: POSTGRES_USER: root POSTGRES_PASSWORD: password From 8ccc3db0c65b8382263f950fc31534b53bc5a36f Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:43:35 +0800 Subject: [PATCH 36/53] =?UTF-8?q?=E6=89=93=E5=8D=B0=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6b891534..7c0b3b28 100644 --- a/composer.json +++ b/composer.json @@ -54,8 +54,8 @@ "@composer bin phinx update --ansi" ], "db-migrate": [ - "./vendor/bin/phinx migrate -e mysql", - "./vendor/bin/phinx migrate -e pgsql" + "./vendor/bin/phinx migrate -e mysql -vvv", + "./vendor/bin/phinx migrate -e pgsql -vvv" ], "db-rollback": [ "./vendor/bin/phinx rollback -f -e mysql", From aeb51a52cab35842ad8baf0553a56756d3769ebf Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:50:13 +0800 Subject: [PATCH 37/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/codecov.yml | 2 +- .github/workflows/tests.yml | 2 +- src/db/connector/Pgsql.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2ef92d62..034e5fc2 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -18,7 +18,7 @@ jobs: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: - image: postgres:13 + image: postgres:15 env: POSTGRES_USER: root POSTGRES_PASSWORD: password diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 51460a86..ef70cc0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,7 @@ jobs: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: - image: postgres:13 + image: postgres:15 env: POSTGRES_USER: root POSTGRES_PASSWORD: password diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 1f941c58..1cc868c9 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -125,7 +125,7 @@ public function insert(BaseQuery $query, bool $getLastInsID = false) if ($result) { // todo 应该改造为使用 returning 返回完全解决该问题 $sequence = $options['sequence'] ?? null; - $lastInsId = $this->getLastInsID($query, $sequence); + $lastInsId = $getLastInsID ? $this->getLastInsID($query, $sequence) : null; $data = $options['data']; From 7ba458317fdd642a44f87b98eb90d3032649a88c Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 13:56:19 +0800 Subject: [PATCH 38/53] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 87f92af5..472017c5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,11 +7,12 @@ colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" - convertWarningsToExceptions="true" + convertWarningsToExceptions="false" displayDetailsOnTestsThatTriggerDeprecations="true" displayDetailsOnTestsThatTriggerErrors="true" displayDetailsOnTestsThatTriggerNotices="true" displayDetailsOnTestsThatTriggerWarnings="true" + failOnWarning="false" processIsolation="false" stopOnError="false" stopOnFailure="false" From 23cc17fc1a6a86d18d778a4277e4b4d3b485b5eb Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 14:01:06 +0800 Subject: [PATCH 39/53] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/orm/{BaseDbTest.php => DbTestBase.php} | 2 +- .../{BaseDbTransactionTest.php => DbTransactionTestBase.php} | 2 +- tests/orm/MysqlDbTest.php | 2 +- tests/orm/MysqlDbTransactionTest.php | 2 +- tests/orm/PgsqlDbTest.php | 2 +- tests/orm/PgsqlDbTransactionTest.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename tests/orm/{BaseDbTest.php => DbTestBase.php} (99%) rename tests/orm/{BaseDbTransactionTest.php => DbTransactionTestBase.php} (99%) diff --git a/tests/orm/BaseDbTest.php b/tests/orm/DbTestBase.php similarity index 99% rename from tests/orm/BaseDbTest.php rename to tests/orm/DbTestBase.php index 567780fe..19020feb 100644 --- a/tests/orm/BaseDbTest.php +++ b/tests/orm/DbTestBase.php @@ -20,7 +20,7 @@ use function tests\pg_install_func; use function tests\pg_reset_function; -abstract class BaseDbTest extends Base +abstract class DbTestBase extends Base { public ConnectionInterface $db; protected string $dbName; diff --git a/tests/orm/BaseDbTransactionTest.php b/tests/orm/DbTransactionTestBase.php similarity index 99% rename from tests/orm/BaseDbTransactionTest.php rename to tests/orm/DbTransactionTestBase.php index 3a7afef1..016e815f 100644 --- a/tests/orm/BaseDbTransactionTest.php +++ b/tests/orm/DbTransactionTestBase.php @@ -18,7 +18,7 @@ use Throwable; use function tests\query_pgsql_connection_id; -abstract class BaseDbTransactionTest extends Base +abstract class DbTransactionTestBase extends Base { protected ConnectionInterface $db; protected string $dbName; diff --git a/tests/orm/MysqlDbTest.php b/tests/orm/MysqlDbTest.php index 2b2478fd..2b9b8bc2 100644 --- a/tests/orm/MysqlDbTest.php +++ b/tests/orm/MysqlDbTest.php @@ -3,7 +3,7 @@ namespace tests\orm; -class MysqlDbTest extends BaseDbTest +class MysqlDbTest extends DbTestBase { protected string $dbName = 'mysql'; } diff --git a/tests/orm/MysqlDbTransactionTest.php b/tests/orm/MysqlDbTransactionTest.php index c3b5d4ee..ed7b6f7b 100644 --- a/tests/orm/MysqlDbTransactionTest.php +++ b/tests/orm/MysqlDbTransactionTest.php @@ -3,7 +3,7 @@ namespace tests\orm; -class MysqlDbTransactionTest extends BaseDbTransactionTest +class MysqlDbTransactionTest extends DbTransactionTestBase { protected string $dbName = 'mysql'; } diff --git a/tests/orm/PgsqlDbTest.php b/tests/orm/PgsqlDbTest.php index a8687ba7..c409334f 100644 --- a/tests/orm/PgsqlDbTest.php +++ b/tests/orm/PgsqlDbTest.php @@ -3,7 +3,7 @@ namespace tests\orm; -class PgsqlDbTest extends BaseDbTest +class PgsqlDbTest extends DbTestBase { protected string $dbName = 'pgsql'; diff --git a/tests/orm/PgsqlDbTransactionTest.php b/tests/orm/PgsqlDbTransactionTest.php index e2e05cce..5e41c76a 100644 --- a/tests/orm/PgsqlDbTransactionTest.php +++ b/tests/orm/PgsqlDbTransactionTest.php @@ -3,7 +3,7 @@ namespace tests\orm; -class PgsqlDbTransactionTest extends BaseDbTransactionTest +class PgsqlDbTransactionTest extends DbTransactionTestBase { protected string $dbName = 'pgsql'; } From 6feacf0a5ab0bc63f6ff5c49c75c91a76f6089ba Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 19 May 2025 14:48:42 +0800 Subject: [PATCH 40/53] =?UTF-8?q?=E8=B0=83=E6=95=B4=20phpunit=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=8C=E9=81=BF=E5=85=8D=E9=85=8D=E7=BD=AE=E5=90=84?= =?UTF-8?q?=E7=A7=8D=E7=BA=A6=E6=9D=9F=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + composer.json | 2 +- phpunit.xml | 10 +++------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4bce57ca..5a2d75d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /vendor composer.phar composer.lock +.phpunit.result.cache .DS_Store Thumbs.db /.idea diff --git a/composer.json b/composer.json index 7c0b3b28..00e5a714 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^9.6|^10" + "phpunit/phpunit": "^9.6" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index 472017c5..464f7650 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -5,19 +5,15 @@ beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="tests/bootstrap.php" colors="true" + convertDeprecationsToExceptions="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" - convertWarningsToExceptions="false" - displayDetailsOnTestsThatTriggerDeprecations="true" - displayDetailsOnTestsThatTriggerErrors="true" - displayDetailsOnTestsThatTriggerNotices="true" - displayDetailsOnTestsThatTriggerWarnings="true" + convertWarningsToExceptions="true" failOnWarning="false" processIsolation="false" stopOnError="false" stopOnFailure="false" - verbose="true" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" > From 86e900e3cf948c7d0d3ef5de688ef9720884e7f7 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 20 May 2025 17:34:19 +0800 Subject: [PATCH 41/53] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=B5=8B=E8=AF=95=20Db?= =?UTF-8?q?JsonFields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Base.php | 28 +++++ .../migrations/20250519162501_test_goods.php | 43 +++++++ tests/orm/DbJsonFieldsBase.php | 101 +++++++++++++++++ tests/orm/DbJsonFieldsTest.php | 106 ------------------ tests/orm/MysqlDbJsonFieldsTest.php | 9 ++ tests/orm/PgsqlDbJsonFieldsTest.php | 19 ++++ 6 files changed, 200 insertions(+), 106 deletions(-) create mode 100644 tests/db/migrations/20250519162501_test_goods.php create mode 100644 tests/orm/DbJsonFieldsBase.php delete mode 100644 tests/orm/DbJsonFieldsTest.php create mode 100644 tests/orm/MysqlDbJsonFieldsTest.php create mode 100644 tests/orm/PgsqlDbJsonFieldsTest.php diff --git a/tests/Base.php b/tests/Base.php index 6c64c2b2..ee94dcca 100644 --- a/tests/Base.php +++ b/tests/Base.php @@ -6,10 +6,38 @@ use PHPUnit\Framework\TestCase; use PHPUnit\Runner\Version; +use think\db\BaseQuery; +use think\db\ConnectionInterface; +use think\db\connector\Pgsql; +use think\facade\Db; use function version_compare; class Base extends TestCase { + protected ConnectionInterface $db; + protected string $dbName; + + public function setUp(): void + { + $this->db = Db::connect($this->dbName); + + if ($this->dbName === 'pgsql') { + pg_reset_function(); + pg_install_func(); + } + } + + protected static function compatibleInsertAll(BaseQuery $query, array $data): void + { + if ($query->getConnection() instanceof Pgsql) { + foreach ($data as $datum) { + (clone $query)->insert($datum); + } + } else { + $query->insertAll($data); + } + } + protected function proxyAssertMatchesRegularExpression(string $pattern, string $string, string $message = '') { if (version_compare(Version::id(), '9.1', '>=')) { diff --git a/tests/db/migrations/20250519162501_test_goods.php b/tests/db/migrations/20250519162501_test_goods.php new file mode 100644 index 00000000..4f000758 --- /dev/null +++ b/tests/db/migrations/20250519162501_test_goods.php @@ -0,0 +1,43 @@ +table('test_goods', [ + 'id' => false, + 'primary_key' => ['id'], + ]) + ->addColumn('id', 'integer', [ + 'signed' => false, + 'identity' => true, + 'null' => false, + ]) + ->addColumn('name', 'string', [ + 'limit' => 32, + 'default' => '', + 'null' => false, + ]) + ->addColumn('extend', 'json', [ + 'null' => true, + 'default' => null, + ]) + ->create(); + } +} diff --git a/tests/orm/DbJsonFieldsBase.php b/tests/orm/DbJsonFieldsBase.php new file mode 100644 index 00000000..73ae03cc --- /dev/null +++ b/tests/orm/DbJsonFieldsBase.php @@ -0,0 +1,101 @@ + 1, 'name' => '肥皂', 'extend' => '{"brand": "TP6", "standard": null, "type": "清洁"}'], + ['id' => 2, 'name' => '牙膏', 'extend' => '{"brand": "TP8", "standard": "大", "type": "清洁"}'], + ['id' => 3, 'name' => '牙刷', 'extend' => '{"brand": "TP8", "standard": "大", "type": "清洁"}'], + ['id' => 4, 'name' => '卫生纸', 'extend' => '{"brand": null, "standard": null, "type": "日用品" ,"amount": 20}'], + ['id' => 5, 'name' => '香肠', 'extend' => '{"brand": null, "weight": 480, "type": "食品" ,"pack": 1}'], + ]; + return array_map(fn ($item) => ['extend' => json_decode($item['extend'], true)] + $item, $data); + } + + public function testInitGoods(): array + { + $this->db->execute('TRUNCATE TABLE test_goods;'); + + $userData = $this->provideTestData(); + $this->db->table('test_goods')->json(['extend'])->insertAll($userData); + + return $userData; + } + + /** + * @test 测试当 json 字段的指定成员不存在 + * @depends testInitGoods + */ + public function testJsonFieldMemberNotExists(array $goods) + { + $collect = collect($goods); + + $data = $this->db->table('test_goods')->where('extend->weight', null)->select(); + $this->assertSame($data->count(), $collect->where('extend.weight', null)->count()); + + $data = $this->db->table('test_goods')->where('extend->amount', null)->select(); + $this->assertSame($data->count(), $collect->where('extend.amount', null)->count()); + + $data = $this->db->table('test_goods')->where('extend->pack', null)->select(); + $this->assertSame($data->count(), $collect->where('extend.pack', null)->count()); + } + + /** + * @test 测试当 json 字段的指定成员不存在或为 null + * @depends testInitGoods + */ + public function testJsonFieldMemberNotExistsOrNull(array $goods) + { + $collect = collect($this->provideTestData()); + + $data = $this->db->table('test_goods')->where('extend->brand', null)->select(); + $this->assertSame($data->count(), $collect->where('extend.brand', null)->count()); + + $data = $this->db->table('test_goods')->where('extend->standard', null)->select(); + $this->assertSame($data->count(), $collect->where('extend.standard', null)->count()); + } + + /** + * @test 测试搜索 json 字段指定成员为指定的值 + * @depends testInitGoods + */ + public function testJsonFieldMemberEqual(array $goods) + { + $collect = collect($goods); + + $data = $this->db->table('test_goods')->where('extend->brand', 'TP8')->select(); + $this->assertSame($data->count(), $collect->where('extend.brand', 'TP8')->count()); + + $data = $this->db->table('test_goods')->where('extend->standard', '大')->select(); + $this->assertSame($data->count(), $collect->where('extend.standard', '大')->count()); + + $data = $this->db->table('test_goods')->where('extend->type', '清洁')->select(); + $this->assertSame($data->count(), $collect->where('extend.type', '清洁')->count()); + } + + /** + * @test 测试搜索 json 字段指定成员不为指定的值 + * @depends testInitGoods + */ + public function testJsonFieldMemberNotEqual(array $goods) + { + $collect = collect($goods); + + $data = $this->db->table('test_goods')->where('extend->brand', '<>', 'TP8')->whereNull('extend->brand', "or")->select(); + $this->assertSame($data->count(), $collect->where('extend.brand', '<>', 'TP8')->count()); + + $data = $this->db->table('test_goods')->where('extend->standard', '<>', '大')->whereNull('extend->standard', "or")->select(); + $this->assertSame($data->count(), $collect->where('extend.standard', '<>', '大')->count()); + + $data = $this->db->table('test_goods')->where('extend->type', '<>', '清洁')->whereNull('extend->type', "or")->select(); + $this->assertSame($data->count(), $collect->where('extend.type', '<>', '清洁')->count()); + } +} diff --git a/tests/orm/DbJsonFieldsTest.php b/tests/orm/DbJsonFieldsTest.php deleted file mode 100644 index 10765974..00000000 --- a/tests/orm/DbJsonFieldsTest.php +++ /dev/null @@ -1,106 +0,0 @@ - 1, 'name' => '肥皂', 'extend' => '{"brand": "TP6", "standard": null, "type": "清洁"}'], - ['id' => 2, 'name' => '牙膏', 'extend' => '{"brand": "TP8", "standard": "大", "type": "清洁"}'], - ['id' => 3, 'name' => '牙刷', 'extend' => '{"brand": "TP8", "standard": "大", "type": "清洁"}'], - ['id' => 4, 'name' => '卫生纸', 'extend' => '{"brand": null, "standard": null, "type": "日用品" ,"amount": 20}'], - ['id' => 5, 'name' => '香肠', 'extend' => '{"brand": null, "weight": 480, "type": "食品" ,"pack": 1}'], - ]; - self::$testGoodsData = $data; - foreach ($data as &$item) { - $item['extend'] = json_decode($item['extend'], true); - } - self::$testGoodsDataCollect = collect($data); - Db::table(self::$table)->insertAll(self::$testGoodsData); - } - - /** - * @test 测试当 json 字段的指定成员不存在 - */ - public function testJsonFieldMemberNotExists() - { - $data = Db::table(self::$table)->where('extend->weight', null)->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.weight', null)->count()); - - $data = Db::table(self::$table)->where('extend->amount', null)->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.amount', null)->count()); - - $data = Db::table(self::$table)->where('extend->pack', null)->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.pack', null)->count()); - } - - /** - * @test 测试当 json 字段的指定成员不存在或为 null - */ - public function testJsonFieldMemberNotExistsOrNull() - { - $data = Db::table(self::$table)->where('extend->brand', null)->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.brand', null)->count()); - - $data = Db::table(self::$table)->where('extend->standard', null)->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.standard', null)->count()); - } - - /** - * @test 测试搜索 json 字段指定成员为指定的值 - */ - public function testJsonFieldMemberEqual() - { - $data = Db::table(self::$table)->where('extend->brand', 'TP8')->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.brand', 'TP8')->count()); - - $data = Db::table(self::$table)->where('extend->standard', '大')->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.standard', '大')->count()); - - $data = Db::table(self::$table)->where('extend->type', '清洁')->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.type', '清洁')->count()); - } - - /** - * @test 测试搜索 json 字段指定成员不为指定的值 - */ - public function testJsonFieldMemberNotEqual() - { - $data = Db::table(self::$table)->where('extend->brand', '<>', 'TP8')->whereNull('extend->brand', "or")->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.brand', '<>', 'TP8')->count()); - - $data = Db::table(self::$table)->where('extend->standard', '<>', '大')->whereNull('extend->standard', "or")->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.standard', '<>', '大')->count()); - - $data = Db::table(self::$table)->where('extend->type', '<>', '清洁')->whereNull('extend->type', "or")->select(); - $this->assertSame($data->count(), self::$testGoodsDataCollect->where('extend.type', '<>', '清洁')->count()); - } -} diff --git a/tests/orm/MysqlDbJsonFieldsTest.php b/tests/orm/MysqlDbJsonFieldsTest.php new file mode 100644 index 00000000..88dd42a4 --- /dev/null +++ b/tests/orm/MysqlDbJsonFieldsTest.php @@ -0,0 +1,9 @@ +db->execute('TRUNCATE TABLE test_goods;'); + + $userData = $this->provideTestData(); + self::compatibleInsertAll($this->db->table('test_goods')->json(['extend']), $userData); + + return $userData; + } +} From 03cf83dbd9165997f6e45862e95d4bc1189fe479 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 20 May 2025 17:36:27 +0800 Subject: [PATCH 42/53] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=B5=8B=E8=AF=95=20Mo?= =?UTF-8?q?delOneToOne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Base.php | 17 ++++- .../20250519165915_model_one_to_one.php | 74 +++++++++++++++++++ tests/orm/DbTestBase.php | 2 +- tests/orm/DbTransactionTestBase.php | 2 +- ...OneToOneTest.php => ModelOneToOneBase.php} | 46 ++++++------ tests/orm/MysqlDbJsonFieldsTest.php | 2 +- tests/orm/MysqlDbTest.php | 2 +- tests/orm/MysqlDbTransactionTest.php | 2 +- tests/orm/MysqlModelOneToOneTest.php | 9 +++ tests/orm/PgsqlDbJsonFieldsTest.php | 2 +- tests/orm/PgsqlDbTest.php | 2 +- tests/orm/PgsqlDbTransactionTest.php | 2 +- tests/orm/PgsqlModelOneToOneTest.php | 9 +++ tests/stubs/ProfileModel.php | 2 +- tests/stubs/UserModel.php | 2 +- 15 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 tests/db/migrations/20250519165915_model_one_to_one.php rename tests/orm/{ModelOneToOneTest.php => ModelOneToOneBase.php} (53%) create mode 100644 tests/orm/MysqlModelOneToOneTest.php create mode 100644 tests/orm/PgsqlModelOneToOneTest.php diff --git a/tests/Base.php b/tests/Base.php index ee94dcca..ee4478bc 100644 --- a/tests/Base.php +++ b/tests/Base.php @@ -10,12 +10,25 @@ use think\db\ConnectionInterface; use think\db\connector\Pgsql; use think\facade\Db; +use think\Model; use function version_compare; +/** + * @property-read string $dbName; + */ class Base extends TestCase { protected ConnectionInterface $db; - protected string $dbName; + protected static string $dbName; + + public function __get(string $name) + { + if ($name === 'dbName') { + return static::$dbName; + } + + throw new \Exception('Undefined property: ' . static::class . '::$' . $name); + } public function setUp(): void { @@ -25,6 +38,8 @@ public function setUp(): void pg_reset_function(); pg_install_func(); } + + // var_dump(static::class . '-' . __FUNCTION__ . '-' . spl_object_id($this)); } protected static function compatibleInsertAll(BaseQuery $query, array $data): void diff --git a/tests/db/migrations/20250519165915_model_one_to_one.php b/tests/db/migrations/20250519165915_model_one_to_one.php new file mode 100644 index 00000000..624d3069 --- /dev/null +++ b/tests/db/migrations/20250519165915_model_one_to_one.php @@ -0,0 +1,74 @@ +table('orm_test_user', [ + 'id' => false, + 'primary_key' => ['id'], + ]) + ->addColumn('id', 'integer', [ + 'identity' => true, + 'signed' => true, + 'null' => false, + ]) + ->addColumn('account', 'string', [ + 'limit' => 255, + 'null' => false, + 'default' => '', + ])->create(); + + $this + ->table('orm_test_profile', [ + 'id' => false, + 'primary_key' => ['id'], + ]) + ->addColumn('id', 'integer', [ + 'identity' => true, + 'signed' => true, + 'null' => false, + ]) + ->addColumn('uid', 'integer', [ + 'signed' => true, + 'null' => false, + ]) + ->addColumn('email', 'string', [ + 'limit' => 255, + 'null' => false, + 'default' => '', + ]) + ->addColumn('nickname', 'string', [ + 'limit' => 255, + 'null' => false, + 'default' => '', + ]) + ->addColumn('update_time', 'datetime', [ + 'null' => false, + ]) + ->addColumn('delete_time', 'datetime', [ + 'null' => true, + 'default' => null, + ]) + ->addColumn('create_time', 'datetime', [ + 'null' => false, + ]) + ->create(); + } +} diff --git a/tests/orm/DbTestBase.php b/tests/orm/DbTestBase.php index 19020feb..fc1b0890 100644 --- a/tests/orm/DbTestBase.php +++ b/tests/orm/DbTestBase.php @@ -23,7 +23,7 @@ abstract class DbTestBase extends Base { public ConnectionInterface $db; - protected string $dbName; + protected static string $dbName; protected function provideTestData(): array { diff --git a/tests/orm/DbTransactionTestBase.php b/tests/orm/DbTransactionTestBase.php index 016e815f..74e5b16f 100644 --- a/tests/orm/DbTransactionTestBase.php +++ b/tests/orm/DbTransactionTestBase.php @@ -21,7 +21,7 @@ abstract class DbTransactionTestBase extends Base { protected ConnectionInterface $db; - protected string $dbName; + protected static string $dbName; protected function provideTestData(): array { diff --git a/tests/orm/ModelOneToOneTest.php b/tests/orm/ModelOneToOneBase.php similarity index 53% rename from tests/orm/ModelOneToOneTest.php rename to tests/orm/ModelOneToOneBase.php index bc4664b0..d02c58a5 100644 --- a/tests/orm/ModelOneToOneTest.php +++ b/tests/orm/ModelOneToOneBase.php @@ -4,40 +4,35 @@ namespace tests\orm; use PHPUnit\Framework\TestCase; +use tests\Base; use tests\stubs\ProfileModel; use tests\stubs\UserModel; +use think\db\Query; use think\facade\Db; +use think\Model; /** * 模型一对一关联 */ -class ModelOneToOneTest extends TestCase +abstract class ModelOneToOneBase extends Base { public static function setUpBeforeClass(): void { - self::markTestSkipped('冲突需要更改兼容性'); - $sqlList = [ - 'DROP TABLE IF EXISTS `test_user`;', - 'CREATE TABLE `test_user` ( - `id` int NOT NULL AUTO_INCREMENT, - `account` varchar(255) NOT NULL DEFAULT "", - PRIMARY KEY (`id`) - ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;', - 'DROP TABLE IF EXISTS `test_profile`;', - 'CREATE TABLE `test_profile` ( - `id` int NOT NULL AUTO_INCREMENT, - `uid` int NOT NULL, - `email` varchar(255) NOT NULL DEFAULT "", - `nickname` varchar(255) NOT NULL DEFAULT "", - `update_time` datetime NOT NULL, - `delete_time` datetime DEFAULT NULL, - `create_time` datetime NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;', - ]; - foreach ($sqlList as $sql) { - Db::execute($sql); - } + parent::setUpBeforeClass(); + + // todo 需要一个重置能力更安全 + Model::maker(function (Model $model) { + $model->setConnection(static::$dbName); + var_dump('maker:' . __FUNCTION__ . '-' . $model::class . '-' . spl_object_id($model)); + }); + } + + public function setUp(): void + { + parent::setUp(); + + $this->db->execute('TRUNCATE TABLE orm_test_user;'); + $this->db->execute('TRUNCATE TABLE orm_test_profile;'); } /** @@ -50,7 +45,8 @@ public function testBindAttr() $user = new UserModel(); $user->account = 'thinkphp'; - $user->profile = new ProfileModel(['email' => $email, 'nickname' => $nickname]); + $profile = new ProfileModel(['email' => $email, 'nickname' => $nickname]); + $user->profile = $profile; $user->together(['profile'])->save(); $userID = $user->id; diff --git a/tests/orm/MysqlDbJsonFieldsTest.php b/tests/orm/MysqlDbJsonFieldsTest.php index 88dd42a4..c3a3d654 100644 --- a/tests/orm/MysqlDbJsonFieldsTest.php +++ b/tests/orm/MysqlDbJsonFieldsTest.php @@ -5,5 +5,5 @@ class MysqlDbJsonFieldsTest extends DbJsonFieldsBase { - protected string $dbName = 'mysql'; + protected static string $dbName = 'mysql'; } diff --git a/tests/orm/MysqlDbTest.php b/tests/orm/MysqlDbTest.php index 2b9b8bc2..d1eb952b 100644 --- a/tests/orm/MysqlDbTest.php +++ b/tests/orm/MysqlDbTest.php @@ -5,5 +5,5 @@ class MysqlDbTest extends DbTestBase { - protected string $dbName = 'mysql'; + protected static string $dbName = 'mysql'; } diff --git a/tests/orm/MysqlDbTransactionTest.php b/tests/orm/MysqlDbTransactionTest.php index ed7b6f7b..28214552 100644 --- a/tests/orm/MysqlDbTransactionTest.php +++ b/tests/orm/MysqlDbTransactionTest.php @@ -5,5 +5,5 @@ class MysqlDbTransactionTest extends DbTransactionTestBase { - protected string $dbName = 'mysql'; + protected static string $dbName = 'mysql'; } diff --git a/tests/orm/MysqlModelOneToOneTest.php b/tests/orm/MysqlModelOneToOneTest.php new file mode 100644 index 00000000..43a76972 --- /dev/null +++ b/tests/orm/MysqlModelOneToOneTest.php @@ -0,0 +1,9 @@ + Date: Tue, 20 May 2025 17:48:29 +0800 Subject: [PATCH 43/53] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=9F=BA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Base.php | 15 +++++++----- tests/orm/DbJsonFieldsBase.php | 2 +- tests/orm/DbTestBase.php | 15 +----------- tests/orm/DbTransactionTestBase.php | 35 ++++++++++++---------------- tests/orm/ModelOneToOneBase.php | 3 ++- tests/orm/MysqlDbJsonFieldsTest.php | 2 +- tests/orm/MysqlDbTest.php | 2 +- tests/orm/MysqlDbTransactionTest.php | 2 +- tests/orm/MysqlModelOneToOneTest.php | 2 +- tests/orm/PgsqlDbJsonFieldsTest.php | 12 +--------- tests/orm/PgsqlDbTest.php | 15 +----------- tests/orm/PgsqlDbTransactionTest.php | 2 +- tests/orm/PgsqlModelOneToOneTest.php | 2 +- 13 files changed, 36 insertions(+), 73 deletions(-) diff --git a/tests/Base.php b/tests/Base.php index ee4478bc..0d7c6ea9 100644 --- a/tests/Base.php +++ b/tests/Base.php @@ -14,17 +14,18 @@ use function version_compare; /** - * @property-read string $dbName; + * @property string $connectName; */ class Base extends TestCase { protected ConnectionInterface $db; - protected static string $dbName; + protected static string $connectName; + protected bool $isPgScriptInstalled = false; public function __get(string $name) { - if ($name === 'dbName') { - return static::$dbName; + if ($name === 'connectName') { + return static::$connectName; } throw new \Exception('Undefined property: ' . static::class . '::$' . $name); @@ -32,11 +33,12 @@ public function __get(string $name) public function setUp(): void { - $this->db = Db::connect($this->dbName); + $this->db ??= Db::connect(static::$connectName); - if ($this->dbName === 'pgsql') { + if (static::$connectName === 'pgsql' && $this->isPgScriptInstalled) { pg_reset_function(); pg_install_func(); + $this->isPgScriptInstalled = true; } // var_dump(static::class . '-' . __FUNCTION__ . '-' . spl_object_id($this)); @@ -45,6 +47,7 @@ public function setUp(): void protected static function compatibleInsertAll(BaseQuery $query, array $data): void { if ($query->getConnection() instanceof Pgsql) { + // 当前驱动批量插入不兼容,会产生类型错误,修复后可以移除兼容性 foreach ($data as $datum) { (clone $query)->insert($datum); } diff --git a/tests/orm/DbJsonFieldsBase.php b/tests/orm/DbJsonFieldsBase.php index 73ae03cc..e0957aee 100644 --- a/tests/orm/DbJsonFieldsBase.php +++ b/tests/orm/DbJsonFieldsBase.php @@ -25,7 +25,7 @@ public function testInitGoods(): array $this->db->execute('TRUNCATE TABLE test_goods;'); $userData = $this->provideTestData(); - $this->db->table('test_goods')->json(['extend'])->insertAll($userData); + self::compatibleInsertAll($this->db->table('test_goods')->json(['extend']), $userData); return $userData; } diff --git a/tests/orm/DbTestBase.php b/tests/orm/DbTestBase.php index fc1b0890..82466b28 100644 --- a/tests/orm/DbTestBase.php +++ b/tests/orm/DbTestBase.php @@ -22,9 +22,6 @@ abstract class DbTestBase extends Base { - public ConnectionInterface $db; - protected static string $dbName; - protected function provideTestData(): array { return [ @@ -36,22 +33,12 @@ protected function provideTestData(): array ]; } - public function setUp(): void - { - $this->db = Db::connect($this->dbName); - - if ($this->dbName === 'pgsql') { - pg_reset_function(); - pg_install_func(); - } - } - public function testInitUsers(): array { $this->db->execute('TRUNCATE TABLE test_user;'); $userData = $this->provideTestData(); - $this->db->table('test_user')->insertAll($userData); + self::compatibleInsertAll($this->db->table('test_user'), $userData); return $userData; } diff --git a/tests/orm/DbTransactionTestBase.php b/tests/orm/DbTransactionTestBase.php index 74e5b16f..e3a1ba5a 100644 --- a/tests/orm/DbTransactionTestBase.php +++ b/tests/orm/DbTransactionTestBase.php @@ -21,7 +21,6 @@ abstract class DbTransactionTestBase extends Base { protected ConnectionInterface $db; - protected static string $dbName; protected function provideTestData(): array { @@ -34,21 +33,17 @@ protected function provideTestData(): array public function setUp(): void { + parent::setUp(); + // Db::listen(function ($sql, $time) { // echo "SQL: $sql [$time ms]\n"; // }); - $this->db = Db::connect($this->dbName, true); $this->db->execute('TRUNCATE TABLE test_tran_a;'); - - if ($this->dbName === 'pgsql') { - pg_reset_function(); - pg_install_func(); - } } protected function reconnect(): ConnectionInterface { - return $this->db = Db::connect($this->dbName, true); + return $this->db = Db::connect($this->connectName, true); } protected static function insertAll(ConnectionInterface $db, string $table, array $data): void @@ -92,8 +87,8 @@ public function testBreakReconnect() // 初始化配置 $oldConfig = Db::getConfig(); $config = $oldConfig; - $config['connections'][$this->dbName]['break_reconnect'] = true; - $config['connections'][$this->dbName]['break_match_str'] = [ + $config['connections'][$this->connectName]['break_reconnect'] = true; + $config['connections'][$this->connectName]['break_match_str'] = [ 'query execution was interrupted', 'no connection to the server', ]; @@ -105,7 +100,7 @@ public function testBreakReconnect() self::insertAll($this->db, 'test_tran_a', $this->provideTestData()); $cid = query_connection_id($this->db); - kill_connection($this->dbName . '_manage', $cid); + kill_connection($this->connectName . '_manage', $cid); // 触发重连 $this->db->table('test_tran_a')->where('id', '=', 2)->value('username'); @@ -114,7 +109,7 @@ public function testBreakReconnect() $cid = $newCid; // 事务前重连 - kill_connection($this->dbName . '_manage', $cid); + kill_connection($this->connectName . '_manage', $cid); $this->db->table('test_tran_a')->startTrans(); $this->db->table('test_tran_a')->where('id', '=', 2)->update([ 'username' => '2-8-b', @@ -134,7 +129,7 @@ public function testBreakReconnect() $this->db->table('test_tran_a')->where('id', '=', 2)->update([ 'username' => '2-8-c', ]); - kill_connection($this->dbName . '_manage', $cid); + kill_connection($this->connectName . '_manage', $cid); $this->db->table('test_tran_a')->where('id', '=', 3)->update([ 'username' => '3-7-b', ]); @@ -145,13 +140,13 @@ public function testBreakReconnect() } catch (Exception $rollbackException) { // Ignore exception $this->proxyAssertMatchesRegularExpression( - $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $this->connectName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', $rollbackException->getMessage() ); } // Ignore exception $this->proxyAssertMatchesRegularExpression( - $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $this->connectName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', $exception->getMessage() ); } @@ -216,8 +211,8 @@ public function testTransactionSavepointBreakReconnect() // 初始化配置 $oldConfig = Db::getConfig(); $config = $oldConfig; - $config['connections'][$this->dbName]['break_reconnect'] = true; - $config['connections'][$this->dbName]['break_match_str'] = [ + $config['connections'][$this->connectName]['break_reconnect'] = true; + $config['connections'][$this->connectName]['break_match_str'] = [ 'query execution was interrupted', 'no connection to the server', ]; @@ -242,7 +237,7 @@ public function testTransactionSavepointBreakReconnect() ]); $oldConnect->table('test_tran_a')->commit(); // kill - kill_connection($this->dbName . '_manage', $cid); + kill_connection($this->connectName . '_manage', $cid); // tran 2 $oldConnect->table('test_tran_a')->startTrans(); $newConnect->table('test_tran_a')->where('id', '=', 3)->update([ @@ -257,13 +252,13 @@ public function testTransactionSavepointBreakReconnect() } catch (Exception $rollbackException) { // Ignore exception $this->proxyAssertMatchesRegularExpression( - $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $this->connectName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', $rollbackException->getMessage() ); } // Ignore exception $this->proxyAssertMatchesRegularExpression( - $this->dbName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', + $this->connectName === 'mysql' ? '~(server has gone away)~' : '~(no connection to the server)~', $exception->getMessage() ); } finally { diff --git a/tests/orm/ModelOneToOneBase.php b/tests/orm/ModelOneToOneBase.php index d02c58a5..bf3bd74d 100644 --- a/tests/orm/ModelOneToOneBase.php +++ b/tests/orm/ModelOneToOneBase.php @@ -22,7 +22,7 @@ public static function setUpBeforeClass(): void // todo 需要一个重置能力更安全 Model::maker(function (Model $model) { - $model->setConnection(static::$dbName); + $model->setConnection(static::$connectName); var_dump('maker:' . __FUNCTION__ . '-' . $model::class . '-' . spl_object_id($model)); }); } @@ -31,6 +31,7 @@ public function setUp(): void { parent::setUp(); + // 每个测试执行前重置测试数据 $this->db->execute('TRUNCATE TABLE orm_test_user;'); $this->db->execute('TRUNCATE TABLE orm_test_profile;'); } diff --git a/tests/orm/MysqlDbJsonFieldsTest.php b/tests/orm/MysqlDbJsonFieldsTest.php index c3a3d654..40696af4 100644 --- a/tests/orm/MysqlDbJsonFieldsTest.php +++ b/tests/orm/MysqlDbJsonFieldsTest.php @@ -5,5 +5,5 @@ class MysqlDbJsonFieldsTest extends DbJsonFieldsBase { - protected static string $dbName = 'mysql'; + protected static string $connectName = 'mysql'; } diff --git a/tests/orm/MysqlDbTest.php b/tests/orm/MysqlDbTest.php index d1eb952b..ba54df2a 100644 --- a/tests/orm/MysqlDbTest.php +++ b/tests/orm/MysqlDbTest.php @@ -5,5 +5,5 @@ class MysqlDbTest extends DbTestBase { - protected static string $dbName = 'mysql'; + protected static string $connectName = 'mysql'; } diff --git a/tests/orm/MysqlDbTransactionTest.php b/tests/orm/MysqlDbTransactionTest.php index 28214552..c6af14dd 100644 --- a/tests/orm/MysqlDbTransactionTest.php +++ b/tests/orm/MysqlDbTransactionTest.php @@ -5,5 +5,5 @@ class MysqlDbTransactionTest extends DbTransactionTestBase { - protected static string $dbName = 'mysql'; + protected static string $connectName = 'mysql'; } diff --git a/tests/orm/MysqlModelOneToOneTest.php b/tests/orm/MysqlModelOneToOneTest.php index 43a76972..b9cf2182 100644 --- a/tests/orm/MysqlModelOneToOneTest.php +++ b/tests/orm/MysqlModelOneToOneTest.php @@ -5,5 +5,5 @@ class MysqlModelOneToOneTest extends ModelOneToOneBase { - protected static string $dbName = 'mysql'; + protected static string $connectName = 'mysql'; } diff --git a/tests/orm/PgsqlDbJsonFieldsTest.php b/tests/orm/PgsqlDbJsonFieldsTest.php index b3e0f97c..de42944c 100644 --- a/tests/orm/PgsqlDbJsonFieldsTest.php +++ b/tests/orm/PgsqlDbJsonFieldsTest.php @@ -5,15 +5,5 @@ class PgsqlDbJsonFieldsTest extends DbJsonFieldsBase { - protected static string $dbName = 'pgsql'; - - public function testInitGoods(): array - { - $this->db->execute('TRUNCATE TABLE test_goods;'); - - $userData = $this->provideTestData(); - self::compatibleInsertAll($this->db->table('test_goods')->json(['extend']), $userData); - - return $userData; - } + protected static string $connectName = 'pgsql'; } diff --git a/tests/orm/PgsqlDbTest.php b/tests/orm/PgsqlDbTest.php index bfa2dcbf..0554b38e 100644 --- a/tests/orm/PgsqlDbTest.php +++ b/tests/orm/PgsqlDbTest.php @@ -5,18 +5,5 @@ class PgsqlDbTest extends DbTestBase { - protected static string $dbName = 'pgsql'; - - public function testInitUsers(): array - { - $this->db->execute('TRUNCATE TABLE "test_user";'); - - // 当前驱动批量插入不兼容,会产生类型错误 - $userData = $this->provideTestData(); - foreach ($userData as $datum) { - $this->db->table('test_user')->insert($datum); - } - - return $userData; - } + protected static string $connectName = 'pgsql'; } diff --git a/tests/orm/PgsqlDbTransactionTest.php b/tests/orm/PgsqlDbTransactionTest.php index e36b4441..a2c9b7a5 100644 --- a/tests/orm/PgsqlDbTransactionTest.php +++ b/tests/orm/PgsqlDbTransactionTest.php @@ -5,5 +5,5 @@ class PgsqlDbTransactionTest extends DbTransactionTestBase { - protected static string $dbName = 'pgsql'; + protected static string $connectName = 'pgsql'; } diff --git a/tests/orm/PgsqlModelOneToOneTest.php b/tests/orm/PgsqlModelOneToOneTest.php index 13cc633d..6bf6a566 100644 --- a/tests/orm/PgsqlModelOneToOneTest.php +++ b/tests/orm/PgsqlModelOneToOneTest.php @@ -5,5 +5,5 @@ class PgsqlModelOneToOneTest extends ModelOneToOneBase { - protected static string $dbName = 'pgsql'; + protected static string $connectName = 'pgsql'; } From 4c39281d5fefcc86afc74b96d8eaff5832400a55 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 20 May 2025 17:52:22 +0800 Subject: [PATCH 44/53] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9F=BA=E7=B1=BB?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/{Base.php => TestCaseBase.php} | 3 +-- tests/orm/DbJsonFieldsBase.php | 6 ++---- tests/orm/DbTestBase.php | 17 +++++++---------- tests/orm/DbTransactionTestBase.php | 13 ++++--------- tests/orm/ModelOneToOneBase.php | 7 ++----- 5 files changed, 16 insertions(+), 30 deletions(-) rename tests/{Base.php => TestCaseBase.php} (97%) diff --git a/tests/Base.php b/tests/TestCaseBase.php similarity index 97% rename from tests/Base.php rename to tests/TestCaseBase.php index 0d7c6ea9..12f51b97 100644 --- a/tests/Base.php +++ b/tests/TestCaseBase.php @@ -10,13 +10,12 @@ use think\db\ConnectionInterface; use think\db\connector\Pgsql; use think\facade\Db; -use think\Model; use function version_compare; /** * @property string $connectName; */ -class Base extends TestCase +class TestCaseBase extends TestCase { protected ConnectionInterface $db; protected static string $connectName; diff --git a/tests/orm/DbJsonFieldsBase.php b/tests/orm/DbJsonFieldsBase.php index e0957aee..6b029a3f 100644 --- a/tests/orm/DbJsonFieldsBase.php +++ b/tests/orm/DbJsonFieldsBase.php @@ -2,11 +2,9 @@ namespace tests\orm; -use tests\Base; -use think\Collection; -use think\facade\Db; +use tests\TestCaseBase; -abstract class DbJsonFieldsBase extends Base +abstract class DbJsonFieldsBase extends TestCaseBase { protected function provideTestData(): array { diff --git a/tests/orm/DbTestBase.php b/tests/orm/DbTestBase.php index 82466b28..161b2dd0 100644 --- a/tests/orm/DbTestBase.php +++ b/tests/orm/DbTestBase.php @@ -4,23 +4,20 @@ namespace tests\orm; -use think\db\ConnectionInterface; +use tests\TestCaseBase; +use think\Collection; +use think\db\exception\DbException; +use think\db\Raw; +use think\Exception as ThinkException; +use think\facade\Db; use function array_column; use function array_keys; use function array_unique; use function array_values; use function tests\array_column_ex; use function tests\array_value_sort; -use tests\Base; -use think\Collection; -use think\db\exception\DbException; -use think\db\Raw; -use think\Exception as ThinkException; -use think\facade\Db; -use function tests\pg_install_func; -use function tests\pg_reset_function; -abstract class DbTestBase extends Base +abstract class DbTestBase extends TestCaseBase { protected function provideTestData(): array { diff --git a/tests/orm/DbTransactionTestBase.php b/tests/orm/DbTransactionTestBase.php index e3a1ba5a..35d19184 100644 --- a/tests/orm/DbTransactionTestBase.php +++ b/tests/orm/DbTransactionTestBase.php @@ -5,20 +5,15 @@ namespace tests\orm; use Exception; -use tests\Base; +use tests\TestCaseBase; use think\db\ConnectionInterface; use think\db\connector\Pgsql; -use function tests\kill_connection; -use function tests\mysql_kill_connection; -use function tests\pg_install_func; -use function tests\pg_reset_function; -use function tests\query_connection_id; -use function tests\query_mysql_connection_id; use think\facade\Db; use Throwable; -use function tests\query_pgsql_connection_id; +use function tests\kill_connection; +use function tests\query_connection_id; -abstract class DbTransactionTestBase extends Base +abstract class DbTransactionTestBase extends TestCaseBase { protected ConnectionInterface $db; diff --git a/tests/orm/ModelOneToOneBase.php b/tests/orm/ModelOneToOneBase.php index bf3bd74d..0d13a3dd 100644 --- a/tests/orm/ModelOneToOneBase.php +++ b/tests/orm/ModelOneToOneBase.php @@ -3,18 +3,15 @@ namespace tests\orm; -use PHPUnit\Framework\TestCase; -use tests\Base; use tests\stubs\ProfileModel; use tests\stubs\UserModel; -use think\db\Query; -use think\facade\Db; +use tests\TestCaseBase; use think\Model; /** * 模型一对一关联 */ -abstract class ModelOneToOneBase extends Base +abstract class ModelOneToOneBase extends TestCaseBase { public static function setUpBeforeClass(): void { From 6811bbd232c2215a2a347e80c39ab1a01c801492 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 20 May 2025 18:58:30 +0800 Subject: [PATCH 45/53] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=B5=8B=E8=AF=95=20Mo?= =?UTF-8?q?delFieldType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 22 +++++++ tests/TestCaseBase.php | 22 +++++++ .../20250520095311_model_field_type.php | 64 +++++++++++++++++++ ...eldTypeTest.php => ModelFieldTypeBase.php} | 58 +++++++++-------- tests/orm/ModelOneToOneBase.php | 7 +- tests/orm/MysqlModelFieldTypeTest.php | 9 +++ tests/orm/PgsqlModelFieldTypeTest.php | 9 +++ 7 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 tests/db/migrations/20250520095311_model_field_type.php rename tests/orm/{ModelFieldTypeTest.php => ModelFieldTypeBase.php} (72%) create mode 100644 tests/orm/MysqlModelFieldTypeTest.php create mode 100644 tests/orm/PgsqlModelFieldTypeTest.php diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 1cc868c9..dd365582 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -111,6 +111,28 @@ protected function supportSavepoint(): bool return true; } + protected function getFieldType(string $type): string + { + // 将字段类型转换为小写以进行比较 + $type = strtolower($type); + + return match (true) { + str_starts_with($type, 'set') => 'set', + str_starts_with($type, 'enum') => 'enum', + str_starts_with($type, 'bigint'), + str_contains($type, 'numeric') => 'bigint', + str_contains($type, 'float') || str_contains($type, 'double') || + str_contains($type, 'decimal') || str_contains($type, 'real') || + str_contains($type, 'int') || str_contains($type, 'serial') || + str_contains($type, 'bit') => 'int', + str_contains($type, 'bool') => 'bool', + str_starts_with($type, 'timestamp') => 'timestamp', + str_starts_with($type, 'datetime') => 'datetime', + str_starts_with($type, 'date') => 'date', + default => 'string', + }; + } + public function insert(BaseQuery $query, bool $getLastInsID = false) { // 分析查询表达式 diff --git a/tests/TestCaseBase.php b/tests/TestCaseBase.php index 12f51b97..ee0a5089 100644 --- a/tests/TestCaseBase.php +++ b/tests/TestCaseBase.php @@ -10,6 +10,7 @@ use think\db\ConnectionInterface; use think\db\connector\Pgsql; use think\facade\Db; +use think\Model; use function version_compare; /** @@ -21,6 +22,15 @@ class TestCaseBase extends TestCase protected static string $connectName; protected bool $isPgScriptInstalled = false; + protected static function initModelSupport(): void + { + // todo 需要一个重置能力更安全 + Model::maker(function (Model $model) { + $model->setConnection(static::$connectName); + var_dump('maker:' . __FUNCTION__ . '-' . $model::class . '-' . spl_object_id($model)); + }); + } + public function __get(string $name) { if ($name === 'connectName') { @@ -55,6 +65,18 @@ protected static function compatibleInsertAll(BaseQuery $query, array $data): vo } } + protected static function compatibleModelInsertAll(Model $query, array $data): void + { + if ($query->getConnection() === 'pgsql') { + // 当前驱动批量插入不兼容,会产生类型错误,修复后可以移除兼容性 + foreach ($data as $datum) { + (clone $query)->insert($datum); + } + } else { + $query->insertAll($data); + } + } + protected function proxyAssertMatchesRegularExpression(string $pattern, string $string, string $message = '') { if (version_compare(Version::id(), '9.1', '>=')) { diff --git a/tests/db/migrations/20250520095311_model_field_type.php b/tests/db/migrations/20250520095311_model_field_type.php new file mode 100644 index 00000000..6158096d --- /dev/null +++ b/tests/db/migrations/20250520095311_model_field_type.php @@ -0,0 +1,64 @@ +getAdapter()->getAdapterType(); + + $this + ->table('test_field_type', ['id' => false, 'primary_key' => ['id']]) + ->addColumn( + 'id', + 'integer', + [ + 'identity' => true, + 'signed' => false, // MySQL用UNSIGNED, PostgreSQL需要容错 + 'null' => false, + ] + ) + ->addColumn( + 't_json', + 'json', + [ + 'null' => true, + 'default' => null, + ]) + ->addColumn( + 't_php', + 'text', + [ // 改用text类型更通用 + 'limit' => 512, + 'null' => true, + 'default' => null, + ] + ) + ->addColumn( + 'bigint', + $adapterType === 'pgsql' ? 'decimal' : 'biginteger', + [ + 'signed' => false, + 'null' => true, + 'default' => null, + 'after' => 't_php', // 可选字段排序 + 'precision' => $adapterType === 'pgsql' ? 20 : null, // PG BIGINT 最大19位 + ] + ) + ->create(); + } +} diff --git a/tests/orm/ModelFieldTypeTest.php b/tests/orm/ModelFieldTypeBase.php similarity index 72% rename from tests/orm/ModelFieldTypeTest.php rename to tests/orm/ModelFieldTypeBase.php index 2843b092..c38bf970 100644 --- a/tests/orm/ModelFieldTypeTest.php +++ b/tests/orm/ModelFieldTypeBase.php @@ -3,40 +3,49 @@ namespace tests\orm; -use PHPUnit\Framework\TestCase; use tests\stubs\FieldTypeModel; use tests\stubs\TestFieldJsonDTO; use tests\stubs\TestFieldPhpDTO; -use think\facade\Db; +use tests\TestCaseBase; -class ModelFieldTypeTest extends TestCase +class ModelFieldTypeBase extends TestCaseBase { - public static function setUpBeforeClass(): void + protected function provideTestData(): array { - Db::execute('DROP TABLE IF EXISTS `test_field_type`;'); - Db::execute( - << 1, 't_json' => '{"num1": 1, "str1": "a"}', 't_php' => (string) (new TestFieldPhpDTO(1, 'a')), 'bigint' => '0'], ['id' => 2, 't_json' => '{"num1": 2, "str1": "b"}', 't_php' => (string) (new TestFieldPhpDTO(2, 'b')), 'bigint' => '244791959321042944'], ['id' => 3, 't_json' => '{"num1": 3, "str1": "c"}', 't_php' => (string) (new TestFieldPhpDTO(3, 'c')), 'bigint' => '18374686479671623679'], ]; + } + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + self::initModelSupport(); + } - (new FieldTypeModel())->insertAll($data); + public function testInitData(): array + { + $this->db->execute('TRUNCATE TABLE test_field_type;'); + + $data = $this->provideTestData(); + self::compatibleModelInsertAll(new FieldTypeModel(), $data); + + return $data; + } + + /** + * @depends testInitData + */ + public function testFieldTypeSelect(array $data) + { + var_dump($this->db->getTableFieldsInfo('test_field_type')); + var_dump($this->db->getSchemaInfo('test_field_type')); + var_dump($this->db->getFieldBindType('bigint')); - $result = Db::table('test_field_type')->select(); + $result = $this->db->table('test_field_type')->setFieldType(['bigint' => 'string'])->select(); $this->assertNotEmpty($result->count()); foreach ($data as $index => $item) { $this->assertEquals($item, $result[$index]); @@ -52,7 +61,7 @@ public function testFieldTypeSelect() } /** - * @depends testFieldTypeSelect + * @depends testInitData */ public function testFieldReadAndWrite() { @@ -69,9 +78,6 @@ public function testFieldReadAndWrite() $this->assertEquals($result->id, $result->t_php->getId()); } - /** - * @depends testFieldTypeSelect - */ public function testFieldReadInvalid() { diff --git a/tests/orm/ModelOneToOneBase.php b/tests/orm/ModelOneToOneBase.php index 0d13a3dd..9ab1ca70 100644 --- a/tests/orm/ModelOneToOneBase.php +++ b/tests/orm/ModelOneToOneBase.php @@ -6,7 +6,6 @@ use tests\stubs\ProfileModel; use tests\stubs\UserModel; use tests\TestCaseBase; -use think\Model; /** * 模型一对一关联 @@ -17,11 +16,7 @@ public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - // todo 需要一个重置能力更安全 - Model::maker(function (Model $model) { - $model->setConnection(static::$connectName); - var_dump('maker:' . __FUNCTION__ . '-' . $model::class . '-' . spl_object_id($model)); - }); + self::initModelSupport(); } public function setUp(): void diff --git a/tests/orm/MysqlModelFieldTypeTest.php b/tests/orm/MysqlModelFieldTypeTest.php new file mode 100644 index 00000000..8607011a --- /dev/null +++ b/tests/orm/MysqlModelFieldTypeTest.php @@ -0,0 +1,9 @@ + Date: Tue, 20 May 2025 19:05:31 +0800 Subject: [PATCH 46/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/TestCaseBase.php | 10 ++++++---- tests/orm/ModelFieldTypeBase.php | 4 ---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/TestCaseBase.php b/tests/TestCaseBase.php index ee0a5089..540166f5 100644 --- a/tests/TestCaseBase.php +++ b/tests/TestCaseBase.php @@ -20,7 +20,7 @@ class TestCaseBase extends TestCase { protected ConnectionInterface $db; protected static string $connectName; - protected bool $isPgScriptInstalled = false; + protected static bool $isResetPgScript = false; protected static function initModelSupport(): void { @@ -44,10 +44,12 @@ public function setUp(): void { $this->db ??= Db::connect(static::$connectName); - if (static::$connectName === 'pgsql' && $this->isPgScriptInstalled) { - pg_reset_function(); + if (static::$connectName === 'pgsql') { + if (self::$isResetPgScript === false) { + pg_reset_function(); + self::$isResetPgScript = true; + } pg_install_func(); - $this->isPgScriptInstalled = true; } // var_dump(static::class . '-' . __FUNCTION__ . '-' . spl_object_id($this)); diff --git a/tests/orm/ModelFieldTypeBase.php b/tests/orm/ModelFieldTypeBase.php index c38bf970..0df0670a 100644 --- a/tests/orm/ModelFieldTypeBase.php +++ b/tests/orm/ModelFieldTypeBase.php @@ -41,10 +41,6 @@ public function testInitData(): array */ public function testFieldTypeSelect(array $data) { - var_dump($this->db->getTableFieldsInfo('test_field_type')); - var_dump($this->db->getSchemaInfo('test_field_type')); - var_dump($this->db->getFieldBindType('bigint')); - $result = $this->db->table('test_field_type')->setFieldType(['bigint' => 'string'])->select(); $this->assertNotEmpty($result->count()); foreach ($data as $index => $item) { From 59590faed81c643585eefed4cab1dfe1fd4ddc5f Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Fri, 23 May 2025 14:06:42 +0800 Subject: [PATCH 47/53] =?UTF-8?q?=E4=BC=98=E5=8C=96Connection=E7=B1=BB=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E7=8B=AC=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/Connection.php | 91 ++++++++++++++++++++------------------ src/db/PDOConnection.php | 12 ++--- src/db/connector/Mongo.php | 13 +++--- src/db/connector/Pgsql.php | 2 +- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/db/Connection.php b/src/db/Connection.php index 37a7a002..4305e95d 100644 --- a/src/db/Connection.php +++ b/src/db/Connection.php @@ -168,7 +168,7 @@ public function newQuery() /** @var BaseQuery $query */ $query = new $class($this); - $timeRule = $this->db->getConfig('time_query_rule'); + $timeRule = $this->db?->getConfig('time_query_rule'); if (!empty($timeRule)) { $query->timeRule($timeRule); } @@ -253,44 +253,46 @@ public function getConfig(string $config = '') /** * 数据库SQL监控. * - * @param string $sql 执行的SQL语句 留空自动获取 - * @param bool $master 主从标记 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 * * @return void */ protected function trigger(string $sql = '', bool $master = false): void { - $listen = $this->db->getListen(); - if (empty($listen)) { - $listen[] = function ($sql, $time, $master) { - if (str_starts_with($sql, 'CONNECT:')) { - $this->db->log($sql); - - return; - } - - // 记录SQL - if (is_bool($master)) { - // 分布式记录当前操作的主从 - $master = $master ? 'master|' : 'slave|'; - } else { - $master = ''; - } - - $this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); - }; - } + if ($this->db) { + $listen = $this->db->getListen(); + if (empty($listen)) { + $listen[] = function ($sql, $time, $master) { + if (str_starts_with($sql, 'CONNECT:')) { + $this->db->log($sql); + + return; + } + + // 记录SQL + if (is_bool($master)) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + $this->db->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); + }; + } - $runtime = number_format((microtime(true) - $this->queryStartTime), 6); - $sql = $sql ?: $this->getLastsql(); + $runtime = number_format((microtime(true) - $this->queryStartTime), 6); + $sql = $sql ?: $this->getLastsql(); - if (empty($this->config['deploy'])) { - $master = null; - } + if (empty($this->config['deploy'])) { + $master = null; + } - foreach ($listen as $callback) { - if (is_callable($callback)) { - $callback($sql, $runtime, $master); + foreach ($listen as $callback) { + if (is_callable($callback)) { + $callback($sql, $runtime, $master); + } } } } @@ -303,7 +305,8 @@ protected function trigger(string $sql = '', bool $master = false): void protected function cacheData(CacheItem $cacheItem) { if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) { - $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + $this->cache->tag($cacheItem->getTag()) + ->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); } else { $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); } @@ -312,8 +315,8 @@ protected function cacheData(CacheItem $cacheItem) /** * 分析缓存Key. * - * @param BaseQuery $query 查询对象 - * @param string $method 查询方法 + * @param BaseQuery $query 查询对象 + * @param string $method 查询方法 * * @return string */ @@ -331,9 +334,9 @@ protected function getCacheKey(BaseQuery $query, string $method = ''): string /** * 分析缓存. * - * @param BaseQuery $query 查询对象 - * @param array $cache 缓存信息 - * @param string $method 查询方法 + * @param BaseQuery $query 查询对象 + * @param array $cache 缓存信息 + * @param string $method 查询方法 * * @return CacheItem */ @@ -369,8 +372,8 @@ public function getNumRows(): int /** * 获取最终的SQL语句. * - * @param string $sql 带参数绑定的sql语句 - * @param array $bind 参数绑定列表 + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 * * @return string */ @@ -388,11 +391,11 @@ public function getRealSql(string $sql, array $bind = []): string // 判断占位符 $sql = is_numeric($key) ? - substr_replace($sql, $value, strpos($sql, '?'), 1) : - str_replace( - [':' . $key . ' ', ':' . $key . ',', ':' . $key . ')'], - [$value . ' ', $value . ',', $value . ')'], - $sql); + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ' ', ':' . $key . ',', ':' . $key . ')'], + [$value . ' ', $value . ',', $value . ')'], + $sql); } return rtrim($sql); diff --git a/src/db/PDOConnection.php b/src/db/PDOConnection.php index 6334f5b1..c3efcfcb 100644 --- a/src/db/PDOConnection.php +++ b/src/db/PDOConnection.php @@ -585,7 +585,7 @@ public function connect(array $config = [], $linkNum = 0, $autoConnection = fals return $this->links[$linkNum]; } catch (\PDOException $e) { if ($autoConnection) { - $this->db->log($e->getMessage(), 'error'); + $this->db?->log($e->getMessage(), 'error'); return $this->connect($autoConnection, $linkNum); } else { @@ -912,7 +912,7 @@ public function find(BaseQuery $query): array { // 事件回调 try { - $this->db->trigger('before_find', $query); + $this->db?->trigger('before_find', $query); } catch (DbEventException $e) { return []; } @@ -958,7 +958,7 @@ public function cursor(BaseQuery $query) public function select(BaseQuery $query): array { try { - $this->db->trigger('before_select', $query); + $this->db?->trigger('before_select', $query); } catch (DbEventException $e) { return []; } @@ -1003,7 +1003,7 @@ public function insert(BaseQuery $query, bool $getLastInsID = false) $query->setOption('data', $data); - $this->db->trigger('after_insert', $query); + $this->db?->trigger('after_insert', $query); if ($getLastInsID && $lastInsId) { return $lastInsId; @@ -1167,7 +1167,7 @@ public function update(BaseQuery $query): int $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql); if ($result) { - $this->db->trigger('after_update', $query); + $this->db?->trigger('after_update', $query); } return $result; @@ -1194,7 +1194,7 @@ public function delete(BaseQuery $query): int $result = $this->pdoExecute($query, $sql); if ($result) { - $this->db->trigger('after_delete', $query); + $this->db?->trigger('after_delete', $query); } return $result; diff --git a/src/db/connector/Mongo.php b/src/db/connector/Mongo.php index 43eaacc7..cf4cc06d 100644 --- a/src/db/connector/Mongo.php +++ b/src/db/connector/Mongo.php @@ -245,7 +245,6 @@ public function cursor($query) public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor { $this->initConnect($master); - $this->db->updateQueryTimes(); $options = $query->getOptions(); $namespace = $options['table']; @@ -380,7 +379,6 @@ protected function mongoQuery(BaseQuery $query, $mongoQuery): array protected function mongoExecute(BaseQuery $query, BulkWrite $bulk) { $this->initConnect(true); - $this->db->updateQueryTimes(); $options = $query->getOptions(); $namespace = $options['table']; @@ -447,7 +445,6 @@ protected function mongoExecute(BaseQuery $query, BulkWrite $bulk) public function command(Command $command, string $dbName = '', ?ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array { $this->initConnect($master); - $this->db->updateQueryTimes(); $this->queryStartTime = microtime(true); @@ -760,7 +757,7 @@ public function insert(BaseQuery $query, bool $getLastInsID = false) $query->setOption('data', $data); - $this->db->trigger('after_insert', $query); + $this->db?->trigger('after_insert', $query); if ($getLastInsID) { return $lastInsId; @@ -849,7 +846,7 @@ public function update(BaseQuery $query): int $result = $writeResult->getModifiedCount(); if ($result) { - $this->db->trigger('after_update', $query); + $this->db?->trigger('after_update', $query); } return $result; @@ -881,7 +878,7 @@ public function delete(BaseQuery $query): int $result = $writeResult->getDeletedCount(); if ($result) { - $this->db->trigger('after_delete', $query); + $this->db?->trigger('after_delete', $query); } return $result; @@ -904,7 +901,7 @@ public function delete(BaseQuery $query): int public function select(BaseQuery $query): array { try { - $this->db->trigger('before_select', $query); + $this->db?->trigger('before_select', $query); } catch (DbEventException $e) { return []; } @@ -932,7 +929,7 @@ public function find(BaseQuery $query): array { // 事件回调 try { - $this->db->trigger('before_find', $query); + $this->db?->trigger('before_find', $query); } catch (DbEventException $e) { return []; } diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index dd365582..fe056dec 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -160,7 +160,7 @@ public function insert(BaseQuery $query, bool $getLastInsID = false) $query->setOption('data', $data); - $this->db->trigger('after_insert', $query); + $this->db?->trigger('after_insert', $query); if ($getLastInsID && $lastInsId) { return $lastInsId; From 2179f22a33c9f83329675c3d263782f8ad807dd1 Mon Sep 17 00:00:00 2001 From: auooru Date: Tue, 20 May 2025 19:11:59 +0800 Subject: [PATCH 48/53] README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 30e61fe0..60cf9a54 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,45 @@ composer require topthink/think-orm ## 文档 详细参考 [ThinkORM开发指南](https://doc.thinkphp.cn/@think-orm) + +## 参与开发 + +### 单元测试编写 + +创建创建一个名为 UserInfo 的迁移文件(以测试单元为单位来创建迁移) + +```bash +./vendor/bin/phinx create UserInfo +``` + +### 迁移命令 + +下面相关命令都是 mysql 与 pgsql 同时执行,如果环境不完整可以通过 phinx 手动执行独立的迁移命令。 + +#### 执行迁移(mysql、pgsql) + +```bash +composer run db-migrate +``` + +#### 重建,先回滚在迁移(mysql、pgsql) + +```bash +composer run db-rebuild +``` + +#### 回滚迁移(mysql、pgsql) + +```bash +composer run db-rollback +``` + +#### 迁移状态(mysql、pgsql) + +```bash +composer run db-status +``` + +### 环境问题 + +1. 如果提示 phinx 不存在,尝试手动执行`composer bin phinx install`安装。 From 6ba19758a294cb03666acca9e85f24360d90017a Mon Sep 17 00:00:00 2001 From: auooru Date: Fri, 23 May 2025 13:35:27 +0800 Subject: [PATCH 49/53] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20pg=20=E6=B5=AE?= =?UTF-8?q?=E7=82=B9=E7=B1=BB=E5=9E=8B=E5=8C=B9=E9=85=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index fe056dec..7247c51d 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -119,16 +119,18 @@ protected function getFieldType(string $type): string return match (true) { str_starts_with($type, 'set') => 'set', str_starts_with($type, 'enum') => 'enum', - str_starts_with($type, 'bigint'), - str_contains($type, 'numeric') => 'bigint', + str_starts_with($type, 'bigint') => 'bigint', str_contains($type, 'float') || str_contains($type, 'double') || - str_contains($type, 'decimal') || str_contains($type, 'real') || + str_contains($type, 'decimal') || + str_contains($type, 'real') => 'float', str_contains($type, 'int') || str_contains($type, 'serial') || str_contains($type, 'bit') => 'int', str_contains($type, 'bool') => 'bool', str_starts_with($type, 'timestamp') => 'timestamp', str_starts_with($type, 'datetime') => 'datetime', str_starts_with($type, 'date') => 'date', + // 映射到字符串类型备注 + // numeric => string default => 'string', }; } From f2ea9afffb87d6a778226f7c010e1cef389527fd Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 26 May 2025 10:40:26 +0800 Subject: [PATCH 50/53] =?UTF-8?q?=E5=AE=8C=E5=96=84=20pg=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=BD=AC=E6=8D=A2=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/connector/Pgsql.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/db/connector/Pgsql.php b/src/db/connector/Pgsql.php index 7247c51d..20d955b9 100644 --- a/src/db/connector/Pgsql.php +++ b/src/db/connector/Pgsql.php @@ -120,17 +120,19 @@ protected function getFieldType(string $type): string str_starts_with($type, 'set') => 'set', str_starts_with($type, 'enum') => 'enum', str_starts_with($type, 'bigint') => 'bigint', - str_contains($type, 'float') || str_contains($type, 'double') || - str_contains($type, 'decimal') || + str_contains($type, 'float') || + str_contains($type, 'double') || str_contains($type, 'real') => 'float', - str_contains($type, 'int') || str_contains($type, 'serial') || + str_contains($type, 'int') || + str_contains($type, 'serial') || str_contains($type, 'bit') => 'int', str_contains($type, 'bool') => 'bool', str_starts_with($type, 'timestamp') => 'timestamp', str_starts_with($type, 'datetime') => 'datetime', str_starts_with($type, 'date') => 'date', - // 映射到字符串类型备注 + // 映射到字符串备注(高精度数值支持) // numeric => string + // decimal => string default => 'string', }; } From 0db842eb46af7e90adc62f9096e7398c7274749d Mon Sep 17 00:00:00 2001 From: auooru Date: Mon, 26 May 2025 14:07:50 +0800 Subject: [PATCH 51/53] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20pg=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=90=8D=E8=BD=AC=E4=B9=89=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=AD=97=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/builder/Pgsql.php | 12 +++++++++++- tests/orm/DbTestBase.php | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/db/builder/Pgsql.php b/src/db/builder/Pgsql.php index 384deca3..47d0460e 100644 --- a/src/db/builder/Pgsql.php +++ b/src/db/builder/Pgsql.php @@ -13,8 +13,8 @@ namespace think\db\builder; -use think\db\Builder; use think\db\BaseQuery as Query; +use think\db\Builder; use think\db\Raw; /** @@ -97,6 +97,16 @@ public function parseKey(Query $query, string|int|Raw $key, bool $strict = false $table = $alias[$table]; } + // 尝试对表名/别名进行转义 + if (!preg_match('/[,\"\*\(\).\s]/', $table)) { + $table = '"' . $table . '"'; + } + + if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { + $key = '"' . $key . '"'; + } + } else { + // 尝试对单独的字段名进行转义 if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { $key = '"' . $key . '"'; } diff --git a/tests/orm/DbTestBase.php b/tests/orm/DbTestBase.php index 161b2dd0..7a26e92b 100644 --- a/tests/orm/DbTestBase.php +++ b/tests/orm/DbTestBase.php @@ -155,8 +155,8 @@ public function testWhereIn(array $users) $result = $this->db->table('test_user')->whereNotIn('type', [])->column('*'); $this->assertEquals($expected, $result); - // 合并多余空格,替换 "`" 是为了同时兼容 pg 与 mysql - $sqlLogs = array_map(static fn ($str) => preg_replace(['#\s{2,}#', '~`~'], [' ', ''], $str), $sqlLogs); + // 合并多余空格,替换 "`" 和 "`" 是为了同时兼容 pg 与 mysql + $sqlLogs = array_map(static fn ($str) => preg_replace(['#\s{2,}#', '~[`"]~'], [' ', ''], $str), $sqlLogs); $this->assertEquals([ 'SELECT * FROM test_user WHERE type IN (1,3)', @@ -168,6 +168,40 @@ public function testWhereIn(array $users) ], $sqlLogs); } + /** + * @depends testInitUsers + */ + public function testKeyEscape(array $users) + { + $result = $this->db + ->table('test_user') + ->alias('user') + ->join('test_user union', 'union.id = user.id') + ->order('user.id', 'desc') + ->column([ + 'user.id' => 'order', + 'user.username' => 'select', + 'user.nickname' => 'where', + 'user.type' => 'table', + 'union.password' => 'primary', + ], 'user.id'); + + $expected = array_column_ex( + $users, + [ + 'id' => 'order', + 'username' => 'select', + 'nickname' => 'where', + 'type' => 'table', + 'password' => 'primary', + 'id', + ], + 'id' + ); + + self::assertEquals($expected, $result); + } + public function testException() { $this->expectException(DbException::class); From 312beacc4a82f080fb97eb7339c790972d485536 Mon Sep 17 00:00:00 2001 From: thinkphp Date: Wed, 25 Jun 2025 11:07:33 +0800 Subject: [PATCH 52/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/PDOConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/PDOConnection.php b/src/db/PDOConnection.php index c3efcfcb..a8bca4a1 100644 --- a/src/db/PDOConnection.php +++ b/src/db/PDOConnection.php @@ -734,7 +734,7 @@ protected function pdoQuery(BaseQuery $query, $sql, ?bool $master = null): array if ($sql instanceof Closure) { $sql = $sql($query); - $bind = $query->getBind(); + $bind = array_merge($bind, $query->getBind()); } if (!isset($master)) { From b29b482253eafbf466e5f44609495706efbdb489 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Sat, 30 Aug 2025 22:02:34 +0800 Subject: [PATCH 53/53] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=B8=B8=E6=A0=87?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=97=B6json=E5=AD=97=E6=AE=B5=E6=9C=AA?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E8=A7=A3=E6=9E=90=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/PDOConnection.php | 18 +++--------------- src/db/Query.php | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/db/PDOConnection.php b/src/db/PDOConnection.php index a8bca4a1..26b9d9fa 100644 --- a/src/db/PDOConnection.php +++ b/src/db/PDOConnection.php @@ -648,24 +648,17 @@ public function getPdo() * * @param BaseQuery $query 查询对象 * @param string $sql sql指令 - * @param Model|null $model 模型对象实例 - * @param null $condition 查询条件 - * * @throws DbException * * @return \Generator */ - public function getCursor(BaseQuery $query, string $sql, $model = null, $condition = null) + public function getCursor(BaseQuery $query, string $sql) { $this->queryPDOStatement($query, $sql); // 返回结果集 while ($result = $this->PDOStatement->fetch($this->fetchType)) { - if ($model) { - yield $model->newInstance($result, $condition); - } else { - yield $result; - } + yield $result; } } @@ -934,16 +927,11 @@ public function find(BaseQuery $query): array */ public function cursor(BaseQuery $query) { - // 分析查询表达式 - $options = $query->parseOptions(); - // 生成查询SQL $sql = $this->builder->select($query); - $condition = $options['where']['AND'] ?? null; - // 执行查询操作 - return $this->getCursor($query, $sql, $query->getModel(), $condition); + return $this->getCursor($query, $sql); } /** diff --git a/src/db/Query.php b/src/db/Query.php index 854ace4c..80b0a053 100644 --- a/src/db/Query.php +++ b/src/db/Query.php @@ -573,7 +573,21 @@ public function cursor($data = null) $connection = clone $this->connection; - return $connection->cursor($this); + // 分析查询表达式 + $options = $this->parseOptions(); + $condition = $options['where']['AND'] ?? null; + + foreach ($connection->cursor($this) as $result) { + if ($this->model) { + // JSON数据处理 + if (!empty($options['json'])) { + $this->jsonModelResult($result); + } + yield $this->model->newInstance($result, $condition); + } else { + yield $result; + } + } } /**