Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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.
// 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
}
Comment thread
Prateeks16 marked this conversation as resolved.
if _, ok := cteNames[table.Name]; ok {
continue
}
if _, ok := seen[table.Name]; ok {
continue
}
seen[table.Name] = struct{}{}
names = append(names, table.Name)
}
Comment thread
Prateeks16 marked this conversation as resolved.
sort.Strings(names)
return names
Comment thread
Prateeks16 marked this conversation as resolved.
}

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,
},
} {
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"];
Comment thread
Prateeks16 marked this conversation as resolved.
}

message Parameter {
Expand Down
Loading