Skip to content

Commit 3be2887

Browse files
7ttpavallete
andauthored
fix: atomic parser (#5064)
* fix * test --------- Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
1 parent a1942ee commit 3be2887

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

pkg/parser/state.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,40 @@ func (s *ReadyState) Next(r rune, data []byte) State {
4646
case 'c':
4747
fallthrough
4848
case 'C':
49-
offset := len(data) - len(BEGIN_ATOMIC)
50-
if offset >= 0 && strings.EqualFold(string(data[offset:]), BEGIN_ATOMIC) {
49+
if isBeginAtomic(data) {
5150
return &AtomicState{prev: s, delimiter: []byte(END_ATOMIC)}
5251
}
5352
}
5453
return s
5554
}
5655

56+
func isBeginAtomic(data []byte) bool {
57+
offset := len(data) - len(BEGIN_ATOMIC)
58+
if offset < 0 || !strings.EqualFold(string(data[offset:]), BEGIN_ATOMIC) {
59+
return false
60+
}
61+
if offset > 0 {
62+
r, _ := utf8.DecodeLastRune(data[:offset])
63+
if isIdentifierRune(r) {
64+
return false
65+
}
66+
}
67+
prefix := bytes.TrimRightFunc(data[:offset], unicode.IsSpace)
68+
offset = len(prefix) - len("BEGIN")
69+
if offset < 0 || !strings.EqualFold(string(prefix[offset:]), "BEGIN") {
70+
return false
71+
}
72+
if offset == 0 {
73+
return true
74+
}
75+
r, _ := utf8.DecodeLastRune(prefix[:offset])
76+
return !isIdentifierRune(r)
77+
}
78+
79+
func isIdentifierRune(r rune) bool {
80+
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '$'
81+
}
82+
5783
// Opened a line comment
5884
type CommentState struct{}
5985

pkg/parser/state_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,44 @@ END
167167
;`}
168168
checkSplit(t, sql)
169169
})
170+
171+
t.Run("ignores atomic in identifiers", func(t *testing.T) {
172+
names := []string{
173+
"fn_atomic",
174+
"atomic_fn",
175+
"my_atomic_thing",
176+
"xatomicx",
177+
"fn_ATomiC",
178+
}
179+
for _, name := range names {
180+
t.Run(name, func(t *testing.T) {
181+
sql := []string{
182+
`CREATE OR REPLACE FUNCTION ` + name + `()
183+
RETURNS void LANGUAGE plpgsql AS $$
184+
BEGIN
185+
NULL;
186+
END;
187+
$$;`,
188+
`
189+
SELECT 1;`,
190+
}
191+
checkSplit(t, sql)
192+
})
193+
}
194+
})
195+
196+
t.Run("does not treat schema-qualified atomic function names as begin atomic", func(t *testing.T) {
197+
sql := []string{`CREATE OR REPLACE FUNCTION public.atomic_example()
198+
RETURNS INTEGER
199+
LANGUAGE plpgsql
200+
AS $$
201+
BEGIN
202+
RETURN 1;
203+
END;
204+
$$;`,
205+
`
206+
GRANT EXECUTE ON FUNCTION public.atomic_example() TO authenticated;`,
207+
}
208+
checkSplit(t, sql)
209+
})
170210
}

0 commit comments

Comments
 (0)