Skip to content

Fixed multi-array unnest - #3401

Merged
Hydrocharged merged 1 commit into
mainfrom
daylon/issue-3366
Sep 21, 2026
Merged

Hydrocharged merged 1 commit into
mainfrom
daylon/issue-3366

Conversation

@Hydrocharged

@Hydrocharged Hydrocharged commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 19890 19914
Failures 22200 22176
Partial Successes1 5428 5428
Main PR
Successful 47.2559% 47.3129%
Failures 52.7441% 52.6871%

${\color{lightgreen}Progressions (20)}$

inherit

QUERY: insert into inhpar select x, x::text from generate_series(1,5) x;
QUERY: insert into inhpar select x, x::text from generate_series(1,10) x;

numeric

QUERY: SELECT t1.id1, t1.id2, t1.result, t2.expected
    FROM num_result t1, num_exp_add t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != t2.expected;
QUERY: SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 10) as expected
    FROM num_result t1, num_exp_add t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != round(t2.expected, 10);
QUERY: SELECT t1.id1, t1.id2, t1.result, t2.expected
    FROM num_result t1, num_exp_sub t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != t2.expected;
QUERY: SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 40)
    FROM num_result t1, num_exp_sub t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != round(t2.expected, 40);
QUERY: SELECT t1.id1, t1.id2, t1.result, t2.expected
    FROM num_result t1, num_exp_mul t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != t2.expected;
QUERY: SELECT t1.id1, t1.id2, t1.result, round(t2.expected, 30) as expected
    FROM num_result t1, num_exp_mul t2
    WHERE t1.id1 = t2.id1 AND t1.id2 = t2.id2
    AND t1.result != round(t2.expected, 30);

rangefuncs

QUERY: select * from rngfunct(1) with ordinality as z(a,b,ord);
QUERY: select * from rngfunct(1) with ordinality as z(a,b,ord) where b > 100;
QUERY: select a,b,ord from rngfunct(1) with ordinality as z(a,b,ord);
QUERY: select * from unnest(array[10,20],array['foo','bar'],array[1.0]);
QUERY: select * from unnest(array[10,20],array['foo','bar'],array[1.0]) with ordinality as z(a,b,c,ord);
QUERY: select * from rows from(unnest(array[10,20],array['foo','bar'],array[1.0])) with ordinality as z(a,b,c,ord);
QUERY: select * from rngfunc2, rngfunct(rngfunc2.rngfuncid) with ordinality as z(rngfuncid,f2,ord) where rngfunc2.f2 = z.f2;
QUERY: CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc4(1) WITH ORDINALITY AS t1(a,b,c,o);
QUERY: SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
QUERY: CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc5(1) WITH ORDINALITY AS t1(a,b,c,o);
QUERY: CREATE VIEW vw_getrngfunc AS SELECT * FROM getrngfunc9(1) WITH ORDINALITY AS t1(a,b,c,o);

subselect

QUERY: select count(*) from tenk1 t
where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@itoqa

itoqa Bot commented Sep 17, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: 6b3e161: 16 test cases ran, 1 failed ❌, 15 passed ✅.

Summary

The run covers core database behavior for expanding and aligning array values, preserving types and NULLs, inserting rows, handling invalid input and rollback, and updating existing records during conflicts. Happy paths and edge cases are broadly healthy, but conflict-based projected writes expose a serious failure in a core data-change workflow.

Not safe to merge yet — a PR-attributable high-severity defect breaks projected inserts that update existing records, preventing reliable persistence and returned values. The remaining passing coverage does not offset the risk to this core write path.

Tests run by Ito

View full run

Result Severity Type Description
High severity General The projected insert did not update the existing row or return its final values. It stopped with an error saying that EXCLUDED.note was missing; follow-up checks also reported an ambiguous checked_value reference and a failed source_key conflict path.
General The invalid mixed array/scalar call shows an unsupported-function error, and the next valid array call returns two columns and two rows.
General The insert kept each number paired with the matching text value, and put NULL only in the shorter text column.
General The rejected insert left the table empty, and retrying the acceptable row inserted exactly one row with the correct values.
General The first and second FROM queries returned the same a and b columns with rows (1, 3) and (2, 4). The unsupported SELECT-list form was rejected without affecting the next query.
Insert The insert stored (1, first) and (2, second) in the matching destination columns.
Insert The insert succeeded and stored ids 1 and 2 with the matching labels first and second.
Insert The insert found the existing row, changed its label to new, returned (1, new), and stored (1, new).
Rev The query returned four columns and two rows with each value in the correct position: (1, a, true, 10) and (2, b, false, 20).
Rev The query returned two rows from the first array and showed NULL in the later array column for both rows.
Rev The aliased array values worked in number and text expressions, and the filter returned the expected row: 3 and b!.
Rev The empty array returned no rows with one column, and the populated array returned 1, 2, and 3 in order with the same shape.
Schema The query returned integer and text columns with the expected rows: (1, a) and (2, b).
Unnest The query returned two columns and paired the values as (1, a) and (2, b).
Unnest The query returned three rows, and the empty second array produced NULL in its column for every row.
Unnest The single-array query returned 10, 20, and 30 in order, so the existing SELECT-list behavior still works.

