From 8e2365f421a26c507aa53985a1c4d9286c8fe32e Mon Sep 17 00:00:00 2001 From: Abe Tomoaki Date: Mon, 13 Jul 2026 18:38:07 +0900 Subject: [PATCH] Implement get() to get a value --- grnpy/column.pyx | 3 ++ grnpy/grn_type.pxd | 36 ++++++++++++++++++++ grnpy/grnpy_bulk.c | 43 ++++++++++++++++++++++++ grnpy/grnpy_bulk.h | 27 +++++++++++++++ grnpy/grnpy_obj.c | 6 ++++ grnpy/grnpy_obj.h | 2 ++ grnpy/object.pxd | 3 ++ grnpy/object.pyx | 74 ++++++++++++++++++++++++++++++++++++++++-- grnpy/table.pyx | 3 ++ testing/test_column.py | 21 +++++++++++- testing/test_table.py | 2 +- 11 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 grnpy/grn_type.pxd create mode 100644 grnpy/grnpy_bulk.c create mode 100644 grnpy/grnpy_bulk.h diff --git a/grnpy/column.pyx b/grnpy/column.pyx index 42e1478..2460d2f 100644 --- a/grnpy/column.pyx +++ b/grnpy/column.pyx @@ -22,3 +22,6 @@ from grnpy.object cimport Object cdef class Column(Object): def set(self, grn_id id, value): self._set_value(id, value) + + def get(self, grn_id id): + return self._get_value(id) diff --git a/grnpy/grn_type.pxd b/grnpy/grn_type.pxd new file mode 100644 index 0000000..9d138ce --- /dev/null +++ b/grnpy/grn_type.pxd @@ -0,0 +1,36 @@ +# Copyright (C) 2026 Abe Tomoaki +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program. If not, see +# . + +# cython: language_level = 3 + +from grnpy.grn_id cimport grn_id + +cdef extern from "groonga.h": + cdef grn_id VOID "GRN_DB_VOID" + cdef grn_id BOOL "GRN_DB_BOOL" + cdef grn_id INT8 "GRN_DB_INT8" + cdef grn_id UINT8 "GRN_DB_UINT8" + cdef grn_id INT16 "GRN_DB_INT16" + cdef grn_id UINT16 "GRN_DB_UINT16" + cdef grn_id INT32 "GRN_DB_INT32" + cdef grn_id UINT32 "GRN_DB_UINT32" + cdef grn_id INT64 "GRN_DB_INT64" + cdef grn_id UINT64 "GRN_DB_UINT64" + cdef grn_id FLOAT "GRN_DB_FLOAT" + cdef grn_id FLOAT32 "GRN_DB_FLOAT32" + cdef grn_id SHORT_TEXT "GRN_DB_SHORT_TEXT" + cdef grn_id TEXT "GRN_DB_TEXT" + cdef grn_id LONG_TEXT "GRN_DB_LONG_TEXT" diff --git a/grnpy/grnpy_bulk.c b/grnpy/grnpy_bulk.c new file mode 100644 index 0000000..db81e08 --- /dev/null +++ b/grnpy/grnpy_bulk.c @@ -0,0 +1,43 @@ +/* + Copyright (C) 2026 Abe Tomoaki + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program. If not, see + . +*/ + +#include + +void +grnpy_bulk_init_void(grn_obj *bulk) +{ + GRN_VOID_INIT(bulk); +} + +void +grnpy_bulk_rewind(grn_obj *bulk) +{ + GRN_BULK_REWIND(bulk); +} + +const char * +grnpy_bulk_get_head(grn_obj *bulk) +{ + return GRN_BULK_HEAD(bulk); +} + +size_t +grnpy_bulk_get_size(grn_obj *bulk) +{ + return GRN_BULK_VSIZE(bulk); +} diff --git a/grnpy/grnpy_bulk.h b/grnpy/grnpy_bulk.h new file mode 100644 index 0000000..4eb37cf --- /dev/null +++ b/grnpy/grnpy_bulk.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2026 Abe Tomoaki + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program. If not, see + . +*/ + +#pragma once + +#include + +void grnpy_bulk_init_void(grn_obj *bulk); +void grnpy_bulk_rewind(grn_obj *bulk); + +const char *grnpy_bulk_get_head(grn_obj *bulk); +size_t grnpy_bulk_get_size(grn_obj *bulk); diff --git a/grnpy/grnpy_obj.c b/grnpy/grnpy_obj.c index 19edaa9..3facdb4 100644 --- a/grnpy/grnpy_obj.c +++ b/grnpy/grnpy_obj.c @@ -24,6 +24,12 @@ grnpy_obj_get_type(grn_obj *obj) return obj->header.type; } +grn_id +grnpy_obj_get_domain(grn_obj *obj) +{ + return obj->header.domain; +} + grn_rc grnpy_obj_set_bool(grn_ctx *ctx, grn_obj *obj, grn_id id, int value) { diff --git a/grnpy/grnpy_obj.h b/grnpy/grnpy_obj.h index ec95468..32f51d3 100644 --- a/grnpy/grnpy_obj.h +++ b/grnpy/grnpy_obj.h @@ -21,6 +21,8 @@ #include uint8_t grnpy_obj_get_type(grn_obj *obj); +grn_id grnpy_obj_get_domain(grn_obj *obj); + grn_rc grnpy_obj_set_bool(grn_ctx *ctx, grn_obj *obj, grn_id id, int value); grn_rc grnpy_obj_set_int64(grn_ctx *ctx, grn_obj *obj, grn_id id, int64_t value); grn_rc grnpy_obj_set_float(grn_ctx *ctx, grn_obj *obj, grn_id id, double value); diff --git a/grnpy/object.pxd b/grnpy/object.pxd index 30fe606..2ce49f3 100644 --- a/grnpy/object.pxd +++ b/grnpy/object.pxd @@ -24,8 +24,11 @@ from grnpy.context cimport Context cdef class Object: cdef object _context cdef grn_obj *_obj + cdef grn_obj _value_buffer cdef grn_obj *unwrap(self) cdef _set_value(self, grn_id id, value) + cdef _bulk_to_python(self, grn_obj *bulk) + cdef _get_value(self, grn_id id) cdef build_object(Context context, grn_obj *obj) diff --git a/grnpy/object.pyx b/grnpy/object.pyx index da0a278..c9a5b58 100644 --- a/grnpy/object.pyx +++ b/grnpy/object.pyx @@ -15,9 +15,19 @@ # . # cython: language_level = 3 -# distutils: sources = grnpy/grnpy_obj.c - -from libc.stdint cimport int64_t, uint8_t +# distutils: sources = grnpy/grnpy_bulk.c grnpy/grnpy_obj.c + +from libc.stdint cimport ( + int8_t, + int16_t, + int32_t, + int64_t, + uint8_t, + uint16_t, + uint32_t, + uint64_t, +) +from libcpp cimport bool as c_bool from grnpy.grn_ctx cimport grn_ctx from grnpy.grn_error cimport grn_rc @@ -25,6 +35,7 @@ from grnpy.grn_id cimport grn_id from grnpy.grn_obj cimport grn_obj cimport grnpy.grn_obj +cimport grnpy.grn_type from grnpy.context cimport Context @@ -43,19 +54,31 @@ cdef extern from "groonga.h": grn_rc grn_obj_close(grn_ctx *ctx, grn_obj *obj) void grn_obj_unref(grn_ctx *ctx, grn_obj *obj) int grn_obj_name(grn_ctx *ctx, grn_obj *obj, char *buffer, int buffer_size) + grn_obj *grn_obj_get_value(grn_ctx *ctx, grn_obj *obj, grn_id id, grn_obj *value) + +cdef extern from "grnpy_bulk.h": + void grnpy_bulk_init_void(grn_obj *bulk) + void grnpy_bulk_rewind(grn_obj *bulk) + const char *grnpy_bulk_get_head(grn_obj *bulk) + size_t grnpy_bulk_get_size(grn_obj *bulk) cdef extern from "grnpy_obj.h": uint8_t grnpy_obj_get_type(grn_obj *obj) + grn_id grnpy_obj_get_domain(grn_obj *obj) grn_rc grnpy_obj_set_bool(grn_ctx *ctx, grn_obj *obj, grn_id id, int value) grn_rc grnpy_obj_set_int64(grn_ctx *ctx, grn_obj *obj, grn_id id, int64_t value) grn_rc grnpy_obj_set_float(grn_ctx *ctx, grn_obj *obj, grn_id id, double value) grn_rc grnpy_obj_set_text(grn_ctx *ctx, grn_obj *obj, grn_id id, const char *value, unsigned int length) cdef class Object: + def __cinit__(self, *args, **kwargs): + grnpy_bulk_init_void(&(self._value_buffer)) + def __dealloc__(self): cdef Context context if self._obj is not NULL: context = self._context + grn_obj_close(context.unwrap(), &(self._value_buffer)) # TODO: This may be not worked when a database is closed # and a table in the database isn't deleted yet. grn_obj_unref(context.unwrap(), self._obj) @@ -95,6 +118,50 @@ cdef class Object: raise TypeError(f"unsupported value type: <{type(value)}>") Error.check(rc, f"failed to set a value: <{value}>", context.error_message()) + cdef _bulk_to_python(self, grn_obj *bulk): + cdef Context context = self._context + cdef grn_id domain = grnpy_obj_get_domain(bulk) + cdef const char *head = grnpy_bulk_get_head(bulk) + cdef size_t size = grnpy_bulk_get_size(bulk) + if size == 0: + return None + if domain == grnpy.grn_type.BOOL: + return (head)[0] + elif domain == grnpy.grn_type.INT8: + return (head)[0] + elif domain == grnpy.grn_type.UINT8: + return (head)[0] + elif domain == grnpy.grn_type.INT16: + return (head)[0] + elif domain == grnpy.grn_type.UINT16: + return (head)[0] + elif domain == grnpy.grn_type.INT32: + return (head)[0] + elif domain == grnpy.grn_type.UINT32: + return (head)[0] + elif domain == grnpy.grn_type.INT64: + return (head)[0] + elif domain == grnpy.grn_type.UINT64: + return (head)[0] + elif domain == grnpy.grn_type.FLOAT32: + return (head)[0] + elif domain == grnpy.grn_type.FLOAT: + return (head)[0] + elif domain in (grnpy.grn_type.SHORT_TEXT, + grnpy.grn_type.TEXT, + grnpy.grn_type.LONG_TEXT): + return head[:size].decode(context.encoding_name()) + else: + raise NotImplementedError(f"unsupported value type: <{domain}>") + + cdef _get_value(self, grn_id id): + cdef Context context = self._context + cdef grn_ctx *ctx = context.unwrap() + grnpy_bulk_rewind(&(self._value_buffer)) + grn_obj_get_value(ctx, self.unwrap(), id, &(self._value_buffer)) + context.check(f"failed to get a value: <{id}>") + return self._bulk_to_python(&(self._value_buffer)) + def __eq__(self, other): if not isinstance(other, self.__class__): return False @@ -107,6 +174,7 @@ cdef class Object: return if context is None: return + grn_obj_close(context.unwrap(), &(self._value_buffer)) grn_obj_close(context.unwrap(), self._obj) self._obj = NULL self._context = None diff --git a/grnpy/table.pyx b/grnpy/table.pyx index b9c508f..671af85 100644 --- a/grnpy/table.pyx +++ b/grnpy/table.pyx @@ -213,5 +213,8 @@ cdef class Table(Object): def set(self, grn_id id, value): self._set_value(id, value) + def get(self, grn_id id): + return self._get_value(id) + def open_cursor(self, limit=-1, offset=0): return TableCursor(self, offset, limit) diff --git a/testing/test_column.py b/testing/test_column.py index 78b609c..dddac87 100644 --- a/testing/test_column.py +++ b/testing/test_column.py @@ -31,4 +31,23 @@ def test_set_value(tmpdir): active.set(id, True) address.set(id, 'Tokyo') - # todo: Add a test for the set value + assert age.get(id) == 20 + assert weight.get(id) == 70.5 + assert active.get(id) is True + assert address.get(id) == 'Tokyo' + +def test_get_no_data(tmpdir): + db_path = tmpdir.join('db') + with grnpy.Database.create(db_path): + users = grnpy.PatriciaTrie.create('ShortText', 'Users') + age = users.create_scalar_column('age', 'Int32') + weight = users.create_scalar_column('weight', 'Float') + address = users.create_scalar_column('address', 'ShortText') + active = users.create_scalar_column('active', 'Bool') + + id = users.add('Groonga') + + assert age.get(id) == 0 + assert weight.get(id) == 0.0 + assert active.get(id) is False + assert address.get(id) is None diff --git a/testing/test_table.py b/testing/test_table.py index 39a935c..c398bbd 100644 --- a/testing/test_table.py +++ b/testing/test_table.py @@ -53,7 +53,7 @@ def test_set(tmpdir): id = users.add() users.set(id, 29) - # todo: Add a test for the set value + assert users.get(id) == 29 def test_cursor_all(tmpdir): db_path = tmpdir.join('db')