@@ -389,22 +389,30 @@ def _delete_variable_metadata(
389389 ))
390390
391391
392- def _compact_variable_ordinals (
392+ def _assert_postgresql_column_slot_available (
393393 connection : Any ,
394394 * ,
395- core : Any ,
396- variables : list [ dict [ str , Any ]] ,
395+ relation : Table ,
396+ target_profile : SqlProfile ,
397397) -> None :
398- """Keep normative variable source order contiguous after deletion."""
399- for source_ordinal , variable in enumerate (variables , start = 1 ):
400- if int (variable ["source_ordinal" ]) == source_ordinal :
401- continue
402- connection .execute (
403- update (core .variable )
404- .where (core .variable .c .variable_id == variable ["variable_id" ])
405- .values (source_ordinal = source_ordinal )
398+ """Reject PostgreSQL creates when dropped columns exhaust attribute slots."""
399+ if connection .dialect .name != "postgresql" :
400+ return
401+ qualified_relation = connection .dialect .identifier_preparer .format_table (relation )
402+ attribute_slots = connection .execute (text (
403+ "SELECT COUNT(*) FROM pg_attribute "
404+ "WHERE attrelid = to_regclass(:relation_name) AND attnum > 0"
405+ ), {"relation_name" : qualified_relation }).scalar_one ()
406+ if not isinstance (attribute_slots , int ) or attribute_slots < 1 :
407+ raise TransformationError (
408+ "physical_table_missing" ,
409+ "The target dataset's physical wide table does not exist." ,
410+ )
411+ if attribute_slots >= target_profile .max_source_variables + 1 :
412+ raise TransformationError (
413+ "source_variable_limit" ,
414+ "PostgreSQL physical column slots are exhausted for this dataset table." ,
406415 )
407- variable ["source_ordinal" ] = source_ordinal
408416
409417
410418def _failure_boundary (_name : str ) -> None :
@@ -555,6 +563,9 @@ def _apply_plan_on_connection(
555563 )
556564 by_name = {str (row ["source_name" ]).casefold (): row for row in variables }
557565 used_physical = {str (row ["physical_name" ]).casefold () for row in variables }
566+ next_source_ordinal = max (
567+ (int (row ["source_ordinal" ]) for row in variables ), default = 0 ,
568+ ) + 1
558569 quote = connection .dialect .identifier_preparer .quote
559570 qualified_table = connection .dialect .identifier_preparer .format_table (relation )
560571 numeric_type = (
@@ -603,6 +614,9 @@ def _apply_plan_on_connection(
603614 )
604615 )
605616 if creates_target :
617+ _assert_postgresql_column_slot_available (
618+ connection , relation = relation , target_profile = target_profile ,
619+ )
606620 target_name = (
607621 operation .variable
608622 if isinstance (operation , CreateVariableOperation )
@@ -631,7 +645,7 @@ def _apply_plan_on_connection(
631645 created_target = {
632646 "variable_id" : str (uuid4 ()),
633647 "dataset_id" : dataset_id ,
634- "source_ordinal" : len ( variables ) + 1 ,
648+ "source_ordinal" : next_source_ordinal ,
635649 "source_name" : target_name ,
636650 "physical_name" : target_physical ,
637651 "storage_kind" : storage_kind ,
@@ -648,6 +662,7 @@ def _apply_plan_on_connection(
648662 )
649663 connection .execute (insert (core .variable ).values (** created_target ))
650664 variables .append (created_target )
665+ next_source_ordinal += 1
651666 by_name [str (created_target ["source_name" ]).casefold ()] = created_target
652667 _failure_boundary ("catalog" )
653668 if isinstance (operation , CreateVariableOperation ):
@@ -725,36 +740,10 @@ def _apply_plan_on_connection(
725740 )
726741 _delete_variable_metadata (connection , core = core , variable = variable )
727742 by_name .pop (operation .variable .casefold (), None )
728- used_physical .discard (str (variable ["physical_name" ]).casefold ())
729743 variables = [
730744 row for row in variables
731745 if row ["variable_id" ] != variable ["variable_id" ]
732746 ]
733- _compact_variable_ordinals (
734- connection , core = core , variables = variables ,
735- )
736- canonical_physical = {"__case_ordinal" }
737- for remaining_variable in variables :
738- expected_physical = physical_name (
739- str (remaining_variable ["source_name" ]), canonical_physical ,
740- )
741- current_physical = str (remaining_variable ["physical_name" ])
742- if current_physical == expected_physical :
743- continue
744- connection .exec_driver_sql (
745- f"ALTER TABLE { qualified_table } RENAME COLUMN "
746- f"{ quote (current_physical )} TO { quote (expected_physical )} "
747- )
748- connection .execute (
749- update (core .variable )
750- .where (
751- core .variable .c .variable_id
752- == remaining_variable ["variable_id" ]
753- )
754- .values (physical_name = expected_physical )
755- )
756- remaining_variable ["physical_name" ] = expected_physical
757- used_physical = canonical_physical
758747 relation = Table (
759748 table_name , MetaData (), schema = dataset .get ("physical_table_schema" ),
760749 autoload_with = connection ,
0 commit comments