@@ -355,6 +355,25 @@ def _delete_variable_metadata(
355355 core .variable .c .variable_id == variable_id
356356 ))
357357
358+
359+ def _compact_variable_ordinals (
360+ connection : Any ,
361+ * ,
362+ core : Any ,
363+ variables : list [dict [str , Any ]],
364+ ) -> None :
365+ """Keep normative variable source order contiguous after deletion."""
366+ for source_ordinal , variable in enumerate (variables , start = 1 ):
367+ if int (variable ["source_ordinal" ]) == source_ordinal :
368+ continue
369+ connection .execute (
370+ update (core .variable )
371+ .where (core .variable .c .variable_id == variable ["variable_id" ])
372+ .values (source_ordinal = source_ordinal )
373+ )
374+ variable ["source_ordinal" ] = source_ordinal
375+
376+
358377def _failure_boundary (_name : str ) -> None :
359378 """Synthetic-test hook for schema/data/catalog/audit failure boundaries."""
360379
@@ -477,8 +496,6 @@ def _apply_plan_on_connection(
477496 )
478497 by_name = {str (row ["source_name" ]).casefold (): row for row in variables }
479498 used_physical = {str (row ["physical_name" ]).casefold () for row in variables }
480- next_ordinal = max (int (row ["source_ordinal" ]) for row in variables ) + 1
481- target_rows_by_operation : dict [int , dict [str , Any ]] = {}
482499 quote = connection .dialect .identifier_preparer .quote
483500 qualified_table = connection .dialect .identifier_preparer .format_table (relation )
484501 numeric_type = (
@@ -515,59 +532,61 @@ def _apply_plan_on_connection(
515532 "The Dolt working set changed after locking the dataset." ,
516533 )
517534
518- # All non-transactional DDL precedes every data/catalog mutation. This makes
519- # Compensation is journal-bounded to target columns and catalog identities
520- # created by this apply, without resetting unrelated database state.
521- for operation in create_operations :
522- target_name = (
523- operation .variable
524- if isinstance (operation , CreateVariableOperation )
525- else operation .target
526- )
527- target_physical = physical_name (target_name , used_physical )
528- storage_kind = (
529- operation .storage_kind
530- if isinstance (operation , CreateVariableOperation )
531- else "numeric"
532- )
533- string_width = (
534- operation .declared_string_width
535- if isinstance (operation , CreateVariableOperation )
536- else None
537- )
538- column_type = numeric_type if storage_kind == "numeric" else f"VARCHAR({ string_width } )"
539- connection .exec_driver_sql (
540- f"ALTER TABLE { qualified_table } ADD COLUMN "
541- f"{ quote (target_physical )} { column_type } NULL"
542- )
543- if mutation_journal is not None :
544- mutation_journal ["added_columns" ].append (target_physical )
545- target_row = {
546- "variable_id" : str (uuid4 ()),
547- "dataset_id" : dataset_id ,
548- "source_ordinal" : next_ordinal ,
549- "source_name" : target_name ,
550- "physical_name" : target_physical ,
551- "storage_kind" : storage_kind ,
552- "declared_string_width" : string_width ,
553- "variable_label" : None ,
554- }
555- next_ordinal += 1
556- target_rows_by_operation [id (operation )] = target_row
557- if mutation_journal is not None :
558- mutation_journal ["target_rows" ].append (dict (target_row ))
559- if create_operations :
560- _failure_boundary ("schema" )
561-
562- if create_operations :
563- relation = Table (
564- table_name , MetaData (), schema = dataset .get ("physical_table_schema" ),
565- autoload_with = connection ,
566- )
567-
535+ # Schema changes are allowed only on profiles whose DDL participates in
536+ # this apply transaction, so execute them in canonical operation order.
537+ # That preserves deterministic physical naming across delete/recreate flows.
568538 for operation in plan .operations :
569- created_target = target_rows_by_operation .get (id (operation ))
570- if created_target is not None :
539+ creates_target = (
540+ isinstance (operation , CreateVariableOperation )
541+ or (
542+ isinstance (operation , (RecodeOperation , AssignOperation ))
543+ and operation .target_mode == "create"
544+ )
545+ )
546+ if creates_target :
547+ target_name = (
548+ operation .variable
549+ if isinstance (operation , CreateVariableOperation )
550+ else operation .target
551+ )
552+ target_physical = physical_name (target_name , used_physical )
553+ storage_kind = (
554+ operation .storage_kind
555+ if isinstance (operation , CreateVariableOperation )
556+ else "numeric"
557+ )
558+ string_width = (
559+ operation .declared_string_width
560+ if isinstance (operation , CreateVariableOperation )
561+ else None
562+ )
563+ column_type = numeric_type if storage_kind == "numeric" else "TEXT"
564+ null_clause = (
565+ "NULL" if storage_kind == "numeric"
566+ else "NOT NULL DEFAULT ''"
567+ )
568+ connection .exec_driver_sql (
569+ f"ALTER TABLE { qualified_table } ADD COLUMN "
570+ f"{ quote (target_physical )} { column_type } { null_clause } "
571+ )
572+ created_target = {
573+ "variable_id" : str (uuid4 ()),
574+ "dataset_id" : dataset_id ,
575+ "source_ordinal" : len (variables ) + 1 ,
576+ "source_name" : target_name ,
577+ "physical_name" : target_physical ,
578+ "storage_kind" : storage_kind ,
579+ "declared_string_width" : string_width ,
580+ "variable_label" : None ,
581+ }
582+ if mutation_journal is not None :
583+ mutation_journal ["added_columns" ].append (target_physical )
584+ mutation_journal ["target_rows" ].append (dict (created_target ))
585+ _failure_boundary ("schema" )
586+ relation = Table (
587+ table_name , MetaData (), schema = dataset .get ("physical_table_schema" ),
588+ autoload_with = connection ,
589+ )
571590 connection .execute (insert (core .variable ).values (** created_target ))
572591 variables .append (created_target )
573592 by_name [str (created_target ["source_name" ]).casefold ()] = created_target
@@ -647,10 +666,14 @@ def _apply_plan_on_connection(
647666 )
648667 _delete_variable_metadata (connection , core = core , variable = variable )
649668 by_name .pop (operation .variable .casefold (), None )
669+ used_physical .discard (str (variable ["physical_name" ]).casefold ())
650670 variables = [
651671 row for row in variables
652672 if row ["variable_id" ] != variable ["variable_id" ]
653673 ]
674+ _compact_variable_ordinals (
675+ connection , core = core , variables = variables ,
676+ )
654677 relation = Table (
655678 table_name , MetaData (), schema = dataset .get ("physical_table_schema" ),
656679 autoload_with = connection ,
0 commit comments