From 1cb9eeb3300a00c9935db616ef542715a301bdd9 Mon Sep 17 00:00:00 2001 From: Martin Varga Date: Fri, 13 Mar 2026 11:04:59 +0100 Subject: [PATCH 1/2] Handle nullable latest_file_history_ids This prevent missing item in project version files when list of history ids has not been cached yet. --- server/mergin/sync/models.py | 42 ++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/server/mergin/sync/models.py b/server/mergin/sync/models.py index 6527213c..c4e17bf0 100644 --- a/server/mergin/sync/models.py +++ b/server/mergin/sync/models.py @@ -135,6 +135,36 @@ def workspace(self): project_workspace = current_app.ws_handler.get(self.workspace_id) return project_workspace + def get_latest_file_history_ids(self) -> List[int]: + """Get latest file history ids either from cached table or calculate them on the fly""" + if self.latest_project_files.file_history_ids is not None: + return self.latest_project_files.file_history_ids + + query = f""" + WITH latest_changes AS ( + SELECT + fp.id, + pv.project_id, + max(pv.name) AS version + FROM + project_version pv + LEFT OUTER JOIN file_history fh ON fh.version_id = pv.id + LEFT OUTER JOIN project_file_path fp ON fp.id = fh.file_path_id + WHERE + pv.project_id = :project_id + AND pv.name <= :latest_version + GROUP BY + fp.id, pv.project_id + ) + SELECT + fh.id + FROM latest_changes ch + LEFT OUTER JOIN file_history fh ON (fh.file_path_id = ch.id AND fh.project_version_name = ch.version AND fh.change != 'delete') + WHERE fh.id IS NOT NULL; + """ + params = {"project_id": self.id, "latest_version": self.latest_version} + return [row.id for row in db.session.execute(text(query), params).fetchall()] + def cache_latest_files(self) -> None: """Get project files from changes (FileHistory) and save them for later use.""" if self.latest_version is None: @@ -514,7 +544,11 @@ def generate_diff_name(self): class LatestProjectFiles(db.Model): - """Store project latest version files history ids""" + """Store project latest version files history ids. + + This is a caching table to store the latest relevant files history ids for further use in + Project.files and ProjectVersion.files. It is updated when ProjectVersion itself is created. + """ project_id = db.Column( UUID(as_uuid=True), @@ -1434,7 +1468,7 @@ def __init__( latest_files_map = { fh.path: fh.id for fh in FileHistory.query.filter( - FileHistory.id.in_(self.project.latest_project_files.file_history_ids) + FileHistory.id.in_(self.project.get_latest_file_history_ids()) ).all() } @@ -1565,6 +1599,10 @@ def _files_from_end(self): files that were delete after the version (and thus not necessarily present now). From these candidates get the latest file change before or at the specific version. If that change was not 'delete', file is present. """ + # if we do not have cached file history ids use different strategy where it is not necessary + if self.project.latest_project_files.file_history_ids is None: + return self._files_from_start() + query = f""" WITH files_changes_before_version AS ( WITH files_candidates AS ( From 82bf4b25954669b42971ee16e15900b4201a14e8 Mon Sep 17 00:00:00 2001 From: Martin Varga Date: Mon, 16 Mar 2026 09:53:33 +0100 Subject: [PATCH 2/2] rename function --- server/mergin/sync/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/mergin/sync/models.py b/server/mergin/sync/models.py index c4e17bf0..7a91bc9b 100644 --- a/server/mergin/sync/models.py +++ b/server/mergin/sync/models.py @@ -135,7 +135,7 @@ def workspace(self): project_workspace = current_app.ws_handler.get(self.workspace_id) return project_workspace - def get_latest_file_history_ids(self) -> List[int]: + def get_latest_files_cache(self) -> List[int]: """Get latest file history ids either from cached table or calculate them on the fly""" if self.latest_project_files.file_history_ids is not None: return self.latest_project_files.file_history_ids @@ -1468,7 +1468,7 @@ def __init__( latest_files_map = { fh.path: fh.id for fh in FileHistory.query.filter( - FileHistory.id.in_(self.project.get_latest_file_history_ids()) + FileHistory.id.in_(self.project.get_latest_files_cache()) ).all() }