Skip to content

Fix RANGE window frame panics and bugs - #3030

Open
fulghum wants to merge 5 commits into
mainfrom
fulghum/time-delta
Open

Fix RANGE window frame panics and bugs#3030
fulghum wants to merge 5 commits into
mainfrom
fulghum/time-delta

Conversation

@fulghum

@fulghum fulghum commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes panics and silent mishandling when the frame boundary offset is numeric or an INTERVAL over a DATE/TIMESTAMP column.

Depends on: dolthub/go-mysql-server#3665

@fulghum
fulghum force-pushed the fulghum/time-delta branch from 033eeb7 to 208b92f Compare August 4, 2026 20:46
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
Main PR
covering_index_scan_postgres 2111.73/s 2081.36/s -1.5%
groupby_scan_postgres 158.12/s 154.64/s -2.3%
index_join_postgres 690.10/s 682.09/s -1.2%
index_join_scan_postgres 873.31/s 863.28/s -1.2%
index_scan_postgres 33.07/s 32.47/s -1.9%
oltp_delete_insert_postgres 886.00/s 842.17/s -5.0%
oltp_insert 770.62/s 774.64/s +0.5%
oltp_point_select 3586.62/s 3552.55/s -1.0%
oltp_read_only 3536.39/s 3486.12/s -1.5%
oltp_read_write 2704.18/s 2696.15/s -0.3%
oltp_update_index 802.48/s 803.84/s +0.1%
oltp_update_non_index 846.57/s 849.91/s +0.3%
oltp_write_only 1950.57/s 1949.47/s -0.1%
select_random_points 2201.01/s 2190.49/s -0.5%
select_random_ranges 1647.98/s 1639.78/s -0.5%
table_scan_postgres 32.51/s 31.94/s -1.8%
types_delete_insert_postgres 863.38/s 869.14/s +0.6%
types_table_scan_postgres 14.65/s 14.53/s -0.9%

@fulghum
fulghum force-pushed the fulghum/time-delta branch from 208b92f to 1e2be03 Compare August 4, 2026 21:07
@itoqa

itoqa Bot commented Aug 4, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: 1e2be03: 14 test cases ran, 1 failed ❌, 10 passed ✅, 3 additional findings ⚠️.

Summary

Coverage spans SQL windowed totals across numeric, date, timestamp, month-end, large-value, asymmetric-boundary, and combined-frame scenarios, including both normal results and edge-case boundary behavior. Most supported range and calendar calculations behave correctly, but combined numeric windows can silently produce incorrect totals.

Merge with caution — this PR introduces a medium-severity correctness issue where multiple numeric window calculations in one statement return wrong results without an error. Separate pre-existing numeric-range crashes and unsupported prepared statements are important caveats but are not attributable to this PR.

Tests run by Ito

View full run

