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
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "cci-src"]
path = cci-src
url = https://github.com/CUBRID/cubrid-cci.git
branch = develop
url = https://github.com/cubrid/cubrid-cci.git
branch = cubvec/m1
1 change: 1 addition & 0 deletions CUBRIDdb/FIELD_TYPE.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@

BLOB = 23
CLOB = 24
VECTOR = 41

STRING = VARCHAR
17 changes: 16 additions & 1 deletion CUBRIDdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,22 @@ def __eq__(self, other):
SET = DBAPISet([FIELD_TYPE.SET, FIELD_TYPE.MULTISET, FIELD_TYPE.SEQUENCE])
BLOB = DBAPISet([FIELD_TYPE.BLOB])
CLOB = DBAPISet([FIELD_TYPE.CLOB])
VECTOR = DBAPISet([FIELD_TYPE.VECTOR])
ROWID = DBAPISet()

class Vector(object):
def __init__(self, values):
self.values = tuple(values)

def __iter__(self):
return iter(self.values)

def __len__(self):
return len(self.values)

def __getitem__(self, index):
return self.values[index]

def Connect(*args, **kwargs):
from CUBRIDdb.connections import Connection

Expand All @@ -82,5 +96,6 @@ def connect(*args, **kwargs):
__all__ = [ 'Connect', 'connection', 'connect', 'connections', 'DatabaseError',
'Error', 'InterfaceError', 'NotSupportedError', 'apilevel', 'Cursor',
'DictCursor', 'paramstyle', 'threadsafety', 'STRING', 'BINARY', 'NUMBER',
'DATE', 'TIME', 'TIMESTAMP', 'DATETIME', 'FLOAT', 'ROWID', 'SET', 'BLOB', 'CLOB']
'DATE', 'TIME', 'TIMESTAMP', 'DATETIME', 'FLOAT', 'VECTOR', 'ROWID', 'SET', 'BLOB', 'CLOB',
'VECTOR']

3 changes: 3 additions & 0 deletions CUBRIDdb/cursors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys
from CUBRIDdb import FIELD_TYPE
from CUBRIDdb import InterfaceError
from CUBRIDdb import Vector
from datetime import date, time, datetime
from decimal import Decimal

Expand Down Expand Up @@ -195,6 +196,8 @@ def _bind_params(self, args, set_type=None):
self._cs.bind_param(i, arg)
elif isinstance(arg, bytes):
self._cs.bind_param(i, arg, FIELD_TYPE.VARBIT)
elif isinstance(arg, Vector):
self._cs.bind_param(i, arg.values, FIELD_TYPE.VECTOR)
elif is_iterable(arg):
element_type = None
if set_type is not None:
Expand Down
127 changes: 127 additions & 0 deletions cubrid_ext/python_cubrid.c
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,8 @@ _cubrid_CursorObject_bind_param (_cubrid_CursorObject * self, PyObject * args)
char *str_value = NULL;
T_CCI_DATE date_value;
T_CCI_BIT bit_value;
T_CCI_VECTOR_FLOAT vector_value;
float *vector_buffer = NULL;

if (self->state == CURSOR_STATE_CLOSED)
{
Expand Down Expand Up @@ -1860,6 +1862,52 @@ _cubrid_CursorObject_bind_param (_cubrid_CursorObject * self, PyObject * args)
bind_value = value_view.buf;
}
}
else if (u_type == CCI_U_TYPE_VECTOR && PySequence_Check(value_obj))
{
PyObject *seq = PySequence_Fast(value_obj, "vector bind expects a sequence of floats");
Py_ssize_t i, seq_len;

if (seq == NULL)
{
return NULL;
}

seq_len = PySequence_Fast_GET_SIZE(seq);
if (seq_len < 0)
{
Py_DECREF(seq);
return NULL;
}

vector_buffer = (float *) malloc(sizeof(float) * (size_t) seq_len);
if (vector_buffer == NULL)
{
Py_DECREF(seq);
PyErr_NoMemory();
return NULL;
}

for (i = 0; i < seq_len; i++)
{
PyObject *item = PySequence_Fast_GET_ITEM(seq, i);
double item_value = PyFloat_AsDouble(item);
if (PyErr_Occurred())
{
free(vector_buffer);
vector_buffer = NULL;
Py_DECREF(seq);
return NULL;
}
vector_buffer[i] = (float) item_value;
}

Py_DECREF(seq);

vector_value.dim = (int) seq_len;
vector_value.float_array = vector_buffer;
bind_value = &vector_value;
a_type = CCI_A_TYPE_VECTOR;
}
else
{
return NULL;
Expand All @@ -1879,6 +1927,11 @@ _cubrid_CursorObject_bind_param (_cubrid_CursorObject * self, PyObject * args)
Py_DECREF (temp_str);
}

