Skip to content

Commit 289fc44

Browse files
jhfclaude
andcommitted
Make native Rust planner the default, PL/pgSQL opt-in
The native planner is 10-13x faster with O(1) scaling at the 1M+ row benchmark. It now runs by default without any GUC setting. To fall back to PL/pgSQL: SET sql_saga.temporal_merge.use_plpgsql_planner = true; Swaps primary/alternate expected files so native output is the primary expected result. Removes orphan expected files that didn't match any test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 082929a commit 289fc44

14 files changed

Lines changed: 239 additions & 238 deletions

expected/060_temporal_merge_basic.out

Whitespace-only changes.

expected/066_temporal_merge_range_only.out

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,21 @@ CALL sql_saga.temporal_merge(
281281
primary_identity_columns => '{id}'::text[]
282282
);
283283
ERROR: Source table "source_boundary" must have either the range column "valid_range" or the component column "<NULL>".
284-
CONTEXT: PL/pgSQL function sql_saga.temporal_merge_plan(regclass,regclass,sql_saga.temporal_merge_mode,name,text[],name,name,sql_saga.temporal_merge_delete_mode,jsonb,text[],boolean,boolean) line 294 at RAISE
285-
SQL statement "INSERT INTO temporal_merge_plan
286-
SELECT * FROM sql_saga.temporal_merge_plan(
287-
target_table => temporal_merge.target_table,
288-
source_table => temporal_merge.source_table,
289-
mode => temporal_merge.mode,
290-
era_name => temporal_merge.era_name,
291-
identity_columns => v_identity_cols_discovered,
292-
row_id_column => temporal_merge.row_id_column,
293-
founding_id_column => temporal_merge.founding_id_column,
294-
delete_mode => temporal_merge.delete_mode,
295-
lookup_keys => v_natural_identity_keys_discovered,
296-
ephemeral_columns => temporal_merge.ephemeral_columns,
297-
p_log_trace => v_log_trace,
298-
p_log_sql => v_log_sql
299-
)"
300-
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 207 at SQL statement
284+
CONTEXT: SQL statement "SELECT sql_saga.temporal_merge_plan_native(
285+
target_table => temporal_merge.target_table::oid,
286+
source_table => temporal_merge.source_table::oid,
287+
mode => temporal_merge.mode::text,
288+
era_name => temporal_merge.era_name::text,
289+
identity_columns => v_identity_cols_discovered,
290+
row_id_column => temporal_merge.row_id_column::text,
291+
founding_id_column => temporal_merge.founding_id_column::text,
292+
delete_mode => temporal_merge.delete_mode::text,
293+
lookup_keys => v_natural_identity_keys_discovered,
294+
ephemeral_columns => temporal_merge.ephemeral_columns,
295+
p_log_trace => v_log_trace,
296+
p_log_sql => v_log_sql
297+
)"
298+
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 192 at PERFORM
301299
ROLLBACK TO SAVEPOINT expect_error;
302300
\echo '--- Workaround: Use a view to compute the range from boundary columns ---'
303301
--- Workaround: Use a view to compute the range from boundary columns ---

expected/066_temporal_merge_range_only_1.out

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -281,21 +281,23 @@ CALL sql_saga.temporal_merge(
281281
primary_identity_columns => '{id}'::text[]
282282
);
283283
ERROR: Source table "source_boundary" must have either the range column "valid_range" or the component column "<NULL>".
284-
CONTEXT: SQL statement "SELECT sql_saga.temporal_merge_plan_native(
285-
target_table => temporal_merge.target_table::oid,
286-
source_table => temporal_merge.source_table::oid,
287-
mode => temporal_merge.mode::text,
288-
era_name => temporal_merge.era_name::text,
289-
identity_columns => v_identity_cols_discovered,
290-
row_id_column => temporal_merge.row_id_column::text,
291-
founding_id_column => temporal_merge.founding_id_column::text,
292-
delete_mode => temporal_merge.delete_mode::text,
293-
lookup_keys => v_natural_identity_keys_discovered,
294-
ephemeral_columns => temporal_merge.ephemeral_columns,
295-
p_log_trace => v_log_trace,
296-
p_log_sql => v_log_sql
297-
)"
298-
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 192 at PERFORM
284+
CONTEXT: PL/pgSQL function sql_saga.temporal_merge_plan(regclass,regclass,sql_saga.temporal_merge_mode,name,text[],name,name,sql_saga.temporal_merge_delete_mode,jsonb,text[],boolean,boolean) line 294 at RAISE
285+
SQL statement "INSERT INTO temporal_merge_plan
286+
SELECT * FROM sql_saga.temporal_merge_plan(
287+
target_table => temporal_merge.target_table,
288+
source_table => temporal_merge.source_table,
289+
mode => temporal_merge.mode,
290+
era_name => temporal_merge.era_name,
291+
identity_columns => v_identity_cols_discovered,
292+
row_id_column => temporal_merge.row_id_column,
293+
founding_id_column => temporal_merge.founding_id_column,
294+
delete_mode => temporal_merge.delete_mode,
295+
lookup_keys => v_natural_identity_keys_discovered,
296+
ephemeral_columns => temporal_merge.ephemeral_columns,
297+
p_log_trace => v_log_trace,
298+
p_log_sql => v_log_sql
299+
)"
300+
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 207 at SQL statement
299301
ROLLBACK TO SAVEPOINT expect_error;
300302
\echo '--- Workaround: Use a view to compute the range from boundary columns ---'
301303
--- Workaround: Use a view to compute the range from boundary columns ---

