Skip to content

Commit 05e0dec

Browse files
loryrutagreenrobot
authored andcommitted
Box: add contains/insert/update/get_many #64
1 parent fcf5fe3 commit 05e0dec

3 files changed

Lines changed: 193 additions & 15 deletions

File tree

‎objectbox/box.py‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ def __init__(self, store: Store, entity: _Entity):
3131
self._entity = entity
3232
self._c_box = obx_box(store._c_store, entity._id)
3333

34+
def contains(self, object_id: int) -> bool:
35+
"""Checks whether the box contains the given object ID."""
36+
contained = ctypes.c_bool()
37+
obx_box_contains(self._c_box, object_id, ctypes.byref(contained))
38+
return bool(contained.value)
39+
3440
def is_empty(self) -> bool:
3541
"""Returns true if box is empty (i.e. no objects of entity type are available)."""
3642
is_empty = ctypes.c_bool()
@@ -112,6 +118,26 @@ def _put_many(self, objects) -> None:
112118
for k in new.keys():
113119
self._entity._set_object_id(objects[k], ids[k])
114120

121+
def insert(self, object_id: int, object_):
122+
"""Inserts an object with the given ID.
123+
124+
Raises if the ID already exists.
125+
"""
126+
object_.id = object_id
127+
data = self._entity._marshal(object_, object_id)
128+
obx_box_insert(self._c_box, object_id, bytes(data), len(data))
129+
if object_id != self._entity._get_object_id(object_):
130+
self._entity._set_object_id(object_, object_id)
131+
132+
def update(self, object_id: int, object_):
133+
"""Updates an existing object given by its ID, with the given object.
134+
135+
Raises if the ID doesn't match any object."""
136+
data = self._entity._marshal(object_, object_id)
137+
obx_box_update(self._c_box, object_id, bytes(data), len(data))
138+
if object_id != self._entity._get_object_id(object_):
139+
self._entity._set_object_id(object_, object_id)
140+
115141
def get(self, id: int):
116142
"""Get object by given Id or None if not found."""
117143
with self._store.read_tx():
@@ -126,6 +152,39 @@ def get(self, id: int):
126152
data = c_voidp_as_bytes(c_data, c_size.value)
127153
return self._entity._unmarshal(data)
128154

155+
def get_many(self, id_array: List[int]) -> list:
156+
"""Gets objects matching the given ID list.
157+
158+
:return: objects matching the ID list, preserving the order.
159+
If an invalid ID is supplied, the corresponding object is None.
160+
"""
161+
162+
if not id_array: # Empty
163+
return []
164+
165+
c_ids = (ctypes.c_uint64 * len(id_array))(*id_array)
166+
167+
c_id_array = OBX_id_array()
168+
c_id_array.ids = ctypes.cast(c_ids, ctypes.POINTER(obx_id))
169+
c_id_array.count = len(id_array)
170+
171+
with self._store.read_tx():
172+
c_bytes_array_p = obx_box_get_many(self._c_box, ctypes.byref(c_id_array))
173+
174+
result = []
175+
try:
176+
c_bytes_array = c_bytes_array_p.contents
177+
for i in range(c_bytes_array.count):
178+
c_bytes = c_bytes_array.data[i]
179+
if c_bytes.data is None or c_bytes.size == 0: # User supplied an invalid ID
180+
result.append(None)
181+
else:
182+
data = c_voidp_as_bytes(c_bytes.data, c_bytes.size)
183+
result.append(self._entity._unmarshal(data))
184+
finally:
185+
obx_bytes_array_free(c_bytes_array_p)
186+
return result
187+
129188
def get_all(self) -> list:
130189
"""Get all objects."""
131190
with self._store.read_tx():

‎objectbox/c.py‎

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ def shlib_name(library: str) -> str:
8787
OBXLogLevel = ctypes.c_int
8888

8989
from enum import IntEnum
90+
91+
9092
class LogLevel(IntEnum):
9193
Verbose = 10
9294
Debug = 20
@@ -581,7 +583,7 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
581583
obx_opt_async_object_bytes_max_size_to_cache = c_fn('obx_opt_async_object_bytes_max_size_to_cache', None,
582584
[OBX_store_options_p, ctypes.c_uint64])
583585

584-
#typedef void obx_log_callback(OBXLogLevel log_level, const char* message, size_t message_size, void* user_data);
586+
# typedef void obx_log_callback(OBXLogLevel log_level, const char* message, size_t message_size, void* user_data);
585587
obx_log_callback_fn = ctypes.CFUNCTYPE(None, OBXLogLevel, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_voidp)
586588

587589
# OBX_C_API void obx_opt_log_callback(OBX_store_options* opt, obx_log_callback* callback, void* user_data);
@@ -633,10 +635,16 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
633635
# OBX_box* (OBX_store* store, obx_schema_id entity_id);
634636
obx_box = c_fn('obx_box', OBX_box_p, [OBX_store_p, obx_schema_id])
635637

