Skip to content

Commit de2d6bd

Browse files
committed
Added Db::between(), Db::notBetween(), Db::in(), Db::notIn(), Db::isNull(), Db::isNotNull(), Db::case(), Db::default() helpers
1 parent 58e8b11 commit de2d6bd

9 files changed

Lines changed: 892 additions & 10 deletions

File tree

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,14 @@ $db->find()->table('users')->insert([
298298
* **Db::ilike(string $column, string $pattern)**: ILIKE condition (OR LOWER(column) LIKE LOWER(pattern) if ILIKE is not supported).
299299
* **Db::not(RawValue $value)**: Inverses a RawValue condition using NOT.
300300
* **Db::config(string $key, mixed $value, bool $useEqualSign = true, bool $quoteValue = true)**: returns SET KEY = :value statement (e.g. SET FOREIGN_KEYS_CHECKS = 1 or SET NAMES 'utf8mb4').
301+
* **Db::between(string $column, mixed $min, mixed $max)**: returns column BETWEEN min AND max condition.
302+
* **Db::notBetween(string $column, mixed $min, mixed $max)**: returns column NOT BETWEEN min AND max condition.
303+
* **Db::in(string $column, array $values)**: returns column IN values condition.
304+
* **Db::notIn(string $column, array $values)**: returns column NOT IN values condition.
305+
* **Db::isNull(string $column)**: returns column IS NULL condition.
306+
* **Db::isNotNull(string $column)**: returns column IS NOT NULL condition.
307+
* **Db::case(array $conditions, ?string $else = null)**: returns CASE statement: CASE WHEN ... THEN ... [ELSE ...] END.
308+
* **Db::default()**: returns DEFAULT value for SQL. (not supported in Sqlite).
301309

302310
### QueryBuilder Methods
303311

@@ -352,7 +360,7 @@ Use `Db::raw(string $value, ?array $params)` for SQL fragments that must bypass
352360
* **replace/upsert** returns affected row count when deterministic; semantics follow dialect best practices.
353361
* **RawValue** entries are embedded verbatim into SQL tuples and not bound as parameters.
354362
* **Multi-row inserts** generate unique named placeholders like `:name_0`, `:name_1` to avoid PDO binding conflicts.
355-
* **Helper functions** (`inc`, `dec`, `not`) return arrays that are processed during SQL generation.
363+
* **Helper functions** (`inc`, `dec,`) return arrays that are processed during SQL generation.
356364

357365
---
358366

src/dialects/MySQLDialect.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ public function formatSelectOptions(string $sql, array $options): string
106106
$middle[] = $opt;
107107
}
108108
}
109-
110109
if ($middle) {
111110
$sql = preg_replace('/^SELECT\s+/i', 'SELECT ' . implode(',', $middle) . ' ', $sql, 1);
112111
}

src/dialects/SqliteDialect.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ public function formatSelectOptions(string $sql, array $options): string
9797
$middle[] = $opt;
9898
}
9999
}
100-
101100
if ($middle) {
102101
$sql = preg_replace('/^SELECT\s+/i', 'SELECT ' . implode(',', $middle) . ' ', $sql, 1);
103102
}

src/helpers/CaseValue.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace tommyknocker\pdodb\helpers;
4+
5+
class CaseValue extends RawValue
6+
{
7+
8+
}

src/helpers/Db.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,123 @@ public static function not(RawValue $value): RawValue
126126
{
127127
return new RawValue("NOT (" . $value->getValue() . ")", $value->getParams());
128128
}
129+
130+
/**
131+
* Returns a RawValue instance representing a BETWEEN condition.
132+
*
133+
* @param string $column The column name.
134+
* @param mixed $min The minimum value.
135+
* @param mixed $max The maximum value.
136+
* @return RawValue The RawValue instance for the BETWEEN condition.
137+
*/
138+
public static function between(string $column, mixed $min, mixed $max): RawValue
139+
{
140+
return new RawValue("$column BETWEEN :min AND :max", ['min' => $min, 'max' => $max]);
141+
}
142+
143+
/**
144+
* Returns a RawValue instance representing a NOT BETWEEN condition.
145+
*
146+
* @param string $column The column name.
147+
* @param mixed $min The minimum value.
148+
* @param mixed $max The maximum value.
149+
* @return RawValue The RawValue instance for the NOT BETWEEN condition.
150+
*/
151+
public static function notBetween(string $column, mixed $min, mixed $max): RawValue
152+
{
153+
return new RawValue("$column NOT BETWEEN :min AND :max", ['min' => $min, 'max' => $max]);
154+
}
155+
156+
/**
157+
* Returns a RawValue instance representing an IN condition.
158+
*
159+
* @param string $column The column name.
160+
* @param array $values The array of values for the IN condition.
161+
* @return RawValue The RawValue instance for the IN condition.
162+
*/
163+
public static function in(string $column, array $values): RawValue
164+
{
165+
$params = [];
166+
$placeholders = [];
167+
168+
foreach ($values as $i => $val) {
169+
$key = "in_{$column}_$i";
170+
$params[$key] = $val;
171+
$placeholders[] = ":$key";
172+
}
173+
174+
return new RawValue("$column IN (" . implode(', ', $placeholders) . ")", $params);
175+
}
176+
177+
/**
178+
* Returns a RawValue instance representing a NOT IN condition.
179+
*
180+
* @param string $column The column name.
181+
* @param array $values The array of values for the NOT IN condition.
182+
* @return RawValue The RawValue instance for the NOT IN condition.
183+
*/
184+
public static function notIn(string $column, array $values): RawValue
185+
{
186+
$params = [];
187+
$placeholders = [];
188+
189+
foreach ($values as $i => $val) {
190+
$key = "in_{$column}_$i";
191+
$params[$key] = $val;
192+
$placeholders[] = ":$key";
193+
}
194+
195+
return new RawValue("$column NOT IN (" . implode(', ', $placeholders) . ")", $params);
196+
}
197+
198+
/**
199+
* Returns a RawValue instance representing an IS NULL condition.
200+
* @param string $column The column name.
201+
* @return RawValue
202+
*/
203+
public static function isNull(string $column): RawValue
204+
{
205+
return new RawValue("$column IS NULL");
206+
}
207+
208+
/**
209+
* Returns a RawValue instance representing an IS NULL condition.
210+
* @param string $column The column name.
211+
* @return RawValue
212+
*/
213+
public static function isNotNull(string $column): RawValue
214+
{
215+
return new RawValue("$column IS NOT NULL");
216+
}
217+
218+
/**
219+
* Returns a RawValue instance representing a CASE statement.
220+
*
221+
* @param array $cases An associative array where keys are WHEN conditions and values are THEN results.
222+
* @param string|null $else An optional ELSE result.
223+
* @return CaseValue The RawValue instance for the CASE statement.
224+
*/
225+
public static function case(array $cases, string|null $else = null): CaseValue
226+
{
227+
$sql = 'CASE';
228+
foreach ($cases as $when => $then) {
229+
$sql .= " WHEN $when THEN $then";
230+
}
231+
if ($else !== null) {
232+
$sql .= " ELSE $else";
233+
}
234+
$sql .= ' END';
235+
236+
return new CaseValue($sql);
237+
}
238+
239+
/**
240+
* Returns a RawValue instance representing SQL DEFAULT.
241+
*
242+
* @return RawValue The RawValue instance for DEFAULT.
243+
*/
244+
public static function default(): RawValue
245+
{
246+
return new RawValue('DEFAULT');
247+
}
129248
}

