Skip to content

avoid unnecessary interface conversions for JSONWrappers#2843

Open
jycor wants to merge 1 commit into
mainfrom
james/json
Open

avoid unnecessary interface conversions for JSONWrappers#2843
jycor wants to merge 1 commit into
mainfrom
james/json

Conversation

@jycor

@jycor jycor commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions

Copy link
Copy Markdown
Contributor
Main PR
covering_index_scan_postgres 1843.63/s 1898.34/s +2.9%
groupby_scan_postgres 136.00/s 137.41/s +1.0%
index_join_postgres 666.86/s 664.76/s -0.4%
index_join_scan_postgres 807.79/s 810.09/s +0.2%
index_scan_postgres 25.10/s 24.73/s -1.5%
oltp_delete_insert_postgres 802.76/s 791.41/s -1.5%
oltp_insert 689.98/s 692.74/s +0.4%
oltp_point_select 2941.62/s 2946.56/s +0.1%
oltp_read_only 2972.92/s 2948.23/s -0.9%
oltp_read_write 2351.96/s 2344.99/s -0.3%
oltp_update_index 694.57/s 715.53/s +3.0%
oltp_update_non_index 757.39/s 765.88/s +1.1%
oltp_write_only 1752.47/s 1735.10/s -1.0%
select_random_points 1922.57/s 1889.44/s -1.8%
select_random_ranges 1109.74/s 1116.62/s +0.6%
table_scan_postgres 23.33/s 23.60/s +1.1%
types_delete_insert_postgres 758.86/s 761.93/s +0.4%
types_table_scan_postgres 8.16/s ${\color{lightgreen}10.23/s}$ ${\color{lightgreen}+25.3\%}$

@itoqa

itoqa Bot commented Jun 11, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: a989bc7: 8 test cases ran, 1 failed ❌, 4 passed ✅, 3 additional findings ⚠️.

Summary

Overall, 8 JSON serialization/dispatch tests ran with mixed results (4 passed, 4 failed): core readback and fidelity checks for object/array/scalar values plus large and threshold-adjacent payloads were stable and deterministic after runtime repair, including across retries and restart/cache-clear validation. The most important remaining issues are high-impact product bugs in explicit json_out paths—valid JSON can fail with unsupported types (types.JSONDocument) or panic on missing pg_catalog.cstring_out during output-function resolution (including structured and equivalent JSON forms), causing statement failures and no returned rows.

Tests run by Ito

View full run

Result Severity Type Description
Medium severity Output Structured JSON values should serialize as text, but explicit json_out(...) on structured outputs triggers a server panic (cannot find function ... cstring_out) and returns no rows.
Fidelity Large JSON payloads were serialized consistently across repeated reads with stable length and hash values.
Fidelity Near-threshold JSON payloads remained deterministic before and after restart/cache-clear checks.
Output JSON column reads returned expected text values for object, array, and scalar payloads.
Output Repeated retries showed the same failure position each time, confirming stable statement observability.
⚠️ High severity Dispatch The insert/select flow for valid JSON-producing expressions fails with unsupported type: types.JSONDocument instead of returning JSON text.
⚠️ High severity Dispatch Instead of returning unchanged JSON text, the query panics while resolving pg_catalog.cstring_out in the output-function path.
⚠️ High severity Dispatch Literal and merged JSON values evaluate correctly, but rendering the merged value through json_out panics on missing cstring_out lookup.
Additional Findings Details

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

🟠 JSON dispatch rejects valid runtime document
  • Severity: High High severity
  • Description: The insert/select flow for valid JSON-producing expressions fails with unsupported type: types.JSONDocument instead of returning JSON text.
  • Impact: JSON-producing SQL workflows can fail during normal query execution, which breaks core database behavior for applications that read JSON data. There is no reliable workaround in the failing path besides avoiding affected query patterns.
  • Steps to Reproduce:
    1. Connect to sqllogictest with psql.
    2. Create dispatch_jsonbytes and insert JSON values produced via JSON expression paths.
    3. Select rows through json_out(payload).
  • Stub / mock content: Local authentication checks were temporarily disabled so the SQL session could connect with deterministic credentials. No API route mocks or response stubs were used in the JSON execution path.
  • Code Analysis: Reviewed server/functions/json.go and confirmed the production json_out type switch does not cover all runtime JSON representations that the engine emits, so valid JSON values can fail conversion.
