2020from orso .tools import single_item_cache
2121from orso .types import OrsoTypes
2222
23- from opteryx .connectors import DiskConnector
2423from opteryx .connectors .base .base_connector import BaseConnector
24+ from opteryx .connectors .capabilities import Diachronic
2525from opteryx .connectors .capabilities import LimitPushable
2626from opteryx .connectors .capabilities import PredicatePushable
2727from opteryx .connectors .capabilities import Statistics
2828from opteryx .exceptions import DatasetNotFoundError
29+ from opteryx .exceptions import DatasetReadError
2930from opteryx .exceptions import NotSupportedError
31+ from opteryx .exceptions import UnsupportedSyntaxError
3032from opteryx .managers .expression import NodeType
3133from opteryx .managers .expression import get_all_nodes_of_type
3234from opteryx .models import RelationStatistics
@@ -117,7 +119,7 @@ def _predicate_to_iceberg_filter(root):
117119 return iceberg_filter if iceberg_filter else "True" , unsupported
118120
119121
120- class IcebergConnector (BaseConnector , LimitPushable , Statistics , PredicatePushable ):
122+ class IcebergConnector (BaseConnector , Diachronic , LimitPushable , Statistics , PredicatePushable ):
121123 __mode__ = "Blob"
122124 __type__ = "ICEBERG"
123125
@@ -140,22 +142,55 @@ class IcebergConnector(BaseConnector, LimitPushable, Statistics, PredicatePushab
140142 OrsoTypes .DATE ,
141143 }
142144
143- def __init__ (self , * args , catalog = None , io = DiskConnector , ** kwargs ):
145+ def __init__ (self , * args , catalog = None , ** kwargs ):
144146 BaseConnector .__init__ (self , ** kwargs )
145147 LimitPushable .__init__ (self , ** kwargs )
148+ Diachronic .__init__ (self , ** kwargs )
146149 Statistics .__init__ (self , ** kwargs )
147150 PredicatePushable .__init__ (self , ** kwargs )
148151
149152 import pyiceberg
150153
151154 try :
152155 self .table = catalog .load_table (self .dataset )
153- self .io_connector = io (** kwargs )
156+ self .snapshot = self .table .current_snapshot ()
157+ self .snapshot_id = self .snapshot .snapshot_id
154158 except pyiceberg .exceptions .NoSuchTableError :
155159 raise DatasetNotFoundError (dataset = self .dataset , connector = self .__type__ ) from None
156160
157161 def get_dataset_schema (self ) -> RelationSchema :
158- iceberg_schema = self .table .schema ()
162+ if self .start_date != self .end_date :
163+ if self .start_date .date () != self .end_date .date ():
164+ raise UnsupportedSyntaxError ("This table only supports point in time reads." )
165+ raise UnsupportedSyntaxError (
166+ "This table only supports point in time reads. Are you missing the time component from your FOR clause?"
167+ )
168+
169+ if self .start_date is not None :
170+ snapshots = self .table .inspect .snapshots ().sort_by ("committed_at" )
171+ snapshot_rows = snapshots .to_pylist ()
172+
173+ if not snapshot_rows :
174+ raise DatasetReadError ("No data available for the specified date." )
175+
176+ # Honor dates before the first snapshot, reject dates beyond the newest snapshot
177+ if self .start_date < snapshot_rows [0 ]["committed_at" ]:
178+ selected = snapshot_rows [0 ]
179+ elif self .start_date > snapshot_rows [- 1 ]["committed_at" ]:
180+ raise DatasetReadError ("No data available for the specified date." )
181+ else :
182+ selected = snapshot_rows [0 ]
183+ for candidate in snapshot_rows :
184+ if candidate ["committed_at" ] <= self .start_date :
185+ self .statistics .dataset_committed_at = candidate ["committed_at" ].isoformat ()
186+ selected = candidate
187+ else :
188+ break
189+
190+ self .snapshot_id = selected ["snapshot_id" ]
191+ self .snapshot = self .table .snapshot_by_id (self .snapshot_id )
192+
193+ iceberg_schema = self .table .schemas ()[self .snapshot .schema_id ]
159194 arrow_schema = iceberg_schema .as_arrow ()
160195
161196 self .schema = RelationSchema (
@@ -169,7 +204,7 @@ def get_dataset_schema(self) -> RelationSchema:
169204 column_names = {col .field_id : col .name for col in iceberg_schema .columns }
170205 column_types = {col .field_id : col .field_type for col in iceberg_schema .columns }
171206
172- files = self .table .inspect .files ()
207+ files = self .table .inspect .files (snapshot_id = self . snapshot_id )
173208
174209 # No files = empty table, no stats
175210 if len (files .column ("file_path" )) == 0 :
@@ -225,7 +260,10 @@ def read_dataset(
225260 )
226261
227262 reader = self .table .scan (
228- row_filter = pushed_filters , selected_fields = selected_columns , limit = limit
263+ row_filter = pushed_filters ,
264+ selected_fields = selected_columns ,
265+ limit = limit ,
266+ snapshot_id = self .snapshot_id ,
229267 ).to_arrow_batch_reader ()
230268
231269 batch = None
0 commit comments