1919import io .trino .filesystem .TrinoFileSystem ;
2020import io .trino .filesystem .TrinoInputFile ;
2121import io .trino .plugin .deltalake .transactionlog .DeltaLakeTransactionLogEntry ;
22+ import io .trino .plugin .deltalake .transactionlog .MissingTransactionLogException ;
2223import io .trino .plugin .deltalake .transactionlog .Transaction ;
2324import io .trino .plugin .deltalake .transactionlog .TransactionLogEntries ;
2425
3132import static io .airlift .slice .SizeOf .SIZE_OF_LONG ;
3233import static io .airlift .slice .SizeOf .estimatedSizeOf ;
3334import static io .airlift .slice .SizeOf .instanceSize ;
35+ import static io .trino .plugin .deltalake .transactionlog .TransactionLogUtil .getTransactionLogDir ;
3436import static io .trino .plugin .deltalake .transactionlog .TransactionLogUtil .getTransactionLogJsonEntryPath ;
3537import static java .util .Objects .requireNonNull ;
3638
@@ -47,6 +49,31 @@ public TransactionLogTail(List<Transaction> entries, long version)
4749 this .version = version ;
4850 }
4951
52+ // Load a section of the Transaction Log JSON entries. Optionally from a given start version (exclusive) through an end version (inclusive)
53+ public static TransactionLogTail loadNewTail (
54+ TrinoFileSystem fileSystem ,
55+ String tableLocation ,
56+ Optional <Long > startVersion ,
57+ Optional <Long > endVersion ,
58+ DataSize transactionLogMaxCachedFileSize )
59+ throws IOException
60+ {
61+ if (startVersion .isPresent () && endVersion .isPresent () && startVersion .get ().equals (endVersion .get ())) {
62+ // This is time travel to a specific checkpoint. No need to read transaction log files.
63+ return new TransactionLogTail (ImmutableList .of (), startVersion .get ());
64+ }
65+
66+ if (endVersion .isPresent ()) {
67+ return loadNewTailBackward (fileSystem , tableLocation , startVersion , endVersion .get (), transactionLogMaxCachedFileSize );
68+ }
69+
70+ if (startVersion .isPresent ()) {
71+ return loadNewTail (fileSystem , tableLocation , startVersion .get (), startVersion .get () + 1 , transactionLogMaxCachedFileSize );
72+ }
73+
74+ return loadNewTail (fileSystem , tableLocation , 0L , 0L , transactionLogMaxCachedFileSize );
75+ }
76+
5077 /**
5178 * @deprecated use {@link #getEntriesFromJson(long, TrinoInputFile, DataSize)}
5279 */
@@ -95,4 +122,73 @@ public long getRetainedSizeInBytes()
95122 + SIZE_OF_LONG
96123 + estimatedSizeOf (entries , Transaction ::getRetainedSizeInBytes );
97124 }
125+
126+ /**
127+ * Loads a section of the Transaction Log JSON entries starting from {@code startVersion} (inclusive) up to the latest version.
128+ *
129+ * the {@code version} is the latest table version, which is the last entry number in the transaction log we already know,
130+ * the {@code starVersion} is the first entry number we want to load, but it is not guaranteed to be the first entry in the transaction log.
131+ */
132+ private static TransactionLogTail loadNewTail (
133+ TrinoFileSystem fileSystem ,
134+ String tableLocation ,
135+ long version ,
136+ long startVersion ,
137+ DataSize transactionLogMaxCachedFileSize )
138+ throws IOException
139+ {
140+ ImmutableList .Builder <Transaction > entriesBuilder = ImmutableList .builder ();
141+ String transactionLogDir = getTransactionLogDir (tableLocation );
142+
143+ long entryNumber = startVersion ;
144+ while (true ) {
145+ Optional <TransactionLogEntries > results = getEntriesFromJson (entryNumber , transactionLogDir , fileSystem , transactionLogMaxCachedFileSize );
146+ if (results .isEmpty ()) {
147+ break ;
148+ }
149+
150+ entriesBuilder .add (new Transaction (entryNumber , results .get ()));
151+ version = entryNumber ;
152+ entryNumber ++;
153+ }
154+
155+ return new TransactionLogTail (entriesBuilder .build (), version );
156+ }
157+
158+ // Load a section of the Transaction Log JSON entries. Optionally from a given end version (inclusive) through a start version (exclusive)
159+ private static TransactionLogTail loadNewTailBackward (
160+ TrinoFileSystem fileSystem ,
161+ String tableLocation ,
162+ Optional <Long > startVersion ,
163+ long endVersion ,
164+ DataSize transactionLogMaxCachedFileSize )
165+ throws IOException
166+ {
167+ ImmutableList .Builder <Transaction > transactionsBuilder = ImmutableList .builder ();
168+ String transactionLogDir = getTransactionLogDir (tableLocation );
169+
170+ long version = endVersion ;
171+ long entryNumber = version ;
172+ boolean endOfHead = false ;
173+
174+ while (!endOfHead ) {
175+ Optional <TransactionLogEntries > results = getEntriesFromJson (entryNumber , transactionLogDir , fileSystem , transactionLogMaxCachedFileSize );
176+ if (results .isPresent ()) {
177+ transactionsBuilder .add (new Transaction (entryNumber , results .get ()));
178+ version = entryNumber ;
179+ entryNumber --;
180+ }
181+ else {
182+ // When there is a gap in the transaction log version, indicate the end of the current head
183+ endOfHead = true ;
184+ if (startVersion .isPresent () && entryNumber > startVersion .get () + 1 ) {
185+ throw new MissingTransactionLogException (getTransactionLogJsonEntryPath (transactionLogDir , entryNumber ).toString ());
186+ }
187+ }
188+ if ((startVersion .isPresent () && version == startVersion .get () + 1 ) || entryNumber < 0 ) {
189+ endOfHead = true ;
190+ }
191+ }
192+ return new TransactionLogTail (transactionsBuilder .build ().reversed (), endVersion );
193+ }
98194}
0 commit comments