Skip to content

Commit d25b4a4

Browse files
committed
parameterized query: more types
1 parent a1264fb commit d25b4a4

File tree

8 files changed

+116
-66
lines changed

8 files changed

+116
-66
lines changed

README.md

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ foreach($result as $record) {
123123
}
124124
```
125125

126+
The returned values are always in string representation except the null, which
127+
is always returned as `null`.
128+
126129
## Example 2: Get execution stats
127130

128131
```php
@@ -163,26 +166,20 @@ $result = $connection->Query('
163166
', [ "D'artagnan", 5.3 ]);
164167
```
165168

166-
The parameter types for prepared statements have to match
167-
the column types. See the below example. Never pass `null`,
168-
`true`, `false` values as strings.
169+
In MonetDB the placeholders of prepared statements have specific types.
170+
This library auto-converts some of PHP types to the corresponding MonetDB types.
169171

170-
```php
171-
$result = $connection->Query('
172-
update
173-
"test"
174-
set
175-
"nullable_column" = ?
176-
where
177-
"bool_column" = ?
178-
and "numeric_column" > ?
179-
and "date_column" > ?
180-
and "timestamp_column" < ?
181-
', [ null, false, 5.3, "2020-12-08", new DateTime()]);
182-
```
172+
| MonetDB type | Accepted PHP types | Value examples |
173+
| --- | --- | --- |
174+
| timestamp | `string`, `DateTime` | `"2020-12-20 11:14:26.123456"` |
175+
| date | `string`, `DateTime` | `"2020-12-20"` |
176+
| boolean | `boolean`, `string`, `integer` | `true`, `false`, `"true"`, `0`, `"0"`, `1`, `"t"`, `"f"`, `"yes"`, `"no"`, `"enabled"`, `"disabled"` |
177+
| Numeric values | `integer`, `float`, `string` | `12.34`, `"12.34"` (use string for huge numbers) |
178+
| Character types | `string` | `"Hello World!"` |
179+
| Binary | `string` | `"0f44ba12"` (always interpreted as hexadecimal) |
180+
| time | `string`, `DateTime` | `"11:28"`, `"12:28:34"` |
183181

184-
While the `date` values have to be passed as normal strings, the
185-
`timestamp` type has be passed as a `DateTime` object.
182+
Always pass the null values as `null`, and not as a string.
186183

187184
## Example 4: Using escaping
188185

protocol_doc/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ Discussed in chapter [Data response](#521-data-response---1).
509509
Error responses start with an exclamation mark `!`, followed by an error code, then a text
510510
message after a second exclamation mark. When the server returns an error message,
511511
then it clears the complete session state (forgets everything, including prepared
512-
statements and active queries).
512+
statements and active queries) and closes the connection.
513513

514514
Examples:
515515

src/Connection.php

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,9 @@ private function Write(string $msg)
325325
* @param string $sql
326326
* @param array|null $params An optional array for prepared statement parameters.
327327
* If not provided (or null), then a normal query is executed instead of
328-
* a prepared statement. The parameter values will retain their PHP type if
329-
* possible. The following values won't be converted to string: null, true, false
330-
* and numeric values.
328+
* a prepared statement. The parameter values will be converted to the proper
329+
* MonetDB type when possible. See the relevant section of README.md about
330+
* parameterized queries for more details.
331331
* @return Response
332332
*/
333333
public function Query(string $sql, array $params = null): Response
@@ -502,8 +502,11 @@ private function WritePreparedStatement(string $sql, array $params) {
502502
$escaped[] = "false";
503503
}
504504
elseif (is_string($param)) {
505-
if ($type == "hugeint" || $type == "decimal") {
506-
$escaped[] = preg_replace('/[^0-9\.]/', '', $param);
505+
if (in_array($type, ['char', 'varchar', 'clob'])) {
506+
$escaped[] = "'".$this->Escape($param)."'";
507+
}
508+
else if ($type == "hugeint" || $type == "decimal") {
509+
$escaped[] = preg_replace('/[^0-9\.\+\-ex]/i', '', $param);
507510
}
508511
else if ($type == "timestamp") {
509512
$escaped[] = "TIMESTAMP '".$this->Escape($param)."'";
@@ -514,21 +517,45 @@ private function WritePreparedStatement(string $sql, array $params) {
514517
else if ($type == "double" || $type == "real") {
515518
$escaped[] = (string)((float)$param);
516519
}
517-
518-
// TODO: TIME, INTERVAL, boolean, binary ?
519-
520+
else if ($type == "blob") {
521+
$escaped[] = "x'".preg_replace('/[^0-9a-f]/i', '', $param)."'";
522+
}
523+
else if ($type == "boolean") {
524+
$lower = strtolower($param);
525+
if (in_array($lower, ['1', 'true', 'yes', 't', 'enabled'])) {
526+
$escaped[] = "true";
527+
} else if (in_array($lower, ['0', 'false', 'no', 'f', 'disabled'])) {
528+
$escaped[] = "false";
529+
} else {
530+
throw new MonetException("Invalid value passed for parameter '".($index + 1).
531+
"': Expected boolean, received: {$param}");
532+
}
533+
}
534+
else if ($type == "time") {
535+
$escaped[] = "time '".preg_replace('/[^0-9\:]/i', '', $param)."'";
536+
}
520537
else {
521538
$escaped[] = "'".$this->Escape($param)."'";
522539
}
523540
}
524541
elseif (is_float($param) || is_integer($param)) {
525-
$escaped[] = (string)$param;
542+
if ($type == "boolean") {
543+
if ($param == 0) {
544+
$escaped[] = "false";
545+
} else {
546+
$escaped[] = "true";
547+
}
548+
} else {
549+
$escaped[] = (string)$param;
550+
}
526551
}
527552
elseif ($param instanceof DateTime) {
528553
if ($type == "date") {
529554
$escaped[] = "'".$param->format("Y-m-d")."'";
530-
} else {
555+
} else if ($type == "timestamp") {
531556
$escaped[] = "TIMESTAMP '".$param->format("Y-m-d H:i:s.u")."'";
557+
} else if ($type == "time") {
558+
$escaped[] = "time '".$param->format("H:i:s")."'";
532559
}
533560
}
534561
else {

tests/dec38Test.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@ final class dec38Test extends TestCase {
88
/**
99
* @var Connection
1010
*/
11-
public $conn;
11+
public static $conn;
1212

13-
public function setUp(): void
13+
public static function setUpBeforeClass(): void
1414
{
15-
$this->conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
15+
self::$conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
1616
}
1717

1818
public function testBigIntTable(): void
1919
{
20-
$this->conn->Query("drop table if exists php_dec38");
21-
$res = $this->conn->Query("CREATE TABLE php_dec38 (d38_0 DECIMAL(38,0), d38_19 DECIMAL(38,19), d38_38 DECIMAL(38,38));");
20+
self::$conn->Query("drop table if exists php_dec38");
21+
$res = self::$conn->Query("CREATE TABLE php_dec38 (d38_0 DECIMAL(38,0), d38_19 DECIMAL(38,19), d38_38 DECIMAL(38,38));");
2222

2323
$this->assertCount(0, $res);
2424
}
2525

2626
public function testInsertBigInt(): void
2727
{
28-
$res = $this->conn->Query("INSERT INTO php_dec38 VALUES (12345678901234567899876543210987654321, 1234567890123456789.9876543210987654321, .12345678901234567899876543210987654321);");
28+
$res = self::$conn->Query("INSERT INTO php_dec38 VALUES (12345678901234567899876543210987654321, 1234567890123456789.9876543210987654321, .12345678901234567899876543210987654321);");
2929

3030
$this->assertCount(0, $res);
3131
}
3232

3333
public function testSelectBigInt(): void
3434
{
35-
$res = $this->conn->QueryFirst("SELECT * FROM php_dec38");
35+
$res = self::$conn->QueryFirst("SELECT * FROM php_dec38");
3636

3737
$this->assertEquals($res["d38_0"], "12345678901234567899876543210987654321");
3838
$this->assertEquals($res["d38_19"], "1234567890123456789.9876543210987654321");

tests/int128Test.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,38 @@ final class int128Test extends TestCase {
99
/**
1010
* @var Connection
1111
*/
12-
public $conn;
12+
public static $conn;
1313

14-
public function setUp(): void
14+
public static function setUpBeforeClass(): void
1515
{
16-
$this->conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
16+
self::$conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
1717
}
1818

1919
public function testStartTransaction(): void
2020
{
21-
$res = $this->conn->Query("START TRANSACTION");
21+
$res = self::$conn->Query("START TRANSACTION");
2222

2323
$this->assertCount(0, $res);
2424
}
2525

2626
public function testBigIntTable(): void
2727
{
28-
$this->conn->Query("drop table if exists php_int128");
29-
$res = $this->conn->Query("CREATE TABLE php_int128 (i HUGEINT);");
28+
self::$conn->Query("drop table if exists php_int128");
29+
$res = self::$conn->Query("CREATE TABLE php_int128 (i HUGEINT);");
3030

3131
$this->assertCount(0, $res);
3232
}
3333

3434
public function testInsertBigInt(): void
3535
{
36-
$res = $this->conn->Query("INSERT INTO php_int128 VALUES (123456789098765432101234567890987654321);");
36+
$res = self::$conn->Query("INSERT INTO php_int128 VALUES (123456789098765432101234567890987654321);");
3737

3838
$this->assertCount(0, $res);
3939
}
4040

4141
public function testSelectBigInt(): void
4242
{
43-
$res = $this->conn->QueryFirst("SELECT * FROM php_int128");
43+
$res = self::$conn->QueryFirst("SELECT * FROM php_int128");
4444

4545
$this->assertEquals($res["i"], "123456789098765432101234567890987654321");
4646
}

tests/int64Test.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,31 @@ final class int64Test extends TestCase {
88
/**
99
* @var Connection
1010
*/
11-
public $conn;
11+
public static $conn;
1212

13-
public function setUp(): void
13+
public static function setUpBeforeClass(): void
1414
{
15-
$this->conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
15+
self::$conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
1616
}
1717

1818
public function testBigIntTable(): void
1919
{
20-
$this->conn->Query("drop table if exists php_int64_dec18");
21-
$res = $this->conn->Query("CREATE TABLE php_int64_dec18 (i BIGINT, d0 DECIMAL(18,0), d9 DECIMAL(18,9));");
20+
self::$conn->Query("drop table if exists php_int64_dec18");
21+
$res = self::$conn->Query("CREATE TABLE php_int64_dec18 (i BIGINT, d0 DECIMAL(18,0), d9 DECIMAL(18,9));");
2222

2323
$this->assertCount(0, $res);
2424
}
2525

2626
public function testInsertBigInt(): void
2727
{
28-
$res = $this->conn->Query("INSERT INTO php_int64_dec18 VALUES (1234567890987654321, 123456789987654321, 123456789.987654321);");
28+
$res = self::$conn->Query("INSERT INTO php_int64_dec18 VALUES (1234567890987654321, 123456789987654321, 123456789.987654321);");
2929

3030
$this->assertCount(0, $res);
3131
}
3232

3333
public function testSelectBigInt(): void
3434
{
35-
$res = $this->conn->QueryFirst("SELECT * FROM php_int64_dec18;");
35+
$res = self::$conn->QueryFirst("SELECT * FROM php_int64_dec18;");
3636

3737
$this->assertEquals($res["i"], "1234567890987654321");
3838
$this->assertEquals($res["d0"], "123456789987654321");

tests/preparedTest.php

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ final class preparedTest extends TestCase {
88
/**
99
* @var Connection
1010
*/
11-
private $conn;
11+
private static $conn;
1212

1313
/**
1414
* Next ID for the records
@@ -17,33 +17,37 @@ final class preparedTest extends TestCase {
1717
*/
1818
private static $id = 0;
1919

20-
public function setUp(): void
20+
public static function setUpBeforeClass(): void
2121
{
22-
$this->conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
22+
self::$conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
2323
}
2424

2525
public function testCreateTable(): void
2626
{
27-
$this->conn->Query("drop table if exists php_prepared");
28-
$res = $this->conn->Query("CREATE TABLE php_prepared (id int, h HUGEINT, b BIGINT, i int, d DECIMAL(38,19), d2 dec(38,0),
29-
n numeric(38,2), t timestamp, dat date, r real, f float, dbl double, dbl2 double precision);");
27+
self::$conn->Query("drop table if exists php_prepared");
28+
$res = self::$conn->Query("CREATE TABLE php_prepared (id int, h HUGEINT, b BIGINT, i int, d DECIMAL(38,19), d2 dec(38,0),
29+
n numeric(38,2), t timestamp, dat date, r real, f float, dbl double, dbl2 double precision, bo bool, tim time);");
3030

3131
$this->assertCount(0, $res);
3232
}
3333

34-
private function InsertValuePrepared(string $field, $value, string $strValue) {
34+
private function InsertValuePrepared(string $field, $value, $cmpValue) {
3535
self::$id++;
3636

37-
$this->conn->Query("insert into php_prepared (id, {$field}) values (?, ?)", [self::$id, $value]);
38-
$response = $this->conn->QueryFirst("select {$field} from php_prepared where id = ?", [self::$id]);
37+
self::$conn->Query("insert into php_prepared (id, {$field}) values (?, ?)", [self::$id, $value]);
38+
$response = self::$conn->QueryFirst("select {$field} from php_prepared where id = ?", [self::$id]);
3939

40-
$this->assertEquals($strValue, $response[$field]);
40+
$this->assertEquals($cmpValue, $response[$field]);
4141
}
4242

4343
public function testDec38(): void {
4444
$this->InsertValuePrepared('d', '1234567890123456789.9876543210987654321', '1234567890123456789.9876543210987654321');
4545
}
4646

47+
public function testNull(): void {
48+
$this->InsertValuePrepared('d', null, null);
49+
}
50+
4751
public function testInt32(): void {
4852
$this->InsertValuePrepared('i', 123456, '123456');
4953
$this->InsertValuePrepared('i', '123456', '123456');
@@ -87,4 +91,26 @@ public function testReal(): void {
8791
$this->InsertValuePrepared('r', 3.141592, '3.141592');
8892
$this->InsertValuePrepared('r', '3.141592', '3.141592');
8993
}
94+
95+
public function testBool(): void {
96+
$this->InsertValuePrepared('bo', true, 'true');
97+
$this->InsertValuePrepared('bo', false, 'false');
98+
$this->InsertValuePrepared('bo', 'true', 'true');
99+
$this->InsertValuePrepared('bo', 'false', 'false');
100+
$this->InsertValuePrepared('bo', 'FALSE', 'false');
101+
$this->InsertValuePrepared('bo', 1, 'true');
102+
$this->InsertValuePrepared('bo', 0, 'false');
103+
$this->InsertValuePrepared('bo', 'enabled', 'true');
104+
$this->InsertValuePrepared('bo', 'disabled', 'false');
105+
$this->InsertValuePrepared('bo', 't', 'true');
106+
$this->InsertValuePrepared('bo', 'f', 'false');
107+
}
108+
109+
public function testTime(): void {
110+
$dt = new DateTime();
111+
112+
$this->InsertValuePrepared('tim', '12:28', '12:28:00');
113+
$this->InsertValuePrepared('tim', '12:28:34', '12:28:34');
114+
$this->InsertValuePrepared('tim', $dt, $dt->format("H:i:s"));
115+
}
90116
}

tests/sizeLimitBugTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ final class sizeLimitBugTest extends TestCase {
1010
/**
1111
* @var Connection
1212
*/
13-
public $conn;
13+
public static $conn;
1414

15-
public function setUp(): void
15+
public static function setUpBeforeClass(): void
1616
{
17-
$this->conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
17+
self::$conn = new Connection("127.0.0.1", 50000, "monetdb", "monetdb", "myDatabase");
1818
}
1919

2020
public function testWeCanConnectToDatabase(): void
2121
{
2222
$this->assertInstanceOf(
2323
Connection::class,
24-
$this->conn
24+
self::$conn
2525
);
2626
}
2727

@@ -30,7 +30,7 @@ public function testWeCanFetchRows(): void
3030
$sql = 'select 1';
3131
$sql = str_pad($sql, $this->packet_size , ' ');
3232

33-
$res = $this->conn->Query($sql);
33+
$res = self::$conn->Query($sql);
3434
$this->assertCount(1, $res);
3535
}
3636
}

0 commit comments

Comments
 (0)