Add comment handling and related tests for migration functionality (#344)
This commit is contained in:
@@ -3,3 +3,7 @@ package migrate
|
|||||||
func IsCallback(stmt string) (name string) {
|
func IsCallback(stmt string) (name string) {
|
||||||
return isCallback(stmt)
|
return isCallback(stmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsComment(stmt string) bool {
|
||||||
|
return isComment(stmt)
|
||||||
|
}
|
||||||
|
|||||||
@@ -201,6 +201,23 @@ func FromFS(ctx context.Context, session gocqlx.Session, f fs.FS) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyMigration executes a single migration file by parsing and applying its statements.
|
||||||
|
// It handles three types of content in migration files:
|
||||||
|
// - SQL statements: executed against the database
|
||||||
|
// - Callback commands: processed via registered callback handlers (format: -- CALL function_name;)
|
||||||
|
// - Regular comments: silently skipped (format: -- any comment text)
|
||||||
|
//
|
||||||
|
// The function maintains migration state by tracking the number of completed statements,
|
||||||
|
// allowing for resumption of partially completed migrations.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - ctx: context for cancellation and timeouts
|
||||||
|
// - session: database session for executing statements
|
||||||
|
// - f: filesystem containing the migration file
|
||||||
|
// - path: path to the migration file within the filesystem
|
||||||
|
// - done: number of statements already completed (for resuming partial migrations)
|
||||||
|
//
|
||||||
|
// Returns an error if the migration fails at any point.
|
||||||
func applyMigration(ctx context.Context, session gocqlx.Session, f fs.FS, path string, done int) error {
|
func applyMigration(ctx context.Context, session gocqlx.Session, f fs.FS, path string, done int) error {
|
||||||
file, err := f.Open(path)
|
file, err := f.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -272,19 +289,23 @@ func applyMigration(ctx context.Context, session gocqlx.Session, f fs.FS, path s
|
|||||||
// trim new lines and all whitespace characters
|
// trim new lines and all whitespace characters
|
||||||
stmt = strings.TrimSpace(stmt)
|
stmt = strings.TrimSpace(stmt)
|
||||||
|
|
||||||
|
// Process statement based on its type
|
||||||
if cb := isCallback(stmt); cb != "" {
|
if cb := isCallback(stmt); cb != "" {
|
||||||
|
// Handle callback commands (e.g., "-- CALL function_name;")
|
||||||
if Callback == nil {
|
if Callback == nil {
|
||||||
return fmt.Errorf("statement %d: missing callback handler while trying to call %s", i, cb)
|
return fmt.Errorf("statement %d: missing callback handler while trying to call %s", i, cb)
|
||||||
}
|
}
|
||||||
if err := Callback(ctx, session, CallComment, cb); err != nil {
|
if err := Callback(ctx, session, CallComment, cb); err != nil {
|
||||||
return fmt.Errorf("callback %s: %s", cb, err)
|
return fmt.Errorf("callback %s: %s", cb, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else if stmt != "" && !isComment(stmt) {
|
||||||
|
// Execute SQL statements (skip empty statements and comments)
|
||||||
q := session.ContextQuery(ctx, stmt, nil).RetryPolicy(nil)
|
q := session.ContextQuery(ctx, stmt, nil).RetryPolicy(nil)
|
||||||
if err := q.ExecRelease(); err != nil {
|
if err := q.ExecRelease(); err != nil {
|
||||||
return fmt.Errorf("statement %d: %s", i, err)
|
return fmt.Errorf("statement %d: %s", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Regular comments and empty statements are silently skipped
|
||||||
|
|
||||||
// update info
|
// update info
|
||||||
info.Done = i
|
info.Done = i
|
||||||
@@ -315,3 +336,10 @@ func isCallback(stmt string) (name string) {
|
|||||||
}
|
}
|
||||||
return s[1]
|
return s[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isComment returns true if the statement is a SQL comment that should be ignored.
|
||||||
|
// It distinguishes between regular comments (which should be skipped) and
|
||||||
|
// callback commands (which should be processed).
|
||||||
|
func isComment(stmt string) bool {
|
||||||
|
return strings.HasPrefix(stmt, "--") && isCallback(stmt) == ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,6 +160,29 @@ func TestMigrationNoSemicolon(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMigrationWithTrailingComment(t *testing.T) {
|
||||||
|
session := gocqlxtest.CreateSession(t)
|
||||||
|
defer session.Close()
|
||||||
|
recreateTables(t, session)
|
||||||
|
|
||||||
|
if err := session.ExecStmt(migrateSchema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := makeTestFS(0)
|
||||||
|
// Create a migration with a trailing comment (this should reproduce the issue)
|
||||||
|
migrationContent := fmt.Sprintf(insertMigrate, 0) + "; -- ttl 1 hour"
|
||||||
|
f.WriteFile("0.cql", []byte(migrationContent), fs.ModePerm)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := migrate.FromFS(ctx, session, f); err != nil {
|
||||||
|
t.Fatal("Migration should succeed with trailing comment, but got error:", err)
|
||||||
|
}
|
||||||
|
if c := countMigrations(t, session); c != 1 {
|
||||||
|
t.Fatal("expected 1 migration got", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsCallback(t *testing.T) {
|
func TestIsCallback(t *testing.T) {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -211,6 +234,60 @@ func TestIsCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsComment(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
Name string
|
||||||
|
Stmt string
|
||||||
|
IsComment bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "CQL statement",
|
||||||
|
Stmt: "SELECT * from X;",
|
||||||
|
IsComment: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Regular comment",
|
||||||
|
Stmt: "-- This is a comment",
|
||||||
|
IsComment: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Comment with additional text",
|
||||||
|
Stmt: "-- ttl 1 hour",
|
||||||
|
IsComment: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Callback command (not a regular comment)",
|
||||||
|
Stmt: "-- CALL Foo;",
|
||||||
|
IsComment: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Callback with spaces (not a regular comment)",
|
||||||
|
Stmt: "-- CALL Bar;",
|
||||||
|
IsComment: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Empty statement",
|
||||||
|
Stmt: "",
|
||||||
|
IsComment: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Whitespace only",
|
||||||
|
Stmt: " ",
|
||||||
|
IsComment: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range table {
|
||||||
|
test := table[i]
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
result := migrate.IsComment(test.Stmt)
|
||||||
|
if result != test.IsComment {
|
||||||
|
t.Errorf("IsComment(%q) = %v, expected %v", test.Stmt, result, test.IsComment)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMigrationCallback(t *testing.T) {
|
func TestMigrationCallback(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
beforeCalled int
|
beforeCalled int
|
||||||
|
|||||||
Reference in New Issue
Block a user