Result Severity Type Description
Medium severity Boundary The four frame types work when queried one at a time, but the same frames give current-row-only totals when combined in one SELECT. The query completes without an error, so the wrong result can be mistaken for valid data.
Boundary The query completed without an executor error and returned sums of 10, 30, and 50 for values 1, 2, and 3.
Boundary Numeric window queries completed with stable frame totals in the current local test environment. An earlier direct query showed a panic, but the prescribed in-process regression suite passed three times and did not reproduce it.
Boundary Adjacent large BIGINT values stayed distinct in the RANGE frame, and the exact frame sums were unchanged on the repeated run.
Calendar The one-month date range returned the expected totals of 3, 6, and 6 across January 31, February 28, and March 1. February is included for January 31, while March 1 is excluded from that boundary.
Calendar Date-based and fractional-second time windows included rows exactly on and inside each limit, while excluding the row just outside the limit.
Calendar One-month date ranges handled February and leap-year boundaries correctly in both directions. January 31 clamped to the proper February endpoint, and the returned totals matched the expected rows.
Framer The PostgreSQL-compatible RANGE queries completed successfully. Valid numeric and quoted date-interval cases ran, the intentionally invalid form was rejected, and the test harness closed normally.
Framer Window queries passed for numeric and date-based ranges, including the month-end case where January 31 plus one month includes February 28 but not March 1. ROWS frames, named windows, and other window functions also stayed healthy.
Framer The database ran valid numeric and quoted interval window cases. It rejected only the documented invalid interval form, so valid regressions would remain visible.
Window The timestamp window included the correct rows on both sides of each row. The two-hour preceding and 30-minute following limits stayed separate.
⚠️ High severity Window The value-based RANGE query crashes, while the matching physical-row ROWS query succeeds.
⚠️ Medium severity Calendar The server rejects the valid prepared statement before the query runs. The expected prepared and rebound executions cannot be compared with the working unprepared query.
⚠️ Medium severity Window The named RANGE query panicked while evaluating its numeric boundary, so it returned no aggregate rows.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Numeric RANGE windows crash during execution
  • Severity: High High severity
  • Description: The value-based RANGE query crashes, while the matching physical-row ROWS query succeeds.
  • Impact: Users cannot run numeric RANGE window queries that need a value-based frame. The query ends with a server panic instead of returning aggregate results.
  • Steps to Reproduce:
    1. Create a table with a numeric ordering column and integer values, such as (1,10), (1,20), (2,30), and (4,40).
    2. Run SUM(val) OVER (ORDER BY ord ROWS BETWEEN 1 PRECEDING AND CURRENT ROW); this returns 30, 20, 40, and 70 for the fixture.
    3. Run SUM(val) OVER (ORDER BY ord RANGE BETWEEN 1 PRECEDING AND CURRENT ROW).
    4. Observe that the RANGE query returns a DoltgresHandler panic instead of result rows: interface {} is float64, not *apd.Decimal.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The failing query is converted by server/ast/window.go:59-62 and server/ast/window.go:96-104, which preserve RANGE mode and convert the numeric offset through nodeExpr. Numeric expression conversion in server/ast/expr.go:522-530 creates numeric literals through the decimal and float paths, while server/expression/literal.go:161-179 converts a numeric literal to a Vitess value by asserting l.Value() to *apd.Decimal at line 178. The recorded panic is exactly the resulting runtime type mismatch: the window framer supplies a float64 value, but this conversion path assumes a *apd.Decimal. server/types/type.go:306-312 contains the PR's added mismatched-numeric handling, but its numericAsFloat64 helper at lines 222-253 does not accept *apd.Decimal, and it does not remove the unsafe assertion in literal.go. Therefore the source still contains a concrete production path that can panic for the tested inline numeric RANGE frame. The smallest practical fix is to normalize the RANGE offset and order value to one numeric representation before comparison, or to make ToVitessLiteral handle the actual numeric value type without an unchecked *apd.Decimal assertion; the fix should then cover the inline numeric RANGE query directly.
Evidence Package
🟡 Prepared queries reject interval windows
  • Severity: Medium Medium severity
  • Description: The server rejects the valid prepared statement before the query runs. The expected prepared and rebound executions cannot be compared with the working unprepared query.
  • Impact: Clients that use prepared statements cannot run valid date-range queries. They can use the unprepared query path instead, but prepared execution remains unavailable.
  • Steps to Reproduce:
    1. Create a table with DATE rows at a month boundary, such as January 31, February 28, and March 1, and insert numeric values.
    2. Prepare a query that sums the values over an ORDER BY date RANGE frame with INTERVAL '1' MONTH FOLLOWING.
    3. Execute the prepared statement and observe that the server returns a PREPARE unsupported error instead of returning the window results.
    4. Send the equivalent query without PREPARE and compare the result; the unprepared query returns the expected calendar-aware totals 3, 6, and 6.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The recorded SQL response is a direct application error: PREPARE is not yet supported. In server/ast/prepare.go, nodePrepare handles every parsed PREPARE node and unconditionally returns NotYetSupportedError at line 29; it never creates a prepared plan or forwards the statement to the query planner. The PR's server/ast/window.go change at lines 89-94 only affects interval offsets after a query reaches window conversion, and server/expression/interval.go provides the calendar-aware DeltaExpression used by that path. Those changes cannot make a PREPARE statement executable because the request is rejected earlier. The smallest practical fix is to implement the existing PREPARE path so it stores and executes the parsed statement while preserving the injected interval expression during planning; alternatively, explicitly document prepared statements as unsupported rather than treating this test path as available.
