support array_to_json and row_to_json functions#2730
Conversation
|
|
Ito Test Report ❌10 test cases ran. 1 failed, 9 passed. Across the unified run, 10 test cases were recorded with 9 passes and 1 failure (plus one execution that ended before any verifiable test outcome), indicating generally stable behavior with a single confirmed defect. The key finding was a medium, PR-introduced bug where row_to_json(d.*) on a derived-table alias fails with a composite-type creation error, while other checks confirmed expected behavior: table-backed row_to_json preserves named keys, anonymous/projection forms intentionally fall back to f1/f2/f3, and CREATE VIEW security_barrier handling is intentionally split (false accepted, true rejected) with policy enforcement remaining consistent. ❌ Failed (1)
🟠 Derived-table alias composite resolution fails
Relevant code:
// TODO: we need to get the schema, but the GMS builder doesn't have that information
typ, err := coll.GetType(ctx, id.NewType("", tableName))
if err != nil {
return nil, err
}
if typ == nil {
return nil, errors.New(fmt.Sprintf(`could not create a composite type for table "%s"`, tableName))
}
} else if paramType.IsRecordType() && argTypes[i].IsCompositeType() {
// Composite types (e.g. table row types) are compatible with the generic Record parameter.
overloadCasts[i] = casts.Cast{
ID: id.NewCast(argTypes[i].ID, paramType.ID),
CastType: casts.CastType_Implicit,
Function: id.NullFunction,
UseInOut: false,
}
func fieldNames(rowType *pgtypes.DoltgresType, count int) []string {
names := make([]string, count)
if rowType != nil && len(rowType.CompositeAttrs) == count {
for i, attr := range rowType.CompositeAttrs {
names[i] = attr.Name
}
} else {✅ Passed (9)Commit: Tell us how we did: Give Ito Feedback |
Ito Diff Report ❌Tested: Across 7 test cases, 3 passed and 4 failed, confirming multiple real product defects despite some successful baseline behavior. The most critical findings were a High-severity import blocker where dumps fail on unsupported ALTER DEFAULT PRIVILEGES statements and a High-severity row_to_json inconsistency for derived composite rows/casts, alongside Medium-severity issues where CREATE VIEW rejects WITH (check_option = local) and row_to_json(NULL) is incorrectly accepted as NULL, while control checks like base composite row_to_json resolution and numeric array_to_json formatting behaved as expected. ❌ Failures (3)
|
| Category | Summary | Screenshot |
|---|---|---|
| Array | Decimal array elements were serialized as JSON numbers ([1.25,2.50]) without string quoting. |
N/A |
| Record | The problematic composite-subquery invocation was rejected consistently during resolution with a stable error, while normal composite invocation executed successfully; no late Eval cast metadata failure was observed. | N/A |
| Resolve | row_to_json on rtj_test composite row resolved and executed successfully, returning JSON output without function resolution errors. | N/A |
↪️ Inherited from Prior Run (12)
Tests that passed in the prior run. c3 judged them unaffected by the diff and did not retest.
| Category | Summary | Screenshot |
|---|---|---|
| Array | array_to_json(ARRAY[1,NULL,3]) returned [1,null,3] as expected. |
N/A |
| Import | Missing setup role is reported clearly during import interception. | N/A |
| Import | Malicious privilege escalation statement is rejected without lasting elevation. | N/A |
| Import | Immediate retry after partial setup failure remains stable with consistent diagnostics. | N/A |
| Render | After seeding rtj_test with Alice/Bob, SELECT row_to_json(t) FROM rtj_test AS t WHERE id=1 returned {"id":1,"name":"Alice"}, confirming named-column keys (id,name) rather than positional keys (f1,f2). | N/A |
| Resolve | Executing SELECT row_to_json(1); produced the expected function-resolution error (row_to_json(integer) does not exist), confirming scalar input is not silently coerced. |
N/A |
| Resolve | SELECT row_to_json(row(1, true, null), true) succeeded and returned JSON with keys f1/f2/f3, value 1 for f1, true for f2, and JSON null for f3. |
N/A |
| Resolve | Controlled nested row_to_json error handling was observed and the SQL session remained responsive. |
N/A |
| Resolve | Compile-time and runtime row_to_json behavior aligned after re-running against the source-built server. |
N/A |
| Resolve | Composite, record, and scalar invocations produced consistent explicit errors without destabilizing the session. | N/A |
| View | CREATE VIEW with security_barrier=false succeeded and querying v_sb_false returned c=1. | N/A |
| View | CREATE VIEW with security_barrier=true was rejected and v_sb_true was not created. | N/A |
⚠️ Additional Findings (1)
These findings are unrelated to the current changes but were observed during testing.
| Origin | Category | Severity | Summary | Screenshot |
|---|---|---|---|---|
| 🆕 New | Record | 🟠 Medium | Confirmed real bug: SELECT row_to_json(NULL); resolves and returns NULL instead of emitting a function-resolution error for unknown NULL input. |
N/A |
🟠 Unknown NULL input bypasses row_to_json resolution checks
- What failed: The call resolves and returns SQL NULL, but this case should be rejected at resolution for unknown NULL input lacking explicit record type information.
- Impact: Type-resolution rules become overly permissive, allowing invalid
row_to_jsoncalls to pass silently as NULL instead of producing deterministic errors. This can hide query mistakes and make behavior less predictable for client code. - Steps to reproduce:
- Connect to the local database and run SELECT row_to_json(NULL) without any explicit cast.
- Observe function resolution and evaluation result.
- Compare with expected resolver rejection for unknown NULL without record typing.
- Stub / mock context: No stubs, mocks, or bypasses were applied for this test in the recorded run.
- Code analysis: I reviewed
server/functions/row_to_json.go,server/functions/framework/compiled_function.go, andcore/casts/collection.go. The function is strict (returns NULL on NULL args), and unknown is globally treated as implicitly castable, so overload resolution accepts unknown NULL then strict short-circuits instead of rejecting the call. - Why this is likely a bug: The implementation accepts unknown NULL through implicit casting and strict-null short-circuiting, which bypasses expected resolver rejection for an untyped record argument.
Relevant code:
server/functions/row_to_json.go (lines 35-40)
var row_to_json_record = framework.Function1{
Name: "row_to_json",
Return: pgtypes.Json,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Record},
Strict: true,server/functions/framework/compiled_function.go (lines 295-297)
if args[i] == nil && isStrict {
return nil, nil
}core/casts/collection.go (lines 189-196)
// It is always valid to convert from the `unknown` type
if sourceType.ID == pgtypes.Unknown.ID {
return Cast{
ID: castID,
CastType: CastType_Implicit,
Function: id.NullFunction,
UseInOut: true,Tell us how we did: Give Ito Feedback




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