Skip to content

Commit 1425697

Browse files
authored
Merge pull request #2327 from dolthub/fulghum/record_type_fix
bugfix: support records containing non-DoltgresType values
2 parents dc21e09 + eea3844 commit 1425697

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

server/functions/framework/interpreted_function.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ func (iFunc InterpretedFunction) QuerySingleReturn(ctx *sql.Context, stack plpgs
138138
}
139139
fromType, ok := sch[0].Type.(*pgtypes.DoltgresType)
140140
if !ok {
141+
// TODO: We ensure we have a DoltgresType, but we should also convert the value to
142+
// ensure it's in the correct form for the DoltgresType. This logic lives in
143+
// pgexpressions.GMSCast, but need to be extracted to avoid a dependency cycle
144+
// so it can be used here and from server.plpgsql.
141145
fromType, err = pgtypes.FromGmsTypeToDoltgresType(sch[0].Type)
142146
if err != nil {
143147
return nil, err

server/plpgsql/interpreter_logic.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,11 @@ func call(ctx *sql.Context, iFunc InterpretedFunction, stack InterpreterStack) (
273273
if err != nil {
274274
return nil, err
275275
}
276-
stack.BufferReturnQueryResults(convertRowsToRecords(schema, rows))
276+
records, err := convertRowsToRecords(schema, rows)
277+
if err != nil {
278+
return nil, err
279+
}
280+
stack.BufferReturnQueryResults(records)
277281

278282
case OpCode_ScopeBegin:
279283
stack.PushScope()
@@ -292,15 +296,26 @@ func call(ctx *sql.Context, iFunc InterpretedFunction, stack InterpreterStack) (
292296

293297
// convertRowsToRecords iterates overs |rows| and converts each field in each row
294298
// into a RecordValue. |schema| is specified for type information.
295-
func convertRowsToRecords(schema sql.Schema, rows []sql.Row) [][]pgtypes.RecordValue {
299+
func convertRowsToRecords(schema sql.Schema, rows []sql.Row) ([][]pgtypes.RecordValue, error) {
296300
records := make([][]pgtypes.RecordValue, 0, len(rows))
297301
for _, row := range rows {
298302
record := make([]pgtypes.RecordValue, len(row))
299303
for i, field := range row {
300304
t := schema[i].Type
301305
doltgresType, ok := t.(*pgtypes.DoltgresType)
302306
if !ok {
303-
panic("expected Doltgres type")
307+
// non-Doltgres types are still used in analysis, but we only support disk serialization
308+
// for Doltgres types, so we must convert the GMS type to the nearest Doltgres type here.
309+
// TODO: this conversion isn't fully accurate. expression.GMSCast has additional logic in
310+
// its Eval() method to handle types more exactly and also handles converting the
311+
// value to ensure it is well formed for the returned DoltgresType. We can't
312+
// currently use GMSCast directly here though, because of a dependency cycle, so
313+
// that conversion logic needs to be extracted into a package both places can import.
314+
var err error
315+
doltgresType, err = pgtypes.FromGmsTypeToDoltgresType(t)
316+
if err != nil {
317+
return nil, err
318+
}
304319
}
305320

306321
record[i] = pgtypes.RecordValue{
@@ -311,7 +326,7 @@ func convertRowsToRecords(schema sql.Schema, rows []sql.Row) [][]pgtypes.RecordV
311326
records = append(records, record)
312327
}
313328

314-
return records
329+
return records, nil
315330
}
316331

317332
// applyNoticeOptions adds the specified |options| to the |noticeResponse|.

testing/go/create_function_plpgsql_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,56 @@ $$ LANGUAGE plpgsql;`},
654654
},
655655
},
656656
},
657+
{
658+
Name: "RETURNS TABLE with join query",
659+
SetUpScript: []string{
660+
`CREATE TABLE customers (
661+
id INT PRIMARY KEY,
662+
name TEXT
663+
);`,
664+
`CREATE TABLE orders (
665+
id SERIAL PRIMARY KEY,
666+
customer_id INT,
667+
amount INT
668+
);`,
669+
`INSERT INTO customers VALUES (1, 'John'), (2, 'Jane');`,
670+
`INSERT INTO orders VALUES (1, 1, 100), (2, 2, 10);`,
671+
`CREATE OR REPLACE FUNCTION func2(n INT) RETURNS TABLE (c_id INT, c_name TEXT, c_total_spent INT)
672+
LANGUAGE plpgsql
673+
AS $$
674+
BEGIN
675+
RETURN QUERY
676+
SELECT c.id,
677+
c.name,
678+
SUM(o.amount) AS total_spent
679+
FROM customers c
680+
JOIN orders o ON o.customer_id = c.id
681+
GROUP BY c.id, c.name
682+
HAVING SUM(o.amount) >= n
683+
;
684+
END;
685+
$$;`,
686+
},
687+
Assertions: []ScriptTestAssertion{
688+
{
689+
Query: "SELECT func2(1);",
690+
Expected: []sql.Row{
691+
{"(1,John,100)"},
692+
{"(2,Jane,10)"},
693+
},
694+
},
695+
{
696+
Query: "SELECT func2(11);",
697+
Expected: []sql.Row{
698+
{"(1,John,100)"},
699+
},
700+
},
701+
{
702+
Query: "SELECT func2(111);",
703+
Expected: []sql.Row{},
704+
},
705+
},
706+
},
657707
{
658708
Name: "RETURNS SETOF with composite param",
659709
SetUpScript: []string{

0 commit comments

Comments
 (0)