Skip to content

Commit 3c527fe

Browse files
authored
Merge pull request #44 from gatewayd-io/detect-all-changes-to-db
Detect all changes to db
2 parents 5dc7356 + 33bb8a7 commit 3c527fe

3 files changed

Lines changed: 102 additions & 40 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ GatewayD plugin for caching query results.
1010
- Support for setting expiry time on cached data
1111
- Support for caching responses from multiple databases on multiple servers
1212
- Support nested queries: joins, unions, multi-table selects, and the like
13+
- Support for invalidating cache based on truncate, drop and alter statements
1314
- Detect client's chosen database from the client's startup message
1415
- Metrics for quantifying cache hits, misses, gets, sets and deletes
1516
- Logging at various levels

plugin/utils.go

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,39 @@ func GetQueryFromRequest(req string) (string, error) {
3434
return string(requestDecoded[MinPgSQLMessageLength:size]), nil
3535
}
3636

37+
// isMulti checks if the query is a union, intersect, or except.
38+
func isMulti(stmt *pgQuery.SelectStmt) bool {
39+
return stmt.GetOp() == pgQuery.SetOperation_SETOP_UNION ||
40+
stmt.GetOp() == pgQuery.SetOperation_SETOP_INTERSECT ||
41+
stmt.GetOp() == pgQuery.SetOperation_SETOP_EXCEPT
42+
}
43+
44+
// getSingleTable returns the tables used in a query.
45+
func getSingleTable(stmt *pgQuery.SelectStmt) []string {
46+
tables := []string{}
47+
for _, from := range stmt.FromClause {
48+
rangeVar := from.GetRangeVar()
49+
if rangeVar != nil {
50+
tables = append(tables, rangeVar.Relname)
51+
}
52+
}
53+
54+
return tables
55+
}
56+
57+
// getMultiTable returns the tables used in a union, intersect, or except query.
58+
func getMultiTable(stmt *pgQuery.SelectStmt) []string {
59+
tables := []string{}
60+
// Get the tables from the left side.
61+
left := stmt.GetLarg()
62+
tables = append(tables, getSingleTable(left)...)
63+
// Get the tables from the right side.
64+
right := stmt.GetRarg()
65+
tables = append(tables, getSingleTable(right)...)
66+
67+
return tables
68+
}
69+
3770
// GetTablesFromQuery returns the tables used in a query.
3871
func GetTablesFromQuery(query string) ([]string, error) {
3972
stmt, err := pgQuery.Parse(query)
@@ -47,59 +80,27 @@ func GetTablesFromQuery(query string) ([]string, error) {
4780

4881
tables := []string{}
4982

50-
isMulti := func(stmt *pgQuery.SelectStmt) bool {
51-
return stmt.GetOp() == pgQuery.SetOperation_SETOP_UNION ||
52-
stmt.GetOp() == pgQuery.SetOperation_SETOP_INTERSECT ||
53-
stmt.GetOp() == pgQuery.SetOperation_SETOP_EXCEPT
54-
}
55-
56-
// If the query is a union, we need to get the tables from both sides.
57-
getMultiTable := func(stmt *pgQuery.SelectStmt) {
58-
// Get the tables from the left side.
59-
for _, fromClause := range stmt.GetLarg().FromClause {
60-
rangeVar := fromClause.GetRangeVar()
61-
if rangeVar != nil {
62-
tables = append(tables, rangeVar.Relname)
63-
}
64-
}
65-
66-
// Get the tables from the right side.
67-
for _, fromClause := range stmt.GetRarg().FromClause {
68-
rangeVar := fromClause.GetRangeVar()
69-
if rangeVar != nil {
70-
tables = append(tables, rangeVar.Relname)
71-
}
72-
}
73-
}
74-
75-
getSingleTable := func(fromClause []*pgQuery.Node) {
76-
for _, from := range fromClause {
77-
rangeVar := from.GetRangeVar()
78-
if rangeVar != nil {
79-
tables = append(tables, rangeVar.Relname)
80-
}
81-
}
82-
}
83-
8483
for _, stmt := range stmt.Stmts {
85-
if isMulti(stmt.Stmt.GetSelectStmt()) {
86-
getMultiTable(stmt.Stmt.GetSelectStmt())
84+
// Get the tables from the left and right side of the complex query.
85+
selectStatement := stmt.Stmt.GetSelectStmt()
86+
if isMulti(selectStatement) {
87+
tables = append(tables, getMultiTable(selectStatement)...)
8788
}
8889

8990
// Get the table from the WITH clause.
9091
if withClause := stmt.Stmt.GetSelectStmt().GetWithClause(); withClause != nil {
9192
for _, cte := range withClause.Ctes {
9293
selectStmt := cte.GetCommonTableExpr().Ctequery.GetSelectStmt()
9394
if isMulti(selectStmt) {
94-
getMultiTable(selectStmt)
95+
tables = append(tables, getMultiTable(selectStmt)...)
9596
} else {
96-
getSingleTable(selectStmt.FromClause)
97+
tables = append(tables, getSingleTable(selectStmt)...)
9798
}
9899
}
99100
} else {
100101
// Get the table from the FROM clause.
101-
if selectQuery := stmt.Stmt.GetSelectStmt(); selectQuery != nil {
102-
getSingleTable(selectQuery.FromClause)
102+
if selectStatement := stmt.Stmt.GetSelectStmt(); selectStatement != nil {
103+
tables = append(tables, getSingleTable(selectStatement)...)
103104
}
104105
}
105106

@@ -114,6 +115,24 @@ func GetTablesFromQuery(query string) ([]string, error) {
114115
if deleteQuery := stmt.Stmt.GetDeleteStmt(); deleteQuery != nil {
115116
tables = append(tables, deleteQuery.Relation.Relname)
116117
}
118+
119+
if truncateQuery := stmt.Stmt.GetTruncateStmt(); truncateQuery != nil {
120+
for _, relation := range truncateQuery.Relations {
121+
tables = append(tables, relation.GetRangeVar().Relname)
122+
}
123+
}
124+
125+
if dropTable := stmt.Stmt.GetDropStmt(); dropTable != nil {
126+
for _, object := range dropTable.GetObjects() {
127+
for _, table := range object.GetList().GetItems() {
128+
tables = append(tables, table.GetString_().Str)
129+
}
130+
}
131+
}
132+
133+
if alterTable := stmt.Stmt.GetAlterTableStmt(); alterTable != nil {
134+
tables = append(tables, alterTable.Relation.Relname)
135+
}
117136
}
118137

119138
return tables, nil

plugin/utils_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,48 @@ func Test_GetTablesFromQuery_DeleteMultiple(t *testing.T) {
240240
assert.Equal(t, []string{"users", "comments"}, tables)
241241
}
242242

243+
func Test_GetTablesFromQuery_Truncate(t *testing.T) {
244+
query := "TRUNCATE users"
245+
tables, err := GetTablesFromQuery(query)
246+
assert.Nil(t, err)
247+
assert.Equal(t, []string{"users"}, tables)
248+
}
249+
250+
func Test_GetTablesFromQuery_TruncateMultiple(t *testing.T) {
251+
query := "TRUNCATE users, comments"
252+
tables, err := GetTablesFromQuery(query)
253+
assert.Nil(t, err)
254+
assert.Equal(t, []string{"users", "comments"}, tables)
255+
}
256+
257+
func Test_GetTablesFromQuery_Drop(t *testing.T) {
258+
query := "DROP TABLE users"
259+
tables, err := GetTablesFromQuery(query)
260+
assert.Nil(t, err)
261+
assert.Equal(t, []string{"users"}, tables)
262+
}
263+
264+
func Test_GetTablesFromQuery_DropMultiple(t *testing.T) {
265+
query := "DROP TABLE users, comments"
266+
tables, err := GetTablesFromQuery(query)
267+
assert.Nil(t, err)
268+
assert.Equal(t, []string{"users", "comments"}, tables)
269+
}
270+
271+
func Test_GetTablesFromQuery_Alter(t *testing.T) {
272+
query := "ALTER TABLE users ADD COLUMN name VARCHAR(255)"
273+
tables, err := GetTablesFromQuery(query)
274+
assert.Nil(t, err)
275+
assert.Equal(t, []string{"users"}, tables)
276+
}
277+
278+
func Test_GetTablesFromQuery_AlterMultiple(t *testing.T) {
279+
query := "ALTER TABLE users ADD COLUMN name VARCHAR(255); ALTER TABLE comments ADD COLUMN name VARCHAR(255)"
280+
tables, err := GetTablesFromQuery(query)
281+
assert.Nil(t, err)
282+
assert.Equal(t, []string{"users", "comments"}, tables)
283+
}
284+
243285
func Test_validateAddressPort_Hostname(t *testing.T) {
244286
assert.True(t, validateAddressPort("localhost:5432"))
245287
assert.True(t, validateAddressPort(" localhost:5432"))

0 commit comments

Comments
 (0)