Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/cmd/shim.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func pluginQueries(r *compiler.Result) []*plugin.Query {
Params: params,
Filename: q.Metadata.Filename,
InsertIntoTable: iit,
SourceTables: q.SourceTables,
})
}
return out
Expand Down
1 change: 1 addition & 0 deletions internal/compiler/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query,
Columns: anlys.Columns,
SQL: trimmed,
InsertIntoTable: anlys.Table,
SourceTables: sourceTableNames(raw.Stmt),
}, nil
}

Expand Down
6 changes: 6 additions & 0 deletions internal/compiler/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ type Query struct {
Columns []*Column
Params []Parameter

// SourceTables lists the names of the base tables the query reads from,
// including tables that appear only in joins, subqueries, or common table
// expression bodies. Common table expression names and the target relations
// of write statements are excluded.
SourceTables []string

// Needed for CopyFrom
InsertIntoTable *ast.TableName

Expand Down
72 changes: 72 additions & 0 deletions internal/compiler/source_tables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package compiler

import (
"sort"

"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/astutils"
)

// sourceTableNames returns the sorted, deduplicated names of the base tables a
// statement reads from. It covers every table referenced in a FROM, a JOIN, or
// a subquery in any clause, including the bodies of common table expressions.
Comment on lines +10 to +12
// The names of common table expressions and the target relations of INSERT,
// UPDATE, DELETE, and TRUNCATE statements are not reads and are excluded.
func sourceTableNames(root ast.Node) []string {
cteNames := map[string]struct{}{}
writeTargets := map[*ast.RangeVar]struct{}{}

collect := astutils.VisitorFunc(func(node ast.Node) {
switch n := node.(type) {
case *ast.CommonTableExpr:
if n.Ctename != nil {
cteNames[*n.Ctename] = struct{}{}
}
case *ast.InsertStmt:
if n.Relation != nil {
markRangeVars(writeTargets, n.Relation)
}
case *ast.UpdateStmt:
if n.Relations != nil {
markRangeVars(writeTargets, n.Relations)
}
case *ast.DeleteStmt:
if n.Relations != nil {
markRangeVars(writeTargets, n.Relations)
}
case *ast.TruncateStmt:
if n.Relations != nil {
markRangeVars(writeTargets, n.Relations)
}
}
})
astutils.Walk(collect, root)

seen := map[string]struct{}{}
var names []string
for _, rv := range rangeVars(root) {
if _, ok := writeTargets[rv]; ok {
continue
}
table, err := ParseTableName(rv)
if err != nil {
continue
}
if _, ok := cteNames[table.Name]; ok {
continue
}
if _, ok := seen[table.Name]; ok {
continue
}
seen[table.Name] = struct{}{}
names = append(names, table.Name)
Comment on lines +55 to +62
}
sort.Strings(names)
return names
}

func markRangeVars(set map[*ast.RangeVar]struct{}, node ast.Node) {
for _, rv := range rangeVars(node) {
set[rv] = struct{}{}
}
}
60 changes: 60 additions & 0 deletions internal/compiler/source_tables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package compiler

import (
"reflect"
"strings"
"testing"

"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
)

func TestSourceTableNames(t *testing.T) {
for _, tc := range []struct {
name string
sql string
want []string
}{
{
name: "cte, join and subquery dependencies",
sql: `WITH filtered_accounts AS (
SELECT account_id FROM accounts WHERE accounts.space_id = $1
AND NOT EXISTS (
SELECT 1 FROM account_tags t WHERE t.account_id = accounts.account_id
)
)
SELECT acc.* FROM accounts acc
JOIN filtered_accounts fa ON acc.account_id = fa.account_id
LEFT JOIN transactions t ON t.debit_account_id = acc.account_id`,
want: []string{"account_tags", "accounts", "transactions"},
},
{
name: "deduplicated across aliases",
sql: `SELECT a1.account_id FROM accounts a1 JOIN accounts a2 ON a1.space_id = a2.space_id`,
want: []string{"accounts"},
},
{
name: "insert excludes write target, includes read",
sql: `INSERT INTO audit_log (account_id) SELECT account_id FROM accounts`,
want: []string{"accounts"},
},
{
name: "no base tables",
sql: `SELECT 1`,
want: nil,
},
Comment on lines +11 to +44
} {
t.Run(tc.name, func(t *testing.T) {
stmts, err := postgresql.NewParser().Parse(strings.NewReader(tc.sql + ";"))
if err != nil {
t.Fatalf("parse: %v", err)
}
if len(stmts) != 1 {
t.Fatalf("expected 1 statement, got %d", len(stmts))
}
got := sourceTableNames(stmts[0].Raw.Stmt)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("sourceTableNames\n got: %v\n want: %v", got, tc.want)
}
})
}
}
16 changes: 12 additions & 4 deletions internal/endtoend/testdata/codegen_json/gen/codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -65079,7 +65079,10 @@
],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": [
"authors"
]
},
{
"text": "SELECT id, name, bio FROM authors\nORDER BY name",
Expand Down Expand Up @@ -65168,7 +65171,10 @@
"params": [],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": [
"authors"
]
},
{
"text": "INSERT INTO authors (\n name, bio\n) VALUES (\n $1, $2\n)\nRETURNING id, name, bio",
Expand Down Expand Up @@ -65320,7 +65326,8 @@
"catalog": "",
"schema": "",
"name": "authors"
}
},
"source_tables": []
},
{
"text": "DELETE FROM authors\nWHERE id = $1",
Expand Down Expand Up @@ -65360,7 +65367,8 @@
],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": []
}
],
"sqlc_version": "v1.31.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65081,7 +65081,10 @@
],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": [
"authors"
]
},
{
"text": "SELECT id, name, bio FROM authors\nORDER BY name",
Expand Down Expand Up @@ -65170,7 +65173,10 @@
"params": [],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": [
"authors"
]
},
{
"text": "INSERT INTO authors (\n name, bio\n) VALUES (\n $1, $2\n)\nRETURNING id, name, bio",
Expand Down Expand Up @@ -65322,7 +65328,8 @@
"catalog": "",
"schema": "",
"name": "authors"
}
},
"source_tables": []
},
{
"text": "DELETE FROM authors\nWHERE id = $1",
Expand Down Expand Up @@ -65362,7 +65369,8 @@
],
"comments": [],
"filename": "query.sql",
"insert_into_table": null
"insert_into_table": null,
"source_tables": []
}
],
"sqlc_version": "v1.31.1",
Expand Down
91 changes: 51 additions & 40 deletions internal/plugin/codegen.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions protos/plugin/codegen.proto
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ message Query {
repeated string comments = 6 [json_name = "comments"];
string filename = 7 [json_name = "filename"];
Identifier insert_into_table = 8 [json_name = "insert_into_table"];
repeated string source_tables = 9 [json_name = "source_tables"];
}

message Parameter {
Expand Down
Loading