@ -60,6 +60,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
self . orderSQL = orderSQL
self . dataQuery = dataQuery
self . associatedRecords = associatedRecords
. map { $0 . settingPagedTableName ( pagedTableName : pagedTable . databaseTableName ) }
self . onChangeUnsorted = onChangeUnsorted
// C o m b i n e t h e v a r i o u s o b s e r v e d c h a n g e s i n t o a s i n g l e s e t
@ -141,6 +142,7 @@ public class PagedDatabaseObserver<ObservedTable, T>: TransactionObserver where
let hasChanges : Bool = associatedRecord . tryUpdateForDatabaseCommit (
db ,
changes : committedChanges ,
joinSQL : joinSQL ,
orderSQL : orderSQL ,
filterSQL : filterSQL ,
pageInfo : updatedPageInfo
@ -616,13 +618,15 @@ public protocol FetchableRecordWithRowId: FetchableRecord {
public protocol ErasedAssociatedRecord {
var databaseTableName : String { get }
var pagedTableName : String { get }
var observedChanges : [ PagedData . ObservedChanges ] { get }
var joinToPagedType : SQL { get }
var groupPagedType : SQL ? { get }
func settingPagedTableName ( pagedTableName : String ) -> Self
func tryUpdateForDatabaseCommit (
_ db : Database ,
changes : Set < PagedData . TrackedChange > ,
joinSQL : SQL ? ,
orderSQL : SQL ,
filterSQL : SQL ,
pageInfo : PagedData . PageInfo
@ -886,45 +890,11 @@ public enum PagedData {
tableName : String ,
requiredJoinSQL : SQL ? = nil ,
orderSQL : SQL ,
filterSQL : SQL ,
joinToPagedType : SQL ? = nil ,
groupPagedType : SQL ? = nil
filterSQL : SQL
) -> [ Int64 ] {
guard ! rowIds . isEmpty else { return [ ] }
let tableNameLiteral : SQL = SQL ( stringLiteral : tableName )
// / * * N o t e : * * ` R O W _ N U M B E R ` w o r k s b y r e t u r n i n g t h e i n d e x o f t h e r o w i n a g i v e n q u e r y , u n f o r t u n a t e l y w h e n d e a l i n g
// / w i t h a s s o c i a t e d d a t a i t s p o s s i b l e f o r m u l t i p l e r e s u l t s t o c o n n e c t t o a n i n d i v i d u a l p a g e d r e s u l t , t h i s t h r o w s o f f t h e
// / i n d e x e s s o i n t h i s c a s e w e n e e d t o d o s o m e s n e a k y a g g r e g a t i o n a n d g r o u p i n g a n d t h e n i n d i v i d u a l l y r e t r i e v e e a c h
// / i n d e x t o p r e v e n t t h i s
guard joinToPagedType = = nil || rowIds . count = = 1 else {
guard let groupPagedType : SQL = groupPagedType else { return [ ] }
let groupByLiteral : SQL = SQL ( stringLiteral : " GROUP BY " )
return rowIds . compactMap { rowId in
let groupedRequest : SQLRequest < Int64 > = " " "
SELECT
( data . rowIndex - 1 ) AS rowIndex -- Converting from 1 - Indexed to 0 - indexed
FROM (
SELECT
\ ( tableNameLiteral ) . rowid AS rowid ,
\ ( SQL ( " MAX( \( tableNameLiteral ) .rowid = \( rowId ) ) " ) ) ,
ROW_NUMBER ( ) OVER ( ORDER BY \ ( orderSQL ) ) AS rowIndex
FROM \ ( tableNameLiteral )
\ ( requiredJoinSQL ? ? " " )
\ ( joinToPagedType ? ? " " )
WHERE \ ( filterSQL )
\ ( groupByLiteral ) \ ( groupPagedType )
) AS data
WHERE \ ( SQL ( " data.rowid = \( rowId ) " ) )
" " "
return try ? groupedRequest . fetchOne ( db )
}
}
let request : SQLRequest < Int64 > = " " "
SELECT
( data . rowIndex - 1 ) AS rowIndex -- Converting from 1 - Indexed to 0 - indexed
@ -934,7 +904,6 @@ public enum PagedData {
ROW_NUMBER ( ) OVER ( ORDER BY \ ( orderSQL ) ) AS rowIndex
FROM \ ( tableNameLiteral )
\ ( requiredJoinSQL ? ? " " )
\ ( joinToPagedType ? ? " " )
WHERE \ ( filterSQL )
) AS data
WHERE \ ( SQL ( " data.rowid IN \( rowIds ) " ) )
@ -958,7 +927,7 @@ public enum PagedData {
let pagedTableNameLiteral : SQL = SQL ( stringLiteral : pagedTableName )
let request : SQLRequest < Int64 > = " " "
SELECT \ ( tableNameLiteral ) . rowid AS rowid
FROM \ ( t ableNameLiteral)
FROM \ ( pagedT ableNameLiteral)
\ ( joinToPagedType )
WHERE \ ( pagedTableNameLiteral ) . rowId IN \ ( pagedTypeRowIds )
" " "
@ -995,9 +964,9 @@ public enum PagedData {
public class AssociatedRecord < T , PagedType > : ErasedAssociatedRecord where T : FetchableRecordWithRowId & Identifiable , PagedType : FetchableRecordWithRowId & Identifiable {
public let databaseTableName : String
public private ( set ) var pagedTableName : String = " "
public let observedChanges : [ PagedData . ObservedChanges ]
public let joinToPagedType : SQL
public let groupPagedType : SQL ?
fileprivate let dataCache : Atomic < DataCache < T > > = Atomic ( DataCache ( ) )
fileprivate let dataQuery : ( SQL ? ) -> AdaptedFetchRequest < SQLRequest < T > >
@ -1010,14 +979,12 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
observedChanges : [ PagedData . ObservedChanges ] ,
dataQuery : @ escaping ( SQL ? ) -> AdaptedFetchRequest < SQLRequest < T > > ,
joinToPagedType : SQL ,
groupPagedType : SQL ? = nil ,
associateData : @ escaping ( DataCache < T > , DataCache < PagedType > ) -> DataCache < PagedType >
) {
self . databaseTableName = trackedAgainst . databaseTableName
self . observedChanges = observedChanges
self . dataQuery = dataQuery
self . joinToPagedType = joinToPagedType
self . groupPagedType = groupPagedType
self . associateData = associateData
}
@ -1026,7 +993,6 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
observedChanges : [ PagedData . ObservedChanges ] ,
dataQuery : @ escaping ( SQL ? ) -> SQLRequest < T > ,
joinToPagedType : SQL ,
groupPagedType : SQL ? = nil ,
associateData : @ escaping ( DataCache < T > , DataCache < PagedType > ) -> DataCache < PagedType >
) {
self . init (
@ -1036,16 +1002,21 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
dataQuery ( additionalFilters ) . adapted { _ in ScopeAdapter ( [ : ] ) }
} ,
joinToPagedType : joinToPagedType ,
groupPagedType : groupPagedType ,
associateData : associateData
)
}
// MARK: - A s s o c i a t e d R e c o r d
public func settingPagedTableName ( pagedTableName : String ) -> Self {
self . pagedTableName = pagedTableName
return self
}
public func tryUpdateForDatabaseCommit (
_ db : Database ,
changes : Set < PagedData . TrackedChange > ,
joinSQL : SQL ? ,
orderSQL : SQL ,
filterSQL : SQL ,
pageInfo : PagedData . PageInfo
@ -1075,44 +1046,52 @@ public class AssociatedRecord<T, PagedType>: ErasedAssociatedRecord where T: Fet
guard ! rowIdsToQuery . isEmpty else { return ( oldCount != countAfterDeletions ) }
// F e t c h t h e i n d e x e s o f t h e r o w I d s s o w e c a n d e t e r m i n e w h e t h e r t h e y s h o u l d b e a d d e d t o t h e s c r e e n
let itemIndexes: [ Int64 ] = PagedData . indexe s(
let pagedRowIds: [ Int64 ] = PagedData . pagedRowIdsForRelatedRowId s(
db ,
rowIds : rowIdsToQuery ,
tableName : databaseTableName ,
pagedTableName : pagedTableName ,
relatedRowIds : rowIdsToQuery ,
joinToPagedType : joinToPagedType
)
// I f t h e a s s o c i a t e d d a t a c h a n g e i s n ' t r e l a t e d t o t h e p a g e d t y p e t h e n n o n e e d t o c o n t i n u e
guard ! pagedRowIds . isEmpty else { return ( oldCount != countAfterDeletions ) }
let pagedItemIndexes : [ Int64 ] = PagedData . indexes (
db ,
rowIds : pagedRowIds ,
tableName : pagedTableName ,
requiredJoinSQL : joinSQL ,
orderSQL : orderSQL ,
filterSQL : filterSQL ,
joinToPagedType : joinToPagedType ,
groupPagedType : groupPagedType
filterSQL : filterSQL
)
// D e t e r m i n e i f t h e i n d e x e s f o r t h e r o w i d s s h o u l d b e d i s p l a y e d o n t h e s c r e e n a n d r e m o v e a n y
// w h i c h s h o u l d n ' t - v a l u e s l e s s t h a n ' c u r r e n t C o u n t ' o r i f t h e r e i s a t l e a s t o n e v a l u e l e s s t h a n
// ' c u r r e n t C o u n t ' a n d t h e i n d e x e s a r e s e q u e n t i a l ( i e . m o r e t h a n t h e c u r r e n t l o a d e d c o n t e n t w a s
// a d d e d a t o n c e )
let uniqueIndexes : [ Int64 ] = itemIndexes . asSet ( ) . sorted ( )
let itemIndexesAreSequential : Bool = ( uniqueIndexes . map { $0 - 1 } . dropFirst ( ) = = uniqueIndexes . dropLast ( ) )
let hasOneValidIndex : Bool = itemIndexes . contains ( where : { index -> Bool in
// I f w e c a n ' t g e t t h e i t e m i n d e x e s f o r t h e p a g e d r o w i d s t h e n i t ' s l i k e l y r e l a t e d t o d a t a
// w h i c h w a s f i l t e r e d o u t ( e g . m e s s a g e a t t a c h m e n t r e l a t e d t o a d i f f e r e n t t h r e a d )
guard ! pagedItemIndexes . isEmpty else { return ( oldCount != countAfterDeletions ) }
// / * * N o t e : * * T h e ` P a g e d D a t a . i n d e x e s ` w o r k s b y r e t u r n i n g t h e i n d e x o f a r o w i n a g i v e n q u e r y , u n f o r t u n a t e l y w h e n
// / d e a l i n g w i t h a s s o c i a t e d d a t a i t s p o s s i b l e f o r m u l t i p l e a s s o c i a t e d d a t a v a l u e s t o c o n n e c t t o a n i n d i v i d u a l p a g e d r e s u l t ,
// / t h i s t h r o w s o f f t h e i n d e x e s s o w e c a n ' t a c t u a l l y t e l l w h a t ` r o w I d s T o Q u e r y ` v a l u e i s a s s o c i a t e d t o w h i c h
// / ` p a g e d I t e m I n d e x e s ` v a l u e
// /
// / I n s t e a d o f f o l l o w i n g t h e p a t t e r n t h e ` P a g e d D a t a b a s e O b s e r v e r ` d o e s w h e r e w e g e t t h e p r o p e r ` v a l i d R o w I d s ` w e
// / b a s i c a l l y h a v e t o c h e c k i f t h e r e i s a s i n g l e v a l i d i n d e x , a n d i f s o r e t r i e v e a n d s t o r e a l l d a t a r e l a t e d t o t h e c h a n g e s f o r t h i s
// / c o m m i t - t h i s w i l l m e a n i n s o m e c a s e s w e c a c h e d a t a w h i c h i s a c t u a l l y u n r e l a t e d t o t h e f i l t e r e d p a g e d d a t a
let hasOneValidIndex : Bool = pagedItemIndexes . contains ( where : { index -> Bool in
index >= pageInfo . pageOffset && (
index < pageInfo . currentCount ||
pageInfo . currentCount = = 0
)
} )
let validRowIds : [ Int64 ] = ( itemIndexesAreSequential && hasOneValidIndex ?
rowIdsToQuery :
zip ( itemIndexes , rowIdsToQuery )
. filter { index , _ -> Bool in
index >= pageInfo . pageOffset && (
index < pageInfo . currentCount ||
pageInfo . currentCount = = 0
)
}
. map { _ , rowId -> Int64 in rowId }
)
// D o n ' t b o t h e r c o n t i n u i n g i f w e d o n ' t h a v e a v a l i d i n d e x
guard hasOneValidIndex else { return ( oldCount != countAfterDeletions ) }
// A t t e m p t t o u p d a t e t h e c a c h e w i t h t h e ` v a l i d R o w I d s ` a r r a y
return updateCache (
db ,
rowIds : validRowIds ,
rowIds : rowIdsToQuery ,
hasOtherChanges : ( oldCount != countAfterDeletions )
)
}