Files
dolthub--doltgresql/testing/go/create_function_sql_test.go
T
2026-07-13 12:32:25 +08:00

435 lines
12 KiB
Go

// Copyright 2025 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 TestCreateFunctionsLanguageSQL(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "unnamed parameter",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION alt_func1(int) RETURNS int LANGUAGE sql AS 'SELECT $1 + 1';`,
Expected: []sql.Row{},
},
{
Query: `SELECT alt_func1(3);`,
Expected: []sql.Row{{4}},
},
},
},
{
Name: "named parameter",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION alt_func1(x int) RETURNS int LANGUAGE sql AS 'SELECT x + 1';`,
Expected: []sql.Row{},
},
{
Query: `SELECT alt_func1(3);`,
Expected: []sql.Row{{4}},
},
{
Query: `CREATE FUNCTION sub_numbers(x int, y int) RETURNS int LANGUAGE sql AS 'SELECT y - x';`,
Expected: []sql.Row{},
},
{
Query: `SELECT sub_numbers(1, 2);`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "unknown functions",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION get_grade_description(score INT)
RETURNS TEXT
LANGUAGE SQL
AS $$
SELECT
CASE
WHEN score >= 90 THEN 'Excellent'
WHEN score >= 75 THEN 'Good'
WHEN score >= 50 THEN 'Average'
ELSE 'Fail'
END;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT get_grade_description(92);`,
Expected: []sql.Row{{"Excellent"}},
},
{
Query: `SELECT get_grade_description(65);`,
Expected: []sql.Row{{"Average"}},
},
},
},
{
Name: "nested functions",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION calculate_double_sum(x INT, y INT)
RETURNS INT
LANGUAGE SQL
AS $$
SELECT add_numbers(x, y) * 2;
$$;`,
// TODO: error message should be: function add_numbers(integer, integer) does not exist
ExpectedErr: "function: 'add_numbers' not found",
},
{
Query: `CREATE FUNCTION add_numbers(int, int) RETURNS int LANGUAGE sql AS 'SELECT $1 + $2';`,
Expected: []sql.Row{},
},
{
Query: `CREATE FUNCTION calculate_double_sum(x INT, y INT)
RETURNS INT
LANGUAGE SQL
AS $$
SELECT add_numbers(x, y) * 2;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT calculate_double_sum(1, 2);`,
Expected: []sql.Row{{6}},
},
},
},
{
Name: "function returning multiple rows",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION gen(a int) RETURNS SETOF INT LANGUAGE SQL AS $$ SELECT generate_series(1, a) $$ STABLE;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM gen(3);`,
Expected: []sql.Row{{1}, {2}, {3}},
},
},
},
{
Name: "function with create or replace view",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION public.sp_build_view_bathymetry_layer() RETURNS void
LANGUAGE sql
AS $$
CREATE OR REPLACE VIEW public.view_bathymetry_layer AS
SELECT 1;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT public.sp_build_view_bathymetry_layer()`,
Expected: []sql.Row{},
},
{
Query: `SELECT * from view_bathymetry_layer`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT public.sp_build_view_bathymetry_layer()`,
Expected: []sql.Row{},
},
{
Query: `SELECT * from view_bathymetry_layer`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "function with update ... returning",
SetUpScript: []string{
`CREATE TYPE public.tax_job_state AS ENUM (
'sched',
'busy',
'final',
'help'
);`,
`CREATE TABLE public.tax_job (
id bigint NOT NULL,
state public.tax_job_state NOT NULL,
created timestamp NOT NULL,
modified timestamp NOT NULL,
scheduled timestamp,
worker text,
processor text,
ext_id text,
data jsonb,
gross integer,
notes text[],
ops jsonb,
CONSTRAINT tax_job_check CHECK ((NOT ((state = 'sched'::public.tax_job_state) AND (scheduled IS NULL)))),
CONSTRAINT tax_job_check1 CHECK ((NOT ((state = 'busy'::public.tax_job_state) AND (worker IS NULL))))
);`,
`INSERT INTO tax_job (id, state, created, modified, scheduled, worker, processor, ext_id, data) VALUES (1, 'sched', '2025-05-05 05:05:05', '2025-05-05 05:05:05', '2025-05-05 05:05:05', 'worker', 'processor', 'ext_id', NULL)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION public.tax_job_take(arg_worker text) RETURNS SETOF public.tax_job
LANGUAGE sql
AS '
UPDATE
tax_job
SET
state = ''busy'',
worker = arg_worker
WHERE
state = ''sched''
AND scheduled <= CURRENT_TIMESTAMP
RETURNING
*;
';`,
Expected: []sql.Row{},
},
{
Query: `SELECT public.tax_job_take('worker')`,
Expected: []sql.Row{{`(1,busy,"2025-05-05 05:05:05","2025-05-05 05:05:05","2025-05-05 05:05:05",worker,processor,ext_id,,,,)`}},
},
{
Query: `INSERT INTO tax_job (id, state, created, modified, scheduled, worker, processor, ext_id, data) VALUES (2, 'sched', '2025-05-05 05:05:06', '2025-05-05 05:05:06', '2025-05-05 05:05:06', 'worker', 'processor', 'ext_id', NULL), (3, 'sched', '2025-05-05 05:05:07', '2025-05-05 05:05:07', '2025-05-05 05:05:07', 'worker', 'processor', 'ext_id', NULL)`,
},
{
Query: `SELECT public.tax_job_take('worker')`,
Expected: []sql.Row{
{`(2,busy,"2025-05-05 05:05:06","2025-05-05 05:05:06","2025-05-05 05:05:06",worker,processor,ext_id,,,,)`},
{`(3,busy,"2025-05-05 05:05:07","2025-05-05 05:05:07","2025-05-05 05:05:07",worker,processor,ext_id,,,,)`},
},
},
},
},
{
Name: "function with delete",
SetUpScript: []string{
`CREATE TABLE test (id bigint NOT NULL, state text NOT NULL);`,
`INSERT INTO test VALUES (1, 'sched'), (2, 'busy');`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION d(w text) RETURNS bigint
LANGUAGE sql
AS '
DELETE FROM test
WHERE
state = w
RETURNING
id;
';`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test;`,
Expected: []sql.Row{{1, "sched"}, {2, "busy"}},
},
{
Query: `SELECT d('sched');`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT * FROM test;`,
Expected: []sql.Row{{2, "busy"}},
},
},
},
{
Name: "multiple statements in function",
SetUpScript: []string{
`CREATE TABLE test (id int);`,
`INSERT INTO test VALUES (1), (2), (3);`,
`CREATE VIEW test1 AS SELECT * FROM test WHERE id = 1;`,
`CREATE VIEW test2 AS SELECT * FROM test WHERE id = 2;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION drop_views() RETURNS void
LANGUAGE sql
AS $$
DROP VIEW test1;
DROP VIEW test2;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test1`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT * FROM test2`,
Expected: []sql.Row{{2}},
},
{
Query: `SELECT drop_views();`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT * FROM test1`,
ExpectedErr: `not found`,
},
{
Query: `SELECT * FROM test2`,
ExpectedErr: `not found`,
},
},
},
{
Name: "function with default expression in parameter",
SetUpScript: []string{
`CREATE TABLE cp_test (a int, b text);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, d text, f int default 100)
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e+f, d);
SELECT a FROM cp_test WHERE b = d;
$$;`,
Expected: []sql.Row{},
},
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, f int default 100)
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e+f, 'seconddfunc');
SELECT a FROM cp_test WHERE b = 'seconddfunc';
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM dfunc(10, 'Hello', 20);`,
Expected: []sql.Row{{30}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}},
},
{
Query: `SELECT * FROM dfunc(50, 'Bye');`,
Expected: []sql.Row{{150}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}, {150, "Bye"}},
},
{
Query: `SELECT dfunc(2, 'After');`,
Expected: []sql.Row{{102}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}, {150, "Bye"}, {102, "After"}},
},
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, f text default '100')
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e, f);
SELECT a FROM cp_test WHERE b = f;
$$;`,
Expected: []sql.Row{},
},
{
// TODO: the error message should be "function dfunc(integer) is not unique"
Query: `SELECT dfunc(50);`,
ExpectedErr: `does not exist`,
},
},
},
{
Name: "use sql statements in BEGIN ATOMIC ... END in sql_body",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION match_default() RETURNS jsonb
LANGUAGE sql
BEGIN ATOMIC
SELECT jsonb_build_object('k', 6, 'm', 2048, 'include_original', true, 'tokenizer', json_build_object('kind', 'ngram', 'token_length', 3), 'token_filters', json_build_array(json_build_object('kind', 'downcase'))) AS jsonb_build_object;
END;`,
Expected: []sql.Row{},
},
{
Skip: true, // TODO support json_build_object() function
Query: `SELECT public.match_default();`,
Expected: []sql.Row{{`{"k": 6, "m": 2048, "tokenizer": {"kind": "ngram", "token_length": 3}, "token_filters": [{"kind": "downcase"}], "include_original": true}`}},
},
{
Query: `CREATE FUNCTION select1() RETURNS int
LANGUAGE sql
BEGIN ATOMIC
SELECT 1;
END;`,
Expected: []sql.Row{},
},
{
Query: `SELECT select1();`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "use RETURN in BEGIN ATOMIC ... END in sql_body",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION return1() RETURNS text
LANGUAGE sql
BEGIN ATOMIC
RETURN 1::text || 'one';
END;`,
Expected: []sql.Row{},
},
{
Query: `SELECT return1();`,
Expected: []sql.Row{{"1one"}},
},
},
},
{
Name: "user-defined composite array parameter and return type",
SetUpScript: []string{
`CREATE TYPE aggtype AS (a integer, b integer, c text)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION aggf_trans(aggtype[],integer,integer,text) RETURNS aggtype[] AS 'select array_append($1,ROW($2,$3,$4)::aggtype)' LANGUAGE sql STRICT IMMUTABLE`,
Expected: []sql.Row{},
},
{
Query: `SELECT aggf_trans(ARRAY[]::aggtype[], 1, 2, 'hello')`,
Expected: []sql.Row{{`{"(1,2,hello)"}`}},
},
{
Query: `SELECT aggf_trans(ARRAY[ROW(1,2,'hello')::aggtype], 3, 4, 'world')`,
Expected: []sql.Row{{`{"(1,2,hello)","(3,4,world)"}`}},
},
},
},
})
}