Evidence Package
🟡 Numeric RANGE windows crash during execution
  • Severity: Medium Medium severity
  • Description: The named RANGE query panicked while evaluating its numeric boundary, so it returned no aggregate rows.
  • Impact: Queries that use a numeric RANGE window bound can crash instead of returning aggregate rows. Users can still use other window forms, but this supported query pattern does not work.
  • Steps to Reproduce:
    1. Create a table with a numeric ordering column and an integer value column.
    2. Insert rows with ordering values 1, 2, and 4.
    3. Run SUM(val) OVER named_window with WINDOW named_window AS (ORDER BY ord RANGE BETWEEN 1 PRECEDING AND CURRENT ROW).
    4. Observe that the query returns a server panic instead of frame sums 10, 30, and 40.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The failure is reproducible in the SQL path: the recorded query reaches the window-frame conversion in server/ast/window.go. At lines 89-100, only DInterval offsets receive special handling; a numeric offset follows the else branch and is passed through nodeExpr. In server/ast/expr.go:527-530, a DFloat is converted to a raw float64 literal. The numeric literal adapter in server/expression/literal.go:177-179 then handles Numeric by asserting l.Value() to *apd.Decimal before formatting it for Vitess. When the RANGE machinery supplies the float64 produced by the numeric boundary, that assertion fails with interface conversion: interface {} is float64, not *apd.Decimal. The PR diff does not modify this numeric branch; its server/ast/window.go change only adds direct handling for DInterval offsets, while the test change removes the previous skip and exposes the existing numeric defect.
Evidence Package

Tip

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

Comment thread server/ast/window.go
@itoqa

itoqa Bot commented Aug 4, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report1e2be03261ab05: 23 test cases ran, 4 fixed ✅, 18 passing ✅, 1 additional finding ⚠️.

Diff Summary

Coverage spans SQL window and aggregate behavior across numeric, ranking, partitioning, ordering, row-based, and time-based ranges, including boundary, tie, repetition, calendar, leap-year, and dialect-validation cases. The application behavior is broadly healthy, with both ordinary usage and difficult edge conditions behaving as expected except for a narrow subsecond timestamp boundary issue.

