diff --git a/docs/index.rst b/docs/index.rst index 0386a8175f..af212430ec 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -63,6 +63,7 @@ code ever again. reference/datatypes.md reference/query-annotations.md reference/language-support.rst + reference/environment-variables.md .. toctree:: :maxdepth: 2 diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md new file mode 100644 index 0000000000..359bebcc3f --- /dev/null +++ b/docs/reference/environment-variables.md @@ -0,0 +1,112 @@ +# Environment variables + +## SQLCDEBUG + +The `SQLCDEBUG` variable controls debugging variables within the runtime. It is +a comma-separated list of name=val pairs settings. + +### dumpast + +The `dumpast` command shows the SQL AST that was generated by the parser. Note +that this is the generic SQL AST, not the engine-specific SQL AST. + +``` +SQLCDEBUG=dumpast=1 +``` + +``` +([]interface {}) (len=1 cap=1) { + (*catalog.Catalog)(0xc0004f48c0)({ + Comment: (string) "", + DefaultSchema: (string) (len=6) "public", + Name: (string) "", + Schemas: ([]*catalog.Schema) (len=3 cap=4) { + (*catalog.Schema)(0xc0004f4930)({ + Name: (string) (len=6) "public", + Tables: ([]*catalog.Table) (len=1 cap=1) { + (*catalog.Table)(0xc00052ff20)({ + Rel: (*ast.TableName)(0xc00052fda0)({ + Catalog: (string) "", + Schema: (string) "", + Name: (string) (len=7) "authors" + }), +``` + +### dumpcatalog + +The `dumpcatalog` command outputs the entire catalog. If you're using MySQL or +PostgreSQL, this can be a bit overwhelming. Expect this output to change in +future versions. + +``` +SQLCDEBUG=dumpcatalog=1 +``` + +``` +([]interface {}) (len=1 cap=1) { + (*catalog.Catalog)(0xc00050d1f0)({ + Comment: (string) "", + DefaultSchema: (string) (len=6) "public", + Name: (string) "", + Schemas: ([]*catalog.Schema) (len=3 cap=4) { + (*catalog.Schema)(0xc00050d260)({ + Name: (string) (len=6) "public", + Tables: ([]*catalog.Table) (len=1 cap=1) { + (*catalog.Table)(0xc0000c0840)({ + Rel: (*ast.TableName)(0xc0000c06c0)({ + Catalog: (string) "", + Schema: (string) "", + Name: (string) (len=7) "authors" + }), +``` + +### trace + +The `trace` command is helpful for tracking down performance issues. + +`SQLCDEBUG=trace=1` + +By default, the trace output is written to `trace.out` in the current working +directory. You can configure a different path if needed. + +`SQLCDEBUG=trace=name.out` + +View the execution trace using the Go `trace` tool. + +``` +go tool trace trace.out +``` + +There's a ton of different views for the trace output, but here's an example +log showing the execution time for each package. + +``` +0.000043897 . 1 task sqlc (id 1, parent 0) created +0.000144923 . 101026 1 region generate started (duration: 47.619781ms) +0.001048975 . 904052 1 region package started (duration: 14.588456ms) +0.001054616 . 5641 1 name=authors dir=/Users/kyle/projects/sqlc/examples/python language=python +0.001071257 . 16641 1 region parse started (duration: 7.966549ms) +0.009043960 . 7972703 1 region codegen started (duration: 6.587086ms) +0.009171704 . 127744 1 new goroutine 35: text/template/parse.lex·dwrap·1 +0.010361654 . 1189950 1 new goroutine 36: text/template/parse.lex·dwrap·1 +0.015641815 . 5280161 1 region package started (duration: 10.904938ms) +0.015644943 . 3128 1 name=booktest dir=/Users/kyle/projects/sqlc/examples/python language=python +0.015647431 . 2488 1 region parse started (duration: 4.207749ms) +0.019860308 . 4212877 1 region codegen started (duration: 6.681624ms) +0.020028488 . 168180 1 new goroutine 37: text/template/parse.lex·dwrap·1 +0.021020310 . 991822 1 new goroutine 8: text/template/parse.lex·dwrap·1 +0.026551163 . 5530853 1 region package started (duration: 9.217294ms) +0.026554368 . 3205 1 name=jets dir=/Users/kyle/projects/sqlc/examples/python language=python +0.026556804 . 2436 1 region parse started (duration: 3.491005ms) +0.030051911 . 3495107 1 region codegen started (duration: 5.711931ms) +0.030213937 . 162026 1 new goroutine 20: text/template/parse.lex·dwrap·1 +0.031099938 . 886001 1 new goroutine 38: text/template/parse.lex·dwrap·1 +0.035772637 . 4672699 1 region package started (duration: 10.267039ms) +0.035775688 . 3051 1 name=ondeck dir=/Users/kyle/projects/sqlc/examples/python language=python +0.035778150 . 2462 1 region parse started (duration: 4.094518ms) +0.039877181 . 4099031 1 region codegen started (duration: 6.156341ms) +0.040010771 . 133590 1 new goroutine 39: text/template/parse.lex·dwrap·1 +0.040894567 . 883796 1 new goroutine 40: text/template/parse.lex·dwrap·1 +0.046042779 . 5148212 1 region writefiles started (duration: 1.718259ms) +0.047767781 . 1725002 1 task end +``` diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 0dbb084549..b6a3ce077e 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -196,7 +196,7 @@ func outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, error) { } fun, err := qc.catalog.ResolveFuncCall(n) if err == nil { - cols = append(cols, &Column{Name: name, DataType: dataType(fun.ReturnType), NotNull: true}) + cols = append(cols, &Column{Name: name, DataType: dataType(fun.ReturnType), NotNull: !fun.ReturnTypeNullable}) } else { cols = append(cols, &Column{Name: name, DataType: "any"}) } diff --git a/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/db.go b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/db.go new file mode 100644 index 0000000000..aafbb25321 --- /dev/null +++ b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/db.go @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. + +package aggregate_functions + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/group_concat.sql.go b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/group_concat.sql.go new file mode 100644 index 0000000000..6bf897f3ce --- /dev/null +++ b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/group_concat.sql.go @@ -0,0 +1,78 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: group_concat.sql + +package aggregate_functions + +import ( + "context" + "database/sql" +) + +const groupConcat = `-- name: GroupConcat :many +SELECT student_name, GROUP_CONCAT(test_score) +FROM student +GROUP BY student_name +` + +type GroupConcatRow struct { + StudentName sql.NullString + GroupConcat sql.NullString +} + +func (q *Queries) GroupConcat(ctx context.Context) ([]GroupConcatRow, error) { + rows, err := q.db.QueryContext(ctx, groupConcat) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GroupConcatRow + for rows.Next() { + var i GroupConcatRow + if err := rows.Scan(&i.StudentName, &i.GroupConcat); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const groupConcatOrderBy = `-- name: GroupConcatOrderBy :many +SELECT student_name, + GROUP_CONCAT(DISTINCT test_score ORDER BY test_score DESC SEPARATOR ' ') +FROM student +GROUP BY student_name +` + +type GroupConcatOrderByRow struct { + StudentName sql.NullString + GroupConcat sql.NullString +} + +func (q *Queries) GroupConcatOrderBy(ctx context.Context) ([]GroupConcatOrderByRow, error) { + rows, err := q.db.QueryContext(ctx, groupConcatOrderBy) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GroupConcatOrderByRow + for rows.Next() { + var i GroupConcatOrderByRow + if err := rows.Scan(&i.StudentName, &i.GroupConcat); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/models.go b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/models.go new file mode 100644 index 0000000000..358a98b131 --- /dev/null +++ b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/go/models.go @@ -0,0 +1,12 @@ +// Code generated by sqlc. DO NOT EDIT. + +package aggregate_functions + +import ( + "database/sql" +) + +type Student struct { + StudentName sql.NullString + Score sql.NullFloat64 +} diff --git a/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/group_concat.sql b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/group_concat.sql new file mode 100644 index 0000000000..f1858e42fd --- /dev/null +++ b/internal/endtoend/testdata/mysql_reference_manual/aggregate_functions/group_concat.sql @@ -0,0 +1,15 @@ +CREATE TABLE student ( + student_name VARCHAR(255), + score DOUBLE +); + +-- name: GroupConcat :many +SELECT student_name, GROUP_CONCAT(test_score) +FROM student +GROUP BY student_name; + +-- name: GroupConcatOrderBy :many +SELECT student_name, + GROUP_CONCAT(DISTINCT test_score ORDER BY test_score DESC SEPARATOR ' ') +FROM student +GROUP BY student_name; diff --git a/internal/endtoend/testdata/mysql_reference_manual/sqlc.json b/internal/endtoend/testdata/mysql_reference_manual/sqlc.json index 31e209605f..26e8643999 100644 --- a/internal/endtoend/testdata/mysql_reference_manual/sqlc.json +++ b/internal/endtoend/testdata/mysql_reference_manual/sqlc.json @@ -7,6 +7,13 @@ "schema": "date_and_time_functions", "queries": "date_and_time_functions", "engine": "mysql" + }, + { + "name": "aggregate_functions", + "path": "aggregate_functions/go", + "schema": "aggregate_functions", + "queries": "aggregate_functions", + "engine": "mysql" } ] } diff --git a/internal/engine/dolphin/stdlib.go b/internal/engine/dolphin/stdlib.go index 9744fea435..7fa567729d 100644 --- a/internal/engine/dolphin/stdlib.go +++ b/internal/engine/dolphin/stdlib.go @@ -1069,8 +1069,13 @@ func defaultSchema(name string) *catalog.Schema { { Type: &ast.TypeName{Name: "any"}, }, + { + Type: &ast.TypeName{Name: "any"}, + Mode: ast.FuncParamVariadic, + }, }, - ReturnType: &ast.TypeName{Name: "any"}, + ReturnType: &ast.TypeName{Name: "text"}, + ReturnTypeNullable: true, }, { Name: "GTID_SUBSET", diff --git a/internal/sql/catalog/catalog.go b/internal/sql/catalog/catalog.go index d81ab57008..e7964cf16f 100644 --- a/internal/sql/catalog/catalog.go +++ b/internal/sql/catalog/catalog.go @@ -228,11 +228,12 @@ func (ct *CompositeType) SetComment(c string) { } type Function struct { - Name string - Args []*Argument - ReturnType *ast.TypeName - Comment string - Desc string + Name string + Args []*Argument + ReturnType *ast.TypeName + Comment string + Desc string + ReturnTypeNullable bool } func (f *Function) InArgs() []*Argument {