From 609d69e1df92c9d844bd1aab07caa772b228d3a5 Mon Sep 17 00:00:00 2001 From: Thiery Laverdure Date: Wed, 26 Nov 2025 16:16:02 -0500 Subject: [PATCH 1/2] Improve type handling and add integration test for all column types Refactored LitebaseStatement to use ColumnType enums for type mapping and improved float detection. Updated rowCount assignment to use 'changes' property. Added integration test to verify handling of INTEGER, FLOAT, TEXT, BLOB, and NULL column types. --- src/LitebaseClient.php | 6 ++ src/LitebaseStatement.php | 41 ++++++------ tests/Integration/LitebasePDOTest.php | 90 ++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 20 deletions(-) diff --git a/src/LitebaseClient.php b/src/LitebaseClient.php index 7ce18f2..dc0697a 100644 --- a/src/LitebaseClient.php +++ b/src/LitebaseClient.php @@ -161,6 +161,12 @@ public function exec(array $input): ?QueryResult $parameters = []; foreach ($input['parameters'] ?? [] as $param) { + // Base64 encode BLOB values for HTTP transport (JSON serialization) + // The binary streaming transport handles raw binary data + if (isset($param['type']) && $param['type'] === 'BLOB' && $this->transport instanceof HttpTransport) { + $param['value'] = base64_encode($param['value']); + } + $parameters[] = new StatementParameter($param); } diff --git a/src/LitebaseStatement.php b/src/LitebaseStatement.php index 0f6c097..5b47be7 100644 --- a/src/LitebaseStatement.php +++ b/src/LitebaseStatement.php @@ -60,29 +60,29 @@ public function bindParam( */ public function bindValue(int|string $parameter, mixed $value, int $data_type = PDO::PARAM_STR): bool { - $type = 'NULL'; + $type = ColumnType::TEXT->name; switch ($data_type) { case PDO::PARAM_BOOL: case PDO::PARAM_INT: - $type = 'INTEGER'; + $type = ColumnType::INTEGER->name; break; case PDO::PARAM_STR: - $type = 'TEXT'; + // Auto-detect float type when PDO::PARAM_STR is passed + if (is_float($value)) { + $type = ColumnType::FLOAT->name; + } else { + $type = ColumnType::TEXT->name; + } break; case PDO::PARAM_NULL: - $type = 'NULL'; + $type = ColumnType::NULL->name; break; - // TODO: Test BLOB type case PDO::PARAM_LOB: - $type = 'BLOB'; + $type = ColumnType::BLOB->name; break; - // TODO: Add a case for float type - // case PDO::PARAM_FLOAT: - // $type = "REAL"; - // break; default: - $type = 'TEXT'; // Default to TEXT if no match + $type = ColumnType::TEXT->name; // Default to TEXT if no match break; } @@ -164,11 +164,11 @@ public function execute(?array $params = null): bool foreach ($params as $key => $value) { // Determine the type based on the value $type = match (true) { - $value === null => 'NULL', - is_int($value) => 'INTEGER', - is_float($value) => 'REAL', - is_bool($value) => 'INTEGER', - default => 'TEXT', + $value === null => ColumnType::NULL->name, + is_int($value) => ColumnType::INTEGER->name, + is_float($value) => ColumnType::FLOAT->name, + is_bool($value) => ColumnType::INTEGER->name, + default => ColumnType::TEXT->name, }; $transformedParams[$key] = [ @@ -212,14 +212,14 @@ public function execute(?array $params = null): bool $columns = $this->columns ?? []; return array_combine( - array_map(fn ($col) => $col['name'], $columns), + array_map(fn($col) => $col['name'], $columns), $row ); }, $this->result->rows); } - if (isset($this->result->rowCount)) { - $this->rowCount = $this->result->rowCount; + if (isset($this->result->changes)) { + $this->rowCount = $this->result->changes; } return true; @@ -282,6 +282,9 @@ public function fetchColumn($columnIndex = 0): mixed return $value !== false ? $row[$value] : null; } + /** + * {@inheritDoc} + */ public function rowCount(): int { return $this->rowCount; diff --git a/tests/Integration/LitebasePDOTest.php b/tests/Integration/LitebasePDOTest.php index be1c5d6..34140a9 100644 --- a/tests/Integration/LitebasePDOTest.php +++ b/tests/Integration/LitebasePDOTest.php @@ -29,7 +29,7 @@ 'name' => 'test', ])); } catch (\Exception $e) { - throw new \RuntimeException('Failed to connect to Litebase server for integration tests: '.$e->getMessage()); + throw new \RuntimeException('Failed to connect to Litebase server for integration tests: ' . $e->getMessage()); } }); @@ -140,4 +140,92 @@ expect($user['name'])->toBe('Alice'); expect($user['email'])->toBe('alice@example.com'); }); + + test('can handle all column data types', function () { + $client = new LitebaseClient( + Configuration::create([ + 'host' => 'localhost', + 'port' => '8888', + 'username' => 'root', + 'password' => 'password', + 'database' => 'test/main', + ]) + ); + + $pdo = new LitebasePDO($client); + + // Create table with all supported column types + $pdo->exec('CREATE TABLE IF NOT EXISTS type_test ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + int_col INTEGER, + float_col REAL, + text_col TEXT, + blob_col BLOB, + null_col TEXT + )'); + + // Insert data with different types using execute with params + $statement = $pdo->prepare('INSERT INTO type_test (int_col, float_col, text_col, blob_col, null_col) VALUES (?, ?, ?, ?, ?)'); + + $blobData = hex2bin('48656c6c6f20576f726c64'); // "Hello World" as binary + + $statement->execute([ + 42, // INTEGER + 3.14159, // FLOAT + 'Hello World', // TEXT + $blobData, // BLOB + null, // NULL + ]); + + expect($statement->rowCount())->toBe(1); + + // Now test bindValue for a second insert + $statement2 = $pdo->prepare('INSERT INTO type_test (int_col, float_col, text_col, blob_col, null_col) VALUES (?, ?, ?, ?, ?)'); + + $blobData2 = hex2bin('776f726c6420686921'); // "world hi!" as binary + + $statement2->bindValue(1, 99, PDO::PARAM_INT); + $statement2->bindValue(2, 2.71828, PDO::PARAM_STR); + $statement2->bindValue(3, 'Test String', PDO::PARAM_STR); + $statement2->bindValue(4, $blobData2, PDO::PARAM_LOB); + $statement2->bindValue(5, null, PDO::PARAM_NULL); + + $statement2->execute(); + + expect($statement2->rowCount())->toBe(1); + + // Retrieve and verify the first row + $statement = $pdo->prepare('SELECT * FROM type_test WHERE int_col = ?'); + $statement->execute([42]); + + /** @var array $row */ + $row = $statement->fetch(PDO::FETCH_ASSOC); + /** @var float $floatValue */ + $floatValue = $row['float_col']; + /** @var string $blobValue */ + $blobValue = $row['blob_col']; + + expect($row)->not->toBeNull(); + expect($row['int_col'])->toBe(42); + expect($row['float_col'])->toBeFloat(); + expect(abs($floatValue - 3.14159))->toBeLessThan(0.00001); + expect($row['text_col'])->toBe('Hello World'); + expect($row['blob_col'])->toBe($blobData); + expect(bin2hex($blobValue))->toBe('48656c6c6f20576f726c64'); + expect($row['null_col'])->toBeNull(); + + // Retrieve and verify the second row + $statement = $pdo->prepare('SELECT * FROM type_test WHERE int_col = ?'); + $statement->execute([99]); + + /** @var array $row2 */ + $row2 = $statement->fetch(PDO::FETCH_ASSOC); + + expect($row2)->not->toBeNull(); + expect($row2['int_col'])->toBe(99); + expect($row2['float_col'])->toBeFloat(); + expect($row2['text_col'])->toBe('Test String'); + expect($row2['blob_col'])->toBe($blobData2); + expect($row2['null_col'])->toBeNull(); + }); }); From 18ee188bb9b6a870a44cfda29bc23f40239c3a06 Mon Sep 17 00:00:00 2001 From: Thiery Laverdure Date: Wed, 26 Nov 2025 16:21:28 -0500 Subject: [PATCH 2/2] Update LitebaseClient.php --- src/LitebaseClient.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LitebaseClient.php b/src/LitebaseClient.php index dc0697a..8105835 100644 --- a/src/LitebaseClient.php +++ b/src/LitebaseClient.php @@ -163,10 +163,10 @@ public function exec(array $input): ?QueryResult foreach ($input['parameters'] ?? [] as $param) { // Base64 encode BLOB values for HTTP transport (JSON serialization) // The binary streaming transport handles raw binary data - if (isset($param['type']) && $param['type'] === 'BLOB' && $this->transport instanceof HttpTransport) { - $param['value'] = base64_encode($param['value']); + if ($param['type'] === 'BLOB' && $this->transport instanceof HttpTransport) { + $param['value'] = base64_encode((string) $param['value']); } - + $parameters[] = new StatementParameter($param); } @@ -241,7 +241,7 @@ public function withTransport(string $transportType): LitebaseClient $this->transport = new HttpStreamingTransport($this->configuration); break; default: - throw new Exception('Invalid transport type: '.$transportType); + throw new Exception('Invalid transport type: ' . $transportType); } return $this;