Safe to merge — the only failure is a high-severity correctness issue in microsecond timestamp windows, but it is explicitly unrelated to this PR and affects unchanged or dependency-level behavior. No PR-attributable regressions or previously flagged failures were found; the timestamp issue is a flag for later investigation rather than a merge blocker.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Boundary Queries using zero, following, and unbounded numeric frames returned the expected sums for every row.
❌->✅ Fixed Calendar Running the same interval query again with different dates returned the same correct calendar results as the matching literal queries. The separate SQL PREPARE form is not supported, but the supported parameterized query path passed.
❌->✅ Fixed Window A named RANGE window ran successfully and returned the expected running totals: 10, 30, 50, and 70.
❌->✅ Fixed Window Inline ROWS and RANGE window queries both return the expected totals. ROWS follows physical rows, while RANGE includes rows within the order-value distance.
Passing Aggregate The SQL query returned the correct total for each of four SUM columns with different range frames. Each column kept its own frame instead of reusing another column's result.
Passing Aggregate The query returned independent totals for the unpartitioned ascending window and the partitioned descending window. Changing the partition or sort direction did not replace one result column with the other.
Passing Aggregate Running SUM without an OVER clause returned one result containing 60. The query completed without a planning or execution error.
Passing Aggregate The query returned the correct result for every aggregate and ranking column, even when the function and window settings differed.
Passing Boundary The query handled mixed integer range boundaries without a crash and returned the expected boundary values.
Passing Boundary The window queries returned stable results for the tested numeric ordering cases, with no type error or execution panic.
Passing Boundary The local SQL window test completed without a panic, and the boundary checks kept different RANGE results separate and stable.
Passing Calendar A one-month date range includes February 28 from January 31, but excludes March 1. The three rows return the expected totals of 3, 6, and 6.
Passing Calendar One-month window queries handled January 31, February 28, leap-day February 29, and March 1 correctly in both directions. The returned totals matched calendar month clamping for every date.
Passing Framer The PostgreSQL-compatible RANGE checks completed successfully. Numeric frames and valid quoted date intervals returned without a crash, while unsupported MySQL-only syntax and malformed interval syntax were handled as expected dialect differences.
Passing Framer Numeric and date/time window queries completed successfully, and existing row-based and other window queries still returned results afterward without a crash.
Passing Framer Valid numeric and quoted time-range queries ran successfully without being hidden by dialect filtering. The test also handled PostgreSQL's expected rejection of malformed or MySQL-only interval syntax.
Passing Planner The combined window query returned the correct sums for four different numeric frames and the correct ascending and descending row numbers.
Passing Planner The repeated SQL checks kept each window definition separate and returned the correct columns on both runs.
Passing Ranking The query returned separate rankings for both sort directions: rows 1, 2, and 3 received ascending ranks 1, 2, and 3, while the descending ranks were 3, 2, and 1.
Passing Ranking Partitioned row numbers restart at one for each group, and cumulative rankings handle ties correctly within each group.
Passing Ranking The SQL queries keep row numbers, ranks, and cumulative distributions tied to their own window rules. Calls without an OVER clause also return the expected validation error.
Passing Window The query kept the 2-day lookback and 3-day lookahead separate, returning the expected frame sums for every timestamp.
⚠️ Additional Finding High severity Calendar A timestamp window with INTERVAL '1 microsecond' returned the wrong rows at fractional-second boundaries. The preceding query returned 1, 2, 5, 9 instead of 1, 3, 5, 7, and the following query returned 1, 9, 7, 4 instead of 1, 5, 7, 4. The quoted DATE INTERVAL '1 day' checks passed, so the failure is specific to subsecond timestamp frame membership.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Microsecond window boundaries include wrong rows
  • Severity: High High severity
  • Description: A timestamp window with INTERVAL '1 microsecond' returned the wrong rows at fractional-second boundaries. The preceding query returned 1, 2, 5, 9 instead of 1, 3, 5, 7, and the following query returned 1, 9, 7, 4 instead of 1, 5, 7, 4. The quoted DATE INTERVAL '1 day' checks passed, so the failure is specific to subsecond timestamp frame membership.
  • Impact: Queries using one-microsecond timestamp windows can include the wrong rows, so reported totals are incorrect. Users must avoid this window feature or rewrite the query to get a reliable result.
  • Steps to Reproduce:
    1. Create timestamp rows whose values are separated by exactly one microsecond and give each row a distinct numeric value.
    2. Run a SUM window ordered by the timestamp with RANGE BETWEEN INTERVAL '1 microsecond' PRECEDING AND CURRENT ROW.
    3. Run the matching query with RANGE BETWEEN CURRENT ROW AND INTERVAL '1 microsecond' FOLLOWING.
    4. Compare each sum with the rows whose timestamps are no more than one microsecond from the current row. The returned totals are 1,2,5,9 for PRECEDING and 1,9,7,4 for FOLLOWING instead of 1,3,5,7 and 1,5,7,4.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The production parser and adapter preserve the tested interval unit: server/ast/window.go:89-99 detects a parsed *tree.DInterval and injects pgexprs.NewInterval(intervalOffset.Duration) instead of converting it through the ordinary literal path. server/expression/interval.go:43-52 constructs the interval's expression.TimeDelta with Months, Days, and Microseconds: d.Nanos()/1000, and EvalDelta at lines 79-83 returns that delta to the go-mysql-server window arithmetic. postgres/parser/duration/duration.go:98-119 documents and applies microsecond rounding when creating a Duration, while lines 161-170 ensure the stored value is an integral microsecond. Those paths explain why INTERVAL '1 microsecond' reaches execution as a valid one-microsecond delta; they do not explain away the observed wrong membership. The focused in-process harness therefore establishes a production semantic defect in the timestamp RANGE framer or its dependency's temporal comparison, affecting both PRECEDING and FOLLOWING directions. The PR diff changes only String methods in server/functions/framework/compiled_aggregate_function.go and server/functions/framework/compiled_window_function.go plus a numeric window regression in testing/go/window_test.go. It does not modify server/ast/window.go, server/expression/interval.go, duration parsing, or the dependency framer, so there is no direct changed-line causal path to this defect. The smallest practical fix is to correct the temporal RANGE comparison or interval-delta handling at the responsible framer/dependency boundary, then add this exact microsecond PRECEDING/FOLLOWING regression; changing the PR's expression identity methods would not fix it.
Evidence Package

Tip

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

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 18908 18906
Failures 23182 23184
Partial Successes1 5325 5323
Main PR
Successful 44.9228% 44.9180%
Failures 55.0772% 55.0820%

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

window

QUERY: WITH cte (x) AS (
        SELECT * FROM generate_series(1, 35, 2)
)
SELECT x, (sum(x) over w)
FROM cte
WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);
QUERY: WITH cte (x) AS (
        select 1 union all select 1 union all select 1 union all
        SELECT * FROM generate_series(5, 49, 2)
)
SELECT x, (sum(x) over w)
FROM cte
WINDOW w AS (ORDER BY x range between 1 preceding and 1 following);

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.

@fulghum
fulghum requested a review from Hydrocharged August 5, 2026 00:23

@Hydrocharged Hydrocharged left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! Not a fan of how we have to compare mismatched numbers, but it's just a limitation of GMS rather than an issue with the PR.

@fulghum
fulghum enabled auto-merge August 5, 2026 22:47
@itoqa

