Files
2026-07-13 12:32:25 +08:00

453 lines
17 KiB
Go

/// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCasts(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "CREATE CAST with function creates explicit-only cast",
SetUpScript: []string{
`CREATE TABLE cast_explicit_src (v text);`,
`CREATE TABLE cast_explicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_explicit_fn(src cast_explicit_src) RETURNS cast_explicit_dst
AS $$ SELECT ROW((src).v, 'explicit')::cast_explicit_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_explicit_accept(dst cast_explicit_dst) RETURNS text
AS $$ SELECT (dst).v || ':' || (dst).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_explicit_holder (v cast_explicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_explicit_accept((ROW('one')::cast_explicit_src)::cast_explicit_dst);`,
Expected: []sql.Row{{"one:explicit"}},
},
{
Query: `SELECT cast_explicit_accept(ROW('one')::cast_explicit_src);`,
ExpectedErr: "does not exist",
},
{
Query: `INSERT INTO cast_explicit_holder VALUES (ROW('one')::cast_explicit_src);`,
ExpectedErr: "cast_explicit_src",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_explicit_src'
AND dst.typname = 'cast_explicit_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
ExpectedErr: "already exists",
},
},
},
{
Name: "CREATE CAST with PL/pgSQL function creates explicit-only cast",
SetUpScript: []string{
`CREATE TABLE cast_explicit_src (v text);`,
`CREATE TABLE cast_explicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_explicit_fn(src cast_explicit_src) RETURNS cast_explicit_dst AS $$ BEGIN
RETURN ROW((src).v, 'explicit')::cast_explicit_dst;
END; $$ LANGUAGE plpgsql;`,
`CREATE FUNCTION cast_explicit_accept(dst cast_explicit_dst) RETURNS text AS $$ BEGIN
RETURN (dst).v || ':' || (dst).tag;
END; $$ LANGUAGE plpgsql;`,
`CREATE TABLE cast_explicit_holder (v cast_explicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_explicit_accept((ROW('one')::cast_explicit_src)::cast_explicit_dst);`,
Expected: []sql.Row{{"one:explicit"}},
},
{
Query: `SELECT cast_explicit_accept(ROW('one')::cast_explicit_src);`,
ExpectedErr: "does not exist",
},
{
Query: `INSERT INTO cast_explicit_holder VALUES (ROW('one')::cast_explicit_src);`,
ExpectedErr: "cast_explicit_src",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_explicit_src'
AND dst.typname = 'cast_explicit_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
ExpectedErr: "already exists",
},
},
},
{
Name: "CREATE CAST AS ASSIGNMENT works for assignment contexts only",
SetUpScript: []string{
`CREATE TABLE cast_assignment_src (v text);`,
`CREATE TABLE cast_assignment_dst (v text, tag text);`,
`CREATE FUNCTION cast_assignment_fn(cast_assignment_src) RETURNS cast_assignment_dst
AS $$ SELECT ROW(($1).v, 'assignment')::cast_assignment_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_assignment_accept(cast_assignment_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_assignment_holder (v cast_assignment_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_assignment_src AS cast_assignment_dst) WITH FUNCTION cast_assignment_fn(cast_assignment_src) AS ASSIGNMENT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_assignment_accept((ROW('on')::cast_assignment_src)::cast_assignment_dst);`,
Expected: []sql.Row{{"on:assignment"}},
},
{
Query: `INSERT INTO cast_assignment_holder VALUES (ROW('on')::cast_assignment_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_assignment_accept(v) FROM cast_assignment_holder;`,
Expected: []sql.Row{{"on:assignment"}},
},
{
Query: `SELECT cast_assignment_accept(ROW('off')::cast_assignment_src);`,
ExpectedErr: "does not exist",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_assignment_src'
AND dst.typname = 'cast_assignment_dst';`,
Expected: []sql.Row{{"a", "f"}},
},
},
},
{
Name: "CREATE CAST AS IMPLICIT works for function resolution",
SetUpScript: []string{
`CREATE TABLE cast_implicit_src (v text);`,
`CREATE TABLE cast_implicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_implicit_fn(cast_implicit_src) RETURNS cast_implicit_dst
AS $$ SELECT ROW(($1).v, 'implicit')::cast_implicit_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_implicit_accept(cast_implicit_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_implicit_holder (v cast_implicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_implicit_src AS cast_implicit_dst) WITH FUNCTION cast_implicit_fn(cast_implicit_src) AS IMPLICIT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_implicit_accept((ROW('x')::cast_implicit_src)::cast_implicit_dst);`,
Expected: []sql.Row{{"x:implicit"}},
},
{
Query: `SELECT cast_implicit_accept(ROW('y')::cast_implicit_src);`,
Expected: []sql.Row{{"y:implicit"}},
},
{
Query: `INSERT INTO cast_implicit_holder VALUES (ROW('z')::cast_implicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_implicit_accept(v) FROM cast_implicit_holder;`,
Expected: []sql.Row{{"z:implicit"}},
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_implicit_src'
AND dst.typname = 'cast_implicit_dst';`,
Expected: []sql.Row{{"i", "f"}},
},
},
},
{
Name: "CREATE CAST WITH INOUT",
SetUpScript: []string{
`CREATE TABLE cast_inout_src (v text);`,
`CREATE TABLE cast_inout_dst (v int);`,
`CREATE FUNCTION cast_inout_accept(cast_inout_dst) RETURNS text
AS $$ SELECT (($1).v)::text $$ LANGUAGE SQL;`,
`CREATE TABLE cast_inout_holder (v cast_inout_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_inout_src AS cast_inout_dst) WITH INOUT AS ASSIGNMENT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_inout_accept((ROW('42')::cast_inout_src)::cast_inout_dst);`,
Expected: []sql.Row{{"42"}},
},
{
Query: `INSERT INTO cast_inout_holder VALUES (ROW('99')::cast_inout_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_inout_accept(v) FROM cast_inout_holder;`,
Expected: []sql.Row{{"99"}},
},
{
Query: `SELECT cast_inout_accept((ROW('not_an_int')::cast_inout_src)::cast_inout_dst);`,
ExpectedErr: "invalid input syntax",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_inout_src'
AND dst.typname = 'cast_inout_dst';`,
Expected: []sql.Row{{"a", "i"}},
},
},
},
{
Name: "CREATE CAST with three-argument function receives explicit flag",
SetUpScript: []string{
`CREATE TABLE cast_three_arg_src (v text);`,
`CREATE TABLE cast_three_arg_dst (v text, tag text);`,
`CREATE FUNCTION cast_three_arg_fn(cast_three_arg_src, integer, boolean) RETURNS cast_three_arg_dst
AS $$
SELECT ROW(
($1).v,
CASE WHEN $3 THEN 'explicit' ELSE 'implicit_or_assignment' END
)::cast_three_arg_dst
$$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_three_arg_accept(cast_three_arg_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_three_arg_holder (v cast_three_arg_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_three_arg_src AS cast_three_arg_dst) WITH FUNCTION cast_three_arg_fn(cast_three_arg_src, integer, boolean) AS IMPLICIT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_three_arg_accept((ROW('a')::cast_three_arg_src)::cast_three_arg_dst);`,
Expected: []sql.Row{{"a:explicit"}},
},
{
Query: `SELECT cast_three_arg_accept(ROW('b')::cast_three_arg_src);`,
Expected: []sql.Row{{"b:implicit_or_assignment"}},
},
{
Query: `INSERT INTO cast_three_arg_holder VALUES (ROW('c')::cast_three_arg_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_three_arg_accept(v) FROM cast_three_arg_holder;`,
Expected: []sql.Row{{"c:implicit_or_assignment"}},
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_three_arg_src'
AND dst.typname = 'cast_three_arg_dst';`,
Expected: []sql.Row{{"i", "f"}},
},
},
},
{
Name: "DROP CAST removes catalog entry and allows recreation",
SetUpScript: []string{
`CREATE TABLE cast_drop_src (v text);`,
`CREATE TABLE cast_drop_dst (v text, tag text);`,
`CREATE FUNCTION cast_drop_fn(cast_drop_src) RETURNS cast_drop_dst
AS $$ SELECT ROW(($1).v, 'drop')::cast_drop_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_drop_accept(cast_drop_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE CAST (cast_drop_src AS cast_drop_dst) WITH FUNCTION cast_drop_fn(cast_drop_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT cast_drop_accept((ROW('before')::cast_drop_src)::cast_drop_dst);`,
Expected: []sql.Row{{"before:drop"}},
},
{
Query: `SELECT EXISTS (
SELECT 1
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_drop_src'
AND dst.typname = 'cast_drop_dst'
);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `DROP CAST (cast_drop_src AS cast_drop_dst);`,
Expected: []sql.Row{},
},
{
Query: `SELECT EXISTS (
SELECT 1
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_drop_src'
AND dst.typname = 'cast_drop_dst'
);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT cast_drop_accept((ROW('after')::cast_drop_src)::cast_drop_dst);`,
ExpectedErr: "cast_drop_src",
},
{
Query: `CREATE CAST (cast_drop_src AS cast_drop_dst) WITH FUNCTION cast_drop_fn(cast_drop_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_drop_accept((ROW('after')::cast_drop_src)::cast_drop_dst);`,
Expected: []sql.Row{{"after:drop"}},
},
},
},
{
Name: "CREATE CAST function is invoked for NULL when function is not STRICT",
SetUpScript: []string{
`CREATE TABLE cast_null_src (v text);`,
`CREATE TABLE cast_null_dst (v text, tag text);`,
`CREATE FUNCTION cast_null_fn(cast_null_src) RETURNS cast_null_dst
AS $$ SELECT CASE
WHEN $1 IS NULL THEN ROW('saw_null', 'called')::cast_null_dst
ELSE ROW('hi', 'nonnull')::cast_null_dst
END $$ LANGUAGE SQL;`,
`CREATE CAST (cast_null_src AS cast_null_dst) WITH FUNCTION cast_null_fn(cast_null_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT ((NULL::cast_null_src)::cast_null_dst);`,
Expected: []sql.Row{{"(saw_null,called)"}},
},
{
Query: `SELECT ((ROW('hi')::cast_null_src)::cast_null_dst);`,
Expected: []sql.Row{{"(hi,nonnull)"}},
},
},
},
{
Name: "CREATE CAST STRICT function is not invoked for NULL",
SetUpScript: []string{
`CREATE TABLE cast_strict_null_src (v text);`,
`CREATE TABLE cast_strict_null_dst (v text, tag text);`,
`CREATE FUNCTION cast_strict_null_fn(cast_strict_null_src) RETURNS cast_strict_null_dst
AS $$ SELECT ROW('bad', 'called')::cast_strict_null_dst $$ LANGUAGE SQL STRICT;`,
`CREATE CAST (cast_strict_null_src AS cast_strict_null_dst) WITH FUNCTION cast_strict_null_fn(cast_strict_null_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT ((NULL::cast_strict_null_src)::cast_strict_null_dst) IS NULL;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT ((ROW('there')::cast_strict_null_src)::cast_strict_null_dst);`,
Expected: []sql.Row{{"(bad,called)"}},
},
},
},
{
Name: "CREATE CAST validation",
SetUpScript: []string{
`CREATE TABLE cast_bad_src (v text);`,
`CREATE TABLE cast_bad_dst (v text, tag text);`,
`CREATE FUNCTION cast_bad_wrong_return(cast_bad_src) RETURNS text
AS $$ SELECT ($1).v $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_source(text) RETURNS cast_bad_dst
AS $$ SELECT ROW($1, 'wrong_source')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_second(cast_bad_src, text) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'wrong_second')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_third(cast_bad_src, integer, integer) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'wrong_third')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_too_many(cast_bad_src, integer, boolean, integer) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'too_many')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_good(cast_bad_src) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'good')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE TABLE cast_without_src (v text);`,
`CREATE TABLE cast_without_dst (v text, tag text);`,
`CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_good(cast_bad_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_missing(cast_bad_src);`,
ExpectedErr: "does not exist",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_return(cast_bad_src);`,
ExpectedErr: "return data type",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_source(text);`,
ExpectedErr: "source data type",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_second(cast_bad_src, text);`,
ExpectedErr: "second argument",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_third(cast_bad_src, integer, integer);`,
ExpectedErr: "third argument",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_too_many(cast_bad_src, integer, boolean, integer);`,
ExpectedErr: "one to three arguments",
},
{
Query: `CREATE CAST (cast_without_src AS cast_without_dst) WITHOUT FUNCTION;`,
ExpectedErr: "composite data types are not binary-compatible",
},
{
Query: `CREATE CAST (int4 AS float8) WITHOUT FUNCTION;`,
ExpectedErr: "source and target data types are not physically compatible",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_src) WITH INOUT;`,
ExpectedErr: "source data type and target data type are the same",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_good(cast_bad_src);`,
ExpectedErr: "cast from type cast_bad_src to type cast_bad_dst already exists",
},
},
},
})
}