migrations_runner_notx_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "testing"
  6. "testing/fstest"
  7. sqlmock "github.com/DATA-DOG/go-sqlmock"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestValidateMigrationExecutionMode(t *testing.T) {
  11. t.Run("事务迁移包含CONCURRENTLY会被拒绝", func(t *testing.T) {
  12. nonTx, err := validateMigrationExecutionMode("001_add_idx.sql", "CREATE INDEX CONCURRENTLY idx_a ON t(a);")
  13. require.False(t, nonTx)
  14. require.Error(t, err)
  15. })
  16. t.Run("notx迁移要求CREATE使用IF NOT EXISTS", func(t *testing.T) {
  17. nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "CREATE INDEX CONCURRENTLY idx_a ON t(a);")
  18. require.False(t, nonTx)
  19. require.Error(t, err)
  20. })
  21. t.Run("notx迁移要求DROP使用IF EXISTS", func(t *testing.T) {
  22. nonTx, err := validateMigrationExecutionMode("001_drop_idx_notx.sql", "DROP INDEX CONCURRENTLY idx_a;")
  23. require.False(t, nonTx)
  24. require.Error(t, err)
  25. })
  26. t.Run("notx迁移禁止事务控制语句", func(t *testing.T) {
  27. nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "BEGIN; CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a); COMMIT;")
  28. require.False(t, nonTx)
  29. require.Error(t, err)
  30. })
  31. t.Run("notx迁移禁止混用非CONCURRENTLY语句", func(t *testing.T) {
  32. nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a); UPDATE t SET a = 1;")
  33. require.False(t, nonTx)
  34. require.Error(t, err)
  35. })
  36. t.Run("notx迁移允许幂等并发索引语句", func(t *testing.T) {
  37. nonTx, err := validateMigrationExecutionMode("001_add_idx_notx.sql", `
  38. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_a ON t(a);
  39. DROP INDEX CONCURRENTLY IF EXISTS idx_b;
  40. `)
  41. require.True(t, nonTx)
  42. require.NoError(t, err)
  43. })
  44. }
  45. func TestApplyMigrationsFS_NonTransactionalMigration(t *testing.T) {
  46. db, mock, err := sqlmock.New()
  47. require.NoError(t, err)
  48. defer func() { _ = db.Close() }()
  49. prepareMigrationsBootstrapExpectations(mock)
  50. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  51. WithArgs("001_add_idx_notx.sql").
  52. WillReturnError(sql.ErrNoRows)
  53. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t\\(a\\)").
  54. WillReturnResult(sqlmock.NewResult(0, 0))
  55. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  56. WithArgs("001_add_idx_notx.sql", sqlmock.AnyArg()).
  57. WillReturnResult(sqlmock.NewResult(1, 1))
  58. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  59. WithArgs(migrationsAdvisoryLockID).
  60. WillReturnResult(sqlmock.NewResult(0, 1))
  61. fsys := fstest.MapFS{
  62. "001_add_idx_notx.sql": &fstest.MapFile{
  63. Data: []byte("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t(a);"),
  64. },
  65. }
  66. err = applyMigrationsFS(context.Background(), db, fsys)
  67. require.NoError(t, err)
  68. require.NoError(t, mock.ExpectationsWereMet())
  69. }
  70. func TestApplyMigrationsFS_NonTransactionalMigration_MultiStatements(t *testing.T) {
  71. db, mock, err := sqlmock.New()
  72. require.NoError(t, err)
  73. defer func() { _ = db.Close() }()
  74. prepareMigrationsBootstrapExpectations(mock)
  75. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  76. WithArgs("001_add_multi_idx_notx.sql").
  77. WillReturnError(sql.ErrNoRows)
  78. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t\\(a\\)").
  79. WillReturnResult(sqlmock.NewResult(0, 0))
  80. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_b ON t\\(b\\)").
  81. WillReturnResult(sqlmock.NewResult(0, 0))
  82. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  83. WithArgs("001_add_multi_idx_notx.sql", sqlmock.AnyArg()).
  84. WillReturnResult(sqlmock.NewResult(1, 1))
  85. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  86. WithArgs(migrationsAdvisoryLockID).
  87. WillReturnResult(sqlmock.NewResult(0, 1))
  88. fsys := fstest.MapFS{
  89. "001_add_multi_idx_notx.sql": &fstest.MapFile{
  90. Data: []byte(`
  91. -- first
  92. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_a ON t(a);
  93. -- second
  94. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_t_b ON t(b);
  95. `),
  96. },
  97. }
  98. err = applyMigrationsFS(context.Background(), db, fsys)
  99. require.NoError(t, err)
  100. require.NoError(t, mock.ExpectationsWereMet())
  101. }
  102. func TestApplyMigrationsFS_NonTransactionalMigration_LatestAPIKeyIPIndexDropsInvalidIndexBeforeRetry(t *testing.T) {
  103. db, mock, err := sqlmock.New()
  104. require.NoError(t, err)
  105. defer func() { _ = db.Close() }()
  106. prepareMigrationsBootstrapExpectations(mock)
  107. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  108. WithArgs(latestAPIKeyIPIndexMigration).
  109. WillReturnError(sql.ErrNoRows)
  110. mock.ExpectQuery("SELECT EXISTS \\(").
  111. WithArgs(latestAPIKeyIPIndex).
  112. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  113. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_usage_logs_api_key_latest_ip").
  114. WillReturnResult(sqlmock.NewResult(0, 0))
  115. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_api_key_latest_ip").
  116. WillReturnResult(sqlmock.NewResult(0, 0))
  117. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  118. WithArgs(latestAPIKeyIPIndexMigration, sqlmock.AnyArg()).
  119. WillReturnResult(sqlmock.NewResult(1, 1))
  120. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  121. WithArgs(migrationsAdvisoryLockID).
  122. WillReturnResult(sqlmock.NewResult(0, 1))
  123. fsys := fstest.MapFS{
  124. latestAPIKeyIPIndexMigration: &fstest.MapFile{
  125. Data: []byte(`
  126. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_api_key_latest_ip
  127. ON usage_logs (api_key_id, created_at DESC, id DESC)
  128. INCLUDE (ip_address)
  129. WHERE ip_address IS NOT NULL AND ip_address <> '';
  130. `),
  131. },
  132. }
  133. err = applyMigrationsFS(context.Background(), db, fsys)
  134. require.NoError(t, err)
  135. require.NoError(t, mock.ExpectationsWereMet())
  136. }
  137. func TestApplyMigrationsFS_NonTransactionalMigration_UsageModelMismatchIndexDropsInvalidIndexBeforeRetry(t *testing.T) {
  138. db, mock, err := sqlmock.New()
  139. require.NoError(t, err)
  140. defer func() { _ = db.Close() }()
  141. prepareMigrationsBootstrapExpectations(mock)
  142. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  143. WithArgs(usageLogsUpstreamModelMismatchIndexMigration).
  144. WillReturnError(sql.ErrNoRows)
  145. mock.ExpectQuery("SELECT EXISTS \\(").
  146. WithArgs(usageLogsUpstreamModelMismatchIndex).
  147. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  148. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_usage_logs_upstream_model_mismatch_created_at").
  149. WillReturnResult(sqlmock.NewResult(0, 0))
  150. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_upstream_model_mismatch_created_at").
  151. WillReturnResult(sqlmock.NewResult(0, 0))
  152. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  153. WithArgs(usageLogsUpstreamModelMismatchIndexMigration, sqlmock.AnyArg()).
  154. WillReturnResult(sqlmock.NewResult(1, 1))
  155. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  156. WithArgs(migrationsAdvisoryLockID).
  157. WillReturnResult(sqlmock.NewResult(0, 1))
  158. fsys := fstest.MapFS{
  159. usageLogsUpstreamModelMismatchIndexMigration: &fstest.MapFile{Data: []byte(`
  160. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_upstream_model_mismatch_created_at
  161. ON usage_logs (created_at DESC, id DESC)
  162. WHERE upstream_model_mismatch IS TRUE;
  163. `)},
  164. }
  165. err = applyMigrationsFS(context.Background(), db, fsys)
  166. require.NoError(t, err)
  167. require.NoError(t, mock.ExpectationsWereMet())
  168. }
  169. func TestApplyMigrationsFS_NonTransactionalMigration_EffectiveModelIndexesDropInvalidIndexesBeforeRetry(t *testing.T) {
  170. db, mock, err := sqlmock.New()
  171. require.NoError(t, err)
  172. defer func() { _ = db.Close() }()
  173. prepareMigrationsBootstrapExpectations(mock)
  174. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  175. WithArgs(usageLogsEffectiveModelIndexesMigration).
  176. WillReturnError(sql.ErrNoRows)
  177. for _, indexName := range []string{usageLogsEffectiveRequestedModelIndex, usageLogsEffectiveUpstreamModelIndex} {
  178. mock.ExpectQuery("SELECT EXISTS \\(").
  179. WithArgs(indexName).
  180. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  181. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS " + indexName).
  182. WillReturnResult(sqlmock.NewResult(0, 0))
  183. }
  184. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_requested_model_created").
  185. WillReturnResult(sqlmock.NewResult(0, 0))
  186. mock.ExpectExec("CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_upstream_model_created").
  187. WillReturnResult(sqlmock.NewResult(0, 0))
  188. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  189. WithArgs(usageLogsEffectiveModelIndexesMigration, sqlmock.AnyArg()).
  190. WillReturnResult(sqlmock.NewResult(1, 1))
  191. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  192. WithArgs(migrationsAdvisoryLockID).
  193. WillReturnResult(sqlmock.NewResult(0, 1))
  194. fsys := fstest.MapFS{
  195. usageLogsEffectiveModelIndexesMigration: &fstest.MapFile{Data: []byte(`
  196. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_requested_model_created
  197. ON usage_logs ((COALESCE(NULLIF(BTRIM(requested_model), ''), model)), created_at DESC, id DESC);
  198. CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_effective_upstream_model_created
  199. ON usage_logs ((COALESCE(NULLIF(BTRIM(upstream_model), ''), model)), created_at DESC, id DESC);
  200. `)},
  201. }
  202. err = applyMigrationsFS(context.Background(), db, fsys)
  203. require.NoError(t, err)
  204. require.NoError(t, mock.ExpectationsWereMet())
  205. }
  206. func TestApplyMigrationsFS_PaymentOrdersOutTradeNoUniqueMigration_FailsFastOnDuplicatePrecheck(t *testing.T) {
  207. db, mock, err := sqlmock.New()
  208. require.NoError(t, err)
  209. defer func() { _ = db.Close() }()
  210. prepareMigrationsBootstrapExpectations(mock)
  211. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  212. WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql").
  213. WillReturnError(sql.ErrNoRows)
  214. mock.ExpectQuery("SELECT out_trade_no, COUNT\\(\\*\\) AS duplicate_count FROM payment_orders").
  215. WillReturnRows(sqlmock.NewRows([]string{"out_trade_no", "duplicate_count"}).AddRow("dup-out-trade-no", 2))
  216. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  217. WithArgs(migrationsAdvisoryLockID).
  218. WillReturnResult(sqlmock.NewResult(0, 1))
  219. fsys := fstest.MapFS{
  220. "120_enforce_payment_orders_out_trade_no_unique_notx.sql": &fstest.MapFile{
  221. Data: []byte(`
  222. CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique
  223. ON payment_orders (out_trade_no)
  224. WHERE out_trade_no <> '';
  225. DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no;
  226. `),
  227. },
  228. }
  229. err = applyMigrationsFS(context.Background(), db, fsys)
  230. require.Error(t, err)
  231. require.Contains(t, err.Error(), "duplicate out_trade_no")
  232. require.Contains(t, err.Error(), "dup-out-trade-no")
  233. require.NoError(t, mock.ExpectationsWereMet())
  234. }
  235. func TestApplyMigrationsFS_PaymentOrdersOutTradeNoUniqueMigration_DropsInvalidIndexBeforeRetry(t *testing.T) {
  236. db, mock, err := sqlmock.New()
  237. require.NoError(t, err)
  238. defer func() { _ = db.Close() }()
  239. prepareMigrationsBootstrapExpectations(mock)
  240. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  241. WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql").
  242. WillReturnError(sql.ErrNoRows)
  243. mock.ExpectQuery("SELECT out_trade_no, COUNT\\(\\*\\) AS duplicate_count FROM payment_orders").
  244. WillReturnRows(sqlmock.NewRows([]string{"out_trade_no", "duplicate_count"}))
  245. mock.ExpectQuery("SELECT EXISTS \\(").
  246. WithArgs("paymentorder_out_trade_no_unique").
  247. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  248. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no_unique").
  249. WillReturnResult(sqlmock.NewResult(0, 0))
  250. mock.ExpectExec("CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique").
  251. WillReturnResult(sqlmock.NewResult(0, 0))
  252. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no").
  253. WillReturnResult(sqlmock.NewResult(0, 0))
  254. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  255. WithArgs("120_enforce_payment_orders_out_trade_no_unique_notx.sql", sqlmock.AnyArg()).
  256. WillReturnResult(sqlmock.NewResult(1, 1))
  257. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  258. WithArgs(migrationsAdvisoryLockID).
  259. WillReturnResult(sqlmock.NewResult(0, 1))
  260. fsys := fstest.MapFS{
  261. "120_enforce_payment_orders_out_trade_no_unique_notx.sql": &fstest.MapFile{
  262. Data: []byte(`
  263. CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS paymentorder_out_trade_no_unique
  264. ON payment_orders (out_trade_no)
  265. WHERE out_trade_no <> '';
  266. DROP INDEX CONCURRENTLY IF EXISTS paymentorder_out_trade_no;
  267. `),
  268. },
  269. }
  270. err = applyMigrationsFS(context.Background(), db, fsys)
  271. require.NoError(t, err)
  272. require.NoError(t, mock.ExpectationsWereMet())
  273. }
  274. func TestApplyMigrationsFS_SchedulerOutboxPendingDedupKeyMigration_DropsInvalidIndexBeforeRetry(t *testing.T) {
  275. db, mock, err := sqlmock.New()
  276. require.NoError(t, err)
  277. defer func() { _ = db.Close() }()
  278. prepareMigrationsBootstrapExpectations(mock)
  279. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  280. WithArgs("153_scheduler_outbox_pending_dedup_key_index_notx.sql").
  281. WillReturnError(sql.ErrNoRows)
  282. mock.ExpectQuery("SELECT EXISTS \\(").
  283. WithArgs("idx_scheduler_outbox_pending_dedup_key").
  284. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  285. mock.ExpectExec("DROP INDEX CONCURRENTLY IF EXISTS idx_scheduler_outbox_pending_dedup_key").
  286. WillReturnResult(sqlmock.NewResult(0, 0))
  287. mock.ExpectExec("CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_scheduler_outbox_pending_dedup_key").
  288. WillReturnResult(sqlmock.NewResult(0, 0))
  289. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  290. WithArgs("153_scheduler_outbox_pending_dedup_key_index_notx.sql", sqlmock.AnyArg()).
  291. WillReturnResult(sqlmock.NewResult(1, 1))
  292. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  293. WithArgs(migrationsAdvisoryLockID).
  294. WillReturnResult(sqlmock.NewResult(0, 1))
  295. fsys := fstest.MapFS{
  296. "153_scheduler_outbox_pending_dedup_key_index_notx.sql": &fstest.MapFile{
  297. Data: []byte(`
  298. CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_scheduler_outbox_pending_dedup_key
  299. ON scheduler_outbox (dedup_key)
  300. WHERE dedup_key IS NOT NULL;
  301. `),
  302. },
  303. }
  304. err = applyMigrationsFS(context.Background(), db, fsys)
  305. require.NoError(t, err)
  306. require.NoError(t, mock.ExpectationsWereMet())
  307. }
  308. func TestApplyMigrationsFS_TransactionalMigration(t *testing.T) {
  309. db, mock, err := sqlmock.New()
  310. require.NoError(t, err)
  311. defer func() { _ = db.Close() }()
  312. // The advisory lock and all migration work must share one session. This also
  313. // proves startup cannot self-deadlock when deployments cap the pool at one.
  314. db.SetMaxOpenConns(1)
  315. prepareMigrationsBootstrapExpectations(mock)
  316. mock.ExpectQuery("SELECT checksum FROM schema_migrations WHERE filename = \\$1").
  317. WithArgs("001_add_col.sql").
  318. WillReturnError(sql.ErrNoRows)
  319. mock.ExpectBegin()
  320. mock.ExpectExec("ALTER TABLE t ADD COLUMN name TEXT").
  321. WillReturnResult(sqlmock.NewResult(0, 0))
  322. mock.ExpectExec("INSERT INTO schema_migrations \\(filename, checksum\\) VALUES \\(\\$1, \\$2\\)").
  323. WithArgs("001_add_col.sql", sqlmock.AnyArg()).
  324. WillReturnResult(sqlmock.NewResult(1, 1))
  325. mock.ExpectCommit()
  326. mock.ExpectExec("SELECT pg_advisory_unlock\\(\\$1\\)").
  327. WithArgs(migrationsAdvisoryLockID).
  328. WillReturnResult(sqlmock.NewResult(0, 1))
  329. fsys := fstest.MapFS{
  330. "001_add_col.sql": &fstest.MapFile{
  331. Data: []byte("ALTER TABLE t ADD COLUMN name TEXT;"),
  332. },
  333. }
  334. err = applyMigrationsFS(context.Background(), db, fsys)
  335. require.NoError(t, err)
  336. require.NoError(t, mock.ExpectationsWereMet())
  337. }
  338. func prepareMigrationsBootstrapExpectations(mock sqlmock.Sqlmock) {
  339. mock.ExpectQuery("SELECT pg_try_advisory_lock\\(\\$1\\)").
  340. WithArgs(migrationsAdvisoryLockID).
  341. WillReturnRows(sqlmock.NewRows([]string{"pg_try_advisory_lock"}).AddRow(true))
  342. mock.ExpectExec("CREATE TABLE IF NOT EXISTS schema_migrations").
  343. WillReturnResult(sqlmock.NewResult(0, 0))
  344. mock.ExpectQuery("SELECT EXISTS \\(").
  345. WithArgs("schema_migrations").
  346. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  347. mock.ExpectQuery("SELECT EXISTS \\(").
  348. WithArgs("atlas_schema_revisions").
  349. WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
  350. mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM atlas_schema_revisions").
  351. WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
  352. }