@@ -164,14 +164,18 @@ def files(self) -> List[ProjectFile]:
164164 SELECT
165165 fp.path,
166166 fh.size,
167- fh.diff,
168167 fh.location,
169168 fh.checksum,
170- pv.created AS mtime
169+ pv.created AS mtime,
170+ fd.path as diff_path,
171+ fd.size as diff_size,
172+ fd.checksum as diff_checksum,
173+ fd.location as diff_location
171174 FROM files_ids
172175 LEFT OUTER JOIN file_history fh ON fh.id = files_ids.fh_id
173176 LEFT OUTER JOIN project_file_path fp ON fp.id = fh.file_path_id
174- LEFT OUTER JOIN project_version pv ON pv.id = fh.version_id;
177+ LEFT OUTER JOIN project_version pv ON pv.id = fh.version_id
178+ LEFT OUTER JOIN file_diff fd ON fd.file_path_id = fh.file_path_id AND fd.version = fh.project_version_name and fd.rank = 0;
175179 """
176180 params = {"project_id" : self .id }
177181 files = [
@@ -181,7 +185,16 @@ def files(self) -> List[ProjectFile]:
181185 checksum = row .checksum ,
182186 location = row .location ,
183187 mtime = row .mtime ,
184- diff = File (** row .diff ) if row .diff else None ,
188+ diff = (
189+ File (
190+ path = row .diff_path ,
191+ size = row .diff_size ,
192+ checksum = row .diff_checksum ,
193+ location = row .diff_location ,
194+ )
195+ if row .diff_path
196+ else None
197+ ),
185198 )
186199 for row in db .session .execute (query , params ).fetchall ()
187200 ]
@@ -436,7 +449,6 @@ class FileHistory(db.Model):
436449 location = db .Column (db .String )
437450 size = db .Column (db .BigInteger , nullable = False )
438451 checksum = db .Column (db .String , nullable = False )
439- diff = db .Column (JSONB )
440452 change = db .Column (
441453 ENUM (
442454 * PushChangeType .values (),
@@ -470,17 +482,6 @@ class FileHistory(db.Model):
470482 file_path_id ,
471483 project_version_name .desc (),
472484 ),
473- db .CheckConstraint (
474- text (
475- """
476- CASE
477- WHEN (change = 'update_diff') THEN diff IS NOT NULL
478- ELSE diff IS NULL
479- END
480- """
481- ),
482- name = "changes_with_diff" ,
483- ),
484485 )
485486
486487 def __init__ (
@@ -491,22 +492,55 @@ def __init__(
491492 location : str ,
492493 change : PushChangeType ,
493494 diff : dict = None ,
495+ version_name : int = None ,
494496 ):
495497 self .file = file
496498 self .size = size
497499 self .checksum = checksum
498500 self .location = location
499- self .diff = diff if diff is not None else null ()
500501 self .change = change .value
502+ self .project_version_name = version_name
503+
504+ if diff is not None :
505+ basefile = FileHistory .get_basefile (file .id , version_name )
506+ diff_file = FileDiff (
507+ basefile ,
508+ diff .get ("path" ),
509+ diff .get ("size" ),
510+ diff .get ("checksum" ),
511+ rank = 0 ,
512+ version = version_name ,
513+ )
514+ db .session .add (diff_file )
501515
502516 @property
503517 def path (self ) -> str :
504518 return self .file .path
505519
520+ @property
521+ def diff (self ) -> Optional [FileDiff ]:
522+ """Diff file pushed with UPDATE_DIFF change type.
523+
524+ In FileDiff table it is defined as diff related to file, saved for the same project version with rank 0 (elementar diff)
525+ """
526+ if self .change != PushChangeType .UPDATE_DIFF .value :
527+ return
528+
529+ return FileDiff .query .filter_by (
530+ file_path_id = self .file_path_id , version = self .project_version_name , rank = 0
531+ ).first ()
532+
506533 @property
507534 def diff_file (self ) -> Optional [File ]:
508- if self .diff :
509- return File (** self .diff )
535+ if not self .diff :
536+ return
537+
538+ return File (
539+ path = self .diff .path ,
540+ size = self .diff .size ,
541+ checksum = self .diff .checksum ,
542+ location = self .diff .location ,
543+ )
510544
511545 @property
512546 def mtime (self ) -> datetime :
@@ -518,7 +552,7 @@ def abs_path(self) -> str:
518552
519553 @property
520554 def expiration (self ) -> Optional [datetime ]:
521- if not self .diff :
555+ if not self .diff_file :
522556 return
523557
524558 if os .path .exists (self .abs_path ):
@@ -564,11 +598,7 @@ def changes(
564598 break
565599
566600 # if we are interested only in 'diffable' history (not broken with forced update)
567- if (
568- diffable
569- and item .change == PushChangeType .UPDATE .value
570- and not item .diff
571- ):
601+ if diffable and item .change == PushChangeType .UPDATE .value :
572602 break
573603
574604 return history
@@ -615,7 +645,7 @@ def diffs_chain(
615645 if history :
616646 first_change = history [- 1 ]
617647 # we have either full history of changes or v_x = v_x+n => no basefile in way, it is 'diffable' from the end
618- if first_change .diff :
648+ if first_change .change == PushChangeType . UPDATE_DIFF . value :
619649 # omit diff for target version as it would lead to previous version if reconstructed backward
620650 diffs = [
621651 value .diff_file
@@ -665,6 +695,75 @@ def diffs_chain(
665695
666696 return basefile , diffs
667697
698+ @classmethod
699+ def get_basefile (cls , file_path_id : int , version : int ) -> Optional [FileHistory ]:
700+ """Get basefile (start of file diffable history) for diff file change at some version"""
701+ return (
702+ FileHistory .query .filter_by (file_path_id = file_path_id )
703+ .filter (
704+ FileHistory .project_version_name < version ,
705+ FileHistory .change .in_ (
706+ [PushChangeType .CREATE .value , PushChangeType .UPDATE .value ]
707+ ),
708+ )
709+ .order_by (desc (FileHistory .project_version_name ))
710+ .first ()
711+ )
712+
713+
714+ class FileDiff (db .Model ):
715+ """File diffs related to versioned files, also contain higher order (rank) merged diffs"""
716+
717+ id = db .Column (db .BigInteger , primary_key = True , autoincrement = True )
718+ file_path_id = db .Column (
719+ db .BigInteger ,
720+ db .ForeignKey ("project_file_path.id" , ondelete = "CASCADE" ),
721+ nullable = False ,
722+ )
723+ # reference to actual full gpkg file
724+ basefile_id = db .Column (
725+ db .BigInteger ,
726+ db .ForeignKey ("file_history.id" , ondelete = "CASCADE" ),
727+ index = True ,
728+ nullable = False ,
729+ )
730+ path = db .Column (db .String , nullable = False , index = True )
731+ # exponential order of merged diff, 0 is a source diff file uploaded by user, > 0 is merged diff
732+ rank = db .Column (db .Integer , nullable = False , index = True )
733+ # to which project version is this linked
734+ version = db .Column (db .Integer , nullable = False , index = True )
735+ # path on FS relative to project directory
736+ location = db .Column (db .String )
737+ size = db .Column (db .BigInteger , nullable = False )
738+ checksum = db .Column (db .String , nullable = False )
739+
740+ __table_args__ = (
741+ db .UniqueConstraint ("file_path_id" , "rank" , "version" , name = "unique_diff" ),
742+ db .Index ("ix_file_diff_file_path_id_version_rank" , file_path_id , version , rank ),
743+ )
744+
745+ def __init__ (
746+ self ,
747+ basefile : FileHistory ,
748+ path : str ,
749+ size : int ,
750+ checksum : str ,
751+ rank : int ,
752+ version : int ,
753+ ):
754+ self .basefile_id = basefile .id
755+ self .file_path_id = basefile .file_path_id
756+ self .path = path
757+ self .size = size
758+ self .checksum = checksum
759+ self .rank = rank
760+ self .version = version
761+
762+ if rank > 0 :
763+ self .location = f"diffs/{ path } "
764+ else :
765+ self .location = f"v{ version } /{ path } "
766+
668767
669768class ProjectVersion (db .Model ):
670769 id = db .Column (db .Integer , primary_key = True , autoincrement = True )
@@ -760,11 +859,12 @@ def __init__(
760859 diff = (
761860 asdict (upload_file .diff )
762861 if (is_diff_change and upload_file .diff )
763- else null ()
862+ else None
764863 ),
765864 change = (
766865 PushChangeType .UPDATE_DIFF if is_diff_change else change_type
767866 ),
867+ version_name = self .name ,
768868 )
769869 fh .version = self
770870 fh .project_version_name = self .name
@@ -822,14 +922,18 @@ def _files_from_start(self):
822922 SELECT
823923 fp.path,
824924 fh.size,
825- fh.diff,
826925 fh.location,
827926 fh.checksum,
828- pv.created AS mtime
927+ pv.created AS mtime,
928+ fd.path as diff_path,
929+ fd.size as diff_size,
930+ fd.checksum as diff_checksum,
931+ fd.location as diff_location
829932 FROM latest_changes ch
830933 LEFT OUTER JOIN file_history fh ON (fh.file_path_id = ch.id AND fh.project_version_name = ch.version)
831934 LEFT OUTER JOIN project_file_path fp ON fp.id = fh.file_path_id
832935 LEFT OUTER JOIN project_version pv ON pv.id = fh.version_id
936+ LEFT OUTER JOIN file_diff fd ON fd.file_path_id = fh.file_path_id AND fd.version = fh.project_version_name and fd.rank = 0
833937 WHERE fh.change != 'delete';
834938 """
835939 params = {"project_id" : self .project_id , "version" : self .name }
@@ -878,14 +982,18 @@ def _files_from_end(self):
878982 SELECT
879983 fp.path,
880984 fh.size,
881- fh.diff,
882985 fh.location,
883986 fh.checksum,
884- pv.created AS mtime
987+ pv.created AS mtime,
988+ fd.path as diff_path,
989+ fd.size as diff_size,
990+ fd.checksum as diff_checksum,
991+ fd.location as diff_location
885992 FROM files_changes_before_version ch
886993 INNER JOIN file_history fh ON (fh.file_path_id = ch.file_id AND fh.project_version_name = ch.version)
887994 INNER JOIN project_file_path fp ON fp.id = fh.file_path_id
888995 INNER JOIN project_version pv ON pv.id = fh.version_id
996+ LEFT OUTER JOIN file_diff fd ON fd.file_path_id = fh.file_path_id AND fd.version = fh.project_version_name and fd.rank = 0
889997 WHERE fh.change != 'delete'
890998 ORDER BY fp.path;
891999 """
@@ -909,7 +1017,16 @@ def files(self) -> List[ProjectFile]:
9091017 checksum = row .checksum ,
9101018 location = row .location ,
9111019 mtime = row .mtime ,
912- diff = File (** row .diff ) if row .diff else None ,
1020+ diff = (
1021+ File (
1022+ path = row .diff_path ,
1023+ checksum = row .diff_checksum ,
1024+ size = row .diff_size ,
1025+ location = row .diff_location ,
1026+ )
1027+ if row .diff_path
1028+ else None
1029+ ),
9131030 )
9141031 for row in result
9151032 ]
0 commit comments