| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- package repository
- import (
- "context"
- "database/sql"
- "testing"
- "testing/fstest"
- sqlmock "github.com/DATA-DOG/go-sqlmock"
- "github.com/stretchr/testify/require"
- )
- func TestValidateMigrationExecutionMode(t *testing.T) {
- t.Run("事务迁移包含CONCURRENTLY会被拒绝", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_add_idx.sql", "CREATE INDEX CONCURRENTLY idx_a ON t(a);")
- require.False(t, nonTx)
- require.Error(t, err)
- })
- t.Run("notx迁移要求CREATE使用IF NOT EXISTS", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "CREATE INDEX CONCURRENTLY idx_a ON t(a);")
- require.False(t, nonTx)
- require.Error(t, err)
- })
- t.Run("notx迁移要求DROP使用IF EXISTS", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_drop_idx_notx.sql", "DROP INDEX CONCURRENTLY idx_a;")
- require.False(t, nonTx)
- require.Error(t, err)
- })
- t.Run("notx迁移禁止事务控制语句", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "BEGIN; CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a); COMMIT;")
- require.False(t, nonTx)
- require.Error(t, err)
- })
- t.Run("notx迁移禁止混用非CONCURRENTLY语句", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a); UPDATE t SET a = 1;")
- require.False(t, nonTx)
- require.Error(t, err)
- })
- t.Run("notx迁移允许幂等并发索引语句", func(t *testing.T) {
- nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", `
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a);
- DROP INDEX CONCURRENTLY IF EXISTS idx_b;
- `)
- require.True(t, nonTx)
- require.NoError(t, err)
- })
- }
- func TestApplyMigrationsFS_NonTransactionalMigration(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("001_add_idx_notx.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t\\(a\\)").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs("001_add_idx_notx.sql", sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "001_add_idx_notx.sql": &fstest.MapFile{
- Data: []byte("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t(a);"),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_NonTransactionalMigration_MultiStatements(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("001_add_multi_idx_notx.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t\\(a\\)").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_b ON t\\(b\\)").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs("001_add_multi_idx_notx.sql", sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "001_add_multi_idx_notx.sql": &fstest.MapFile{
- Data: []byte(`
- -- first
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t(a);
- -- second
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_b ON t(b);
- `),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_NonTransactionalMigration_LatestAPIKeyIPIndexDropsInvalidIndexBeforeRetry(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs(latestAPIKeyIPIndexMigration).
- WillReturnError(sql.ErrNoRows)
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs(latestAPIKeyIPIndex).
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_usage_logs_api_key_latest_ip").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_api_key_latest_ip").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs(latestAPIKeyIPIndexMigration, sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- latestAPIKeyIPIndexMigration: &fstest.MapFile{
- Data: []byte(`
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_api_key_latest_ip
- ON usage_logs (api_key_id, created_at DESC, id DESC)
- INCLUDE (ip_address)
- WHERE ip_address IS NOT NULL AND ip_address <> '';
- `),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_NonTransactionalMigration_UsageModelMismatchIndexDropsInvalidIndexBeforeRetry(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs(usageLogsUpstreamModelMismatchIndexMigration).
- WillReturnError(sql.ErrNoRows)
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs(usageLogsUpstreamModelMismatchIndex).
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_usage_logs_upstream_model_mismatch_created_at").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_upstream_model_mismatch_created_at").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs(usageLogsUpstreamModelMismatchIndexMigration, sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- usageLogsUpstreamModelMismatchIndexMigration: &fstest.MapFile{Data: []byte(`
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_upstream_model_mismatch_created_at
- ON usage_logs (created_at DESC, id DESC)
- WHERE upstream_model_mismatch IS TRUE;
- `)},
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_NonTransactionalMigration_EffectiveModelIndexesDropInvalidIndexesBeforeRetry(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs(usageLogsEffectiveModelIndexesMigration).
- WillReturnError(sql.ErrNoRows)
- for _, indexName := range []string{usageLogsEffectiveRequestedModelIndex, usageLogsEffectiveUpstreamModelIndex} {
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs(indexName).
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS " + indexName).
- WillReturnResult(sqlmock.NewResult(0, 0))
- }
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_requested_model_created").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_upstream_model_created").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs(usageLogsEffectiveModelIndexesMigration, sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- usageLogsEffectiveModelIndexesMigration: &fstest.MapFile{Data: []byte(`
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_requested_model_created
- ON usage_logs ((COALESCE(NULLIF(BTRIM(requested_model), ''), model)), created_at DESC, id DESC);
- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_upstream_model_created
- ON usage_logs ((COALESCE(NULLIF(BTRIM(upstream_model), ''), model)), created_at DESC, id DESC);
- `)},
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_PaymentOrdersOutTradeNoUniqueMigration_FailsFastOnDuplicatePrecheck(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectQuery("SELECT out_trade_no, COUNT\\(\\*\\) AS duplicate_count FROM payment_orders").
- WillReturnRows(sqlmock.NewRows([]string{"out_trade_no", "duplicate_count"}).AddRow("dup-out-trade-no", 2))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "120_enforce_payment_orders_out_trade_no_unique_notx.sql": &fstest.MapFile{
- Data: []byte(`
- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique
- ON payment_orders (out_trade_no)
- WHERE out_trade_no <> '';
- DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no;
- `),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.Error(t, err)
- require.Contains(t, err.Error(), "duplicate out_trade_no")
- require.Contains(t, err.Error(), "dup-out-trade-no")
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_PaymentOrdersOutTradeNoUniqueMigration_DropsInvalidIndexBeforeRetry(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectQuery("SELECT out_trade_no, COUNT\\(\\*\\) AS duplicate_count FROM payment_orders").
- WillReturnRows(sqlmock.NewRows([]string{"out_trade_no", "duplicate_count"}))
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs("paymentorder_out_trade_no_unique").
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no_unique").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql", sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "120_enforce_payment_orders_out_trade_no_unique_notx.sql": &fstest.MapFile{
- Data: []byte(`
- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique
- ON payment_orders (out_trade_no)
- WHERE out_trade_no <> '';
- DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no;
- `),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_SchedulerOutboxPendingDedupKeyMigration_DropsInvalidIndexBeforeRetry(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("153_scheduler_outbox_pending_dedup_key_index_notx.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs("idx_scheduler_outbox_pending_dedup_key").
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_scheduler_outbox_pending_dedup_key").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_scheduler_outbox_pending_dedup_key").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs("153_scheduler_outbox_pending_dedup_key_index_notx.sql", sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "153_scheduler_outbox_pending_dedup_key_index_notx.sql": &fstest.MapFile{
- Data: []byte(`
- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_scheduler_outbox_pending_dedup_key
- ON scheduler_outbox (dedup_key)
- WHERE dedup_key IS NOT NULL;
- `),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func TestApplyMigrationsFS_TransactionalMigration(t *testing.T) {
- db, mock, err := sqlmock.New()
- require.NoError(t, err)
- defer func() { _ = db.Close() }()
- // The advisory lock and all migration work must share one session. This also
- // proves startup cannot self-deadlock when deployments cap the pool at one.
- db.SetMaxOpenConns(1)
- prepareMigrationsBootstrapExpectations(mock)
- mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
- WithArgs("001_add_col.sql").
- WillReturnError(sql.ErrNoRows)
- mock.ExpectBegin()
- mock.ExpectExec("ALTER TABLE t ADD COLUMN name TEXT").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
- WithArgs("001_add_col.sql", sqlmock.AnyArg()).
- WillReturnResult(sqlmock.NewResult(1, 1))
- mock.ExpectCommit()
- mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnResult(sqlmock.NewResult(0, 1))
- fsys := fstest.MapFS{
- "001_add_col.sql": &fstest.MapFile{
- Data: []byte("ALTER TABLE t ADD COLUMN name TEXT;"),
- },
- }
- err = applyMigrationsFS(context.Background(), db, fsys)
- require.NoError(t, err)
- require.NoError(t, mock.ExpectationsWereMet())
- }
- func prepareMigrationsBootstrapExpectations(mock sqlmock.Sqlmock) {
- mock.ExpectQuery("SELECT pg_try_advisory_lock\\(\\$1\\)").
- WithArgs(migrationsAdvisoryLockID).
- WillReturnRows(sqlmock.NewRows([]string{"pg_try_advisory_lock"}).AddRow(true))
- mock.ExpectExec("CREATE TABLE IF NOT EXISTS schema_migrations").
- WillReturnResult(sqlmock.NewResult(0, 0))
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs("schema_migrations").
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectQuery("SELECT EXISTS \\(").
- WithArgs("atlas_schema_revisions").
- WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
- mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM atlas_schema_revisions").
- WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
- }
|