Tip

Reply with @itoqa to send us feedback on this test run.

@coffeegoddd

coffeegoddd commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

@Hydrocharged DOLT

read_tests from_latency to_latency percent_change
covering_index_scan_postgres 2.43 2.43 0.0
groupby_scan_postgres 82.96 80.03 -3.53
index_join_postgres 2.39 2.35 -1.67
index_join_scan_postgres 1.64 1.61 -1.83
index_scan_postgres 484.44 467.3 -3.54
oltp_point_select 0.37 0.37 0.0
oltp_read_only 6.55 6.55 0.0
select_random_points 0.73 0.73 0.0
select_random_ranges 1.04 1.03 -0.96
table_scan_postgres 493.24 475.79 -3.54
types_table_scan_postgres 1235.62 1170.65 -5.26
write_tests from_latency to_latency percent_change
oltp_delete_insert_postgres 6.67 6.67 0.0
oltp_insert 3.36 3.36 0.0
oltp_read_write 13.46 13.7 1.78
oltp_update_index 3.62 3.62 0.0
oltp_update_non_index 3.3 3.3 0.0
oltp_write_only 7.04 7.04 0.0
types_delete_insert_postgres 7.17 7.17 0.0

@itoqa

itoqa Bot commented Sep 17, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: feb6cd3: 13 test cases ran, 13 passed ✅.

Summary

The change is covered across normal array expansion and data-insert flows, including ordering, column types, aliases, joins, and persistence. Boundary and adversarial behavior is also exercised through unequal, empty, and null inputs, invalid mixed input, concurrent clients, and preservation of source-row alignment; all exercised behaviors are healthy.

Safe to merge — the run found no regressions, new failures, or previously reported failures attributable to this PR, and the covered behavior is passing across both normal and edge-case scenarios.

Tests run by Ito

View full run

Result Severity Type Description
Array The query returned three rows in order: (1, a), (2, b), and (3, NULL).
General Both array orderings returned two rows. Empty and NULL inputs stayed NULL in their own columns, while the two values from the non-empty array stayed in order.
General The invalid call returned the expected function-not-found error with no rows, and the next valid query returned 4 and 5 normally.
General The query returned integer and text columns with the requested aliases. The shorter text array produced a NULL value in the third row, and the column metadata showed integer and text types with the padded text value nullable.
General Joined rows kept each source identifier with its matching values and ordinal positions. Missing values stayed NULL instead of moving across source rows.
General The insert succeeded after the source columns were renamed and reordered. The destination kept 101 with alpha, 202 with beta, and 303 with gamma.
General Inserting unequal arrays kept the values in order and stored NULL for the missing label.
General Two SQL clients ran different array queries at the same time for 20 iterations. Each client kept the correct columns, rows, and NULL padding without affecting the other client.
Insert The insert succeeded, and the table returned the two expected id and label pairs in order.
Record Running the existing single-array query returned one column with the ordered values 7, 8, and 9.
Rev The query returned all four expected rows. Rows with empty or NULL arrays stayed in the result with the correct NULL values.
Rev The insert succeeded once, and the saved rows kept each label paired with its matching ID. The ID values were stored as bigint in the target table.
Unnest The table function returned item_id and item_label with rows 1-a and 2-b. The columns kept integer and text types.

Tip

Reply with @itoqa to send us feedback on this test run.

@fulghum fulghum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, but my gut says we could restructure and remove the need for that new interface that tries to make regular functions act like relation-returning/table functions if it was modeled as a table function. I think that's worth trying out.

Comment thread testing/go/functions_test.go
Comment thread server/analyzer/assign_insert_casts.go
Comment thread server/functions/framework/functions.go Outdated
@itoqa

itoqa Bot commented Sep 18, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: 3d9719f: 17 test cases ran, 17 passed ✅.

Summary

Coverage spans normal and edge-case database behavior for expanding one or more arrays into rows, including empty or null inputs, unequal lengths, ordering, aliases, row numbering, type conversion, joins, inserts, updates, conflict handling, and atomic failure behavior. It also checks that unrelated table functions continue working normally alongside these operations.

Safe to merge — all exercised behaviors passed, including boundary, error-handling, data-integrity, and compatibility checks, with no PR-attributable regressions or unresolved failures. Any environmental observations are not merge blockers.

