Skip to content

Commit f522cf7

Browse files
committed
carray doc tweaks, format detection, parameter validation
explicit test code still required
1 parent 12e73fc commit f522cf7

2 files changed

Lines changed: 79 additions & 8 deletions

File tree

src/apsw.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ static int allow_missing_dict_bindings = 0;
291291
/** .. method:: carray(object: Buffer, *, start: int = 0, stop: int = -1, flags: int = -1)
292292
293293
Indicates a Python object is being provided as a runtime array for the
294-
`Carray extension <https://sqlite.org/carray.html>`__.
294+
`Carray extension <https://sqlite.org/carray.html>`__. This is useful if you
295+
need a large number of int or float available during a query, as they will
296+
be used without calling back into Python code or acquiring the GIL.
295297
296298
.. code-block:: python
297299
@@ -301,19 +303,20 @@ static int allow_missing_dict_bindings = 0;
301303
ids = array.array("l", [1, 73, 94567, 62])
302304
303305
# get records matching those ids
304-
for row in con.execute("SELECT * FROM record WHERE record.id in CARRAY(?)",
306+
for row in con.execute("SELECT * FROM record WHERE record.id IN CARRAY(?)",
305307
apsw.carray(ids)):
306308
print(row)
307309
308310
:param object: Any object that implements the buffer protocol as
309311
a single contiguous binary data like :class:`bytes`, :class:`bytearray`,
310-
:class:`array.array`, numpy etc.
312+
:class:`array.array`, numpy.array etc.
311313
:param start: Index of the first entry to bind
312314
:param stop: Index to stop at - ie one beyond the last entry bound. Default
313-
is all remaining members. There is a limit of 2 billion.
315+
is all remaining members. There is a limit of 2 billion, and a minimum
316+
of 1.
314317
:param flags: Indicates if the data is 32/64 bit int, or 64 bit double (floating point).
315-
If not supplied then the buffer format is examined and accepted if native byte order.
316-
You can see the `format string <https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment>`
318+
If not supplied then the buffer format is detected.
319+
You can see the `format string <https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment>`__
317320
with :code:`memoryview(object).format`. Alternately provide a :data:`constant <apsw.mapping_carray>`
318321
like :code:`apsw.SQLITE_CARRAY_INT32`
319322

src/carray.c

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,76 @@ CArrayBind_init(PyObject *self_, PyObject *args, PyObject *kwargs)
5555
goto error;
5656
}
5757

58-
self->aData = self->view.buf;
59-
self->nData = self->view.len / 8;
58+
if (flags == -1)
59+
{
60+
/* try to auto-detect format */
61+
if (0 == strcmp(self->view.format, "i"))
62+
flags = SQLITE_CARRAY_INT32;
63+
else if (0 == strcmp(self->view.format, "l"))
64+
flags = SQLITE_CARRAY_INT64;
65+
else if (0 == strcmp(self->view.format, "d"))
66+
flags = SQLITE_CARRAY_DOUBLE;
67+
else
68+
{
69+
PyErr_Format(PyExc_ValueError, "unable to detect array type from format \"%s\"", self->view.format);
70+
goto error;
71+
}
72+
}
73+
74+
switch (flags)
75+
{
76+
case SQLITE_CARRAY_INT32:
77+
case SQLITE_CARRAY_INT64:
78+
case SQLITE_CARRAY_DOUBLE:
79+
break;
80+
default:
81+
PyErr_Format(PyExc_ValueError, "Unsupported flags value %d", flags);
82+
goto error;
83+
}
84+
85+
const unsigned item_size = (flags == SQLITE_CARRAY_INT32) ? 4 : 8;
86+
if (self->view.len % item_size)
87+
{
88+
PyErr_Format(PyExc_ValueError, "Array size %lld bytes is not a multiple of item size %u bytes", self->view.len,
89+
item_size);
90+
goto error;
91+
}
92+
93+
size_t nitems = self->view.len / item_size;
94+
if (start > nitems)
95+
{
96+
PyErr_Format(PyExc_ValueError, "Start %lld is beyond end of %lld item array", start, nitems);
97+
goto error;
98+
}
99+
if (stop < 0)
100+
stop = nitems;
101+
if (stop > nitems)
102+
{
103+
PyErr_Format(PyExc_ValueError, "Stop %lld is beyond end of %lld item array", stop, nitems);
104+
goto error;
105+
}
106+
107+
if (stop < start)
108+
{
109+
PyErr_Format(PyExc_ValueError, "Stop %lld is before start %lld", stop, start);
110+
goto error;
111+
}
112+
113+
nitems = stop - start;
114+
if (!nitems)
115+
{
116+
PyErr_Format(PyExc_ValueError, "CARRAY can't work with a zero item array");
117+
goto error;
118+
}
119+
120+
if (nitems >= INT32_MAX)
121+
{
122+
PyErr_Format(PyExc_ValueError, "CARRAY supports a maximum of 2 billion items");
123+
goto error;
124+
}
125+
126+
self->aData = ((uint8_t *)self->view.buf) + (start * item_size);
127+
self->nData = nitems;
60128
self->mFlags = flags;
61129
return 0;
62130

0 commit comments

Comments
 (0)