From 4fa3d8b325bc4fb7db87c54eacd8850dd3c14157 Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Ryoo Date: Fri, 27 Mar 2026 20:17:34 +0900 Subject: [PATCH 1/6] v type --- .gitmodules | 4 +- CUBRIDdb/FIELD_TYPE.py | 1 + CUBRIDdb/__init__.py | 17 ++++- CUBRIDdb/cursors.py | 3 + cubrid_ext/python_cubrid.c | 127 +++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index e7deb46..97772bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cci-src"] path = cci-src - url = https://github.com/CUBRID/cubrid-cci.git - branch = develop + url = https://github.com/hgryoo/cubrid-cci.git + branch = cubvec/m1-nv diff --git a/CUBRIDdb/FIELD_TYPE.py b/CUBRIDdb/FIELD_TYPE.py index 041bfb3..ad2318b 100644 --- a/CUBRIDdb/FIELD_TYPE.py +++ b/CUBRIDdb/FIELD_TYPE.py @@ -38,5 +38,6 @@ BLOB = 23 CLOB = 24 +VECTOR = 41 STRING = VARCHAR diff --git a/CUBRIDdb/__init__.py b/CUBRIDdb/__init__.py index 59f3a73..be5bcf6 100644 --- a/CUBRIDdb/__init__.py +++ b/CUBRIDdb/__init__.py @@ -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 @@ -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'] diff --git a/CUBRIDdb/cursors.py b/CUBRIDdb/cursors.py index a9a7ddb..f98b70b 100644 --- a/CUBRIDdb/cursors.py +++ b/CUBRIDdb/cursors.py @@ -1,4 +1,5 @@ import sys +import CUBRIDdb from CUBRIDdb import FIELD_TYPE from CUBRIDdb import InterfaceError from datetime import date, time, datetime @@ -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, CUBRIDdb.Vector): + self._cs.bind_param(i, arg.values, FIELD_TYPE.VECTOR) elif is_iterable(arg): element_type = None if set_type is not None: diff --git a/cubrid_ext/python_cubrid.c b/cubrid_ext/python_cubrid.c index 0b72f12..a67ddea 100644 --- a/cubrid_ext/python_cubrid.c +++ b/cubrid_ext/python_cubrid.c @@ -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) { @@ -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; @@ -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); @@ -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; @@ -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) From 11530c9478f15d4f19d70af61ca0c07d275d1a2d Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Date: Tue, 31 Mar 2026 16:17:27 +0900 Subject: [PATCH 2/6] add .gitmodules --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 97772bc..89980ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cci-src"] path = cci-src - url = https://github.com/hgryoo/cubrid-cci.git - branch = cubvec/m1-nv + url = https://github.com/cubrid/cubrid-cci.git + branch = cubvec/m1 From 8478cc1f4379f9fc4822414b193b527394e61fc5 Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Date: Tue, 31 Mar 2026 16:17:27 +0900 Subject: [PATCH 3/6] update .gitmodules --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 97772bc..89980ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "cci-src"] path = cci-src - url = https://github.com/hgryoo/cubrid-cci.git - branch = cubvec/m1-nv + url = https://github.com/cubrid/cubrid-cci.git + branch = cubvec/m1 From 8a8455e292dc83d39d63afbfb77d9dbd832bb055 Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Date: Tue, 31 Mar 2026 16:18:31 +0900 Subject: [PATCH 4/6] add test for vector type --- tests3/test_vector.py | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests3/test_vector.py diff --git a/tests3/test_vector.py b/tests3/test_vector.py new file mode 100644 index 0000000..32901ed --- /dev/null +++ b/tests3/test_vector.py @@ -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)] From 5d1a380c4ebbf3b81bb105c9441cf0e8e9ce3c21 Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Date: Tue, 31 Mar 2026 16:20:16 +0900 Subject: [PATCH 5/6] uppercase --- CUBRIDdb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CUBRIDdb/__init__.py b/CUBRIDdb/__init__.py index be5bcf6..4b620a1 100644 --- a/CUBRIDdb/__init__.py +++ b/CUBRIDdb/__init__.py @@ -97,5 +97,5 @@ def connect(*args, **kwargs): 'Error', 'InterfaceError', 'NotSupportedError', 'apilevel', 'Cursor', 'DictCursor', 'paramstyle', 'threadsafety', 'STRING', 'BINARY', 'NUMBER', 'DATE', 'TIME', 'TIMESTAMP', 'DATETIME', 'FLOAT', 'VECTOR', 'ROWID', 'SET', 'BLOB', 'CLOB', - 'Vector'] + 'VECTOR'] From e96d48e3a783e97f932ebc57767e0ef591d2f353 Mon Sep 17 00:00:00 2001 From: Hyung-Gyu Date: Tue, 31 Mar 2026 16:23:32 +0900 Subject: [PATCH 6/6] refactor --- CUBRIDdb/cursors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CUBRIDdb/cursors.py b/CUBRIDdb/cursors.py index f98b70b..34edae7 100644 --- a/CUBRIDdb/cursors.py +++ b/CUBRIDdb/cursors.py @@ -1,7 +1,7 @@ import sys -import CUBRIDdb from CUBRIDdb import FIELD_TYPE from CUBRIDdb import InterfaceError +from CUBRIDdb import Vector from datetime import date, time, datetime from decimal import Decimal @@ -196,7 +196,7 @@ 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, CUBRIDdb.Vector): + elif isinstance(arg, Vector): self._cs.bind_param(i, arg.values, FIELD_TYPE.VECTOR) elif is_iterable(arg): element_type = None