objectbox-api 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,450 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ from enum import IntEnum
5
+ from typing import *
6
+
7
+ from objectbox.c import *
8
+ from objectbox.condition import QueryCondition
9
+ from objectbox.exceptions import IllegalArgumentError, IllegalStateError
10
+ from objectbox.model.properties import Property, Relation
11
+ from objectbox.store import Store
12
+ from objectbox.query import Query
13
+ from objectbox.utils import check_float_vector
14
+
15
+ if TYPE_CHECKING:
16
+ import numpy as np
17
+
18
+
19
+ class OrderFlags(IntEnum):
20
+ """ Flags to adjust the order of query results, see :func:`QueryBuilder.order`.
21
+ Combine multiple flags using ``|``. """
22
+
23
+ DESCENDING = OBXOrderFlags_DESCENDING
24
+ """Reverses the order from ascending (default) to descending."""
25
+
26
+ CASE_SENSITIVE = OBXOrderFlags_CASE_SENSITIVE
27
+ """Sorts upper case letters before lower case letters.
28
+ If not specified, the default is case insensitive for ASCII characters."""
29
+
30
+ UNSIGNED = OBXOrderFlags_UNSIGNED
31
+ """For scalars only: changes the comparison to unsigned (default is signed)."""
32
+
33
+ NULLS_LAST = OBXOrderFlags_NULLS_LAST
34
+ """Null values will be put last.
35
+ If not specified, by default null values will be put first."""
36
+
37
+ NULLS_ZERO = OBXOrderFlags_NULLS_ZERO
38
+ """Null values should be treated equal to zero (scalars only)."""
39
+
40
+
41
+ class QueryBuilder:
42
+ """
43
+ Query builder
44
+ """
45
+ def __init__(self, store: Store, box: 'Box'):
46
+ self._box = box
47
+ self._entity = box._entity
48
+ self._c_builder_or_none = obx_query_builder(store._c_store, box._entity._id) # None once closed
49
+ # Entity and property ID of the conditions' aliases; for Query.set_parameter_alias()
50
+ self._alias_properties: Dict[str, Tuple['_Entity', int]] = {}
51
+ self._root: Optional[QueryBuilder] = None # For a builder of a linked entity (see link()): the query's builder
52
+ self._last_condition_prop_id: Optional[int] = None # Property of the last condition, for alias()
53
+ store._add_native_resource(self)
54
+
55
+ @property
56
+ def _c_builder(self):
57
+ root = self._root or self
58
+ if root._c_builder_or_none is None:
59
+ self._box._store._c_store # Raises if the store is closed (which closed the builder)
60
+ raise IllegalStateError("The query builder is closed: build() closes it")
61
+ return self._c_builder_or_none
62
+
63
+ def _linked(self, c_builder, entity: '_Entity', condition: Optional[QueryCondition]) -> 'QueryBuilder':
64
+ """ Creates the builder for a linked entity, owned by the C builder of the query. """
65
+ linked = QueryBuilder.__new__(QueryBuilder)
66
+ linked._box = self._box
67
+ linked._entity = entity
68
+ linked._c_builder_or_none = c_builder # Owned by the root's C builder
69
+ linked._alias_properties = self._alias_properties
70
+ linked._root = self._root or self
71
+ linked._last_condition_prop_id = None
72
+ if condition is not None:
73
+ condition.apply(linked)
74
+ return linked
75
+
76
+ def _assert_root(self, method: str):
77
+ if self._root is not None:
78
+ raise IllegalStateError(f"{method}() is not supported on the builder of a linked entity (returned by "
79
+ f"link() or backlink()); call it on the query's builder (e.g. from box.query())")
80
+
81
+ def link(self, relation: Relation, condition: Optional[QueryCondition] = None) -> 'QueryBuilder':
82
+ """ Adds conditions on the target of a to-one relation of this builder's entity: matches objects whose
83
+ related object matches the condition. Returns the builder of the target entity, e.g. to link further.
84
+ Results are still objects of the query's entity.
85
+
86
+ Example (orders of customers named "Ann")::
87
+
88
+ qb = box.query(Order.text.starts_with("A"))
89
+ qb.link(Order.customer_id, Customer.name.equals("Ann"))
90
+ query = qb.build()
91
+
92
+ :param relation:
93
+ A :class:`Relation` property of this builder's entity.
94
+ :param condition:
95
+ The condition(s) on the target entity (optional, e.g. to only link further).
96
+ :return:
97
+ The builder of the target entity; call ``build()`` on the query's builder, not on this one.
98
+ """
99
+ self._assert_relation(relation)
100
+ if relation._entity._name != self._entity._name:
101
+ raise IllegalArgumentError(f"link(): relation {relation._entity._name}.{relation.name} is not a property "
102
+ f"of entity {self._entity._name}")
103
+ target = self._entity_by_name(relation._target_name)
104
+ return self._linked(obx_qb_link_property(self._c_builder, relation.id), target, condition)
105
+
106
+ def backlink(self, relation: Relation, condition: Optional[QueryCondition] = None) -> 'QueryBuilder':
107
+ """ Adds conditions on the objects whose to-one relation points to the objects of this builder's entity
108
+ (the relation used in reverse): matches objects referred to by at least one object that matches the condition.
109
+ Returns the builder of the relation's entity, e.g. to link further.
110
+ Results are still objects of the query's entity.
111
+
112
+ Example (customers with an order of more than 100)::
113
+
114
+ qb = customer_box.query()
115
+ qb.backlink(Order.customer_id, Order.total.greater_than(100))
116
+ query = qb.build()
117
+
118
+ :param relation:
119
+ A :class:`Relation` property of another entity (or of this one) targeting this builder's entity.
120
+ :param condition:
121
+ The condition(s) on the relation's entity (optional, e.g. to only link further).
122
+ :return:
123
+ The builder of the relation's entity; call ``build()`` on the query's builder, not on this one.
124
+ """
125
+ self._assert_relation(relation)
126
+ if relation._target_name != self._entity._name:
127
+ raise IllegalArgumentError(f"backlink(): relation {relation._entity._name}.{relation.name} targets entity "
128
+ f"{relation._target_name}, not {self._entity._name}")
129
+ source = self._entity_by_name(relation._entity._name)
130
+ c_builder = obx_qb_backlink_property(self._c_builder, source._id, relation.id)
131
+ return self._linked(c_builder, source, condition)
132
+
133
+ @staticmethod
134
+ def _assert_relation(relation):
135
+ if not isinstance(relation, Relation):
136
+ raise TypeError(f"Expected a Relation property, got {relation!r}")
137
+ relation._assert_ids_assigned()
138
+
139
+ def _entity_by_name(self, name: str) -> '_Entity':
140
+ """ The entity of the store's model (the entity of a relation may be an equal one of another model). """
141
+ model = self._box._store._model
142
+ if model is not None:
143
+ for entity in model.entities:
144
+ if entity._name == name:
145
+ return entity
146
+ raise IllegalArgumentError(f"Entity {name} is not in the store's model")
147
+
148
+ def close(self):
149
+ """ Closes the builder, freeing its native resources; ``build()`` does this, so only needed for a builder
150
+ that is not built. Closing again does nothing. """
151
+ self._assert_root("close")
152
+ c_builder = self._c_builder_or_none
153
+ if c_builder is not None:
154
+ self._c_builder_or_none = None
155
+ self._box._store._remove_native_resource(self)
156
+ obx_qb_close(c_builder)
157
+
158
+ def __del__(self):
159
+ if getattr(self, "_root", True) is None: # A root builder (a linked one is owned by it)
160
+ try:
161
+ self.close()
162
+ except Exception: # E.g. during interpreter shutdown
163
+ pass
164
+
165
+ def error_code(self) -> int:
166
+ return obx_qb_error_code(self._c_builder)
167
+
168
+ def error_message(self) -> str:
169
+ return obx_qb_error_message(self._c_builder)
170
+
171
+ def equals_string(self, prop: Union[int, str, Property], value: str, case_sensitive: bool = True) -> obx_qb_cond:
172
+ prop_id = self._condition_property_id(prop)
173
+ cond = obx_qb_equals_string(self._c_builder, prop_id, c_str(value), case_sensitive)
174
+ return cond
175
+
176
+ def not_equals_string(self, prop: Union[int, str, Property], value: str,
177
+ case_sensitive: bool = True) -> obx_qb_cond:
178
+ prop_id = self._condition_property_id(prop)
179
+ cond = obx_qb_not_equals_string(self._c_builder, prop_id, c_str(value), case_sensitive)
180
+ return cond
181
+
182
+ def contains_string(self, prop: Union[int, str, Property], value: str, case_sensitive: bool = True) -> obx_qb_cond:
183
+ """Checks if value is contained by the given string property."""
184
+ prop_id = self._condition_property_id(prop)
185
+ cond = obx_qb_contains_string(self._c_builder, prop_id, c_str(value), case_sensitive)
186
+ return cond
187
+
188
+ def starts_with_string(self, prop: Union[int, str, Property], value: str,
189
+ case_sensitive: bool = True) -> obx_qb_cond:
190
+ prop_id = self._condition_property_id(prop)
191
+ cond = obx_qb_starts_with_string(self._c_builder, prop_id, c_str(value), case_sensitive)
192
+ return cond
193
+
194
+ def ends_with_string(self, prop: Union[int, str, Property], value: str, case_sensitive: bool = True) -> obx_qb_cond:
195
+ prop_id = self._condition_property_id(prop)
196
+ cond = obx_qb_ends_with_string(self._c_builder, prop_id, c_str(value), case_sensitive)
197
+ return cond
198
+
199
+ def greater_than_string(self, prop: Union[int, str, Property], value: str,
200
+ case_sensitive: bool = True) -> obx_qb_cond:
201
+ prop_id = self._condition_property_id(prop)
202
+ cond = obx_qb_greater_than_string(self._c_builder, prop_id, c_str(value), case_sensitive)
203
+ return cond
204
+
205
+ def greater_or_equal_string(self, prop: Union[int, str, Property], value: str,
206
+ case_sensitive: bool = True) -> obx_qb_cond:
207
+ prop_id = self._condition_property_id(prop)
208
+ cond = obx_qb_greater_or_equal_string(self._c_builder, prop_id, c_str(value), case_sensitive)
209
+ return cond
210
+
211
+ def less_than_string(self, prop: Union[int, str, Property], value: str, case_sensitive: bool = True) -> obx_qb_cond:
212
+ prop_id = self._condition_property_id(prop)
213
+ cond = obx_qb_less_than_string(self._c_builder, prop_id, c_str(value), case_sensitive)
214
+ return cond
215
+
216
+ def less_or_equal_string(self, prop: Union[int, str, Property], value: str,
217
+ case_sensitive: bool = True) -> obx_qb_cond:
218
+ prop_id = self._condition_property_id(prop)
219
+ cond = obx_qb_less_or_equal_string(self._c_builder, prop_id, c_str(value), case_sensitive)
220
+ return cond
221
+
222
+ def in_strings(self, prop: Union[int, str, Property], values: List[str],
223
+ case_sensitive: bool = True) -> obx_qb_cond:
224
+ """ Matches if the string property equals any of the given values. """
225
+ prop_id = self._condition_property_id(prop)
226
+ # The C array references the encoded strings, so they stay alive for the duration of the call
227
+ c_values = c_array([c_str(value) for value in values], ctypes.c_char_p)
228
+ cond = obx_qb_in_strings(self._c_builder, prop_id, c_values, len(values), case_sensitive)
229
+ return cond
230
+
231
+ def equals_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
232
+ prop_id = self._condition_property_id(prop)
233
+ cond = obx_qb_equals_int(self._c_builder, prop_id, value)
234
+ return cond
235
+
236
+ def not_equals_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
237
+ prop_id = self._condition_property_id(prop)
238
+ cond = obx_qb_not_equals_int(self._c_builder, prop_id, value)
239
+ return cond
240
+
241
+ def in_ints(self, prop: Union[int, str, Property], values: List[int]) -> obx_qb_cond:
242
+ """ Matches if the integer property equals any of the given values.
243
+ Supported for 32-bit (Int32) and 64-bit (Int64, Id, Date, DateNano) integer properties. """
244
+ prop_id = self._condition_property_id(prop)
245
+ if self._is_int32_property(prop_id):
246
+ c_values = c_array([int(value) for value in values], ctypes.c_int32)
247
+ return obx_qb_in_int32s(self._c_builder, prop_id, c_values, len(values))
248
+ else:
249
+ c_values = c_array([int(value) for value in values], ctypes.c_int64)
250
+ return obx_qb_in_int64s(self._c_builder, prop_id, c_values, len(values))
251
+
252
+ def not_in_ints(self, prop: Union[int, str, Property], values: List[int]) -> obx_qb_cond:
253
+ """ Matches if the integer property equals none of the given values.
254
+ Supported for 32-bit (Int32) and 64-bit (Int64, Id, Date, DateNano) integer properties. """
255
+ prop_id = self._condition_property_id(prop)
256
+ if self._is_int32_property(prop_id):
257
+ c_values = c_array([int(value) for value in values], ctypes.c_int32)
258
+ return obx_qb_not_in_int32s(self._c_builder, prop_id, c_values, len(values))
259
+ else:
260
+ c_values = c_array([int(value) for value in values], ctypes.c_int64)
261
+ return obx_qb_not_in_int64s(self._c_builder, prop_id, c_values, len(values))
262
+
263
+ def _is_int32_property(self, prop_id: int) -> bool:
264
+ """ The C API has separate functions for 32-bit and 64-bit integer properties (e.g. for "in" conditions). """
265
+ for prop in self._entity._properties:
266
+ if prop.id == prop_id:
267
+ return prop._ob_type == OBXPropertyType_Int
268
+ raise IllegalArgumentError(f"Property ID {prop_id} not found in Entity \"{self._entity._name}\"")
269
+
270
+ def greater_than_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
271
+ prop_id = self._condition_property_id(prop)
272
+ cond = obx_qb_greater_than_int(self._c_builder, prop_id, value)
273
+ return cond
274
+
275
+ def greater_than_double(self, prop: Union[int, str, Property], value: float) -> obx_qb_cond:
276
+ prop_id = self._condition_property_id(prop)
277
+ cond = obx_qb_greater_than_double(self._c_builder, prop_id, value)
278
+ return cond
279
+
280
+ def greater_or_equal_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
281
+ prop_id = self._condition_property_id(prop)
282
+ cond = obx_qb_greater_or_equal_int(self._c_builder, prop_id, value)
283
+ return cond
284
+
285
+ def greater_or_equal_double(self, prop: Union[int, str, Property], value: float) -> obx_qb_cond:
286
+ prop_id = self._condition_property_id(prop)
287
+ cond = obx_qb_greater_or_equal_double(self._c_builder, prop_id, value)
288
+ return cond
289
+
290
+ def less_than_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
291
+ prop_id = self._condition_property_id(prop)
292
+ cond = obx_qb_less_than_int(self._c_builder, prop_id, value)
293
+ return cond
294
+
295
+ def less_than_double(self, prop: Union[int, str, Property], value: float) -> obx_qb_cond:
296
+ prop_id = self._condition_property_id(prop)
297
+ cond = obx_qb_less_than_double(self._c_builder, prop_id, value)
298
+ return cond
299
+
300
+ def less_or_equal_int(self, prop: Union[int, str, Property], value: int) -> obx_qb_cond:
301
+ prop_id = self._condition_property_id(prop)
302
+ cond = obx_qb_less_or_equal_int(self._c_builder, prop_id, value)
303
+ return cond
304
+
305
+ def less_or_equal_double(self, prop: Union[int, str, Property], value: float) -> obx_qb_cond:
306
+ prop_id = self._condition_property_id(prop)
307
+ cond = obx_qb_less_or_equal_double(self._c_builder, prop_id, value)
308
+ return cond
309
+
310
+ def between_2ints(self, prop: Union[int, str, Property], value_a: int, value_b: int) -> obx_qb_cond:
311
+ prop_id = self._condition_property_id(prop)
312
+ cond = obx_qb_between_2ints(self._c_builder, prop_id, value_a, value_b)
313
+ return cond
314
+
315
+ def between_2doubles(self, prop: Union[int, str, Property], value_a: float, value_b: float) -> obx_qb_cond:
316
+ prop_id = self._condition_property_id(prop)
317
+ cond = obx_qb_between_2doubles(self._c_builder, prop_id, value_a, value_b)
318
+ return cond
319
+
320
+ def equals_bytes(self, prop: Union[int, str, Property], value: bytes) -> obx_qb_cond:
321
+ prop_id = self._condition_property_id(prop)
322
+ cond = obx_qb_equals_bytes(self._c_builder, prop_id, value, len(value))
323
+ return cond
324
+
325
+ def greater_than_bytes(self, prop: Union[int, str, Property], value: bytes) -> obx_qb_cond:
326
+ prop_id = self._condition_property_id(prop)
327
+ cond = obx_qb_greater_than_bytes(self._c_builder, prop_id, value, len(value))
328
+ return cond
329
+
330
+ def greater_or_equal_bytes(self, prop: Union[int, str, Property], value: bytes) -> obx_qb_cond:
331
+ prop_id = self._condition_property_id(prop)
332
+ cond = obx_qb_greater_or_equal_bytes(self._c_builder, prop_id, value, len(value))
333
+ return cond
334
+
335
+ def less_than_bytes(self, prop: Union[int, str, Property], value: bytes) -> obx_qb_cond:
336
+ prop_id = self._condition_property_id(prop)
337
+ cond = obx_qb_less_than_bytes(self._c_builder, prop_id, value, len(value))
338
+ return cond
339
+
340
+ def less_or_equal_bytes(self, prop: Union[int, str, Property], value: bytes) -> obx_qb_cond:
341
+ prop_id = self._condition_property_id(prop)
342
+ cond = obx_qb_less_or_equal_bytes(self._c_builder, prop_id, value, len(value))
343
+ return cond
344
+
345
+ def nearest_neighbors_f32(self,
346
+ prop: Union[int, str, Property],
347
+ query_vector: Union[np.ndarray, List[float]],
348
+ element_count: int):
349
+ check_float_vector(query_vector, "query_vector")
350
+ prop_id = self._condition_property_id(prop)
351
+ c_query_vector = c_array(query_vector, ctypes.c_float)
352
+ cond = obx_qb_nearest_neighbors_f32(self._c_builder, prop_id, c_query_vector, element_count)
353
+ return cond
354
+
355
+ def is_null(self, prop: Union[int, str, Property]) -> obx_qb_cond:
356
+ """ Matches objects without a value for the given property (null, i.e. put with None). """
357
+ return obx_qb_null(self._c_builder, self._condition_property_id(prop))
358
+
359
+ def not_null(self, prop: Union[int, str, Property]) -> obx_qb_cond:
360
+ """ Matches objects with a value for the given property (not null). """
361
+ return obx_qb_not_null(self._c_builder, self._condition_property_id(prop))
362
+
363
+ def contains_key_value(self, prop: Union[int, str, Property], key: str, value: str,
364
+ case_sensitive: bool = True) -> obx_qb_cond:
365
+ """ Checks whether the given Flex property, interpreted as a dictionary and indexed at key, has a value
366
+ corresponding to the given value.
367
+
368
+ :param case_sensitive:
369
+ If false, ignore case when matching value
370
+ """
371
+ prop_id = self._condition_property_id(prop)
372
+ cond = obx_qb_contains_key_value_string(self._c_builder, prop_id, c_str(key), c_str(value), case_sensitive)
373
+ return cond
374
+
375
+ def _condition_property_id(self, prop: Union[int, str, Property]) -> int:
376
+ """ The ID of the property of a new condition, remembered for alias(). """
377
+ prop_id = self._entity._get_property_id(prop)
378
+ self._last_condition_prop_id = prop_id
379
+ return prop_id
380
+
381
+ def any(self, conditions: List[obx_qb_cond]) -> obx_qb_cond:
382
+ self._last_condition_prop_id = None # A group: alias() has no property
383
+ c_conditions = c_array(conditions, obx_qb_cond)
384
+ cond = obx_qb_any(self._c_builder, c_conditions, len(conditions))
385
+ return cond
386
+
387
+ def all(self, conditions: List[obx_qb_cond]) -> obx_qb_cond:
388
+ self._last_condition_prop_id = None # A group: alias() has no property
389
+ c_conditions = c_array_pointer(conditions, obx_qb_cond)
390
+ cond = obx_qb_all(self._c_builder, c_conditions, len(conditions))
391
+ return cond
392
+
393
+ def order(self, prop: Union[int, str, Property], flags: Union[OrderFlags, int] = 0) -> 'QueryBuilder':
394
+ """ Orders the query results by the given property, ascending by default.
395
+ Call multiple times to order by multiple properties: the first call sets the primary order,
396
+ the following ones are used for results that are equal according to the preceding orders.
397
+ Applies to ``find()`` and ``find_ids()``; note that ordering requires to sort all matching objects.
398
+
399
+ Example: ``box.query(Person.age.greater_than(17)).order(Person.name).build()``
400
+
401
+ :param prop:
402
+ The property (or its name or ID) to order by.
403
+ :param flags:
404
+ :class:`OrderFlags` to adjust the order; combine multiple using ``|``,
405
+ e.g. ``OrderFlags.DESCENDING | OrderFlags.CASE_SENSITIVE``.
406
+ :return:
407
+ This QueryBuilder (to chain calls).
408
+ """
409
+ self._assert_root("order") # The core ignores orders of linked entities
410
+ prop_id = self._entity._get_property_id(prop) # Not a condition (alias() does not apply)
411
+ obx_qb_order(self._c_builder, prop_id, flags)
412
+ return self
413
+
414
+ def order_desc(self, prop: Union[int, str, Property], flags: Union[OrderFlags, int] = 0) -> 'QueryBuilder':
415
+ """ Orders the query results by the given property, descending;
416
+ the same as ``order(prop, OrderFlags.DESCENDING | flags)``, see :meth:`order`.
417
+
418
+ Example: ``box.query().order_desc(Person.age).build()``
419
+
420
+ :param prop:
421
+ The property (or its name or ID) to order by.
422
+ :param flags:
423
+ Further :class:`OrderFlags` to adjust the order, e.g. ``OrderFlags.CASE_SENSITIVE``.
424
+ :return:
425
+ This QueryBuilder (to chain calls).
426
+ """
427
+ return self.order(prop, OrderFlags.DESCENDING | flags)
428
+
429
+ def build(self) -> Query:
430
+ """ Builds the query; this closes the builder (it can not be used afterwards). """
431
+ self._assert_root("build")
432
+ try:
433
+ c_query = obx_query(self._c_builder)
434
+ finally:
435
+ self.close()
436
+ return Query(c_query, self._box, self._alias_properties)
437
+
438
+ def alias(self, alias: str):
439
+ """ Sets an alias for the last condition added to this builder, to change its value(s) on the built query via
440
+ ``Query.set_parameter_alias()``; that then knows the condition's property (and so how to set the value).
441
+ Conditions passed to ``box.query()`` set their aliases via ``PropertyQueryCondition.alias()``. """
442
+ obx_qb_param_alias(self._c_builder, c_str(alias)) # Raises if the last condition has no property (a group)
443
+ if self._last_condition_prop_id is not None:
444
+ self._alias_properties[alias] = (self._entity, self._last_condition_prop_id)
445
+ return self
446
+
447
+ def _alias_for_property(self, alias: str, prop_id: int):
448
+ """ Like alias(), for the condition of the given property (for Query.set_parameter_alias()). """
449
+ self._last_condition_prop_id = prop_id
450
+ return self.alias(alias)