Skip to content

Commit e51e629

Browse files
loryrutadan-obx
authored andcommitted
query_condition: add LogicQueryCondition, rename QueryCondition to PropertyQueryCondition #39
1 parent 2f6d8ac commit e51e629

2 files changed

Lines changed: 133 additions & 77 deletions

File tree

objectbox/condition.py

Lines changed: 105 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,62 @@
1+
from __future__ import annotations
2+
13
from enum import Enum
24
from typing import *
35
import numpy as np
46

7+
if TYPE_CHECKING:
8+
from objectbox.c import obx_qb_cond
9+
from objectbox.query_builder import QueryBuilder
10+
11+
12+
class QueryCondition:
13+
def and_(self, other: QueryCondition) -> QueryCondition:
14+
return LogicQueryCondition(self, other, LogicQueryConditionOp.AND)
15+
16+
def or_(self, other: QueryCondition) -> QueryCondition:
17+
return LogicQueryCondition(self, other, LogicQueryConditionOp.OR)
18+
19+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
20+
""" Applies the QueryCondition to the supplied QueryBuilder.
21+
22+
:return:
23+
The C handle for the applied condition.
24+
"""
25+
raise NotImplementedError
26+
27+
28+
class LogicQueryConditionOp(Enum):
29+
AND = 1
30+
OR = 2
31+
32+
33+
class LogicQueryCondition(QueryCondition):
34+
""" A QueryCondition describing a logical operation between two inner QueryCondition's (e.g. AND/OR). """
535

6-
class _QueryConditionOp(Enum):
36+
def __init__(self, cond1: QueryCondition, cond2: QueryCondition, op: LogicQueryConditionOp):
37+
self._cond1 = cond1
38+
self._cond2 = cond2
39+
self._op = op
40+
41+
def _apply_conditions(self, qb: QueryBuilder) -> List[obx_qb_cond]:
42+
return [self._cond1.apply(qb), self._cond2.apply(qb)]
43+
44+
def _apply_and(self, qb: QueryBuilder) -> obx_qb_cond:
45+
return qb.all(self._apply_conditions(qb))
46+
47+
def _apply_or(self, qb: QueryBuilder) -> obx_qb_cond:
48+
return qb.any(self._apply_conditions(qb))
49+
50+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
51+
if self._op == LogicQueryConditionOp.AND:
52+
return self._apply_and(qb)
53+
elif self._op == LogicQueryConditionOp.OR:
54+
return self._apply_or(qb)
55+
else:
56+
raise Exception(f"Unknown LogicQueryCondition op: {self._op.name}")
57+
58+
59+
class PropertyQueryConditionOp(Enum):
760
EQ = 1
861
NOT_EQ = 2
962
CONTAINS = 3
@@ -18,8 +71,10 @@ class _QueryConditionOp(Enum):
1871
CONTAINS_KEY_VALUE = 12
1972

2073

21-
class QueryCondition:
22-
def __init__(self, property_id: int, op: _QueryConditionOp, args: Dict[str, Any]):
74+
class PropertyQueryCondition(QueryCondition):
75+
""" A QueryCondition describing an operation to be applied on a property (e.g. name == "John", age == 24) """
76+
77+
def __init__(self, property_id: int, op: PropertyQueryConditionOp, args: Dict[str, Any]):
2378
if op not in self._get_op_map():
2479
raise Exception(f"Invalid query condition op with ID: {op}")
2580

