3131 union ,
3232 update ,
3333)
34- from sqlalchemy .exc import IntegrityError , OperationalError
34+ from sqlalchemy .exc import IntegrityError , NoResultFound , OperationalError
3535from sqlalchemy .orm import (
3636 DeclarativeBase ,
3737 Mapped ,
4848 PropertiesUpdateSummary ,
4949)
5050from pyiceberg .exceptions import (
51+ CommitFailedException ,
5152 NamespaceAlreadyExistsError ,
5253 NamespaceNotEmptyError ,
5354 NoSuchNamespaceError ,
5960from pyiceberg .partitioning import UNPARTITIONED_PARTITION_SPEC , PartitionSpec
6061from pyiceberg .schema import Schema
6162from pyiceberg .serializers import FromInputFile
62- from pyiceberg .table import CommitTableRequest , CommitTableResponse , Table
63+ from pyiceberg .table import CommitTableRequest , CommitTableResponse , Table , update_table_metadata
6364from pyiceberg .table .metadata import new_table_metadata
6465from pyiceberg .table .sorting import UNSORTED_SORT_ORDER , SortOrder
6566from pyiceberg .typedef import EMPTY_DICT
@@ -268,16 +269,32 @@ def drop_table(self, identifier: Union[str, Identifier]) -> None:
268269 identifier_tuple = self .identifier_to_tuple_without_catalog (identifier )
269270 database_name , table_name = self .identifier_to_database_and_table (identifier_tuple , NoSuchTableError )
270271 with Session (self .engine ) as session :
271- res = session .execute (
272- delete (IcebergTables ).where (
273- IcebergTables .catalog_name == self .name ,
274- IcebergTables .table_namespace == database_name ,
275- IcebergTables .table_name == table_name ,
272+ if self .engine .dialect .supports_sane_rowcount :
273+ res = session .execute (
274+ delete (IcebergTables ).where (
275+ IcebergTables .catalog_name == self .name ,
276+ IcebergTables .table_namespace == database_name ,
277+ IcebergTables .table_name == table_name ,
278+ )
276279 )
277- )
280+ if res .rowcount < 1 :
281+ raise NoSuchTableError (f"Table does not exist: { database_name } .{ table_name } " )
282+ else :
283+ try :
284+ tbl = (
285+ session .query (IcebergTables )
286+ .with_for_update (of = IcebergTables )
287+ .filter (
288+ IcebergTables .catalog_name == self .name ,
289+ IcebergTables .table_namespace == database_name ,
290+ IcebergTables .table_name == table_name ,
291+ )
292+ .one ()
293+ )
294+ session .delete (tbl )
295+ except NoResultFound as e :
296+ raise NoSuchTableError (f"Table does not exist: { database_name } .{ table_name } " ) from e
278297 session .commit ()
279- if res .rowcount < 1 :
280- raise NoSuchTableError (f"Table does not exist: { database_name } .{ table_name } " )
281298
282299 def rename_table (self , from_identifier : Union [str , Identifier ], to_identifier : Union [str , Identifier ]) -> Table :
283300 """Rename a fully classified table name.
@@ -301,18 +318,35 @@ def rename_table(self, from_identifier: Union[str, Identifier], to_identifier: U
301318 raise NoSuchNamespaceError (f"Namespace does not exist: { to_database_name } " )
302319 with Session (self .engine ) as session :
303320 try :
304- stmt = (
305- update (IcebergTables )
306- .where (
307- IcebergTables .catalog_name == self .name ,
308- IcebergTables .table_namespace == from_database_name ,
309- IcebergTables .table_name == from_table_name ,
321+ if self .engine .dialect .supports_sane_rowcount :
322+ stmt = (
323+ update (IcebergTables )
324+ .where (
325+ IcebergTables .catalog_name == self .name ,
326+ IcebergTables .table_namespace == from_database_name ,
327+ IcebergTables .table_name == from_table_name ,
328+ )
329+ .values (table_namespace = to_database_name , table_name = to_table_name )
310330 )
311- .values (table_namespace = to_database_name , table_name = to_table_name )
312- )
313- result = session .execute (stmt )
314- if result .rowcount < 1 :
315- raise NoSuchTableError (f"Table does not exist: { from_table_name } " )
331+ result = session .execute (stmt )
332+ if result .rowcount < 1 :
333+ raise NoSuchTableError (f"Table does not exist: { from_table_name } " )
334+ else :
335+ try :
336+ tbl = (
337+ session .query (IcebergTables )
338+ .with_for_update (of = IcebergTables )
339+ .filter (
340+ IcebergTables .catalog_name == self .name ,
341+ IcebergTables .table_namespace == from_database_name ,
342+ IcebergTables .table_name == from_table_name ,
343+ )
344+ .one ()
345+ )
346+ tbl .table_namespace = to_database_name
347+ tbl .table_name = to_table_name
348+ except NoResultFound as e :
349+ raise NoSuchTableError (f"Table does not exist: { from_table_name } " ) from e
316350 session .commit ()
317351 except IntegrityError as e :
318352 raise TableAlreadyExistsError (f"Table { to_database_name } .{ to_table_name } already exists" ) from e
@@ -329,8 +363,62 @@ def _commit_table(self, table_request: CommitTableRequest) -> CommitTableRespons
329363
330364 Raises:
331365 NoSuchTableError: If a table with the given identifier does not exist.
366+ CommitFailedException: If the commit failed.
332367 """
333- raise NotImplementedError
368+ identifier_tuple = self .identifier_to_tuple_without_catalog (
369+ tuple (table_request .identifier .namespace .root + [table_request .identifier .name ])
370+ )
371+ current_table = self .load_table (identifier_tuple )
372+ database_name , table_name = self .identifier_to_database_and_table (identifier_tuple , NoSuchTableError )
373+ base_metadata = current_table .metadata
374+ for requirement in table_request .requirements :
375+ requirement .validate (base_metadata )
376+
377+ updated_metadata = update_table_metadata (base_metadata , table_request .updates )
378+ if updated_metadata == base_metadata :
379+ # no changes, do nothing
380+ return CommitTableResponse (metadata = base_metadata , metadata_location = current_table .metadata_location )
381+
382+ # write new metadata
383+ new_metadata_version = self ._parse_metadata_version (current_table .metadata_location ) + 1
384+ new_metadata_location = self ._get_metadata_location (current_table .metadata .location , new_metadata_version )
385+ self ._write_metadata (updated_metadata , current_table .io , new_metadata_location )
386+
387+ with Session (self .engine ) as session :
388+ if self .engine .dialect .supports_sane_rowcount :
389+ stmt = (
390+ update (IcebergTables )
391+ .where (
392+ IcebergTables .catalog_name == self .name ,
393+ IcebergTables .table_namespace == database_name ,
394+ IcebergTables .table_name == table_name ,
395+ IcebergTables .metadata_location == current_table .metadata_location ,
396+ )
397+ .values (metadata_location = new_metadata_location , previous_metadata_location = current_table .metadata_location )
398+ )
399+ result = session .execute (stmt )
400+ if result .rowcount < 1 :
401+ raise CommitFailedException (f"Table has been updated by another process: { database_name } .{ table_name } " )
402+ else :
403+ try :
404+ tbl = (
405+ session .query (IcebergTables )
406+ .with_for_update (of = IcebergTables )
407+ .filter (
408+ IcebergTables .catalog_name == self .name ,
409+ IcebergTables .table_namespace == database_name ,
410+ IcebergTables .table_name == table_name ,
411+ IcebergTables .metadata_location == current_table .metadata_location ,
412+ )
413+ .one ()
414+ )
415+ tbl .metadata_location = new_metadata_location
416+ tbl .previous_metadata_location = current_table .metadata_location
417+ except NoResultFound as e :
418+ raise CommitFailedException (f"Table has been updated by another process: { database_name } .{ table_name } " ) from e
419+ session .commit ()
420+
421+ return CommitTableResponse (metadata = updated_metadata , metadata_location = new_metadata_location )
334422
335423 def _namespace_exists (self , identifier : Union [str , Identifier ]) -> bool :
336424 namespace = self .identifier_to_database (identifier )
0 commit comments