Skip to content

Commit 9d1a68d

Browse files
committed
Improve support for postgresql functions that return tables:
- add IsArray field for function parameters - support struct creation for function queries
1 parent 7d6377f commit 9d1a68d

File tree

11 files changed

+293
-4
lines changed

11 files changed

+293
-4
lines changed

internal/compiler/output_columns.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,22 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
310310
}
311311
fun, err := qc.catalog.ResolveFuncCall(n)
312312
if err == nil {
313+
var tableCols []*catalog.Argument
314+
for _, arg := range fun.Args {
315+
if arg.Mode == ast.FuncParamTable {
316+
tableCols = append(tableCols, arg)
317+
}
318+
}
319+
var dt string
320+
if len(tableCols) == 1 {
321+
// A single column will later generate a scalar (or slice of scalar) return type.
322+
dt = dataType(tableCols[0].Type)
323+
} else {
324+
dt = dataType(fun.ReturnType)
325+
}
313326
cols = append(cols, &Column{
314327
Name: name,
315-
DataType: dataType(fun.ReturnType),
328+
DataType: dt,
316329
NotNull: !fun.ReturnTypeNullable,
317330
IsFuncCall: true,
318331
})
@@ -526,6 +539,37 @@ func (c *Compiler) sourceTables(qc *QueryCatalog, node ast.Node) ([]*Table, erro
526539
if err != nil {
527540
continue
528541
}
542+
fnsWithName, err := c.catalog.ListFuncsByName(&ast.FuncName{
543+
Catalog: fn.ReturnType.Catalog,
544+
Schema: fn.ReturnType.Schema,
545+
Name: fn.ReturnType.Name,
546+
})
547+
// If the function was found, build a table structure to hold the columns to output.
548+
if err == nil && len(fnsWithName) == 1 {
549+
fnWithName := fnsWithName[0]
550+
rel := &ast.TableName{
551+
Catalog: fn.ReturnType.Catalog,
552+
Schema: fn.ReturnType.Schema,
553+
Name: fn.ReturnType.Name,
554+
}
555+
var cols []*Column
556+
for _, arg := range fnWithName.Args {
557+
if arg.Mode == ast.FuncParamTable {
558+
col := &catalog.Column{
559+
Name: arg.Name,
560+
Type: *arg.Type,
561+
IsArray: arg.IsArray,
562+
}
563+
convertedCol := ConvertColumn(rel, col)
564+
cols = append(cols, convertedCol)
565+
}
566+
}
567+
tables = append(tables, &Table{
568+
Rel: rel,
569+
Columns: cols,
570+
})
571+
continue
572+
}
529573
table, err := qc.GetTable(&ast.TableName{
530574
Catalog: fn.ReturnType.Catalog,
531575
Schema: fn.ReturnType.Schema,

internal/endtoend/testdata/func_return_table/postgresql/pgx/v5/go/db.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/func_return_table/postgresql/pgx/v5/go/models.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/func_return_table/postgresql/pgx/v5/go/query.sql.go

Lines changed: 97 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- name: BooksByTagsViaFunc :many
2+
SELECT * FROM sqlc_book_schema.books_by_tags_func($1::VARCHAR[]);
3+
4+
-- name: BooksByTagsViaFuncLimitedFields :many
5+
SELECT book_id, tags FROM sqlc_book_schema.books_by_tags_func($1::VARCHAR[]);
6+
7+
-- name: CreateAuthorViaFunc :one
8+
SELECT * FROM create_author_func($1, $2);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
CREATE TABLE authors (
2+
author_id SERIAL PRIMARY KEY,
3+
name text NOT NULL DEFAULT '',
4+
num integer NOT NULL DEFAULT 0
5+
);
6+
7+
CREATE TABLE books (
8+
book_id SERIAL PRIMARY KEY,
9+
author_id integer NOT NULL REFERENCES authors(author_id),
10+
isbn text NOT NULL DEFAULT '' UNIQUE,
11+
book_type text NOT NULL DEFAULT 'FICTION',
12+
title text NOT NULL DEFAULT '',
13+
year integer NOT NULL DEFAULT 2000,
14+
available timestamp with time zone NOT NULL DEFAULT 'NOW()',
15+
tags varchar[] NOT NULL DEFAULT '{}'
16+
);
17+
18+
-- to test query return type / schema interaction
19+
CREATE SCHEMA sqlc_book_schema;
20+
21+
CREATE OR REPLACE FUNCTION sqlc_book_schema.books_by_tags_func(_tags VARCHAR[])
22+
RETURNS TABLE(book_id INT, title TEXT, name TEXT, isbn TEXT, tags VARCHAR[], available timestamp) AS $$
23+
BEGIN
24+
RETURN QUERY
25+
SELECT
26+
b.book_id,
27+
b.title,
28+
a.name,
29+
b.isbn,
30+
b.tags,
31+
available
32+
FROM books b
33+
LEFT JOIN authors a ON b.author_id = a.author_id
34+
WHERE b.tags && _tags
35+
ORDER BY name, title ASC
36+
;
37+
END;
38+
$$ LANGUAGE plpgsql;
39+
40+
CREATE OR REPLACE FUNCTION create_author_func(_name TEXT, _num INTEGER)
41+
RETURNS TABLE(id INTEGER) AS $$
42+
BEGIN
43+
INSERT INTO authors(name, num)
44+
VALUES (_name, _num)
45+
RETURNING author_id into id;
46+
RETURN NEXT;
47+
END;
48+
$$ LANGUAGE plpgsql;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "1",
3+
"packages": [
4+
{
5+
"path": "go",
6+
"engine": "postgresql",
7+
"sql_package": "pgx/v5",
8+
"name": "querytest",
9+
"schema": "schema.sql",
10+
"queries": "query.sql"
11+
}
12+
]
13+
}

internal/engine/postgresql/parse.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,10 @@ func translate(node *nodes.Node) (ast.Node, error) {
495495
return nil, err
496496
}
497497
fp := &ast.FuncParam{
498-
Name: &arg.Name,
499-
Type: rel.TypeName(),
500-
Mode: mode,
498+
Name: &arg.Name,
499+
Type: rel.TypeName(),
500+
Mode: mode,
501+
IsArray: isArray(arg.ArgType),
501502
}
502503
if arg.Defexpr != nil {
503504
fp.DefExpr = &ast.TODO{}

internal/sql/ast/func_param.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type FuncParam struct {
1616
Type *TypeName
1717
DefExpr Node // Will always be &ast.TODO
1818
Mode FuncParamMode
19+
IsArray bool
1920
}
2021

2122
func (n *FuncParam) Pos() int {

internal/sql/ast/function_parameter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type FunctionParameter struct {
55
ArgType *TypeName
66
Mode FunctionParameterMode
77
Defexpr Node
8+
IsArray bool
89
}
910

1011
func (n *FunctionParameter) Pos() int {

internal/sql/catalog/func.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Argument struct {
2424
Type *ast.TypeName
2525
HasDefault bool
2626
Mode ast.FuncParamMode
27+
IsArray bool
2728
}
2829

2930
func (f *Function) InArgs() []*Argument {
@@ -54,21 +55,38 @@ func (c *Catalog) createFunction(stmt *ast.CreateFunctionStmt) error {
5455
ReturnType: stmt.ReturnType,
5556
}
5657
types := make([]*ast.TypeName, len(stmt.Params.Items))
58+
var cols []*ast.ColumnDef
5759
for i, item := range stmt.Params.Items {
5860
arg := item.(*ast.FuncParam)
5961
var name string
6062
if arg.Name != nil {
6163
name = *arg.Name
6264
}
65+
if arg.Mode == ast.FuncParamTable {
66+
cols = append(cols, &ast.ColumnDef{
67+
Colname: name,
68+
TypeName: arg.Type,
69+
IsArray: arg.IsArray,
70+
})
71+
}
6372
fn.Args[i] = &Argument{
6473
Name: name,
6574
Type: arg.Type,
6675
Mode: arg.Mode,
6776
HasDefault: arg.DefExpr != nil,
77+
IsArray: arg.IsArray,
6878
}
6979
types[i] = arg.Type
7080
}
7181

82+
if len(cols) > 0 {
83+
fn.ReturnType = &ast.TypeName{
84+
Name: stmt.Func.Name,
85+
Schema: stmt.Func.Schema,
86+
Catalog: stmt.Func.Catalog,
87+
}
88+
}
89+
7290
_, idx, err := s.getFunc(stmt.Func, types)
7391
if err == nil && !stmt.Replace {
7492
return sqlerr.RelationExists(stmt.Func.Name)

0 commit comments

Comments
 (0)