🟠 json_out panics on valid JSON input
  • Severity: High High severity
  • Description: Instead of returning unchanged JSON text, the query panics while resolving pg_catalog.cstring_out in the output-function path.
  • Impact: A direct JSON output function call on valid input crashes the statement path, blocking a core JSON retrieval workflow. Users cannot safely rely on json_out for routine JSON text conversion.
  • Steps to Reproduce:
    1. Connect to sqllogictest with psql.
    2. Run SELECT json_out('{"name":"alice"}'::json);.
    3. Observe server response for the single-row result.
  • Stub / mock content: Local authentication checks were temporarily disabled so the SQL session could connect with deterministic credentials. No API route mocks or response stubs were used in the JSON execution path.
  • Code Analysis: Inspected server/types/type.go, server/types/cstring.go, and server/types/function_registry.go; IoOutput always resolves the output function, but Cstring points to cstring_out without a corresponding registered function, which triggers panic in lookup.
🟠 Equivalent JSON forms fail at render
  • Severity: High High severity
  • Description: Literal and merged JSON values evaluate correctly, but rendering the merged value through json_out panics on missing cstring_out lookup.
  • Impact: Equivalent valid JSON values can fail at render time, so applications may observe statement failures even when JSON computation succeeds. This undermines reliability of JSON query output paths.
  • Steps to Reproduce:
    1. Run SELECT '{"x":1}'::json and SELECT ('{"x":1}'::jsonb || '{"y":2}'::jsonb)::json to produce equivalent valid JSON forms.
    2. Run SELECT json_out((...)::json) on the merged value.
    3. Compare successful JSON evaluation with final render behavior.
  • Stub / mock content: Local authentication checks were temporarily disabled so the SQL session could connect with deterministic credentials. No API route mocks or response stubs were used in the JSON execution path.
  • Code Analysis: Reviewed the JSON output path in server/functions/json.go and the output-function chain in server/types/type.go with server/types/cstring.go; successful logical JSON generation still fails because final text serialization depends on missing cstring output support.

Tip

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

Comment thread server/functions/json.go

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 Structured json_out serialization panics on cstring resolution

What failed: Structured JSON values should serialize as text, but explicit json_out(...) on structured outputs triggers a server panic (cannot find function ... cstring_out) and returns no rows.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Queries that rely on explicit json_out(...) for structured JSON serialization fail hard instead of returning data. This blocks a meaningful SQL serialization workflow and requires query rewrites to avoid the broken path.
  • Steps to Reproduce:
    1. Create a table with JSON values that can be serialized through row_to_json or array_to_json.
    2. Run SELECT json_out(row_to_json(t)::json) or SELECT json_out(array_to_json(array_agg(...))::json).
    3. Observe a server panic reporting missing pg_catalog.cstring_out and no returned rows.
  • Stub / mock content: SCRAM authentication was disabled in server/authentication_scram.go so the run could connect with deterministic local credentials. The failing behavior came from normal SQL execution against the local server, without mocked response payloads for the exercised query path.
  • Code Analysis: I inspected server/functions/json.go, server/types/type.go, and server/types/function_registry.go. The new JSONBytes branch in json_out_callable now returns a string for this path; IoOutput then resolves the output function for the declared return type, and the registry panics when cstring_out is not loadable.
  • Why this is likely a bug: Production code reaches a deterministic panic path in core type-output resolution for valid structured JSON serialization queries, which is incorrect behavior for a user query path.
Relevant code

server/functions/json.go:80-95

func json_out_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
	switch v := val.(type) {
	case string:
		return v, nil
	case types.JSONBytes:
		bytes, err := v.GetBytes(ctx)
		if err != nil {
			return nil, err
		}
		return string(bytes), err
	case sql.JSONWrapper:
		// JSON type is stored as binary JSON (same as JSONB), so output is normalized with spaces
		return jsonWrapperToFormattedString(ctx, v)
	default:
		return nil, fmt.Errorf("unexpected type for json_out: %T", val)
	}
}

server/types/type.go:603-614