itoqa Bot commented Aug 5, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report261ab05674f965: 21 test cases ran, 1 fixed ✅, 18 passing ✅, 2 additional findings ⚠️.

Diff Summary

Coverage spans core database behavior including windowed calculations, date and numeric boundary handling, custom types, extensions, persistence, concurrent updates, branch isolation, rollback safety, and invalid-input handling. It primarily exercises end-to-end SQL flows and business logic across happy paths, edge cases, error recovery, and concurrency scenarios, with some build and packaging checks.

Safe to merge — no failures are attributable to this PR, and the change-related coverage shows no regressions. The unrelated findings are medium-severity pre-existing limitations in backup support and type merging, suitable for follow-up rather than merge blockers.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Calendar Date rows used the quoted one-day range correctly, and timestamp rows used the quoted fractional-second range correctly. All eight expected frame counts passed.
Passing Extension The UUID extension installed successfully, and its constant, random, and deterministic UUID functions returned the expected results.
Passing Extension The database reported uuid-ossp version 1.1 before installation, including its trusted, superuser, and relocatable flags. After installation, the extension appeared in the catalog with version 1.1, installed=true, and schema public.
Passing Extension Installing the extension in a missing schema failed cleanly. The original search path stayed unchanged, and no extension was left installed.
Passing Extension Creating an unknown extension returned a clear unavailable-extension error. No extension record or extension-backed callable was saved.
Passing Extension UUID functions kept NULL values as NULL, rejected malformed text with the expected invalid-input error, accepted the boundary name, and continued working afterward.
Passing Extension An invalid extension install left no metadata or UUID functions behind. A valid retry installed one extension with all 10 functions, and the functions remained available after the session reset when called with their schema.
Passing Native The parser build generated its sources and completed package verification without a PostgreSQL installation or native extension files.
Passing Native The Windows build check could not run because the local build service and required compiler tools were unavailable. The repository still contains the intended cross-build and archive steps, so this run does not confirm an application bug.
Passing Native The extension checks passed and the deleted native loader files were absent. Windows package startup could not be checked because the cross-linker failed and the local build container was unavailable.
Passing Root The new sequence, cast, domain, and composite type stayed usable after the root was committed and reloaded.
Passing Root An empty collection had the same hash before an object was added and after it was removed. The removed sequence was also gone from the catalog.
Passing Root Two database sessions added separate sequences, and both sequences were still present after the commits were reloaded.
Passing Root The same sequence advanced correctly on the original root, while a sequence created on a derived branch was not visible after returning to the main root.
Passing Root Two writers added different database objects at the same time, and all of the objects were still present after the database was reloaded.
Passing Type The domain and composite type were created successfully. A row containing 7 and hello was stored and read back with both values intact.
Passing Type A fresh SQL session found the saved domain and composite definitions, then read both saved rows with the expected values after reload.
Passing Type Invalid type definitions return clear errors, and valid types stay available after the failed attempts. Empty composite types can still be created; the unsupported serialized-payload check is confirmed in source because SQL cannot reach that internal path.
Passing Type A failed type save does not appear as saved data, and a later retry can continue using the existing valid types. The attempted SQL failure was a test-surface limit, not a product failure.
⏸️ Skipped Aggregate The SQL query returned the correct total for each of four SUM columns with different range frames. Each column kept its own frame instead of reusing another column's result.
⏸️ Skipped Aggregate The query returned independent totals for the unpartitioned ascending window and the partitioned descending window. Changing the partition or sort direction did not replace one result column with the other.
⏸️ Skipped Aggregate Running SUM without an OVER clause returned one result containing 60. The query completed without a planning or execution error.
⏸️ Skipped Aggregate The query returned the correct result for every aggregate and ranking column, even when the function and window settings differed.
⏸️ Skipped Boundary The query handled mixed integer range boundaries without a crash and returned the expected boundary values.
⏸️ Skipped Boundary Queries using zero, following, and unbounded numeric frames returned the expected sums for every row.
⏸️ Skipped Boundary The window queries returned stable results for the tested numeric ordering cases, with no type error or execution panic.
⏸️ Skipped Boundary The local SQL window test completed without a panic, and the boundary checks kept different RANGE results separate and stable.
⏸️ Skipped Calendar A one-month date range includes February 28 from January 31, but excludes March 1. The three rows return the expected totals of 3, 6, and 6.
⏸️ Skipped Calendar Running the same interval query again with different dates returned the same correct calendar results as the matching literal queries. The separate SQL PREPARE form is not supported, but the supported parameterized query path passed.
⏸️ Skipped Calendar One-month window queries handled January 31, February 28, leap-day February 29, and March 1 correctly in both directions. The returned totals matched calendar month clamping for every date.
⏸️ Skipped Framer The PostgreSQL-compatible RANGE checks completed successfully. Numeric frames and valid quoted date intervals returned without a crash, while unsupported MySQL-only syntax and malformed interval syntax were handled as expected dialect differences.
⏸️ Skipped Framer Numeric and date/time window queries completed successfully, and existing row-based and other window queries still returned results afterward without a crash.
⏸️ Skipped Framer Valid numeric and quoted time-range queries ran successfully without being hidden by dialect filtering. The test also handled PostgreSQL's expected rejection of malformed or MySQL-only interval syntax.
⏸️ Skipped Planner The combined window query returned the correct sums for four different numeric frames and the correct ascending and descending row numbers.
⏸️ Skipped Planner The repeated SQL checks kept each window definition separate and returned the correct columns on both runs.
⏸️ Skipped Ranking The query returned separate rankings for both sort directions: rows 1, 2, and 3 received ascending ranks 1, 2, and 3, while the descending ranks were 3, 2, and 1.
⏸️ Skipped Ranking Partitioned row numbers restart at one for each group, and cumulative rankings handle ties correctly within each group.
⏸️ Skipped Ranking The SQL queries keep row numbers, ranks, and cumulative distributions tied to their own window rules. Calls without an OVER clause also return the expected validation error.
⏸️ Skipped Window A named RANGE window ran successfully and returned the expected running totals: 10, 30, 50, and 70.
⏸️ Skipped Window Inline ROWS and RANGE window queries both return the expected totals. ROWS follows physical rows, while RANGE includes rows within the order-value distance.
⏸️ Skipped Window The query kept the 2-day lookback and 3-day lookahead separate, returning the expected frame sums for every timestamp.
⚠️ Additional Finding Medium severity Native The unsupported local extension was rejected clearly, uuid-ossp worked, and unrelated SQL remained healthy. The required logical backup did not complete because pg_dump issued SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY and the server returned 'SET TRANSACTION is not yet supported'.
⚠️ Additional Finding Medium severity Type The branch merge did not keep a validation rule that existed only on the incoming branch.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 Database backup fails during export
  • Severity: Medium Medium severity
  • Description: The unsupported local extension was rejected clearly, uuid-ossp worked, and unrelated SQL remained healthy. The required logical backup did not complete because pg_dump issued SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY and the server returned 'SET TRANSACTION is not yet supported'.
  • Impact: Database operators cannot complete standard logical backups with pg_dump. This can leave teams without their usual backup path, but there is no evidence of data loss or corruption.
  • Steps to Reproduce:
    1. Create a disposable local database and add a small table with one row.
    2. Run pg_dump against that database to create a logical backup.
    3. Observe that pg_dump stops when the server rejects SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: The failure is supported by production code rather than the test harness. In server/ast/set_transaction.go, nodeSetTransaction returns NotYetSupportedError("SET TRANSACTION is not yet supported") for every parsed SET TRANSACTION statement. pg_dump's recorded query is exactly SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY, so the server intentionally rejects a standard backup-tool setup statement before the backup can proceed. The PR context shows changes for native extension registry and loader removal, but no change to server/ast/set_transaction.go or transaction handling. The smallest practical fix is to implement the transaction settings needed by pg_dump, or at minimum accept this read-only isolation setup without changing the database contents; this should be addressed separately from the native-extension change.
