diff --git a/Makefile b/Makefile index 01e4eb8..eb331e4 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ bench: .PHONY: run-examples run-examples: - @go test -tags all -v -run=Example + @go test -tags all -v -run=Example ./... .PHONY: run-scylla run-scylla: diff --git a/gocqlxtest/gocqlxtest.go b/gocqlxtest/gocqlxtest.go index 09c1ace..6a9524f 100644 --- a/gocqlxtest/gocqlxtest.go +++ b/gocqlxtest/gocqlxtest.go @@ -62,42 +62,48 @@ func CreateCluster() *gocql.ClusterConfig { return cluster } -func createSessionFromCluster(cluster *gocql.ClusterConfig, tb testing.TB) gocqlx.Session { - // Drop and re-create the keyspace once. Different tests should use their own - // individual tables, but can assume that the table does not exist before. - initOnce.Do(func() { - createKeyspace(tb, cluster, "gocqlx_test") - }) - - cluster.Keyspace = "gocqlx_test" - session, err := gocqlx.WrapSession(cluster.CreateSession()) - if err != nil { - tb.Fatal("CreateSession:", err) - } - return session -} - -func createKeyspace(tb testing.TB, cluster *gocql.ClusterConfig, keyspace string) { +// CreateKeyspace creates keyspace with SimpleStrategy and RF derived from flags. +func CreateKeyspace(cluster *gocql.ClusterConfig, keyspace string) error { c := *cluster c.Keyspace = "system" c.Timeout = 30 * time.Second + session, err := gocqlx.WrapSession(c.CreateSession()) if err != nil { - tb.Fatal(err) + return err } defer session.Close() - err = session.ExecStmt(`DROP KEYSPACE IF EXISTS ` + keyspace) - if err != nil { - tb.Fatalf("unable to drop keyspace: %v", err) + { + err := session.ExecStmt(`DROP KEYSPACE IF EXISTS ` + keyspace) + if err != nil { + return fmt.Errorf("drop keyspace: %w", err) + } + } + + { + err := session.ExecStmt(fmt.Sprintf(`CREATE KEYSPACE %s WITH replication = {'class' : 'SimpleStrategy', 'replication_factor' : %d}`, keyspace, *flagRF)) + if err != nil { + return fmt.Errorf("create keyspace: %w", err) + } } - err = session.ExecStmt(fmt.Sprintf(`CREATE KEYSPACE %s - WITH replication = { - 'class' : 'SimpleStrategy', - 'replication_factor' : %d - }`, keyspace, *flagRF)) + return nil +} + +func createSessionFromCluster(cluster *gocql.ClusterConfig, tb testing.TB) gocqlx.Session { + // Drop and re-create the keyspace once. Different tests should use their own + // individual tables, but can assume that the table does not exist before. + initOnce.Do(func() { + if err := CreateKeyspace(cluster, "gocqlx_test"); err != nil { + tb.Fatal(err) + } + }) + + cluster.Keyspace = "gocqlx_test" + session, err := gocqlx.WrapSession(cluster.CreateSession()) if err != nil { - tb.Fatalf("unable to create keyspace: %v", err) + tb.Fatal("CreateSession:", err) } + return session } diff --git a/migrate/README.md b/migrate/README.md index 37741dc..253f3b1 100644 --- a/migrate/README.md +++ b/migrate/README.md @@ -1,35 +1,8 @@ -# GoCQLX Migrations +# 🚀 GocqlX Migrations -Package `migrate` provides simple and flexible CQL migrations. -Migrations can be read from a flat directory containing cql files. -There is no imposed naming schema, migration name is file name and the migrations are processed in lexicographical order. -Caller provides a `gocqlx.Session`, the session must use a desired keyspace as migrate would try to create migrations table. +`migrate` reads migrations from a flat directory containing CQL files. +There is no imposed naming schema. Migration name is file name. +The order of migrations is the lexicographical order of file names in the directory. +You can inject execution of Go code before processing of a migration file, after processing of a migration file, or between statements in a migration file. -## Features - -* Each CQL statement will run once -* Go code migrations using callbacks - -## Example - -```go -package main - -import ( - "context" - - "github.com/scylladb/gocqlx/v2/migrate" -) - -const dir = "./cql" - -func main() { - session := CreateSession() - defer session.Close() - - ctx := context.Background() - if err := migrate.Migrate(ctx, session, dir); err != nil { - panic(err) - } -} -``` \ No newline at end of file +For details see [example](example) migration. \ No newline at end of file diff --git a/migrate/doc.go b/migrate/doc.go index 7d00f30..8dba2fa 100644 --- a/migrate/doc.go +++ b/migrate/doc.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a ALv2-style // license that can be found in the LICENSE file. -// Package migrate provides simple and flexible CLQ migrations. -// Migrations can be read from a flat directory containing cql files. -// There is no imposed naming schema, migration name is file name and the migrations are processed in lexicographical order. -// Caller provides a gocqlx.Session, the session must use a desired keyspace as migrate would try to create migrations table. +// Package migrate reads migrations from a flat directory containing CQL files. +// There is no imposed naming schema. Migration name is file name. +// The order of migrations is the lexicographical order of file names in the directory. +// You can inject execution of Go code before processing of a migration file, after processing of a migration file, or between statements in a migration file. package migrate diff --git a/migrate/example/example_test.go b/migrate/example/example_test.go new file mode 100644 index 0000000..a7b5737 --- /dev/null +++ b/migrate/example/example_test.go @@ -0,0 +1,60 @@ +// Copyright (C) 2017 ScyllaDB +// Use of this source code is governed by a ALv2-style +// license that can be found in the LICENSE file. + +// +build all integration + +package example + +import ( + "context" + "testing" + + "github.com/scylladb/gocqlx/v2" + "github.com/scylladb/gocqlx/v2/gocqlxtest" + "github.com/scylladb/gocqlx/v2/migrate" +) + +// Running examples locally: +// make run-scylla +// make run-examples +func TestExample(t *testing.T) { + const ks = "migrate_example" + + cluster := gocqlxtest.CreateCluster() + cluster.Keyspace = ks + + if err := gocqlxtest.CreateKeyspace(cluster, ks); err != nil { + t.Fatal("CreateKeyspace:", err) + } + session, err := gocqlx.WrapSession(cluster.CreateSession()) + if err != nil { + t.Fatal("CreateSession:", err) + } + defer session.Close() + + // Add callback prints + printEvent := func(ctx context.Context, session gocqlx.Session, ev migrate.CallbackEvent, name string) error { + t.Log(ev, name) + return nil + } + + reg := migrate.CallbackRegister{} + reg.Add(migrate.BeforeMigration, "m1.cql", printEvent) + reg.Add(migrate.AfterMigration, "m1.cql", printEvent) + reg.Add(migrate.CallComment, "1", printEvent) + reg.Add(migrate.CallComment, "2", printEvent) + reg.Add(migrate.CallComment, "3", printEvent) + + migrate.Callback = reg.Callback + + // First run prints data + if err := migrate.Migrate(context.Background(), session, "migrations"); err != nil { + t.Fatal("Migrate:", err) + } + + // Second run skips the processed files + if err := migrate.Migrate(context.Background(), session, "migrations"); err != nil { + t.Fatal("Migrate:", err) + } +} diff --git a/migrate/example/migrations/m1.cql b/migrate/example/migrations/m1.cql new file mode 100644 index 0000000..94d809f --- /dev/null +++ b/migrate/example/migrations/m1.cql @@ -0,0 +1,15 @@ +-- Comment + +CREATE TABLE bar ( id int PRIMARY KEY); + +INSERT INTO bar (id) VALUES (1); + +-- CALL 1; + +INSERT INTO bar (id) VALUES (2); + +-- CALL 2; + +INSERT INTO bar (id) VALUES (3); + +-- CALL 3;