if (vector_buffer)
{
free(vector_buffer);
}

if (res < 0)
{
return handle_error (res, NULL);
Expand Down Expand Up @@ -2338,6 +2391,8 @@ _cubrid_CursorObject_dbval_to_pyvalue (_cubrid_CursorObject * self, int type,
T_CCI_DATE dt;
T_CCI_BIT bit_data;
char *str_buffer;
char *cursor;
char *endptr;
int len;
CUBRID_LONG_LONG int64_value;

Expand Down Expand Up @@ -2541,6 +2596,78 @@ _cubrid_CursorObject_dbval_to_pyvalue (_cubrid_CursorObject * self, int type,
}
}
break;
case CCI_U_TYPE_VECTOR:
res = cci_get_data (self->handle, index, CCI_A_TYPE_STR, &buffer, &ind);
if (res < 0)
{
return handle_error (res, NULL);
}
if (ind < 0)
{
Py_INCREF (Py_None);
val = Py_None;
}
else
{
PyObject *list_obj = PyList_New (0);
if (list_obj == NULL)
{
return NULL;
}

cursor = buffer;
while (*cursor != '\0' && *cursor != '[')
{
cursor++;
}

if (*cursor == '[')
{
cursor++;
}

while (*cursor != '\0' && *cursor != ']')
{
float fval;
PyObject *item;

while (*cursor == ' ' || *cursor == '\t' || *cursor == '\n' || *cursor == ',')
{
cursor++;
}

if (*cursor == '\0' || *cursor == ']')
{
break;
}

fval = strtof (cursor, &endptr);
if (endptr == cursor)
{
Py_DECREF (list_obj);
return PyErr_Format (PyExc_ValueError, "Failed to parse VECTOR value: %s", buffer);
}

item = PyFloat_FromDouble ((double) fval);
if (item == NULL)
{
Py_DECREF (list_obj);
return NULL;
}

if (PyList_Append (list_obj, item) < 0)
{
Py_DECREF (item);
Py_DECREF (list_obj);
return NULL;
}
Py_DECREF (item);
cursor = endptr;
}

val = list_obj;
}
break;
default:
res = cci_get_data (self->handle, index, CCI_A_TYPE_INT, &num, &ind);
if (res == 0)
Expand Down
53 changes: 53 additions & 0 deletions tests3/test_vector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from types import MethodType, SimpleNamespace

import CUBRIDdb
from CUBRIDdb import FIELD_TYPE
from CUBRIDdb.cursors import BaseCursor


class _FakeLowLevelCursor:
def __init__(self):
self.bind_param_calls = []

def bind_param(self, index, value, bind_type=None):
self.bind_param_calls.append((index, value, bind_type))


def _make_cursor():
cursor = BaseCursor.__new__(BaseCursor)
cursor._cs = _FakeLowLevelCursor()
cursor.con = SimpleNamespace(connection=None)
return cursor


def test_field_type_vector_constant():
assert FIELD_TYPE.VECTOR == 41
assert CUBRIDdb.VECTOR == FIELD_TYPE.VECTOR


def test_bind_params_uses_vector_binding_when_requested():
cursor = _make_cursor()
bind_set_calls = []

def _bind_set(self, index, value, element_type=None):
bind_set_calls.append((index, value, element_type))

cursor._bind_set = MethodType(_bind_set, cursor)
cursor._bind_params(([1.5, 2.25, 3.0],), set_type=FIELD_TYPE.VECTOR)

assert cursor._cs.bind_param_calls == [(1, [1.5, 2.25, 3.0], FIELD_TYPE.VECTOR)]
assert bind_set_calls == []


def test_bind_params_keeps_collection_binding_by_default():
cursor = _make_cursor()
bind_set_calls = []

def _bind_set(self, index, value, element_type=None):
bind_set_calls.append((index, value, element_type))

cursor._bind_set = MethodType(_bind_set, cursor)
cursor._bind_params(((1.0, 2.0),))

assert cursor._cs.bind_param_calls == []
assert bind_set_calls == [(1, (1.0, 2.0), None)]