Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/FFI/DuckDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,9 @@ public function createNull(): NativeCData
{
return self::$ffi->duckdb_create_null_value();
}

public function bindParameterIndex(NativeCData $preparedStatement, ?NativeCData &$parameterIndex, string $name): int
{
return self::$ffi->duckdb_bind_parameter_index($preparedStatement, $this->addr($parameterIndex), $name);
}
}
25 changes: 22 additions & 3 deletions src/PreparedStatement/PreparedStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,40 @@ public static function create(
* @throws BindValueException|UnsupportedTypeException
* @throws DateMalformedStringException
*/
public function bindParam(int $parameter, mixed $value, ?Type $type = null): void
public function bindParam(int|string $parameter, mixed $value, ?Type $type = null): void
{
$index = $this->getParameterIndex($parameter);

$value = $this->converter->getDuckDBValue($value, $type);
$status = $this->ffi->bindValue(
$this->preparedStatement,
$parameter,
$index,
$value,
);

$this->ffi->destroyValue($this->ffi->addr($value));

if ($status === $this->ffi->error()) {
$error = $this->ffi->prepareError($this->preparedStatement);
throw new BindValueException("Couldn't bind parameter {$parameter} to prepared statement {$this->query}. Error: {$error}");
throw new BindValueException("Couldn't bind parameter '{$parameter}' to prepared statement {$this->query}. Error: {$error}");
}
}

/**
* @throws BindValueException
*/
private function getParameterIndex(int|string $parameter): int
{
if (is_int($parameter)) {
return $parameter;
}

$index = $this->ffi->new('idx_t');
if ($this->ffi->bindParameterIndex($this->preparedStatement, $index, $parameter) === $this->ffi->success()) {
return $index->cdata;
}

throw new BindValueException("Couldn't bind parameter '{$parameter}' to prepared statement.");
}

public function execute(): ResultSet
Expand Down
20 changes: 20 additions & 0 deletions test/Integration/PreparedStatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPUnit\Framework\TestCase;
use Saturio\DuckDB\DuckDB;
use Saturio\DuckDB\Exception\BindValueException;
use Saturio\DuckDB\Type\Blob;
use Saturio\DuckDB\Type\Date;
use Saturio\DuckDB\Type\Time;
Expand All @@ -24,6 +25,25 @@ protected function setUp(): void
$this->db->query('INSERT INTO test_data VALUES (3, true, 1.1), (5, true, 1.2), (3, false, 1.1), (3, null, 1.2);');
}

public function testPreparedStatementNamedParameter(): void
{
$expectedResult = [[3, true, 1.1], [5, true, 1.2]];
$preparedStatement = $this->db->preparedStatement('SELECT * FROM test_data WHERE b = $bool_value');
$preparedStatement->bindParam('bool_value', true);
$result = $preparedStatement->execute();

$arrayResult = iterator_to_array($result->rows());

$this->assertEqualsWithDelta($expectedResult, $arrayResult, delta: 0.0000001);
}

public function testPreparedStatementNamedParameterInvalid(): void
{
$this->expectException(BindValueException::class);
$preparedStatement = $this->db->preparedStatement('SELECT * FROM test_data WHERE b = $bool_value');
$preparedStatement->bindParam('invalid', true);
}

public function testPreparedStatementBool(): void
{
$expectedResult = [[3, true, 1.1], [5, true, 1.2]];
Expand Down
Loading