136136DO_NOT_UPDATE_STATS = "DO_NOT_UPDATE_STATS"
137137DO_NOT_UPDATE_STATS_DEFAULT = "true"
138138
139+ NO_LOCK_EXPECTED_KEY = "expected_parameter_key"
140+ NO_LOCK_EXPECTED_VALUE = "expected_parameter_value"
141+
139142logger = logging .getLogger (__name__ )
140143
141144
@@ -499,6 +502,66 @@ def _do_wait_for_lock() -> LockResponse:
499502
500503 return _do_wait_for_lock ()
501504
505+ @staticmethod
506+ def _hive_lock_enabled (table_properties : Properties , catalog_properties : Properties ) -> bool :
507+ """Determine whether HMS locking is enabled for a commit.
508+
509+ Matches the Java implementation in HiveTableOperations: checks the table property first,
510+ then falls back to catalog properties, then defaults to True.
511+ """
512+ if TableProperties .HIVE_LOCK_ENABLED in table_properties :
513+ return property_as_bool (
514+ table_properties , TableProperties .HIVE_LOCK_ENABLED , TableProperties .HIVE_LOCK_ENABLED_DEFAULT
515+ )
516+ return property_as_bool (catalog_properties , TableProperties .HIVE_LOCK_ENABLED , TableProperties .HIVE_LOCK_ENABLED_DEFAULT )
517+
518+ def commit_table (
519+ self , table : Table , requirements : tuple [TableRequirement , ...], updates : tuple [TableUpdate , ...]
520+ ) -> CommitTableResponse :
521+ """Commit updates to a table.
522+
523+ Args:
524+ table (Table): The table to be updated.
525+ requirements: (Tuple[TableRequirement, ...]): Table requirements.
526+ updates: (Tuple[TableUpdate, ...]): Table updates.
527+
528+ Returns:
529+ CommitTableResponse: The updated metadata.
530+
531+ Raises:
532+ NoSuchTableError: If a table with the given identifier does not exist.
533+ CommitFailedException: Requirement not met, or a conflict with a concurrent commit.
534+ """
535+ table_identifier = table .name ()
536+ database_name , table_name = self .identifier_to_database_and_table (table_identifier , NoSuchTableError )
537+ lock_enabled = self ._hive_lock_enabled (table .properties , self .properties )
538+ # commit to hive
539+ # https://github.com/apache/hive/blob/master/standalone-metastore/metastore-common/src/main/thrift/hive_metastore.thrift#L1232
540+ with self ._client as open_client :
541+ if lock_enabled :
542+ lock : LockResponse = open_client .lock (self ._create_lock_request (database_name , table_name ))
543+
544+ try :
545+ if lock .state != LockState .ACQUIRED :
546+ if lock .state == LockState .WAITING :
547+ self ._wait_for_lock (database_name , table_name , lock .lockid , open_client )
548+ else :
549+ raise CommitFailedException (f"Failed to acquire lock for { table_identifier } , state: { lock .state } " )
550+
551+ return self ._do_commit (
552+ open_client , table_identifier , database_name , table_name , requirements , updates ,
553+ lock_enabled = True ,
554+ )
555+ except WaitingForLockException as e :
556+ raise CommitFailedException (f"Failed to acquire lock for { table_identifier } , state: { lock .state } " ) from e
557+ finally :
558+ open_client .unlock (UnlockRequest (lockid = lock .lockid ))
559+ else :
560+ return self ._do_commit (
561+ open_client , table_identifier , database_name , table_name , requirements , updates ,
562+ lock_enabled = False ,
563+ )
564+
502565 def _do_commit (
503566 self ,
504567 open_client : Client ,
@@ -507,10 +570,13 @@ def _do_commit(
507570 table_name : str ,
508571 requirements : tuple [TableRequirement , ...],
509572 updates : tuple [TableUpdate , ...],
573+ lock_enabled : bool = True ,
510574 ) -> CommitTableResponse :
511575 """Perform the actual commit logic (get table, update, write metadata, alter/create in HMS).
512576
513577 This method contains the core commit logic, separated from locking concerns.
578+ When lock_enabled is False, an optimistic concurrency check via the HMS EnvironmentContext
579+ is used instead (requires HIVE-26882 on the server).
514580 """
515581 hive_table : HiveTable | None
516582 current_table : Table | None
@@ -566,11 +632,16 @@ def _do_commit(
566632 updated_staged_table .location (),
567633 property_as_bool (self .properties , HIVE2_COMPATIBLE , HIVE2_COMPATIBLE_DEFAULT ),
568634 )
635+ env_context_properties : dict [str , str ] = {DO_NOT_UPDATE_STATS : DO_NOT_UPDATE_STATS_DEFAULT }
636+ if not lock_enabled :
637+ env_context_properties [NO_LOCK_EXPECTED_KEY ] = PROP_METADATA_LOCATION
638+ env_context_properties [NO_LOCK_EXPECTED_VALUE ] = current_table .metadata_location
639+
569640 open_client .alter_table_with_environment_context (
570641 dbname = database_name ,
571642 tbl_name = table_name ,
572643 new_tbl = hive_table ,
573- environment_context = EnvironmentContext (properties = { DO_NOT_UPDATE_STATS : DO_NOT_UPDATE_STATS_DEFAULT } ),
644+ environment_context = EnvironmentContext (properties = env_context_properties ),
574645 )
575646 else :
576647 # Table does not exist, create it.
@@ -589,60 +660,6 @@ def _do_commit(
589660 metadata = updated_staged_table .metadata , metadata_location = updated_staged_table .metadata_location
590661 )
591662
592- @staticmethod
593- def _hive_lock_enabled (table_properties : Properties , catalog_properties : Properties ) -> bool :
594- """Determine whether HMS locking is enabled for a commit.
595-
596- Matches the Java implementation in HiveTableOperations: checks the table property first,
597- then falls back to catalog properties, then defaults to True.
598- """
599- if TableProperties .HIVE_LOCK_ENABLED in table_properties :
600- return property_as_bool (
601- table_properties , TableProperties .HIVE_LOCK_ENABLED , TableProperties .HIVE_LOCK_ENABLED_DEFAULT
602- )
603- return property_as_bool (catalog_properties , TableProperties .HIVE_LOCK_ENABLED , TableProperties .HIVE_LOCK_ENABLED_DEFAULT )
604-
605- def commit_table (
606- self , table : Table , requirements : tuple [TableRequirement , ...], updates : tuple [TableUpdate , ...]
607- ) -> CommitTableResponse :
608- """Commit updates to a table.
609-
610- Args:
611- table (Table): The table to be updated.
612- requirements: (Tuple[TableRequirement, ...]): Table requirements.
613- updates: (Tuple[TableUpdate, ...]): Table updates.
614-
615- Returns:
616- CommitTableResponse: The updated metadata.
617-
618- Raises:
619- NoSuchTableError: If a table with the given identifier does not exist.
620- CommitFailedException: Requirement not met, or a conflict with a concurrent commit.
621- """
622- table_identifier = table .name ()
623- database_name , table_name = self .identifier_to_database_and_table (table_identifier , NoSuchTableError )
624- lock_enabled = self ._hive_lock_enabled (table .properties , self .properties )
625- # commit to hive
626- # https://github.com/apache/hive/blob/master/standalone-metastore/metastore-common/src/main/thrift/hive_metastore.thrift#L1232
627- with self ._client as open_client :
628- if lock_enabled :
629- lock : LockResponse = open_client .lock (self ._create_lock_request (database_name , table_name ))
630-
631- try :
632- if lock .state != LockState .ACQUIRED :
633- if lock .state == LockState .WAITING :
634- self ._wait_for_lock (database_name , table_name , lock .lockid , open_client )
635- else :
636- raise CommitFailedException (f"Failed to acquire lock for { table_identifier } , state: { lock .state } " )
637-
638- return self ._do_commit (open_client , table_identifier , database_name , table_name , requirements , updates )
639- except WaitingForLockException as e :
640- raise CommitFailedException (f"Failed to acquire lock for { table_identifier } , state: { lock .state } " ) from e
641- finally :
642- open_client .unlock (UnlockRequest (lockid = lock .lockid ))
643- else :
644- return self ._do_commit (open_client , table_identifier , database_name , table_name , requirements , updates )
645-
646663 def load_table (self , identifier : str | Identifier ) -> Table :
647664 """Load the table's metadata and return the table instance.
648665
0 commit comments