Evidence Package
🟡 Merge drops domain validation rules
  • Severity: Medium Medium severity
  • Description: The branch merge did not keep a validation rule that existed only on the incoming branch.
  • Impact: After a branch merge, values that should be rejected by an incoming validation rule may be accepted. This can let incorrect data enter the affected workflow, but the issue is limited to merges involving those rules.
  • Steps to Reproduce:
    1. Create the same domain on two branches, with a check rule present only on the incoming branch.
    2. Merge the two domain definitions through the TypeCollection merge path.
    3. Inspect the returned domain and try a value that should be rejected by the incoming branch's check rule.
    4. Observe that the returned domain does not contain the incoming check and the invalid value can pass that missing rule.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In /tmp/output-agent-workspace/repo/core/typecollection/collection_funcs.go, HandleMerge assigns mergedType := ourType.Copy() at lines 60-60. For domains, it resolves Default and NotNull into mergedType at lines 62-84. When the incoming domain has checks, lines 85-88 append theirType.Checks to ourType.Checks, not to mergedType.Checks. The function then returns TypeWrapper{Type: mergedType} at line 89, so the mutation is applied to the original branch object and discarded. The correct targeted fix is to append to mergedType.Checks, while preserving the existing duplicate-check handling decision. The SQL run confirmed that the domain and composite persisted and could be reloaded, but the SQL surface could not exercise branch merge directly; the source path provides the code-first confirmation of the constraint-loss defect.
