Skip to content

Commit

Permalink
Add support for binding parameters by index to cdb2
Browse files Browse the repository at this point in the history
Signed-off-by: Salil Chandra <[email protected]>
  • Loading branch information
chands10 committed Dec 19, 2024
1 parent 518dd75 commit 3280dfd
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
18 changes: 14 additions & 4 deletions comdb2/_ccdb2.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from cpython.mem cimport PyMem_Malloc, PyMem_Free
import datetime

from pytz import timezone, UTC
from collections.abc import Sequence

from ._cdb2_types import Error, Effects, DatetimeUs
from . cimport _cdb2api as lib
Expand Down Expand Up @@ -401,15 +402,24 @@ cdef class Handle(object):
param_guards = []
try:
if parameters is not None:
for key, val in parameters.items():
ckey = _string_as_bytes(key)
items = enumerate(parameters, 1) if isinstance(parameters, Sequence) \
else parameters.items()
for key, val in items:
bind_by_index = isinstance(key, int)
ckey = _string_as_bytes(key) if not bind_by_index else key
cval = _ParameterValue(val, key)
param_guards.append(ckey)
param_guards.append(cval)
if cval.list_size == -1:
rc = lib.cdb2_bind_param(self.hndl, <char*>ckey,
cval.type, cval.data, cval.size)
if bind_by_index:
rc = lib.cdb2_bind_index(self.hndl, ckey,
cval.type, cval.data, cval.size)
else:
rc = lib.cdb2_bind_param(self.hndl, <char*>ckey,
cval.type, cval.data, cval.size)
else:
if bind_by_index:
raise ValueError("Cannot bind an array by index")
# Bind Array if cval is an array
rc = lib.cdb2_bind_array(self.hndl, <char*>ckey, cval.type, cval.data, cval.list_size, cval.size)
_errchk(rc, self.hndl)
Expand Down
1 change: 1 addition & 0 deletions comdb2/_cdb2api.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ cdef extern from "cdb2api.h" nogil:
void* cdb2_column_value(cdb2_hndl_tp* hndl, int col) except +
const char* cdb2_errstr(cdb2_hndl_tp* hndl) except +
int cdb2_bind_param(cdb2_hndl_tp *hndl, const char *name, int type, const void *varaddr, int length) except +
int cdb2_bind_index(cdb2_hndl_tp *hndl, int index, int type, const void *varaddr, int length) except +
int cdb2_bind_array(cdb2_hndl_tp *hndl, const char *name, cdb2_coltype, const void *varaddr, size_t count, size_t typelen) except +
int cdb2_clearbindings(cdb2_hndl_tp *hndl) except +
int cdb2_clear_ack(cdb2_hndl_tp *hndl) except +
33 changes: 31 additions & 2 deletions comdb2/cdb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@
examples we make use of the `list` constructor to turn the iterable returned
by `Handle.execute` into a list of result rows.
You can also bind by index by providing a sequence or mapping (1-based indexing if using a mapping)
instead of by name with placeholders specified using ``?`` in sequence.
Note that binding an array by index is not supported. For example:
>>> query = "select 25 between ? and ?"
>>> print(list(hndl.execute(query, [20, 42])))
[[1]]
>>> query = "select 25 between ? and ?"
>>> print(list(hndl.execute(query, {1: 20, 2: 42})))
[[1]]
Types
-----
Expand Down Expand Up @@ -148,6 +160,7 @@
"ColumnType",
"ConnectionFlags",
"Value",
"IndexParameterValue",
"ParameterValue",
]

Expand All @@ -160,14 +173,17 @@
datetime.datetime,
DatetimeUs,
]
ParameterValue = Union[
IndexParameterValue = Union[
None,
int,
float,
bytes,
str,
datetime.datetime,
DatetimeUs,
]
ParameterValue = Union[
IndexParameterValue,
List[int],
List[float],
List[bytes],
Expand Down Expand Up @@ -359,7 +375,7 @@ def row_factory(
def execute(
self,
sql: str | bytes,
parameters: Mapping[str, ParameterValue] | None = None,
parameters: Mapping[str, ParameterValue] | Sequence[IndexParameterValue] | Mapping[int, IndexParameterValue] | None = None,
*,
column_types: Sequence[ColumnType] | None = None,
) -> Handle:
Expand All @@ -383,6 +399,7 @@ def execute(
sql (str): The SQL string to execute.
parameters (Mapping[str, Any]): An optional mapping from parameter
names to the values to be bound for them.
(Sequence[Any]), (Mapping[int, Any]): Can also use sequence with ``?`` or mapping with 1-based indexing (if not array)
column_types (Sequence[int]): An optional sequence of types (values
of the `ColumnType` enumeration) which the columns of the
result set will be coerced to.
Expand All @@ -403,6 +420,18 @@ def execute(
... print(row)
[1, 2]
[2, 4]
>>> for row in hndl.execute("select 1, 2 UNION ALL select ?, ?",
... [2, 4]):
... print(row)
[1, 2]
[2, 4]
>>> for row in hndl.execute("select 1, 2 UNION ALL select ?, ?",
... {1: 2, 2: 4}):
... print(row)
[1, 2]
[2, 4]
"""
if parameters is None:
parameters = {}
Expand Down
27 changes: 26 additions & 1 deletion tests/test_cdb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,15 @@ def test_binding_parameters():
hndl.execute("insert into simple(key, val) values(@k, @v)", dict(k=3, v=4))
assert hndl.get_effects()[0] == 1

hndl.execute("insert into simple(key, val) values(?, ?)", [5, 6])
assert hndl.get_effects()[0] == 1

hndl.execute("insert into simple(key, val) values(?, ?)", {1: 7, 2: 8})
hndl.execute("insert into simple(key, val) values(?, ?)", {2: 10, 1: 9})
assert hndl.get_effects()[0] == 1

rows = list(hndl.execute("select key, val from simple order by key"))
assert rows == [[1, 2], [3, 4]]
assert rows == [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]


def test_commit_failures():
Expand Down Expand Up @@ -304,6 +311,24 @@ def test_parameter_name_in_binding_errors_noexception():
)


def test_binding_array_by_index():
hndl = cdb2.Handle("mattdb", "dev")

with pytest.raises(Exception) as exc:
hndl.execute("select * from carray(?)", [[1, 2, 3]])

assert exc.value.args[0] == (
"Cannot bind an array by index"
)

with pytest.raises(Exception) as exc:
hndl.execute("select * from carray(?)", {1: [1, 2, 3]})

assert exc.value.args[0] == (
"Cannot bind an array by index"
)


def test_specifying_column_types():
# GIVEN
hndl = cdb2.Handle("mattdb", "dev")
Expand Down

0 comments on commit 3280dfd

Please sign in to comment.