diff --git a/src/LitebaseClient.php b/src/LitebaseClient.php index 7ce18f2..8105835 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 ($param['type'] === 'BLOB' && $this->transport instanceof HttpTransport) { + $param['value'] = base64_encode((string) $param['value']); + } + $parameters[] = new StatementParameter($param); } @@ -235,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; 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(); + }); });