@@ -29,119 +84,120 @@ def __init__(self, property_id: int, op: _QueryConditionOp, args: Dict[str, Any]
2984
self._alias = None
3085

3186
def alias(self, value: str):
87+
""" Sets an alias for this condition that can later be used with Query's set_parameter_* methods. """
3288
self._alias = value
3389
return self
3490

3591
def _get_op_map(self):
3692
return {
37-
_QueryConditionOp.EQ: self._apply_eq,
38-
_QueryConditionOp.NOT_EQ: self._apply_not_eq,
39-
_QueryConditionOp.CONTAINS: self._apply_contains,
40-
_QueryConditionOp.STARTS_WITH: self._apply_starts_with,
41-
_QueryConditionOp.ENDS_WITH: self._apply_ends_with,
42-
_QueryConditionOp.GT: self._apply_gt,
43-
_QueryConditionOp.GTE: self._apply_gte,
44-
_QueryConditionOp.LT: self._apply_lt,
45-
_QueryConditionOp.LTE: self._apply_lte,
46-
_QueryConditionOp.BETWEEN: self._apply_between,
47-
_QueryConditionOp.NEAREST_NEIGHBOR: self._apply_nearest_neighbor,
48-
_QueryConditionOp.CONTAINS_KEY_VALUE: self._contains_key_value
49-
# ... new query condition here ... :)
93+
PropertyQueryConditionOp.EQ: self._apply_eq,
94+
PropertyQueryConditionOp.NOT_EQ: self._apply_not_eq,
95+
PropertyQueryConditionOp.CONTAINS: self._apply_contains,
96+
PropertyQueryConditionOp.STARTS_WITH: self._apply_starts_with,
97+
PropertyQueryConditionOp.ENDS_WITH: self._apply_ends_with,
98+
PropertyQueryConditionOp.GT: self._apply_gt,
99+
PropertyQueryConditionOp.GTE: self._apply_gte,
100+
PropertyQueryConditionOp.LT: self._apply_lt,
101+
PropertyQueryConditionOp.LTE: self._apply_lte,
102+
PropertyQueryConditionOp.BETWEEN: self._apply_between,
103+
PropertyQueryConditionOp.NEAREST_NEIGHBOR: self._apply_nearest_neighbor,
104+
PropertyQueryConditionOp.CONTAINS_KEY_VALUE: self._contains_key_value
105+
# ... new property query condition here ... :)
50106
}
51107

52-
def _apply_eq(self, qb: 'QueryBuilder'):
108+
def _apply_eq(self, qb: QueryBuilder) -> obx_qb_cond:
53109
value = self._args['value']
54110
case_sensitive = self._args['case_sensitive']
55111
if isinstance(value, str):
56-
qb.equals_string(self._property_id, value, case_sensitive)
112+
return qb.equals_string(self._property_id, value, case_sensitive)
57113
elif isinstance(value, int):
58-
qb.equals_int(self._property_id, value)
114+
return qb.equals_int(self._property_id, value)
59115
else:
60116
raise Exception(f"Unsupported type for 'EQ': {type(value)}")
61117

62-
def _apply_not_eq(self, qb: 'QueryBuilder'):
118+
def _apply_not_eq(self, qb: QueryBuilder) -> obx_qb_cond:
63119
value = self._args['value']
64120
case_sensitive = self._args['case_sensitive']
65121
if isinstance(value, str):
66-
qb.not_equals_string(self._property_id, value, case_sensitive)
122+
return qb.not_equals_string(self._property_id, value, case_sensitive)
67123
elif isinstance(value, int):
68-
qb.not_equals_int(self._property_id, value)
124+
return qb.not_equals_int(self._property_id, value)
69125
else:
70126
raise Exception(f"Unsupported type for 'NOT_EQ': {type(value)}")
71127

72-
def _apply_contains(self, qb: 'QueryBuilder'):
128+
def _apply_contains(self, qb: QueryBuilder) -> obx_qb_cond:
73129
value = self._args['value']
74130
case_sensitive = self._args['case_sensitive']
75131
if isinstance(value, str):
76-
qb.contains_string(self._property_id, value, case_sensitive)
132+
return qb.contains_string(self._property_id, value, case_sensitive)
77133
else:
78134
raise Exception(f"Unsupported type for 'CONTAINS': {type(value)}")
79135

80-
def _apply_starts_with(self, qb: 'QueryBuilder'):
136+
def _apply_starts_with(self, qb: QueryBuilder) -> obx_qb_cond:
81137
value = self._args['value']
82138
case_sensitive = self._args['case_sensitive']
83139
if isinstance(value, str):
84-
qb.starts_with_string(self._property_id, value, case_sensitive)
140+
return qb.starts_with_string(self._property_id, value, case_sensitive)
85141
else:
86142
raise Exception(f"Unsupported type for 'STARTS_WITH': {type(value)}")
87143

88-
def _apply_ends_with(self, qb: 'QueryBuilder'):
144+
def _apply_ends_with(self, qb: QueryBuilder) -> obx_qb_cond:
89145
value = self._args['value']
90146
case_sensitive = self._args['case_sensitive']
91147
if isinstance(value, str):
92-
qb.ends_with_string(self._property_id, value, case_sensitive)
148+
return qb.ends_with_string(self._property_id, value, case_sensitive)
93149
else:
94150
raise Exception(f"Unsupported type for 'ENDS_WITH': {type(value)}")
95151

96-
def _apply_gt(self, qb: 'QueryBuilder'):
152+
def _apply_gt(self, qb: QueryBuilder) -> obx_qb_cond:
97153
value = self._args['value']
98154
case_sensitive = self._args['case_sensitive']
99155
if isinstance(value, str):
100-
qb.greater_than_string(self._property_id, value, case_sensitive)
156+
return qb.greater_than_string(self._property_id, value, case_sensitive)
101157
elif isinstance(value, int):
102-
qb.greater_than_int(self._property_id, value)
158+
return qb.greater_than_int(self._property_id, value)
103159
else:
104160
raise Exception(f"Unsupported type for 'GT': {type(value)}")
105161

106-
def _apply_gte(self, qb: 'QueryBuilder'):
162+
def _apply_gte(self, qb: QueryBuilder) -> obx_qb_cond:
107163
value = self._args['value']
108164
case_sensitive = self._args['case_sensitive']
109165
if isinstance(value, str):
110-
qb.greater_or_equal_string(self._property_id, value, case_sensitive)
166+
return qb.greater_or_equal_string(self._property_id, value, case_sensitive)
111167
elif isinstance(value, int):
112-
qb.greater_or_equal_int(self._property_id, value)
168+
return qb.greater_or_equal_int(self._property_id, value)
113169
else:
114170
raise Exception(f"Unsupported type for 'GTE': {type(value)}")
115171

116-
def _apply_lt(self, qb: 'QueryCondition'):
172+
def _apply_lt(self, qb: QueryBuilder) -> obx_qb_cond:
117173
value = self._args['value']
118174
case_sensitive = self._args['case_sensitive']
119175
if isinstance(value, str):
120-
qb.less_than_string(self._property_id, value, case_sensitive)
176+
return qb.less_than_string(self._property_id, value, case_sensitive)
121177
elif isinstance(value, int):
122-
qb.less_than_int(self._property_id, value)
178+
return qb.less_than_int(self._property_id, value)
123179
else:
124180
raise Exception("Unsupported type for 'LT': " + str(type(value)))
125181

126-
def _apply_lte(self, qb: 'QueryBuilder'):
182+
def _apply_lte(self, qb: QueryBuilder) -> obx_qb_cond:
127183
value = self._args['value']
128184
case_sensitive = self._args['case_sensitive']
129185
if isinstance(value, str):
130-
qb.less_or_equal_string(self._property_id, value, case_sensitive)
186+
return qb.less_or_equal_string(self._property_id, value, case_sensitive)
131187
elif isinstance(value, int):
132-
qb.less_or_equal_int(self._property_id, value)
188+
return qb.less_or_equal_int(self._property_id, value)
133189
else:
134190
raise Exception(f"Unsupported type for 'LTE': {type(value)}")
135191

136-
def _apply_between(self, qb: 'QueryBuilder'):
192+
def _apply_between(self, qb: QueryBuilder) -> obx_qb_cond:
137193
a = self._args['a']
138194
b = self._args['b']
139195
if isinstance(a, int):
140-
qb.between_2ints(self._property_id, a, b)
196+
return qb.between_2ints(self._property_id, a, b)
141197
else:
142198
raise Exception(f"Unsupported type for 'BETWEEN': {type(a)}")
143199

144-
def _apply_nearest_neighbor(self, qb: 'QueryBuilder'):
200+
def _apply_nearest_neighbor(self, qb: QueryBuilder) -> obx_qb_cond:
145201
query_vector = self._args['query_vector']
146202
element_count = self._args['element_count']
147203

@@ -152,19 +208,18 @@ def _apply_nearest_neighbor(self, qb: 'QueryBuilder'):
152208
is_float_vector |= isinstance(query_vector, np.ndarray) and query_vector.dtype == np.float32
153209
is_float_vector |= isinstance(query_vector, list) and type(query_vector[0]) == float
154210
if is_float_vector:
155-
qb.nearest_neighbors_f32(self._property_id, query_vector, element_count)
211+
return qb.nearest_neighbors_f32(self._property_id, query_vector, element_count)
156212
else:
157213
raise Exception(f"Unsupported type for 'NEAREST_NEIGHBOR': {type(query_vector)}")
158214

159-
def _contains_key_value(self, qb: 'QueryBuilder'):
215+
def _contains_key_value(self, qb: QueryBuilder) -> obx_qb_cond:
160216
key = self._args['key']
161217
value = self._args['value']
162218
case_sensitive = self._args['case_sensitive']
163-
qb.contains_key_value(self._property_id, key, value, case_sensitive)
164-
165-
def apply(self, qb: 'QueryBuilder'):
166-
""" Applies the stored condition to the supplied query builder. """
167-
self._get_op_map()[self._op](qb)
219+
return qb.contains_key_value(self._property_id, key, value, case_sensitive)
168220

221+
def apply(self, qb: QueryBuilder) -> obx_qb_cond:
222+
c_cond = self._get_op_map()[self._op](qb)
169223
if self._alias is not None:
170224
qb.alias(self._alias)
225+
return c_cond

objectbox/model/properties.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
# limitations under the License.
1414

1515
from enum import IntEnum
16-
17-
from objectbox.condition import QueryCondition, _QueryConditionOp
18-
from objectbox.c import *
1916
import flatbuffers.number_types
2017
import numpy as np
2118
from dataclasses import dataclass
2219

20+
from objectbox.c import *
21+
from objectbox.condition import PropertyQueryCondition, PropertyQueryConditionOp
22+
23+
2324
class PropertyType(IntEnum):
2425
bool = OBXPropertyType_Bool
2526
byte = OBXPropertyType_Byte
@@ -182,53 +183,53 @@ def _set_flags(self):
182183
if isinstance(self._index, Index): # Generic index
183184
self._flags |= self._index.type
184185

185-
def equals(self, value, case_sensitive: bool = True) -> QueryCondition:
186+
def equals(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
186187
args = {'value': value, 'case_sensitive': case_sensitive}
187-
return QueryCondition(self._id, _QueryConditionOp.EQ, args)
188+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.EQ, args)
188189

189-
def not_equals(self, value, case_sensitive: bool = True) -> QueryCondition:
190+
def not_equals(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
190191
args = {'value': value, 'case_sensitive': case_sensitive}
191-
return QueryCondition(self._id, _QueryConditionOp.NOT_EQ, args)
192+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.NOT_EQ, args)
192193

193-
def contains(self, value: str, case_sensitive: bool = True) -> QueryCondition:
194+
def contains(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
194195
args = {'value': value, 'case_sensitive': case_sensitive}
195-
return QueryCondition(self._id, _QueryConditionOp.CONTAINS, args)
196+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.CONTAINS, args)
196197

197-
def starts_with(self, value: str, case_sensitive: bool = True) -> QueryCondition:
198+
def starts_with(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
198199
args = {'value': value, 'case_sensitive': case_sensitive}
199-
return QueryCondition(self._id, _QueryConditionOp.STARTS_WITH, args)
200+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.STARTS_WITH, args)
200201

201-
def ends_with(self, value: str, case_sensitive: bool = True) -> QueryCondition:
202+
def ends_with(self, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
202203
args = {'value': value, 'case_sensitive': case_sensitive}
203-
return QueryCondition(self._id, _QueryConditionOp.ENDS_WITH, args)
204+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.ENDS_WITH, args)
204205

205-
def greater_than(self, value, case_sensitive: bool = True) -> QueryCondition:
206+
def greater_than(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
206207
args = {'value': value, 'case_sensitive': case_sensitive}
207-
return QueryCondition(self._id, _QueryConditionOp.GT, args)
208+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.GT, args)
208209

209-
def greater_or_equal(self, value, case_sensitive: bool = True) -> QueryCondition:
210+
def greater_or_equal(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
210211
args = {'value': value, 'case_sensitive': case_sensitive}
211-
return QueryCondition(self._id, _QueryConditionOp.GTE, args)
212+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.GTE, args)
212213

213-
def less_than(self, value, case_sensitive: bool = True) -> QueryCondition:
214+
def less_than(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
214215
args = {'value': value, 'case_sensitive': case_sensitive}
215-
return QueryCondition(self._id, _QueryConditionOp.LT, args)
216+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.LT, args)
216217

217-
def less_or_equal(self, value, case_sensitive: bool = True) -> QueryCondition:
218+
def less_or_equal(self, value, case_sensitive: bool = True) -> PropertyQueryCondition:
218219
args = {'value': value, 'case_sensitive': case_sensitive}
219-
return QueryCondition(self._id, _QueryConditionOp.LTE, args)
220+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.LTE, args)
220221