Tests run by Ito

View full run

Result Severity Type Description
Argument The query rejected the scalar input and returned the expected function-not-found error.
General Using an empty array beside three text values returned three rows. The empty column was NULL in each row, and the text values stayed in a, b, c order.
General Both query orders returned two rows. The NULL array column stayed NULL, and the boolean values stayed t and f in order.
General The insert kept each source value in its matching destination field. The shorter text array produced NULL only for the missing label, and the destination types stayed integer and text.
General The unrelated table function returned the same result before and after a mixed-case multi-array unnest query.
General The insert stopped on the invalid later value and left the table empty. A corrected retry then inserted both expected rows.
General A base table joined to the array relation returned five rows with the correct base IDs, values in position, and NULLs where a shorter or NULL array had no value.
General The query ran successfully through an outer query. Filtering and sorting kept the aliases and returned (20, y, 2) followed by (10, x, 1).
General The query returned the aliased array columns and ordinality column in the requested order. Sorting by position descending returned y with 20 and position 2, then x with 10 and position 1.
General The existing row was updated, the new row was inserted with the expected empty label, and the returned rows matched the table afterward.
Function The unrelated table function returned rows 1, 2, and 3 as expected.
Insert The insert updated the existing row, added the new row, and returned the same values that appeared in the final table.
Insert The typed insert stored key 7 and label cast-value in the integer and text destination columns.
Ordinality The query returned the two array columns followed by the aliased position column, with rows (10, x, 1) and (20, y, 2).
Ordinality The query returned both array columns and a generated ordinality column with values 1 and 2 in row order.
Unnest The query returned two columns and paired the values by position: (1, 'a') followed by (2, 'b').
Unnest The one-array query succeeded and returned one column with the values 1, 2, and 3 in order.

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Sep 18, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: 1d1feb2: 19 test cases ran, 19 passed ✅.

Summary

The run covers core data insertion and update flows, including matching returned values to stored data, concurrent writes, array alignment and position handling, duplicate column names, and compatibility with related table functions. It also exercises edge and error cases such as empty or uneven arrays, invalid types, failed writes with rollback, and unsupported scalar inputs, with healthy behavior throughout.

Safe to merge — the exercised application behaviors completed without PR-attributable regressions or new failures, including concurrency, rollback, boundary, and invalid-input handling. No merge blocker was identified.

Tests run by Ito

View full run

Result Severity Type Description
General A NULL payload was rejected with a clear type error, and the existing row stayed unchanged. An unknown text value was inserted and returned correctly.
General Multi-array queries kept each key beside the payload value from the same position. When one array was shorter, the missing value was returned as NULL instead of shifting the remaining values.
General The upsert returned id 7 and payload 700, and the table stored the same values.
General The invalid payload caused a clear cast error and left the destination empty. A corrected retry stored only rows 101 and 102 with the expected values.
General Each outer row produced results from its own arrays, with the right row count, NULL padding, and position numbers.
General The conflicting row was updated, the new row was inserted, and both returned values matched the rows stored in the table.
General Two concurrent writes to the same key produced one committed result and one clear serialization error. The final table contained one complete row with no duplicate or partial data.
Duplicate The derived query ran successfully with two columns named value. Adding the row number kept both columns and returned n = 1.
Duplicate The query succeeds and keeps both columns named value, with values 1 and 2.
Insert Rows from two arrays were inserted into the table. Updating the conflicting row returned the new label, and the final table showed the expected two rows.
Insert The row with id 3 and label 'three' was inserted successfully and could be read back from the table.
Ordinality The SQL query returned both array columns with the names value and label, added position, and numbered the rows 1 and 2 in order.
Ordinality The query returned the unnest values 4 and 5 with an ordinality column numbered 1 and 2.
Provider The SQL query returned the expected values 1, 2, and 3 from generate_series.
Scalar The scalar query returns the expected undefined-function error for two array arguments.
Scalar The query returned the two expected rows, 7 and 8.
Unnest The query returned three columns and three rows. Shorter arrays were padded with NULL values in the correct positions.
Unnest The query returned two rows, using the longest boolean array for the row count. The empty integer array and NULL text array were shown as NULL in both rows.
Unnest Using an integer where an array is required shows the expected function error.

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Sep 18, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: e083ba6: 14 test cases ran, 14 passed ✅.

Summary

Coverage spans core database behavior for expanding and aligning multiple arrays, preserving existing query behavior, maintaining row associations through joins, and handling inserts, updates, conflicts, and rollback. It also exercises edge cases such as empty or mismatched arrays, duplicate column names, unsupported forms, failed batches, and session recovery, with healthy results across these paths.