// IoOutput converts given type value to output string.
func (t *DoltgresType) IoOutput(ctx *sql.Context, val any) (string, error) {
	var o any
	var err error
	if t.ModInFunc != 0 || t.IsArrayType() || t.IsCompositeType() {
		send := globalFunctionRegistry.GetFunction(ctx, t.OutputFunc)
		resolvedTypes := send.ResolvedTypes()
		resolvedTypes[0] = t
		o, err = send.WithResolvedTypes(resolvedTypes).(QuickFunction).CallVariadic(ctx, val)
	} else {
		o, err = globalFunctionRegistry.GetFunction(ctx, t.OutputFunc).CallVariadic(ctx, val)
	}

server/types/function_registry.go:88-92

f = r.loadFunction(ctx, id)
if f == nil {
	// If we hit this panic, then we're missing a test that uses this function (and we should add that test)
	panic(errors.Errorf("cannot find function: `%s`", r.revMapping[id]))
}
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Structured json_out serialization panics on cstring resolution**

**What failed:** Structured JSON values should serialize as text, but explicit `json_out(...)` on structured outputs triggers a server panic (`cannot find function ... cstring_out`) and returns no rows.

- **Impact:** Queries that rely on explicit `json_out(...)` for structured JSON serialization fail hard instead of returning data. This blocks a meaningful SQL serialization workflow and requires query rewrites to avoid the broken path.
- **Steps to reproduce:**
  1. Create a table with JSON values that can be serialized through row_to_json or array_to_json.
  2. Run `SELECT json_out(row_to_json(t)::json)` or `SELECT json_out(array_to_json(array_agg(...))::json)`.
  3. Observe a server panic reporting missing `pg_catalog.cstring_out` and no returned rows.
- **Stub / mock content:** SCRAM authentication was disabled in `server/authentication_scram.go` so the run could connect with deterministic local credentials. The failing behavior came from normal SQL execution against the local server, without mocked response payloads for the exercised query path.
- **Code analysis:** I inspected `server/functions/json.go`, `server/types/type.go`, and `server/types/function_registry.go`. The new JSONBytes branch in `json_out_callable` now returns a string for this path; `IoOutput` then resolves the output function for the declared return type, and the registry panics when `cstring_out` is not loadable.
- **Why this is likely a bug:** Production code reaches a deterministic panic path in core type-output resolution for valid structured JSON serialization queries, which is incorrect behavior for a user query path.

**Relevant code:**

`server/functions/json.go:80-95`

~~~go
func json_out_callable(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
	switch v := val.(type) {
	case string:
		return v, nil
	case types.JSONBytes:
		bytes, err := v.GetBytes(ctx)
		if err != nil {
			return nil, err
		}
		return string(bytes), err
	case sql.JSONWrapper:
		// JSON type is stored as binary JSON (same as JSONB), so output is normalized with spaces
		return jsonWrapperToFormattedString(ctx, v)
	default:
		return nil, fmt.Errorf("unexpected type for json_out: %T", val)
	}
}
~~~

`server/types/type.go:603-614`

~~~go
// IoOutput converts given type value to output string.
func (t *DoltgresType) IoOutput(ctx *sql.Context, val any) (string, error) {
	var o any
	var err error
	if t.ModInFunc != 0 || t.IsArrayType() || t.IsCompositeType() {
		send := globalFunctionRegistry.GetFunction(ctx, t.OutputFunc)
		resolvedTypes := send.ResolvedTypes()
		resolvedTypes[0] = t
		o, err = send.WithResolvedTypes(resolvedTypes).(QuickFunction).CallVariadic(ctx, val)
	} else {
		o, err = globalFunctionRegistry.GetFunction(ctx, t.OutputFunc).CallVariadic(ctx, val)
	}
~~~

`server/types/function_registry.go:88-92`

~~~go
f = r.loadFunction(ctx, id)
if f == nil {
	// If we hit this panic, then we're missing a test that uses this function (and we should add that test)
	panic(errors.Errorf("cannot find function: `%s`", r.revMapping[id]))
}
~~~

@github-actions

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 18269 18268
Failures 23821 23822
Partial Successes1 5333 5333
Main PR
Successful 43.4046% 43.4022%
Failures 56.5954% 56.5978%

${\color{red}Regressions (1)}$

random

QUERY:          (SELECT unique1 AS random
  FROM onek ORDER BY random() LIMIT 1)
INTERSECT
(SELECT unique1 AS random
  FROM onek ORDER BY random() LIMIT 1)
INTERSECT
(SELECT unique1 AS random
  FROM onek ORDER BY random() LIMIT 1);
RECEIVED ERROR: expected row count 0 but received 1

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.

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.

1 participant