221-
def between(self, a, b) -> QueryCondition:
222+
def between(self, a, b) -> PropertyQueryCondition:
222223
args = {'a': a, 'b': b}
223-
return QueryCondition(self._id, _QueryConditionOp.BETWEEN, args)
224+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.BETWEEN, args)
224225

225-
def nearest_neighbor(self, query_vector, element_count: int) -> QueryCondition:
226+
def nearest_neighbor(self, query_vector, element_count: int) -> PropertyQueryCondition:
226227
args = {'query_vector': query_vector, 'element_count': element_count}
227-
return QueryCondition(self._id, _QueryConditionOp.NEAREST_NEIGHBOR, args)
228+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.NEAREST_NEIGHBOR, args)
228229

229-
def contains_key_value(self, key: str, value: str, case_sensitive: bool = True) -> QueryCondition:
230+
def contains_key_value(self, key: str, value: str, case_sensitive: bool = True) -> PropertyQueryCondition:
230231
args = {'key': key, 'value': value, 'case_sensitive': case_sensitive}
231-
return QueryCondition(self._id, _QueryConditionOp.CONTAINS_KEY_VALUE, args)
232+
return PropertyQueryCondition(self._id, PropertyQueryConditionOp.CONTAINS_KEY_VALUE, args)
232233

233234

234235
# ID property (primary key)

0 commit comments

Comments
 (0)