src/query/QueryBuilder.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use RuntimeException;
1111
use tommyknocker\pdodb\connection\ConnectionInterface;
1212
use tommyknocker\pdodb\dialects\DialectInterface;
13+
use tommyknocker\pdodb\helpers\CaseValue;
1314
use tommyknocker\pdodb\helpers\ConfigValue;
1415
use tommyknocker\pdodb\helpers\EscapeValue;
1516
use tommyknocker\pdodb\helpers\ILikeValue;
@@ -100,11 +101,15 @@ public function select(RawValue|string|array $cols): self
100101
if (!is_array($cols)) {
101102
$cols = [$cols];
102103
}
103-
foreach ($cols as $col) {
104-
if ($col instanceof RawValue) {
104+
foreach ($cols as $index => $col) {
105+
if ($col instanceof CaseValue) {
106+
$this->select[] = $this->resolveRawValue($col) . ' AS ' . $index;
107+
} elseif ($col instanceof RawValue) {
105108
$this->select[] = $this->resolveRawValue($col);
109+
} elseif (is_string($index)) { // ['total' => 'SUM(amount)] Treat it as SUM(amount) AS total
110+
$this->select[] = $col . ' AS ' . $index;
106111
} else {
107-
$this->select[] = (string)$col;
112+
$this->select[] = $col;
108113
}
109114
}
110115
return $this;
@@ -287,7 +292,7 @@ protected function addCondition(
287292
return $this;
288293
}
289294

290-
if ($exprOrColumn instanceof RawValue && $value) {
295+
if ($exprOrColumn instanceof RawValue) {
291296
$left = $this->resolveRawValue($exprOrColumn);
292297
$this->{$prop}[] = ['sql' => "{$left} {$operator} {$value}", 'cond' => $cond];
293298
return $this;
@@ -384,18 +389,29 @@ protected function addCondition(
384389

385390
/**
386391
* Add ORDER BY clause.
387-
* $expr may be a column name (string) or RawValue instance.
392+
* $expr may be a column name (string), complete expression or RawValue instance.
388393
* $direction can be 'ASC' or 'DESC' (case-insensitive). Defaults to 'ASC'.
394+
*
395+
* Syntax:
396+
*
397+
* orderBy('column_name', 'ASC')
398+
* orderBy(new RawValue('LENGTH(column_name)'), 'DESC')
399+
* orderBy('column_name DESC') // full expression
400+
*
401+
* @param string|RawValue $expr The expression to order by.
402+
* @param string $direction The direction of the ordering (ASC or DESC).
403+
* @return self The current instance.
389404
*/
390405
public function orderBy(string|RawValue $expr, string $direction = 'ASC'): self
391406
{
392407
$dir = strtoupper(trim($direction));
393408
if ($dir !== 'ASC' && $dir !== 'DESC') {
394409
$dir = 'ASC';
395410
}
396-
397411
if ($expr instanceof RawValue) {
398412
$this->order[] = $this->resolveRawValue($expr) . ' ' . $dir;
413+
} elseif (preg_match('/^[a-z0-9]+\s+(ASC|DESC)/iu', $expr)) {
414+
$this->order[] = $expr;
399415
} else {
400416
$this->order[] = $this->dialect->quoteIdentifier($expr) . ' ' . $dir;
401417
}
@@ -710,7 +726,7 @@ protected function buildSelectSql(): string
710726
if (empty($this->select)) {
711727
$select = '*';
712728
} else {
713-
$select = implode(', ', array_map(fn($v) => $this->quoteQualifiedIdentifier($v), $this->select));
729+
$select = implode(', ', array_map(fn($value) => $this->quoteQualifiedIdentifier($value), $this->select));
714730
}
715731

716732
$from = $this->normalizeTable();

0 commit comments

Comments
 (0)