expected/067_temporal_merge_parameters.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ EXCEPTION WHEN others THEN
248248
RAISE NOTICE 'Caught expected error for non-existent row_id_column: %', SQLERRM;
249249
END;
250250
$$;
251-
NOTICE: Caught expected error for non-existent row_id_column: row_id_column "non_existent_row_id" does not exist in source table tm_bad_cols_sources
251+
NOTICE: Caught expected error for non-existent row_id_column: row_id_column "non_existent_row_id" does not exist in source table tm_bad_cols_source
252252
-- Test with non-existent founding_id_column
253253
DO $$
254254
BEGIN
@@ -264,7 +264,7 @@ EXCEPTION WHEN others THEN
264264
RAISE NOTICE 'Caught expected error for non-existent founding_id_column: %', SQLERRM;
265265
END;
266266
$$;
267-
NOTICE: Caught expected error for non-existent founding_id_column: founding_id_column "non_existent_founding_id" does not exist in source table tm_bad_cols_sources
267+
NOTICE: Caught expected error for non-existent founding_id_column: founding_id_column "non_existent_founding_id" does not exist in source table tm_bad_cols_source
268268
ROLLBACK TO SAVEPOINT scenario_10;
269269
SAVEPOINT scenario_11;
270270
-- Scenario 11: Test using a non-default row_id_column name.
@@ -362,7 +362,7 @@ EXCEPTION WHEN others THEN
362362
RAISE NOTICE 'Caught expected error for missing default row_id column: %', SQLERRM;
363363
END;
364364
$$;
365-
NOTICE: Caught expected error for missing default row_id column: row_id_column "row_id" does not exist in source table tm_no_rowid_sourcess
365+
NOTICE: Caught expected error for missing default row_id column: row_id_column "row_id" does not exist in source table tm_no_rowid_sources
366366
ROLLBACK TO SAVEPOINT scenario_12;
367367
SAVEPOINT scenario_13;
368368
-- Scenario 13: Test `temporal_merge` with non-standard column and era names.

expected/067_temporal_merge_parameters_1.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ EXCEPTION WHEN others THEN
248248
RAISE NOTICE 'Caught expected error for non-existent row_id_column: %', SQLERRM;
249249
END;
250250
$$;
251-
NOTICE: Caught expected error for non-existent row_id_column: row_id_column "non_existent_row_id" does not exist in source table tm_bad_cols_source
251+
NOTICE: Caught expected error for non-existent row_id_column: row_id_column "non_existent_row_id" does not exist in source table tm_bad_cols_sources
252252
-- Test with non-existent founding_id_column
253253
DO $$
254254
BEGIN
@@ -264,7 +264,7 @@ EXCEPTION WHEN others THEN
264264
RAISE NOTICE 'Caught expected error for non-existent founding_id_column: %', SQLERRM;
265265
END;
266266
$$;
267-
NOTICE: Caught expected error for non-existent founding_id_column: founding_id_column "non_existent_founding_id" does not exist in source table tm_bad_cols_source
267+
NOTICE: Caught expected error for non-existent founding_id_column: founding_id_column "non_existent_founding_id" does not exist in source table tm_bad_cols_sources
268268
ROLLBACK TO SAVEPOINT scenario_10;
269269
SAVEPOINT scenario_11;
270270
-- Scenario 11: Test using a non-default row_id_column name.
@@ -362,7 +362,7 @@ EXCEPTION WHEN others THEN
362362
RAISE NOTICE 'Caught expected error for missing default row_id column: %', SQLERRM;
363363
END;
364364
$$;
365-
NOTICE: Caught expected error for missing default row_id column: row_id_column "row_id" does not exist in source table tm_no_rowid_sources
365+
NOTICE: Caught expected error for missing default row_id column: row_id_column "row_id" does not exist in source table tm_no_rowid_sourcess
366366
ROLLBACK TO SAVEPOINT scenario_12;
367367
SAVEPOINT scenario_13;
368368
-- Scenario 13: Test `temporal_merge` with non-standard column and era names.