Safe to merge — the exercised behavior is healthy across normal flows and important boundary and error-handling cases, with no PR-attributable regressions or unresolved failures. Any environment-related limitations are not merge blockers.

Tests run by Ito

View full run

Result Severity Type Description
General Both boundary queries returned three rows. Values from the longest array stayed in order, while empty and NULL companion arrays showed NULL in every row without adding an extra row.
General The single-array unnest query kept its column name and rows, and generate_series kept its rows before and after the multi-array query.
General Both duplicate columns stayed visible, the outer row number was calculated, and the second projection returned both values.
General A bad value stopped the batch without changing the existing row or adding a partial row. A later valid insert succeeded on the same connection.
General Implicit and explicit lateral queries returned the same values for each joined row, including the expected NULL padding.
Duplicate The SQL service was unavailable during this check, so the query could not run. Repository tests and configuration support keeping both columns with the same name, and the blocked result was caused by the local test environment.
Insert The initial rows were inserted, the conflicting row was updated, the new row was added, and the RETURNING results matched the final table.
Insert Regular SELECT INSERT applied the needed value casts, updated the conflicting row only when the predicate allowed it, returned the updated row, and kept the final rows correct.
Lateral Each input row produced its own expanded values, and numbering started at 1 for each row.
Rev The left join returned both source rows with NULL values for the empty function result. No row was lost and no extra row was added.
Rev The unsupported scalar form was rejected, and the next table-form query on the same connection returned (9, a) and (10, b). The browser route was unavailable because the local service accepts PostgreSQL connections rather than HTTP requests, but the direct local SQL check confirmed the expected behavior.
Unnest The query returned two columns and three rows. The shorter array was padded with an empty value in the last row, as expected.
Unnest Single-array unnest returned its values, multi-array unnest returned two aligned columns, and the unsupported scalar multi-array form was rejected.
Unnest The query returned the column named unnest with the values 7, 8, and 9 in order.

Tip

Reply with @itoqa to send us feedback on this test run.

@fulghum fulghum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!!

Comment thread testing/go/enginetest/doltgres_engine_test.go
@itoqa

itoqa Bot commented Sep 21, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: 3909696: 16 test cases ran, 16 passed ✅.

Summary

Coverage spans core data flows for expanding and aligning arrays, inserting and updating rows, preserving column order and types, and handling derived values and table functions. It also exercises boundary and invalid-input behavior, including empty or null arrays, ordinality across rows, lateral joins, unsupported scalar forms, and server feature limits; the exercised behavior is healthy overall.

Safe to merge — the run found no regressions, new failures, or previously flagged failures attributable to this PR. The only limitation is an environment-level lack of prepared-statement support, which is a flag for later rather than a merge blocker.

Tests run by Ito

View full run

Result Severity Type Description
General The longest array produced three rows. Empty and NULL arrays were filled with NULL values, and all-empty or all-NULL inputs produced no rows.
General The conflicting rows kept their own incoming labels, and the new row was inserted with the correct label. The returned rows matched the final table.
General The ROWS FROM query and the equivalent ordinary query returned the same two data values, NULL padding, aliases, and one-based row numbers.
Derived The query returned both duplicate values, 10 and 20, and added row number 1.
Function COUNT() returned 2 for both rows, while FIRST_VALUE() returned the expected function error.
Insert The existing row was updated with its new label, the new row was inserted, and both the returned values and saved table values matched.
Insert The insert accepted a smallint source value for the integer id column and stored the row as (3, 'casted').
Provider The existing DOLT_LOG table function resolved normally and returned its expected log rows.
Rev The direct multi-array query returned aligned rows and NULL padding correctly. The required prepared-statement check could not run because the local Doltgres server does not support PREPARE.
Rev The query returned four aligned rows. Numbering started at 1 for each input row, and the shorter text array was filled with NULL.
Rev The left lateral join kept all three input rows. Empty and NULL arrays were represented with NULL values, and the populated row returned the expected padded values.
Rev The insert completed successfully, and the saved rows kept each number paired with the correct label: (11, 'first') and (12, 'second').
Single The single-array FROM query returned the values 1, 2, and 3 in order.
Unnest The query returned two aligned rows, filled the missing second value with NULL, applied all three aliases, and started ordinality at 1.
Unnest The database rejects a scalar argument in a multi-array table query with the expected function error instead of treating the scalar as an array.
Unnest Calling the array-expansion function with two arrays returned the expected function-does-not-exist error.

Tip

Reply with @itoqa to send us feedback on this test run.

@Hydrocharged
Hydrocharged merged commit b78b33c into main Sep 21, 2026
25 checks passed
@Hydrocharged
Hydrocharged deleted the daylon/issue-3366 branch September 21, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants