20251010082205_add_unique_constraint_subscription_v1_id.mjs 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import Helpers from './lib/helpers.mjs'
  2. const tags = ['saas']
  3. const originalIndexes = [
  4. {
  5. key: { v1_id: 1 },
  6. name: 'v1_id_1',
  7. sparse: true,
  8. },
  9. ]
  10. const newIndexes = [
  11. {
  12. key: { v1_id: 1 },
  13. name: 'v1_id_2',
  14. unique: true,
  15. partialFilterExpression: {
  16. v1_id: { $type: 'number' },
  17. },
  18. },
  19. ]
  20. async function assertNoDuplicateV1Ids(collection) {
  21. const duplicates = await collection
  22. .aggregate([
  23. { $match: { v1_id: { $exists: true, $ne: null } } },
  24. {
  25. $group: {
  26. _id: '$v1_id',
  27. count: { $sum: 1 },
  28. docs: { $push: '$_id' },
  29. },
  30. },
  31. { $match: { count: { $gt: 1 } } },
  32. ])
  33. .toArray()
  34. if (duplicates.length > 0) {
  35. const duplicateDetails = duplicates.map(dup => ({
  36. v1_id: dup._id,
  37. count: dup.count,
  38. docs: dup.docs,
  39. }))
  40. throw new Error(
  41. `Duplicate v1_id values found. Migration aborted to prevent data loss. Details: ${JSON.stringify(
  42. duplicateDetails,
  43. null,
  44. 2
  45. )}`
  46. )
  47. }
  48. }
  49. const migrate = async client => {
  50. const { db } = client
  51. // pre‑check (keep old index intact if failing)
  52. await assertNoDuplicateV1Ids(db.subscriptions)
  53. await Helpers.dropIndexesFromCollection(db.subscriptions, originalIndexes)
  54. await Helpers.addIndexesToCollection(db.subscriptions, newIndexes)
  55. }
  56. const rollback = async client => {
  57. const { db } = client
  58. await Helpers.dropIndexesFromCollection(db.subscriptions, newIndexes)
  59. // recreate the original non-unique sparse index
  60. await Helpers.addIndexesToCollection(db.subscriptions, originalIndexes)
  61. }
  62. export default {
  63. tags,
  64. migrate,
  65. rollback,
  66. }