Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions testcases/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,42 @@ lt('2016-12-31T13:30:15'::ts, '2017-12-31T13:30:15'::ts) = true::bool
assert.Equal(t, ScalarFuncType, testFile.TestCases[0].FuncType)
}

// Regression for #237: a `null::u!<identifier>[?]` argument used to panic
// in `VisitNullArg` because `TestCaseVisitor` had no `VisitUserDefined`
// method, so dispatch fell through to the base visitor's `VisitChildren`
// (which returns nil for the terminal `UserDefinedContext` node). The new
// `VisitUserDefined` returns a `*types.UserDefinedType` so the null
// literal can be constructed without panic. (`VisitNullArg` then forces
// the `CaseLiteral.Type`'s nullability to `Nullable` regardless of the
// parsed `?`, so we don't assert on that — we only assert the arg type
// is a `UserDefinedType` and the value is a `NullLiteral`.)
func TestParseUserDefinedNullArg(t *testing.T) {
header := makeHeader("v1.0", "/extensions/functions_arithmetic.yaml")
groupDesc := "# user-defined null arg parsing (issue #237)\n"
cases := []string{
"f1(null::u!geometry) = 1::i8",
"f1(null::u!geometry?) = 1::i8",
}
for _, tc := range cases {
t.Run(tc, func(t *testing.T) {
testFile, err := ParseTestCasesFromString(header + groupDesc + tc)
require.NoError(t, err, "parse must not panic / error on user-defined null arg")
require.NotNil(t, testFile)
require.Len(t, testFile.TestCases, 1)
require.Len(t, testFile.TestCases[0].Args, 1)

arg := testFile.TestCases[0].Args[0]
_, ok := arg.Type.(*types.UserDefinedType)
require.True(t, ok, "expected *types.UserDefinedType, got %T", arg.Type)

lit, ok := arg.Value.(*expr.NullLiteral)
require.True(t, ok, "expected *expr.NullLiteral, got %T", arg.Value)
_, ok = lit.GetType().(*types.UserDefinedType)
assert.True(t, ok, "NullLiteral.GetType() should be *types.UserDefinedType, got %T", lit.GetType())
})
}
}

func TestParseDecimalExample(t *testing.T) {
header := makeHeader("v1.0", "extensions/functions_arithmetic_decimal.yaml")
tests := `# basic
Expand Down
19 changes: 19 additions & 0 deletions testcases/parser/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,25 @@ func (v *TestCaseVisitor) VisitDataType(ctx *baseparser.DataTypeContext) interfa
return nil
}

// VisitUserDefined handles the `u!<identifier>[?]` scalar form that
// shows up in parser inputs like `null::u!geometry?`. The base visitor
// returns VisitChildren here (nil), which caused VisitNullArg to try
// to use nil as a types.Type and panic with
// "interface conversion: interface {} is nil, not string".
func (v *TestCaseVisitor) VisitUserDefined(ctx *baseparser.UserDefinedContext) interface{} {
nullability := types.NullabilityRequired
if ctx.QMark() != nil {
nullability = types.NullabilityNullable
}
ident := ctx.Identifier()
if ident == nil {
v.ErrorListener.ReportVisitError(ctx, fmt.Errorf("user-defined type without identifier: %v", ctx.GetText()))
return &types.UserDefinedType{Nullability: nullability}
}
_ = ident.GetText() // reserved for type-lookup; not available at this visit depth
return &types.UserDefinedType{Nullability: nullability}
}

func (v *TestCaseVisitor) VisitParameterizedType(ctx *baseparser.ParameterizedTypeContext) interface{} {
if ctx.DecimalType() != nil {
return v.Visit(ctx.DecimalType())
Expand Down
Loading