Skip to content

Commit 926ef9b

Browse files
authored
Add IsUtilityStmt to return whether statements are utility statements (#146)
This avoids having to deal with Protobuf serialization/deserialization when the caller is only interested to know if the input statements are utility statements. As a side effect, this method also allows inferring the number of statements (separated by ";") in the input.
1 parent 51c94ef commit 926ef9b

3 files changed

Lines changed: 146 additions & 0 deletions

File tree

is_utility_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//go:build cgo
2+
// +build cgo
3+
4+
package pg_query_test
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
10+
pg_query "github.com/pganalyze/pg_query_go/v6"
11+
"github.com/pganalyze/pg_query_go/v6/parser"
12+
)
13+
14+
var isUtilityStmtTests = []struct {
15+
input string
16+
expected []bool
17+
}{
18+
// DML statements (not utility)
19+
{
20+
"SELECT 1",
21+
[]bool{false},
22+
},
23+
{
24+
"INSERT INTO t (a) VALUES (1)",
25+
[]bool{false},
26+
},
27+
{
28+
"UPDATE t SET a = 1",
29+
[]bool{false},
30+
},
31+
{
32+
"DELETE FROM t",
33+
[]bool{false},
34+
},
35+
// Utility statements
36+
{
37+
"SHOW fsync",
38+
[]bool{true},
39+
},
40+
{
41+
"SET fsync = off",
42+
[]bool{true},
43+
},
44+
{
45+
"CREATE TABLE t (a int)",
46+
[]bool{true},
47+
},
48+
{
49+
"DROP TABLE t",
50+
[]bool{true},
51+
},
52+
// Multi-statement input
53+
{
54+
"SELECT 1; SELECT 2",
55+
[]bool{false, false},
56+
},
57+
{
58+
"SELECT 1; SHOW fsync",
59+
[]bool{false, true},
60+
},
61+
{
62+
"SHOW fsync; SELECT 1",
63+
[]bool{true, false},
64+
},
65+
{
66+
"SET a = 1; SET b = 2",
67+
[]bool{true, true},
68+
},
69+
}
70+
71+
func TestIsUtilityStmt(t *testing.T) {
72+
for _, test := range isUtilityStmtTests {
73+
actual, err := pg_query.IsUtilityStmt(test.input)
74+
75+
if err != nil {
76+
t.Errorf("IsUtilityStmt(%s)\nerror %s\n\n", test.input, err)
77+
} else if !reflect.DeepEqual(actual, test.expected) {
78+
t.Errorf("IsUtilityStmt(%s)\nexpected %v\nactual %v\n\n", test.input, test.expected, actual)
79+
}
80+
}
81+
}
82+
83+
var isUtilityStmtErrorTests = []struct {
84+
input string
85+
expectedErr error
86+
}{
87+
{
88+
"SELECT $",
89+
&parser.Error{
90+
Message: "syntax error at or near \"$\"",
91+
Cursorpos: 8,
92+
Filename: "scan.l",
93+
Funcname: "scanner_yyerror",
94+
},
95+
},
96+
}
97+
98+
func TestIsUtilityStmtError(t *testing.T) {
99+
for _, test := range isUtilityStmtErrorTests {
100+
_, actualErr := pg_query.IsUtilityStmt(test.input)
101+
102+
if actualErr == nil {
103+
t.Errorf("IsUtilityStmt(%s)\nexpected error but none returned\n\n", test.input)
104+
} else {
105+
exp := test.expectedErr.(*parser.Error)
106+
act := actualErr.(*parser.Error)
107+
act.Lineno = 0 // Line number is architecture dependent, so we ignore it
108+
if !reflect.DeepEqual(act, exp) {
109+
t.Errorf(
110+
"IsUtilityStmt(%s)\nexpected error %s at %d (%s:%d), func: %s, context: %s\nactual error %+v at %d (%s:%d), func: %s, context: %s\n\n",
111+
test.input,
112+
exp.Message, exp.Cursorpos, exp.Filename, exp.Lineno, exp.Funcname, exp.Context,
113+
act.Message, act.Cursorpos, act.Filename, act.Lineno, act.Funcname, act.Context)
114+
}
115+
}
116+
}
117+
}

parser/parser.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,24 @@ func HashXXH3_64(input []byte, seed uint64) (result uint64) {
288288

289289
return
290290
}
291+
292+
// IsUtilityStmt - Determines whether each statement in the query is a utility statement
293+
func IsUtilityStmt(input string) (result []bool, err error) {
294+
inputC := C.CString(input)
295+
defer C.free(unsafe.Pointer(inputC))
296+
297+
resultC := C.pg_query_is_utility_stmt(inputC)
298+
defer C.pg_query_free_is_utility_result(resultC)
299+
300+
if resultC.error != nil {
301+
err = newPgQueryError(resultC.error)
302+
return
303+
}
304+
305+
result = make([]bool, resultC.length)
306+
for i, item := range unsafe.Slice(resultC.items, resultC.length) {
307+
result[i] = bool(item)
308+
}
309+
310+
return
311+
}

pg_query.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ func SplitWithScanner(input string, trimSpace bool) (result []string, err error)
8484
func SplitWithParser(input string, trimSpace bool) (result []string, err error) {
8585
return parser.SplitWithParser(input, trimSpace)
8686
}
87+
88+
// IsUtilityStmt - Determines whether each statement in the query is a utility statement
89+
//
90+
// Returns a slice of booleans, one for each statement in the query.
91+
// true = utility statement / DDL, false = SELECT / INSERT / UPDATE / DELETE / MERGE
92+
func IsUtilityStmt(input string) (result []bool, err error) {
93+
return parser.IsUtilityStmt(input)
94+
}

0 commit comments

Comments
 (0)