Skip to content

Commit 13141d9

Browse files
iamwavecutValeriy Selitskiykyleconroy
authored
Enhance sqlpath.Glob to actually support glob wildcard (#2955)
* add: test for `sqlpath.Glob` * fix: improve tests, minor change one expected error message * add: glob expanding maintaining backward compartibility * feat(sqlpath): Support filepath.Glob patterns * fix: Remove nolint comment --------- Co-authored-by: Valeriy Selitskiy <valeriy.s@stackholderlabs.com> Co-authored-by: Kyle Conroy <kyle@sqlc.dev>
1 parent 66293c6 commit 13141d9

File tree

12 files changed

+234
-5
lines changed

12 files changed

+234
-5
lines changed

internal/sql/sqlpath/read.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,33 @@ import (
99
"github.com/sqlc-dev/sqlc/internal/migrations"
1010
)
1111

12-
// Return a list of SQL files in the listed paths. Only includes files ending
13-
// in .sql. Omits hidden files, directories, and migrations.
14-
func Glob(paths []string) ([]string, error) {
15-
var files []string
12+
// Return a list of SQL files in the listed paths.
13+
//
14+
// Only includes files ending in .sql. Omits hidden files, directories, and
15+
// down migrations.
16+
17+
// If a path contains *, ?, [, or ], treat the path as a pattern and expand it
18+
// filepath.Glob.
19+
func Glob(patterns []string) ([]string, error) {
20+
var files, paths []string
21+
for _, pattern := range patterns {
22+
if strings.ContainsAny(pattern, "*?[]") {
23+
matches, err := filepath.Glob(pattern)
24+
if err != nil {
25+
return nil, err
26+
}
27+
// if len(matches) == 0 {
28+
// slog.Warn("zero files matched", "pattern", pattern)
29+
// }
30+
paths = append(paths, matches...)
31+
} else {
32+
paths = append(paths, pattern)
33+
}
34+
}
1635
for _, path := range paths {
1736
f, err := os.Stat(path)
1837
if err != nil {
19-
return nil, fmt.Errorf("path %s does not exist", path)
38+
return nil, fmt.Errorf("path error: %w", err)
2039
}
2140
if f.IsDir() {
2241
listing, err := os.ReadDir(path)

internal/sql/sqlpath/read_test.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package sqlpath
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
// Returns a list of SQL files from given paths.
11+
func TestReturnsListOfSQLFiles(t *testing.T) {
12+
// Arrange
13+
paths := []string{"testdata/file1.sql", "testdata/file2.sql"}
14+
15+
// Act
16+
result, err := Glob(paths)
17+
18+
// Assert
19+
expected := []string{"testdata/file1.sql", "testdata/file2.sql"}
20+
if !cmp.Equal(result, expected) {
21+
t.Errorf("Expected %v, but got %v, %v", expected, result, cmp.Diff(expected, result))
22+
}
23+
if err != nil {
24+
t.Errorf("Expected no error, but got %v", err)
25+
}
26+
}
27+
28+
func TestReturnsNilListWhenNoSQLFilesFound(t *testing.T) {
29+
// Arrange
30+
paths := []string{"testdata/extra.txt"}
31+
32+
// Act
33+
result, err := Glob(paths)
34+
// Assert
35+
var expected []string
36+
if !cmp.Equal(result, expected) {
37+
t.Errorf("Expected %v, but got %v, %v", expected, result, cmp.Diff(expected, result))
38+
}
39+
if err != nil {
40+
t.Errorf("Expected no error, but got %v", err)
41+
}
42+
}
43+
44+
func TestIgnoresHiddenFilesWhenSearchingForSQLFiles(t *testing.T) {
45+
// Arrange
46+
paths := []string{"testdata/.hidden.sql"}
47+
48+
// Act
49+
result, err := Glob(paths)
50+
51+
// Assert
52+
var expected []string
53+
if !cmp.Equal(result, expected) {
54+
t.Errorf("Expected %v, but got %v", expected, result)
55+
}
56+
if err != nil {
57+
t.Errorf("Expected no error, but got %v", err)
58+
}
59+
}
60+
61+
func TestIgnoresNonSQLFilesWhenSearchingForSQLFiles(t *testing.T) {
62+
// Arrange
63+
paths := []string{"testdata/extra.txt"}
64+
65+
// Act
66+
result, err := Glob(paths)
67+
68+
// Assert
69+
var expected []string
70+
if !cmp.Equal(result, expected) {
71+
t.Errorf("Expected %v, but got %v", expected, result)
72+
}
73+
if err != nil {
74+
t.Errorf("Expected no error, but got %v", err)
75+
}
76+
}
77+
78+
func TestExcludesSQLFilesEndingWithDownSQLWhenSearchingForSQLFiles(t *testing.T) {
79+
// Arrange
80+
paths := []string{"testdata/file1.sql", "testdata/file3.down.sql"}
81+
82+
// Act
83+
result, err := Glob(paths)
84+
85+
// Assert
86+
expected := []string{"testdata/file1.sql"}
87+
if !cmp.Equal(result, expected) {
88+
t.Errorf("Expected %v, but got %v", expected, result)
89+
}
90+
if err != nil {
91+
t.Errorf("Expected no error, but got %v", err)
92+
}
93+
}
94+
95+
func TestReturnsErrorWhenPathDoesNotExist(t *testing.T) {
96+
// Arrange
97+
paths := []string{"non_existent_path"}
98+
99+
// Act
100+
result, err := Glob(paths)
101+
102+
// Assert
103+
var expected []string
104+
if !cmp.Equal(result, expected) {
105+
t.Errorf("Expected %v, but got %v", expected, result)
106+
}
107+
if err == nil {
108+
t.Errorf("Expected an error, but got nil")
109+
} else {
110+
expectedError := fmt.Errorf("path error: stat non_existent_path: no such file or directory")
111+
if !cmp.Equal(err.Error(), expectedError.Error()) {
112+
t.Errorf("Expected error %v, but got %v", expectedError, err)
113+
}
114+
}
115+
}
116+
117+
func TestReturnsErrorWhenDirectoryCannotBeRead(t *testing.T) {
118+
// Arrange
119+
paths := []string{"testdata/unreadable"}
120+
121+
// Act
122+
result, err := Glob(paths)
123+
124+
// Assert
125+
var expected []string
126+
if !cmp.Equal(result, expected) {
127+
t.Errorf("Expected %v, but got %v", expected, result)
128+
}
129+
if err == nil {
130+
t.Errorf("Expected an error, but got nil")
131+
} else {
132+
expectedError := fmt.Errorf("path error: stat testdata/unreadable: no such file or directory")
133+
if !cmp.Equal(err.Error(), expectedError.Error()) {
134+
t.Errorf("Expected error %v, but got %v", expectedError, err)
135+
}
136+
}
137+
}
138+
139+
func TestDoesNotIncludesSQLFilesWithUppercaseExtension(t *testing.T) {
140+
// Arrange
141+
paths := []string{"testdata/file4.SQL"}
142+
143+
// Act
144+
result, err := Glob(paths)
145+
146+
// Assert
147+
var expected []string
148+
if !cmp.Equal(result, expected) {
149+
t.Errorf("Expected %v, but got %v", expected, result)
150+
}
151+
if err != nil {
152+
t.Errorf("Expected no error, but got %v", err)
153+
}
154+
}
155+
156+
func TestNotIncludesHiddenFilesAnyPath(t *testing.T) {
157+
// Arrange
158+
paths := []string{
159+
"./testdata/.hiddendir/file1.sql", // pass
160+
"./testdata/.hidden.sql", // skip
161+
}
162+
163+
// Act
164+
result, err := Glob(paths)
165+
166+
// Assert
167+
expectedAny := [][]string{
168+
{"./testdata/.hiddendir/file1.sql"},
169+
{"testdata/.hiddendir/file1.sql"},
170+
}
171+
172+
match := false
173+
for _, expected := range expectedAny {
174+
if cmp.Equal(result, expected) {
175+
match = true
176+
break
177+
}
178+
}
179+
if !match {
180+
t.Errorf("Expected any of %v, but got %v", expectedAny, result)
181+
}
182+
183+
if err != nil {
184+
t.Errorf("Expected no error, but got %v", err)
185+
}
186+
}
187+
188+
func TestFollowSymlinks(t *testing.T) {
189+
// Arrange
190+
paths := []string{"testdata/symlink", "testdata/file1.symlink.sql"}
191+
192+
// Act
193+
result, err := Glob(paths)
194+
195+
// Assert
196+
expected := []string{
197+
"testdata/symlink/file1.sql",
198+
"testdata/symlink/file1.symlink.sql",
199+
"testdata/symlink/file2.sql",
200+
"testdata/file1.symlink.sql",
201+
}
202+
if !cmp.Equal(result, expected) {
203+
t.Errorf("Expected %v, but got %v", expected, result)
204+
}
205+
if err != nil {
206+
t.Errorf("Expected no error, but got %v", err)
207+
}
208+
}

internal/sql/sqlpath/testdata/.hidden.sql

Whitespace-only changes.

internal/sql/sqlpath/testdata/.hiddendir/file1.sql

Whitespace-only changes.

internal/sql/sqlpath/testdata/extra.txt

Whitespace-only changes.

internal/sql/sqlpath/testdata/file1.sql

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
./file1.sql

internal/sql/sqlpath/testdata/file2.sql

Whitespace-only changes.

internal/sql/sqlpath/testdata/file3.down.sql

Whitespace-only changes.

internal/sql/sqlpath/testdata/file4.SQL

Whitespace-only changes.

internal/sql/sqlpath/testdata/subdir/file2.sql

Whitespace-only changes.

internal/sql/sqlpath/testdata/symlink

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.

0 commit comments

Comments
 (0)