Evidence Package

Tip

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

fulghum added 4 commits August 5, 2026 16:25
…oundary offset is numeric or an INTERVAL over a DATE/TIMESTAMP column
… value outside its valid domain, and stop swallowing non-EOF framer errors that let it turn into an out-of-bounds index.
@fulghum
fulghum force-pushed the fulghum/time-delta branch from 674f965 to 2bc8ad7 Compare August 6, 2026 00:43
… value outside its valid domain, and stop swallowing non-EOF framer errors that let it turn into an out-of-bounds index.
@itoqa

itoqa Bot commented Aug 6, 2026

Copy link
Copy Markdown

Ito QA test results

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

Commit: a74c91c: 20 test cases ran, 1 failed ❌, 19 passed ✅.

Summary

Coverage spans windowed calculations over dates, timestamps, and numeric values, including ordering, partitions, frame boundaries, peer rows, precision, and recovery after invalid input. It exercises both normal business logic and adversarial type or syntax edge cases, with the tested behavior broadly healthy aside from one unsafe error path.

Merge with caution — the PR still permits a medium-severity edge case to panic the server when a range comparison crosses incompatible value types, rather than returning a controlled error. The impact appears limited to the failing query without evidence of data corruption or broader query damage, but the issue is directly attributable to this change and remains relevant to its purpose.

Tests run by Ito

View full run

Result Severity Type Description
Medium severity Numeric A range query that crosses from a text order value to a numeric boundary crashes instead of returning a controlled error.
Calendar The month-end window calculation returned 3, 6, and 6. January 31 plus one month reached February 28, included that row, and did not include March 1.
Calendar Timestamp window ranges with one-day and two-hour limits included the expected rows and completed without a panic.
Calendar Malformed interval syntax was rejected, and an interval range using an unsupported order key returned a controlled error without stopping the local database harness.
Calendar An invalid interval window query returned a controlled error, and a later valid window query still ran successfully.
Calendar Timestamp window ranges handled day and sub-day boundaries correctly. The local Go test suite passed without a precision error, silent drift, or crash.
Identity The query returned different totals for the wide and narrow windows on every row: [10, 30, 50] versus [10, 20, 30].
Identity The query returned different row numbers for ascending and descending order. Each result stayed in its own column for all three rows.
Identity Running totals reset for each group, while reverse row numbers keep their own values across both groups.
Numeric The range query returned the expected sums of 10, 30, and 50, and a second query completed successfully afterward.
Numeric Very large integer order keys that differed by one stayed separate. The range calculation included the correct rows and returned the expected sums without an error.
Numeric Mixed numeric values in a window range return the expected frame results without crashing.
Range The SQL engine kept ROWS frames based on physical rows and RANGE frames based on order values. Both frame types returned their expected sums for the three ordered rows.
Range Invalid window frame queries return errors without crashing the SQL engine, and the focused validation tests complete successfully.
Range Window queries with no frame, an unbounded frame, and no order column all completed successfully. The ordered queries used the same running results, and the unordered query covered the whole group.
Range The SQL engine included rows exactly at the requested numeric RANGE boundary and kept each frame's results separate. The focused range and window test suites passed without an error.
Window SUM and AVG without an order column return the same full-group total or average for every row.
Window Ordered window totals used the expected default range and included every row tied at the current order value. Rows with unique order values also produced the correct running results.
Window Named windows keep their explicit frame, and nth_value returns the expected values for both the default frame and an explicit full-partition frame.
Window Partitioned window queries keep each group's results separate, and a new query starts without stale frame state.

Tip

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

Comment thread server/types/type.go
return int(i.(int32)), nil
}

// A value crossing in from GMS's own generic machinery without going through DoltgresType.Convert

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

View replay

Medium severity Numeric range errors can crash queries

