Skip to content

Commit 15e8ef9

Browse files
committed
Fix idsync (support renaming) #25
1 parent 9ac131d commit 15e8ef9

1 file changed

Lines changed: 122 additions & 110 deletions

File tree

objectbox/model/idsync.py

Lines changed: 122 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,33 @@ def _save_model_json(self):
7676

7777
# *** Sync ***
7878

79+
def _find_entity_json_by_uid(self, uid: int) -> Optional[Dict[str, Any]]:
80+
""" Finds entity JSON by UID. """
81+
if self.model_json is None:
82+
return None
83+
for entity_json in self.model_json["entities"]:
84+
if IdUid.from_str(entity_json["id"]).uid == uid:
85+
return entity_json
86+
return None
87+
7988
def _find_entity_json_by_name(self, entity_name: str) -> Optional[Dict[str, Any]]:
80-
""" Finds the entity data by name in the model JSON file. """
89+
""" Finds entity JSON by name. """
8190
if self.model_json is None:
8291
return None
8392
for entity_json in self.model_json["entities"]:
8493
if entity_json["name"] == entity_name:
8594
return entity_json
8695
return None
8796

88-
def _find_property_json_by_name(self, entity_name: str, prop_name: str) -> Optional[Dict[str, Any]]:
89-
""" Finds the entity property data by name in the model JSON file. """
90-
entity_json = self._find_entity_json_by_name(entity_name)
91-
if entity_json is None:
92-
return None
97+
def _find_property_json_by_uid(self, entity_json: Dict[str, Any], uid: int) -> Optional[Dict[str, Any]]:
98+
""" Finds entity property JSON by property UID. """
99+
for prop_json in entity_json["properties"]:
100+
if IdUid.from_str(prop_json["id"]).uid == uid:
101+
return prop_json
102+
return None
103+
104+
def _find_property_json_by_name(self, entity_json: Dict[str, Any], prop_name: str) -> Optional[Dict[str, Any]]:
105+
""" Finds entity property JSON by property name. """
93106
for prop_json in entity_json["properties"]:
94107
if prop_json["name"] == prop_name:
95108
return prop_json
@@ -100,127 +113,126 @@ def _generate_uid() -> int:
100113
return random.getrandbits(63) + 1 # 0 would be invalid
101114

102115
def _validate_uid_unassigned(self, uid: int):
103-
""" Validates that the UID is not assigned for any other entity/property/index. """
104-
pass # TODO
116+
""" Validates that a user supplied UID is not assigned for any other entity/property/index.
117+
Raises a ValueError if the UID is already assigned elsewhere.
118+
"""
105119

106-
def _sync_index_id(self, entity: _Entity, prop: Property, index: Union[Index, HnswIndex]) -> None:
107-
""" Given an index, syncs its ID/UID with the JSON file. """
120+
try:
121+
entity_json = self._find_entity_json_by_uid(uid)
122+
if entity_json is not None:
123+
raise ValueError(f"in Entity \"{entity_json['name']}\" ({entity_json['id']})")
124+
125+
for entity_json in self.model_json["entities"]:
126+
prop_json = self._find_property_json_by_uid(entity_json, uid)
127+
if prop_json is not None:
128+
raise ValueError(f"in Property \"{entity_json['name']}.{prop_json['name']}\" ({prop_json['id']})")
129+
for prop_json in entity_json["properties"]:
130+
if "indexId" in prop_json and IdUid.from_str(prop_json["indexId"]).uid == uid:
131+
raise ValueError(
132+
f"in Property index \"{entity_json['name']}.{prop_json['name']}\" ({prop_json['id']})")
133+
except ValueError as error:
134+
raise ValueError(f"User supplied UID \"{uid}\" found {error}")
135+
136+
def _validate_matching_prop(self, entity: _Entity, prop: Property, prop_json: Dict[str, Any]):
137+
""" Validates that the given property matches the JSON property. """
138+
try:
139+
# Don't check name equality as the property could be matched by UID (rename)
140+
# if validate_name and prop.name != prop_json["name"]:
141+
# raise ValueError(f"name {prop.name} != name {prop_json['name']} (in JSON)")
142+
if prop._ob_type != prop_json["type"]:
143+
raise ValueError(f"OBX type {prop._ob_type} != OBX type {prop_json['type']} (in JSON)")
144+
elif prop._flags != prop_json["flags"]:
145+
raise ValueError(f"flags {prop._flags} != flags {prop_json['flags']} (in JSON)")
146+
elif prop.index is None and "indexId" in prop_json:
147+
raise ValueError("property hasn't index, but index found in JSON")
148+
elif prop.index is not None and "indexId" not in prop_json:
149+
raise ValueError("property has index, but index not found in JSON")
150+
except ValueError as error:
151+
raise ValueError(f"Property {entity.name}.{prop.name} mismatches property found in JSON file: {error}")
152+
153+
def _load_or_assign_index(self, entity: _Entity, prop: Property, prop_json: Optional[Dict[str, Any]]):
154+
assert prop.index is not None
155+
index = prop.index
156+
157+
# Fetch index ID/UID from JSON file
108158
iduid_json = None
109-
prop_json = self._find_property_json_by_name(entity.name, prop.name)
110-
if prop_json is not None and "indexId" in prop_json:
159+
if (prop_json is not None) and ("indexId" in prop_json):
111160
iduid_json = IdUid.from_str(prop_json["indexId"])
112-
if iduid_json is None: # Index not present in JSON
113-
if index.has_uid():
114-
self._validate_uid_unassigned(index.uid)
115-
else:
116-
gen_uid = self._generate_uid()
117-
index.iduid.uid = gen_uid
161+
162+
# User provided a UID not matching index's, make sure it's not assigned elsewhere
163+
if index.has_uid() and (iduid_json is not None) and (index.uid != iduid_json.uid):
164+
self._validate_uid_unassigned(index.uid)
165+
166+
# Generate UID only if not supplied by the user, and index isn't found in JSON
167+
if not index.has_uid() and iduid_json is None:
168+
index.iduid.uid = self._generate_uid()
169+
170+
if (iduid_json is not None) and (not index.has_uid() or index.iduid.uid == iduid_json.uid): # Load
171+
index.iduid = IdUid.from_str(prop_json["indexId"])
172+
else: # Assign
118173
index.iduid = IdUid(self.model.last_index_iduid.id + 1, index.uid)
119-
else: # Index present in JSON
120-
if index.has_uid() and index.uid != iduid_json.uid:
121-
self._validate_uid_unassigned(index.uid)
122-
index.iduid = IdUid(self.model.last_index_iduid.id + 1, index.uid) # Assign ID
123-
else: # not index.has_uid() or index.uid != iduid_json.uid
124-
index.iduid = iduid_json
174+
self.model.last_index_iduid = index.iduid
125175

