Repository navigation
Expand file tree
/
Copy pathtest_unique.py
More file actions
155 lines (120 loc) · 5.45 KB
/
Copy pathtest_unique.py
File metadata and controls
155 lines (120 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import json
import pytest
from objectbox import *
from objectbox.c import (OBXPropertyFlags_INDEXED, OBXPropertyFlags_INDEX_HASH, OBXPropertyFlags_UNIQUE,
OBXPropertyFlags_UNIQUE_ON_CONFLICT_REPLACE)
from objectbox.exceptions import UniqueViolatedError
def _define():
@Entity(model="unique")
class User:
id = Id
email = String(unique=Unique())
code = Int32(unique=Unique(ConflictStrategy.REPLACE))
name = String
return User
def _open(tmp_path, *entities) -> Store:
model = Model()
for entity in entities:
model.entity(entity)
return Store(model=model, directory=str(tmp_path / "db"), model_json_file=str(tmp_path / "model.json"))
def _json_flags(tmp_path):
with open(str(tmp_path / "model.json")) as file:
doc = json.load(file)
return {p["name"]: p.get("flags", 0) for p in doc["entities"][0]["properties"]}
def test_unique_fail(tmp_path):
User = _define()
store = _open(tmp_path, User)
box = store.box(User)
ann = User(email="ann@example.com", code=1, name="Ann")
box.put(ann)
box.put(User(email="bob@example.com", code=2, name="Bob"))
with pytest.raises(UniqueViolatedError, match="Unique constraint for User.email would be violated"):
box.put(User(email="ann@example.com", code=3, name="Other Ann"))
assert box.count() == 2
# Updating the object having the value is fine
ann.name = "Ann B."
box.put(ann)
assert box.get(ann.id).name == "Ann B."
# put(list): nothing is put if one object violates the constraint (also within the list)
with pytest.raises(UniqueViolatedError):
box.put([User(email="cy@example.com", code=4), User(email="cy@example.com", code=5)])
assert box.count() == 2
store.close()
flags = _json_flags(tmp_path)
assert flags["email"] == OBXPropertyFlags_UNIQUE | OBXPropertyFlags_INDEXED | OBXPropertyFlags_INDEX_HASH
assert flags["code"] == (OBXPropertyFlags_UNIQUE | OBXPropertyFlags_UNIQUE_ON_CONFLICT_REPLACE |
OBXPropertyFlags_INDEXED)
assert flags["name"] == 0
def test_unique_replace(tmp_path):
User = _define()
store = _open(tmp_path, User)
box = store.box(User)
old_id = box.put(User(email="ann@example.com", code=1, name="Ann"))
other_id = box.put(User(email="bob@example.com", code=2, name="Bob"))
new_id = box.put(User(email="ann2@example.com", code=1, name="New Ann")) # Replaces the object with code 1
assert new_id != old_id
assert box.get(old_id) is None
assert [(o.id, o.name) for o in box.get_all()] == [(other_id, "Bob"), (new_id, "New Ann")]
store.close()
def test_unique_args():
# The class itself works like an instance (as for Index); unique implies an index (of the default type)
prop = String(unique=Unique)
assert isinstance(prop.unique, Unique) and prop.unique.on_conflict == ConflictStrategy.FAIL
assert isinstance(prop.index, Index)
assert prop._flags == OBXPropertyFlags_UNIQUE | OBXPropertyFlags_INDEXED | OBXPropertyFlags_INDEX_HASH
# The index type can be given
prop = String(unique=Unique(), index=Index(IndexType.VALUE))
assert prop._flags == OBXPropertyFlags_UNIQUE | OBXPropertyFlags_INDEXED
for invalid in (True, ConflictStrategy.REPLACE, "unique"):
with pytest.raises(TypeError, match="unique must be a Unique"):
String(unique=invalid)
with pytest.raises(TypeError, match="A unique property can not have a HnswIndex"):
Float32Vector(unique=Unique(), index=HnswIndex(dimensions=2))
with pytest.raises(TypeError, match="Unexpected property argument.*: unique"):
Id(unique=Unique()) # IDs are unique anyway
def test_unique_replace_only_one(tmp_path):
@Entity(model="unique")
class User:
id = Id
email = String(unique=Unique(ConflictStrategy.REPLACE))
code = Int32(unique=Unique(ConflictStrategy.REPLACE))
with pytest.raises(ValueError, match="Entity User: ConflictStrategy.REPLACE can only be used for one property, "
"but is used for email, code"):
_open(tmp_path, User)
def test_unique_sync_entity_requires_replace(tmp_path):
@SyncEntity()
@Entity(model="unique")
class User:
id = Id
email = String(unique=Unique())
with pytest.raises(ValueError, match=r"Sync entity User: unique properties must use "
r"Unique\(ConflictStrategy.REPLACE\), but email do\(es\) not"):
_open(tmp_path, User)
@SyncEntity()
@Entity(model="unique")
class User:
id = Id
email = String(unique=Unique(ConflictStrategy.REPLACE))
from objectbox.model.idsync import sync_model
model = Model()
model.entity(User)
sync_model(model, str(tmp_path / "model.json")) # Valid (opening needs the Sync library)
assert _json_flags(tmp_path)["email"] & OBXPropertyFlags_UNIQUE_ON_CONFLICT_REPLACE
def test_unique_added(tmp_path):
""" Unique can be added to an existing property (a flag change). """
@Entity(model="unique")
class User:
id = Id
email = String
store = _open(tmp_path, User)
store.box(User).put(User(email="ann@example.com"))
store.close()
@Entity(model="unique")
class User:
id = Id
email = String(unique=Unique())
store = _open(tmp_path, User)
box = store.box(User)
with pytest.raises(UniqueViolatedError):
box.put(User(email="ann@example.com"))
store.close()