638+
# OBX_C_API obx_err obx_box_contains(OBX_box* box, obx_id id, bool* out_contains);
639+
obx_box_contains = c_fn_rc('obx_box_contains', [OBX_box_p, obx_id, ctypes.POINTER(ctypes.c_bool)])
640+
636641
# obx_err (OBX_box* box, obx_id id, const void** data, size_t* size);
637642
obx_box_get = c_fn_nocheck('obx_box_get', obx_err, [
638643
OBX_box_p, obx_id, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_size_t)])
639644

645+
# OBX_C_API OBX_bytes_array* obx_box_get_many(OBX_box* box, const OBX_id_array* ids);
646+
obx_box_get_many = c_fn('obx_box_get_many', OBX_bytes_array_p, [OBX_box_p, OBX_id_array_p])
647+
640648
# OBX_bytes_array* (OBX_box* box);
641649
obx_box_get_all = c_fn('obx_box_get_all', OBX_bytes_array_p, [OBX_box_p])
642650

@@ -650,24 +658,29 @@ def c_array_pointer(py_list: Union[List[Any], np.ndarray], c_type):
650658
# obx_err (OBX_box* box, obx_id id, const void* data, size_t size);
651659
obx_box_put = c_fn_rc('obx_box_put', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
652660

661+
# OBX_C_API obx_err obx_box_insert(OBX_box* box, obx_id id, const void* data, size_t size);
662+
obx_box_insert = c_fn_rc('obx_box_insert', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
663+
664+
# OBX_C_API obx_err obx_box_update(OBX_box* box, obx_id id, const void* data, size_t size);
665+
obx_box_update = c_fn_rc('obx_box_update', [OBX_box_p, obx_id, ctypes.c_void_p, ctypes.c_size_t])
666+
653667
# obx_err (OBX_box* box, const OBX_bytes_array* objects, const obx_id* ids, OBXPutMode mode);
654-
obx_box_put_many = c_fn_rc('obx_box_put_many', [
655-
OBX_box_p, OBX_bytes_array_p, ctypes.POINTER(obx_id), OBXPutMode])
668+
obx_box_put_many = c_fn_rc('obx_box_put_many', [OBX_box_p, OBX_bytes_array_p, ctypes.POINTER(obx_id), OBXPutMode])
656669

657670
# obx_err (OBX_box* box, obx_id id);
658671
obx_box_remove = c_fn_nocheck('obx_box_remove', obx_err, [OBX_box_p, obx_id])
659672

660-
# obx_err (OBX_box* box, uint64_t* out_count);
661-
obx_box_remove_all = c_fn_rc('obx_box_remove_all', [
662-
OBX_box_p, ctypes.POINTER(ctypes.c_uint64)])
673+
# OBX_C_API obx_err obx_box_remove_many(OBX_box* box, const OBX_id_array* ids, uint64_t* out_count);
674+
obx_box_remove_many = c_fn_rc('obx_box_remove_many', [OBX_id_array_p, ctypes.POINTER(ctypes.c_uint64)])
675+
676+
# OBX_C_API obx_err obx_box_remove_all(OBX_box* box, uint64_t* out_count);
677+
obx_box_remove_all = c_fn_rc('obx_box_remove_all', [OBX_box_p, ctypes.POINTER(ctypes.c_uint64)])
663678

664-
# obx_err (OBX_box* box, bool* out_is_empty);
665-
obx_box_is_empty = c_fn_rc('obx_box_is_empty', [
666-
OBX_box_p, ctypes.POINTER(ctypes.c_bool)])
679+
# OBX_C_API obx_err obx_box_is_empty(OBX_box* box, bool* out_is_empty);
680+
obx_box_is_empty = c_fn_rc('obx_box_is_empty', [OBX_box_p, ctypes.POINTER(ctypes.c_bool)])
667681

668-
# obx_err obx_box_count(OBX_box* box, uint64_t limit, uint64_t* out_count);
669-
obx_box_count = c_fn_rc('obx_box_count', [
670-
OBX_box_p, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64)])
682+
# OBX_C_API obx_err obx_box_count(OBX_box* box, uint64_t limit, uint64_t* out_count);
683+
obx_box_count = c_fn_rc('obx_box_count', [OBX_box_p, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64)])
671684

672685
# OBX_query_builder* obx_query_builder(OBX_store* store, obx_schema_id entity_id);
673686
obx_query_builder = c_fn('obx_query_builder', OBX_query_builder_p, [OBX_store_p, obx_schema_id])

‎tests/test_box.py‎

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import pytest
2-
import objectbox
3-
from tests.model import TestEntity, TestEntityDatetime, TestEntityFlex
4-
from tests.common import *
52
import numpy as np
63
from datetime import datetime, timezone
74
import time
85
from math import floor
96

7+
import objectbox
8+
from objectbox.exceptions import *
9+
from tests.common import *
10+
from tests.model import TestEntity, TestEntityDatetime, TestEntityFlex
11+
1012

1113
def test_box_basics(test_store):
1214
box = test_store.box(TestEntity)
@@ -280,3 +282,107 @@ def test_flex_values(test_store):
280282
obj_id = box.put(TestEntityFlex(flex=dict_))
281283
read_obj = box.get(obj_id)
282284
assert read_obj.flex == dict_
285+
286+
287+
def test_contains(test_store):
288+
box = test_store.box(TestEntity)
289+
290+
box.put(TestEntity(str="Object 1"))
291+
box.put(TestEntity(str="Object 2"))
292+
box.put(TestEntity(str="Object 3"))
293+
assert box.count() == 3
294+
295+
assert box.contains(1)
296+
assert box.contains(2)
297+
assert box.contains(3)
298+
assert not box.contains(4)
299+
assert not box.contains(3248)
300+
301+
with pytest.raises(IllegalArgumentError, match="Illegal ID value: 0") as e:
302+
box.contains(0)
303+
304+
305+
def test_insert(test_store):
306+
box = test_store.box(TestEntity)
307+
308+
box.insert(38, TestEntity(str="Object 38"))
309+
box.insert(2, TestEntity(str="Object 2"))
310+
box.insert(98, TestEntity(str="Object 98"))
311+
assert box.count() == 3
312+
313+
with pytest.raises(IdAlreadyExistsError, match="Insert failed, given ID already exists: 98") as e:
314+
box.insert(98, TestEntity(str="Object 98"))
315+
316+
assert box.put(TestEntity(str="Object 1")) == 1
317+
assert box.count() == 4
318+
319+
assert box.get(2).str == "Object 2"
320+
assert box.put(TestEntity(str="Object 2 (updated)")) == 2
321+
assert box.get(2).str == "Object 2 (updated)"
322+
assert box.count() == 4
323+
324+
325+
def test_update(test_store):
326+
box = test_store.box(TestEntity)
327+
328+
box.put(TestEntity(str="Object 1"))
329+
box.put(TestEntity(str="Object 2"))
330+
box.put(TestEntity(str="Object 3"))
331+
assert box.count() == 3
332+
333+
assert box.get(1).str == "Object 1"
334+
assert box.get(2).str == "Object 2"
335+
assert box.get(3).str == "Object 3"
336+
337+
box.update(1, TestEntity(str="Object 1 (updated)"))
338+
box.update(2, TestEntity(str="Object 2 (updated)"))
339+
box.update(3, TestEntity(str="Object 3 (updated)"))
340+
341+
assert box.get(1).str == "Object 1 (updated)"
342+
assert box.get(2).str == "Object 2 (updated)"
343+
assert box.get(3).str == "Object 3 (updated)"
344+
345+
with pytest.raises(IdNotFoundError, match="Update failed, given ID doesn't exist: 100") as e:
346+
box.update(100, TestEntity(str="Object 100 (updated)"))
347+
348+
349+
def test_get_many(test_store):
350+
box = test_store.box(TestEntity)
351+
352+
box.put(TestEntity(str="Object 1"))
353+
box.put(TestEntity(str="Object 2"))
354+
box.put(TestEntity(str="Object 3"))
355+
box.put(TestEntity(str="Object 4"))
356+
box.put(TestEntity(str="Object 5"))
357+
assert box.count() == 5
358+
359+
objects = box.get_many([1, 3, 4])
360+
assert len(objects) == 3
361+
assert objects[0].str == "Object 1"
362+
assert objects[1].str == "Object 3"
363+
assert objects[2].str == "Object 4"
364+
365+
objects = box.get_many([5, 2])
366+
assert len(objects) == 2
367+
assert objects[0].str == "Object 5"
368+
assert objects[1].str == "Object 2"
369+
370+
objects = box.get_many([5, 5, 2])
371+
assert len(objects) == 3
372+
assert objects[0].str == "Object 5"
373+
assert objects[1].str == "Object 5"
374+
assert objects[2].str == "Object 2"
375+
376+
objects = box.get_many([2])
377+
assert len(objects) == 1
378+
assert objects[0].str == "Object 2"
379+
380+
objects = box.get_many([])
381+
assert len(objects) == 0
382+
383+
objects = box.get_many([1, 789, 2, 10])
384+
assert len(objects) == 4
385+
assert objects[0].str == "Object 1"
386+
assert objects[1] is None
387+
assert objects[2].str == "Object 2"
388+
assert objects[3] is None

0 commit comments

Comments
 (0)