126-
def _validate_matching_prop(self, entity: _Entity, prop: Property, prop_json: Dict[str, Any]):
127-
""" Validates that the given property matches the JSON property. """
128-
assert prop.name == prop_json["name"], \
129-
f"Property {entity.name}.{prop.name} mismatches property found in JSON file " \
130-
f"(name {prop.name} != type {prop_json['name']})" # Shouldn't happen (JSON property is got by name)
131-
assert prop._ob_type == prop_json["type"], \
132-
f"Property {entity.name}.{prop.name} mismatches property found in JSON file " \
133-
f"(type {prop._ob_type} != type {prop_json['type']})"
134-
assert prop._flags == prop_json["flags"], \
135-
f"Property {entity.name}.{prop.name} mismatches property found in JSON file " \
136-
f"(flags {prop._flags} != type {prop_json['flags']})"
137-
138-
def _sync_property_id(self, entity: _Entity, prop: Property) -> None:
139-
""" Given an entity's property, syncs its ID/UID with the JSON file. """
140-
prop_json = self._find_property_json_by_name(entity.name, prop.name)
141-
if prop_json is None: # Property not present in JSON
142-
if prop.has_uid():
176+
def _load_or_assign_property(self, entity: _Entity, prop: Property, entity_json: Optional[Dict[str, Any]]):
177+
prop_json = None
178+
if prop.has_uid():
179+
if entity_json is not None:
180+
prop_json = self._find_property_json_by_uid(entity_json, prop.uid)
181+
if prop_json is None:
182+
# User provided a UID not matching any property (within the entity), make sure it's not assigned
183+
# elsewhere
143184
self._validate_uid_unassigned(prop.uid)
144-
else:
185+
else:
186+
if entity_json is not None:
187+
prop_json = self._find_property_json_by_name(entity_json, prop.name)
188+
189+
if prop_json is not None: # Load
190+
# Property was matched with a JSON property (either by UID or by name), make sure they're equal
191+
self._validate_matching_prop(entity, prop, prop_json)
192+
prop.iduid = IdUid.from_str(prop_json["id"])
193+
else: # Assign
194+
if not prop.has_uid():
145195
prop.iduid.uid = self._generate_uid()
146-
prop.iduid = IdUid(entity.last_property_iduid.id + 1, prop.uid) # Assign ID
147-
else: # Property present in JSON
148-
iduid_json = IdUid.from_str(prop_json["id"])
149-
if prop.has_uid() and prop.uid != iduid_json.uid: # New property
150-
self._validate_uid_unassigned(prop.uid)
151-
prop.iduid = IdUid(entity.last_property_iduid.id + 1, prop.uid) # Assign ID
152-
else: # not prop.has_uid() or prop.uid == iduid_json.uid
153-
self._validate_matching_prop(entity, prop, prop_json)
154-
prop.iduid = iduid_json
155-
156-
def _validate_matching_entity(self, entity: _Entity, entity_json: Dict[str, Any]):
157-
""" Validates that the given entity matches the JSON entity. """
158-
assert entity.name == entity_json["name"], \
159-
f"Entity {entity.name} mismatches property found in JSON file " \
160-
f"(name {entity.name} != type {entity_json['name']})" # Shouldn't happen (JSON entity is got by name)
161-
assert len(entity.properties) == len(entity_json["properties"]), \
162-
f"Entity {entity.name} mismatches entity found in JSON file " \
163-
f"({len(entity.properties)} properties != {len(entity_json['properties'])} properties)"
164-
# TODO check relations count
165-
pass # TODO check properties' fields?
166-
167-
def _sync_entity_id(self, entity: _Entity) -> None:
168-
""" Given an entity, syncs its ID/UID with the JSON file. """
169-
entity_json = self._find_entity_json_by_name(entity.name)
170-
if entity_json is None: # Entity not present in JSON file
171-
if entity.has_uid():
196+
prop.iduid = IdUid(entity.last_property_iduid.id + 1, prop.iduid.uid)
197+
entity.last_property_iduid = prop.iduid
198+
199+
if prop.index is not None:
200+
self._load_or_assign_index(entity, prop, prop_json)
201+
202+
def _load_or_assign_entity(self, entity: _Entity):
203+
# entity_json = None
204+
if entity.has_uid():
205+
entity_json = self._find_entity_json_by_uid(entity.uid)
206+
if entity_json is None:
207+
# User provided a UID not matching any entity, make sure it's not assigned elsewhere
172208
self._validate_uid_unassigned(entity.uid)
173-
else:
209+
else:
210+
entity_json = self._find_entity_json_by_name(entity.name)
211+
212+
if entity_json is not None: # Load
213+
entity.iduid = IdUid.from_str(entity_json["id"])
214+
entity.last_property_iduid = IdUid.from_str(entity_json["lastPropertyId"])
215+
else: # Assign
216+
if not entity.has_uid():
174217
entity.iduid.uid = self._generate_uid()
175-
entity.iduid = IdUid(self.model.last_entity_iduid.id + 1, entity.uid) # Assign ID
176-
else: # Entity present in JSON file
177-
iduid_json = IdUid.from_str(entity_json["id"])
178-
if entity.has_uid() and entity.uid != iduid_json.uid: # New entity
179-
self._validate_uid_unassigned(entity.uid)
180-
entity.iduid = IdUid(self.model.last_entity_iduid.id + 1, entity.uid) # Assign ID
181-
else: # not entity.has_uid() or entity.uid == iduid_json.uid
182-
self._validate_matching_entity(entity, entity_json)
183-
entity.iduid = iduid_json
218+
entity.iduid = IdUid(self.model.last_entity_iduid.id + 1, entity.iduid.uid)
219+
self.model.last_entity_iduid = entity.iduid
220+
entity.last_property_iduid = IdUid(0, 0)
221+
222+
# Load properties
223+
for prop in entity.properties:
224+
self._load_or_assign_property(entity, prop, entity_json)
184225

185226
def sync(self):
186227
""" Syncs the provided model with the model JSON file. """
187228

188-
# Sync entities ID/UID
189229
if self.model_json is not None:
190230
self.model.last_entity_iduid = IdUid.from_str(self.model_json["lastEntityId"])
191-
else:
192-
self.model.last_entity_iduid = IdUid(0, 0)
193-
for entity in self.model.entities:
194-
self._sync_entity_id(entity)
195-
if entity.id > self.model.last_entity_iduid.id: # If assignment occurred, update last_entity_iduid
196-
self.model.last_entity_iduid = entity.iduid
197-
198-
# Sync properties ID/UID
199-
for entity in self.model.entities:
200-
entity_json = self._find_entity_json_by_name(entity.name)
201-
if entity_json is not None:
202-
entity.last_property_iduid = IdUid.from_str(entity_json["lastPropertyId"])
203-
else:
204-
entity.last_property_iduid = IdUid(0, 0)
205-
for prop in entity.properties:
206-
self._sync_property_id(entity, prop)
207-
if prop.id > entity.last_property_iduid.id: # If assignment occurred, update last_property_iduid
208-
entity.last_property_iduid = prop.iduid
209-
210-
# Sync indexes ID/UID
211-
if self.model_json is not None:
212231
self.model.last_index_iduid = IdUid.from_str(self.model_json["lastIndexId"])
213-
else:
214-
self.model.last_index_iduid = IdUid(0, 0)
215-
for entity in self.model.entities:
216-
for prop in entity.properties:
217-
if prop.index is not None:
218-
index = prop.index
219-
self._sync_index_id(entity, prop, index)
220-
if index.id > self.model.last_index_iduid.id: # If assignment occurred, update last_index_iduid
221-
self.model.last_index_iduid = index.iduid
232+
# self.model.last_relation_iduid =
222233

223-
# TODO Sync relations ID/UID(s)
234+
for entity in self.model.entities:
235+
self._load_or_assign_entity(entity)
224236

225237
self._save_model_json()
226238

0 commit comments

Comments
 (0)