Skip to content

Enhance sqlpath.Glob to actually support glob wildcard #2955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
29 changes: 24 additions & 5 deletions internal/sql/sqlpath/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,33 @@ import (
"github.com/sqlc-dev/sqlc/internal/migrations"
)

// Return a list of SQL files in the listed paths. Only includes files ending
// in .sql. Omits hidden files, directories, and migrations.
func Glob(paths []string) ([]string, error) {
var files []string
// Return a list of SQL files in the listed paths.
//
// Only includes files ending in .sql. Omits hidden files, directories, and
// down migrations.

// If a path contains *, ?, [, or ], treat the path as a pattern and expand it
// filepath.Glob.
func Glob(patterns []string) ([]string, error) {
var files, paths []string
for _, pattern := range patterns {
if strings.ContainsAny(pattern, "*?[]") {
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
// if len(matches) == 0 {
// slog.Warn("zero files matched", "pattern", pattern)
// }
paths = append(paths, matches...)
} else {
paths = append(paths, pattern)
}
}
for _, path := range paths {
f, err := os.Stat(path)
if err != nil {
return nil, fmt.Errorf("path %s does not exist", path)
return nil, fmt.Errorf("path error: %w", err)
}
if f.IsDir() {
listing, err := os.ReadDir(path)
Expand Down
208 changes: 208 additions & 0 deletions internal/sql/sqlpath/read_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package sqlpath

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
)

// Returns a list of SQL files from given paths.
func TestReturnsListOfSQLFiles(t *testing.T) {
// Arrange
paths := []string{"testdata/file1.sql", "testdata/file2.sql"}

// Act
result, err := Glob(paths)

// Assert
expected := []string{"testdata/file1.sql", "testdata/file2.sql"}
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v, %v", expected, result, cmp.Diff(expected, result))
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestReturnsNilListWhenNoSQLFilesFound(t *testing.T) {
// Arrange
paths := []string{"testdata/extra.txt"}

// Act
result, err := Glob(paths)
// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v, %v", expected, result, cmp.Diff(expected, result))
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestIgnoresHiddenFilesWhenSearchingForSQLFiles(t *testing.T) {
// Arrange
paths := []string{"testdata/.hidden.sql"}

// Act
result, err := Glob(paths)

// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestIgnoresNonSQLFilesWhenSearchingForSQLFiles(t *testing.T) {
// Arrange
paths := []string{"testdata/extra.txt"}

// Act
result, err := Glob(paths)

// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestExcludesSQLFilesEndingWithDownSQLWhenSearchingForSQLFiles(t *testing.T) {
// Arrange
paths := []string{"testdata/file1.sql", "testdata/file3.down.sql"}

// Act
result, err := Glob(paths)

// Assert
expected := []string{"testdata/file1.sql"}
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestReturnsErrorWhenPathDoesNotExist(t *testing.T) {
// Arrange
paths := []string{"non_existent_path"}

// Act
result, err := Glob(paths)

// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err == nil {
t.Errorf("Expected an error, but got nil")
} else {
expectedError := fmt.Errorf("path error: stat non_existent_path: no such file or directory")
if !cmp.Equal(err.Error(), expectedError.Error()) {
t.Errorf("Expected error %v, but got %v", expectedError, err)
}
}
}

func TestReturnsErrorWhenDirectoryCannotBeRead(t *testing.T) {
// Arrange
paths := []string{"testdata/unreadable"}

// Act
result, err := Glob(paths)

// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err == nil {
t.Errorf("Expected an error, but got nil")
} else {
expectedError := fmt.Errorf("path error: stat testdata/unreadable: no such file or directory")
if !cmp.Equal(err.Error(), expectedError.Error()) {
t.Errorf("Expected error %v, but got %v", expectedError, err)
}
}
}

func TestDoesNotIncludesSQLFilesWithUppercaseExtension(t *testing.T) {
// Arrange
paths := []string{"testdata/file4.SQL"}

// Act
result, err := Glob(paths)

// Assert
var expected []string
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestNotIncludesHiddenFilesAnyPath(t *testing.T) {
// Arrange
paths := []string{
"./testdata/.hiddendir/file1.sql", // pass
"./testdata/.hidden.sql", // skip
}

// Act
result, err := Glob(paths)

// Assert
expectedAny := [][]string{
{"./testdata/.hiddendir/file1.sql"},
{"testdata/.hiddendir/file1.sql"},
}

match := false
for _, expected := range expectedAny {
if cmp.Equal(result, expected) {
match = true
break
}
}
if !match {
t.Errorf("Expected any of %v, but got %v", expectedAny, result)
}

if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}

func TestFollowSymlinks(t *testing.T) {
// Arrange
paths := []string{"testdata/symlink", "testdata/file1.symlink.sql"}

// Act
result, err := Glob(paths)

// Assert
expected := []string{
"testdata/symlink/file1.sql",
"testdata/symlink/file1.symlink.sql",
"testdata/symlink/file2.sql",
"testdata/file1.symlink.sql",
}
if !cmp.Equal(result, expected) {
t.Errorf("Expected %v, but got %v", expected, result)
}
if err != nil {
t.Errorf("Expected no error, but got %v", err)
}
}
Empty file.
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions internal/sql/sqlpath/testdata/file1.symlink.sql
Empty file.
Empty file.
Empty file.
Empty file.
1 change: 1 addition & 0 deletions internal/sql/sqlpath/testdata/symlink