expected/068_temporal_merge_natural_key.out

Whitespace-only changes.

expected/088_temporal_merge_source_columns.out

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -91,23 +91,21 @@ CALL sql_saga.temporal_merge(
9191
natural_identity_columns => ARRAY['org_nr_typo']
9292
);
9393
ERROR: lookup_column org_nr_typo does not exist in target table repro.units
94-
CONTEXT: PL/pgSQL function sql_saga.temporal_merge_plan(regclass,regclass,sql_saga.temporal_merge_mode,name,text[],name,name,sql_saga.temporal_merge_delete_mode,jsonb,text[],boolean,boolean) line 493 at RAISE
95-
SQL statement "INSERT INTO temporal_merge_plan
96-
SELECT * FROM sql_saga.temporal_merge_plan(
97-
target_table => temporal_merge.target_table,
98-
source_table => temporal_merge.source_table,
99-
mode => temporal_merge.mode,
100-
era_name => temporal_merge.era_name,
101-
identity_columns => v_identity_cols_discovered,
102-
row_id_column => temporal_merge.row_id_column,
103-
founding_id_column => temporal_merge.founding_id_column,
104-
delete_mode => temporal_merge.delete_mode,
105-
lookup_keys => v_natural_identity_keys_discovered,
106-
ephemeral_columns => temporal_merge.ephemeral_columns,
107-
p_log_trace => v_log_trace,
108-
p_log_sql => v_log_sql
109-
)"
110-
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 207 at SQL statement
94+
CONTEXT: SQL statement "SELECT sql_saga.temporal_merge_plan_native(
95+
target_table => temporal_merge.target_table::oid,
96+
source_table => temporal_merge.source_table::oid,
97+
mode => temporal_merge.mode::text,
98+
era_name => temporal_merge.era_name::text,
99+
identity_columns => v_identity_cols_discovered,
100+
row_id_column => temporal_merge.row_id_column::text,
101+
founding_id_column => temporal_merge.founding_id_column::text,
102+
delete_mode => temporal_merge.delete_mode::text,
103+
lookup_keys => v_natural_identity_keys_discovered,
104+
ephemeral_columns => temporal_merge.ephemeral_columns,
105+
p_log_trace => v_log_trace,
106+
p_log_sql => v_log_sql
107+
)"
108+
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 192 at PERFORM
111109
ROLLBACK TO SAVEPOINT expect_error;
112110
\echo '--- Scenario 3: FAIL - natural_identity_column missing from target ---'
113111
--- Scenario 3: FAIL - natural_identity_column missing from target ---
@@ -120,24 +118,22 @@ CALL sql_saga.temporal_merge(
120118
primary_identity_columns => ARRAY['id'],
121119
natural_identity_columns => ARRAY['org_nr_typo']
122120
);
123-
ERROR: Source table "source_with_typo" must have a "valid_range", "valid_until", or "<NULL>" column.
124-
CONTEXT: PL/pgSQL function sql_saga.temporal_merge_plan(regclass,regclass,sql_saga.temporal_merge_mode,name,text[],name,name,sql_saga.temporal_merge_delete_mode,jsonb,text[],boolean,boolean) line 322 at RAISE
125-
SQL statement "INSERT INTO temporal_merge_plan
126-
SELECT * FROM sql_saga.temporal_merge_plan(
127-
target_table => temporal_merge.target_table,
128-
source_table => temporal_merge.source_table,
129-
mode => temporal_merge.mode,
130-
era_name => temporal_merge.era_name,
131-
identity_columns => v_identity_cols_discovered,
132-
row_id_column => temporal_merge.row_id_column,
133-
founding_id_column => temporal_merge.founding_id_column,
134-
delete_mode => temporal_merge.delete_mode,
135-
lookup_keys => v_natural_identity_keys_discovered,
136-
ephemeral_columns => temporal_merge.ephemeral_columns,
137-
p_log_trace => v_log_trace,
138-
p_log_sql => v_log_sql
139-
)"
140-
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 207 at SQL statement
121+
ERROR: lookup_column org_nr_typo does not exist in target table repro.units
122+
CONTEXT: SQL statement "SELECT sql_saga.temporal_merge_plan_native(
123+
target_table => temporal_merge.target_table::oid,
124+
source_table => temporal_merge.source_table::oid,
125+
mode => temporal_merge.mode::text,
126+
era_name => temporal_merge.era_name::text,
127+
identity_columns => v_identity_cols_discovered,
128+
row_id_column => temporal_merge.row_id_column::text,
129+
founding_id_column => temporal_merge.founding_id_column::text,
130+
delete_mode => temporal_merge.delete_mode::text,
131+
lookup_keys => v_natural_identity_keys_discovered,
132+
ephemeral_columns => temporal_merge.ephemeral_columns,
133+
p_log_trace => v_log_trace,
134+
p_log_sql => v_log_sql
135+
)"
136+
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 192 at PERFORM
141137
ROLLBACK TO SAVEPOINT expect_error;
142138
\echo '--- Scenario 4: FAIL - identity_column missing from target ---'
143139
--- Scenario 4: FAIL - identity_column missing from target ---
@@ -150,24 +146,22 @@ CALL sql_saga.temporal_merge(
150146
primary_identity_columns => ARRAY['id_typo'],
151147
natural_identity_columns => ARRAY['org_nr']
152148
);
153-
ERROR: Source table "source_with_id" must have a "valid_range", "valid_until", or "<NULL>" column.
154-
CONTEXT: PL/pgSQL function sql_saga.temporal_merge_plan(regclass,regclass,sql_saga.temporal_merge_mode,name,text[],name,name,sql_saga.temporal_merge_delete_mode,jsonb,text[],boolean,boolean) line 322 at RAISE
155-
SQL statement "INSERT INTO temporal_merge_plan
156-
SELECT * FROM sql_saga.temporal_merge_plan(
157-
target_table => temporal_merge.target_table,
158-
source_table => temporal_merge.source_table,
159-
mode => temporal_merge.mode,
160-
era_name => temporal_merge.era_name,
161-
identity_columns => v_identity_cols_discovered,
162-
row_id_column => temporal_merge.row_id_column,
163-
founding_id_column => temporal_merge.founding_id_column,
164-
delete_mode => temporal_merge.delete_mode,
165-
lookup_keys => v_natural_identity_keys_discovered,
166-
ephemeral_columns => temporal_merge.ephemeral_columns,
167-
p_log_trace => v_log_trace,
168-
p_log_sql => v_log_sql
169-
)"
170-
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 207 at SQL statement
149+
ERROR: identity_column id_typo does not exist in target table repro.units
150+
CONTEXT: SQL statement "SELECT sql_saga.temporal_merge_plan_native(
151+
target_table => temporal_merge.target_table::oid,
152+
source_table => temporal_merge.source_table::oid,
153+
mode => temporal_merge.mode::text,
154+
era_name => temporal_merge.era_name::text,
155+
identity_columns => v_identity_cols_discovered,
156+
row_id_column => temporal_merge.row_id_column::text,
157+
founding_id_column => temporal_merge.founding_id_column::text,
158+
delete_mode => temporal_merge.delete_mode::text,
159+
lookup_keys => v_natural_identity_keys_discovered,
160+
ephemeral_columns => temporal_merge.ephemeral_columns,
161+
p_log_trace => v_log_trace,
162+
p_log_sql => v_log_sql
163+
)"
164+
PL/pgSQL function sql_saga.temporal_merge(regclass,regclass,text[],sql_saga.temporal_merge_mode,name,name,name,boolean,text[],sql_saga.temporal_merge_delete_mode,boolean,name,name,name,name,text[],boolean) line 192 at PERFORM
171165
ROLLBACK TO SAVEPOINT expect_error;
172166
\echo '--- Scenario 5: Happy path (ID back-filling) ---'
173167
--- Scenario 5: Happy path (ID back-filling) ---

0 commit comments

Comments
 (0)