What failed: A range query that crosses from a text order value to a numeric boundary crashes instead of returning a controlled error.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Users running a range query that compares text with a numeric value receive a server error instead of a clear type error. That query fails, but there is no evidence that other queries or stored data are affected.
  • Steps to Reproduce:
    1. Create a table with a text column used as the window ORDER BY value and numeric rows or a numeric RANGE boundary.
    2. Run a window query whose frame compares the text order value with a numeric boundary, including the nonnumeric-to-numeric direction.
    3. Observe that the query returns SQLSTATE XX000 from an interface type assertion instead of a controlled typed error, and that the following health-check query does not run.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: server/types/type.go:306-312 adds a reflect.Type mismatch check and calls compareMismatchedNumeric only when numericAsFloat64(v1) succeeds. For a reverse mismatch such as v1 being string and v2 being float64, numericAsFloat64(v1) returns false, so execution falls through to the switch at line 314. The string case at lines 378-386 then performs v2.(string), which panics for a float64 value. The typed mismatch error in compareMismatchedNumeric at lines 213-217 and the later default error at lines 482-484 are not reached. The minimal correction is to route any mismatch where either operand is numeric through the helper, allowing its existing type check to return an error when the other operand is nonnumeric.
  • Why this is likely a bug: The focused NUMERIC-4 run reported an SQLSTATE XX000 interface-conversion panic while evaluating DoltgresType.Compare, and the source has a deterministic path that produces exactly that panic for string v1 and float64 v2. The test could not complete its later controlled-mismatch and health checks because the query failed first, but this is not dependent on the missing local ICU header: the unsafe assertion is visible in production code. The PR is specifically intended to prevent RANGE numeric panics, and its new asymmetric guard does not cover this reverse crossing. Extending that guard to handle numeric v2 is a targeted fix; broad changes to window framing are not required.
Relevant code

server/types/type.go:306-312

if reflect.TypeOf(v1) != reflect.TypeOf(v2) {
	if _, ok := numericAsFloat64(v1); ok {
		return compareMismatchedNumeric(v1, v2)
	}
}

server/types/type.go:378-386

case string:
	bb := v2.(string)
	if ab == bb {
		return 0, nil
	} else if ab < bb {
		return -1, nil
	} else {
		return 1, nil
	}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Numeric range errors can crash queries**

**What failed:** A range query that crosses from a text order value to a numeric boundary crashes instead of returning a controlled error.

- **Impact:** Users running a range query that compares text with a numeric value receive a server error instead of a clear type error. That query fails, but there is no evidence that other queries or stored data are affected.
- **Steps to reproduce:**
  1. Create a table with a text column used as the window ORDER BY value and numeric rows or a numeric RANGE boundary.
  2. Run a window query whose frame compares the text order value with a numeric boundary, including the nonnumeric-to-numeric direction.
  3. Observe that the query returns SQLSTATE XX000 from an interface type assertion instead of a controlled typed error, and that the following health-check query does not run.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** server/types/type.go:306-312 adds a reflect.Type mismatch check and calls compareMismatchedNumeric only when numericAsFloat64(v1) succeeds. For a reverse mismatch such as v1 being string and v2 being float64, numericAsFloat64(v1) returns false, so execution falls through to the switch at line 314. The string case at lines 378-386 then performs v2.(string), which panics for a float64 value. The typed mismatch error in compareMismatchedNumeric at lines 213-217 and the later default error at lines 482-484 are not reached. The minimal correction is to route any mismatch where either operand is numeric through the helper, allowing its existing type check to return an error when the other operand is nonnumeric.
- **Why this is likely a bug:** The focused NUMERIC-4 run reported an SQLSTATE XX000 interface-conversion panic while evaluating DoltgresType.Compare, and the source has a deterministic path that produces exactly that panic for string v1 and float64 v2. The test could not complete its later controlled-mismatch and health checks because the query failed first, but this is not dependent on the missing local ICU header: the unsafe assertion is visible in production code. The PR is specifically intended to prevent RANGE numeric panics, and its new asymmetric guard does not cover this reverse crossing. Extending that guard to handle numeric v2 is a targeted fix; broad changes to window framing are not required.

**Relevant code:**

`server/types/type.go:306-312`

~~~go
if reflect.TypeOf(v1) != reflect.TypeOf(v2) {
	if _, ok := numericAsFloat64(v1); ok {
		return compareMismatchedNumeric(v1, v2)
	}
}
~~~

`server/types/type.go:378-386`

~~~go
case string:
	bb := v2.(string)
	if ab == bb {
		return 0, nil
	} else if ab < bb {
		return -1, nil
	} else {
		return 1, nil
	}
~~~

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.

2 participants