Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/mongo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const tempKey = '__temp_minato_mongo__'

interface TableMeta {
_id: string
/** @deprecated legacy format (< 3.7.0) stored one entry per table with an ObjectId `_id` */
table?: string
/** @deprecated legacy format (< 3.7.0) stored one entry per table with an ObjectId `_id` */
field?: string
virtual?: boolean
migrate?: boolean
autoInc?: number
Expand Down Expand Up @@ -149,7 +153,15 @@ export class MongoDriver extends Driver<MongoDriver.Config> {
const meta = { _id: table }, found = await metaTable.findOne(meta)
if (!found?.fields) {
this.ctx.logger?.info('initializing fields for table %s', table)
await metaTable.updateOne(meta, { $set: { fields: Object.keys(fields) } }, { upsert: true })
// inherit `virtual` / `autoInc` from legacy-format entries (see #_migratePrimary)
// otherwise both values are lost after the migration, which will make
// `_migratePrimary` re-scan the whole table on every startup and may even
// trigger an unwanted full-table rewrite in `_migrateVirtual`
const legacy = await metaTable.findOne({ table, field: this.model(table).primary })
const $set: Partial<TableMeta> = { fields: Object.keys(fields) }
if (legacy?.virtual !== undefined) $set.virtual = legacy.virtual
if (legacy?.autoInc !== undefined) $set.autoInc = legacy.autoInc
await metaTable.updateOne(meta, { $set }, { upsert: true })
return
}
for (const key in fields) {
Expand Down
123 changes: 123 additions & 0 deletions packages/mongo/tests/meta-migration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Database, Driver } from '@cordisjs/plugin-database'
import { Context, Fiber } from 'cordis'
import MongoDriver from '@cordisjs/plugin-database-mongo'
import LoggerConsole from '@cordisjs/plugin-logger-console'
import { expect } from '@cordisjs/database-tests'
import { afterAll, beforeAll, describe, it } from 'vitest'

interface Legacy {
id?: number
text?: string
}

declare module '@cordisjs/plugin-database' {
interface Tables {
mongo_legacy: Legacy
}
}

describe('@cordisjs/plugin-database-mongo/meta-migration', () => {
const ctx = new Context()

let database: Database
let fiber: Fiber | undefined

const reset = async () => {
await fiber?.dispose()
fiber = await ctx.plugin(MongoDriver, {
host: 'localhost',
port: 27017,
database: 'test_meta_migration',
})
database.refresh()
await database.prepared()
}

const getMeta = async () => {
const driver = database['getDriver']('mongo_legacy') as MongoDriver
return driver.db.collection('_fields').findOne({ _id: 'mongo_legacy' })
}

const getRawCollection = () => {
const driver = database['getDriver']('mongo_legacy') as MongoDriver
return driver.db.collection('mongo_legacy')
}

beforeAll(async () => {
await ctx.plugin(Database)
await ctx.plugin(LoggerConsole)
fiber = await ctx.plugin(MongoDriver, {
host: 'localhost',
port: 27017,
database: 'test_meta_migration',
})
database = ctx.model as Database
})

afterAll(async () => {
await database.dropAll()
await fiber?.dispose()
})

it('should inherit virtual/autoInc from legacy-format _fields entries', async () => {
database.extend('mongo_legacy', {
id: 'unsigned',
text: 'string',
}, {
autoInc: true,
})

await database.remove('mongo_legacy', {})
const created = []
for (let i = 0; i < 3; i++) {
created.push(await database.create('mongo_legacy', { text: `row ${i}` }))
}
// remove the row with the highest id: if the stored `autoInc` is lost,
// `_migratePrimary` will recalculate it as `max(remaining ids)` and the
// next insert will silently reuse the id of the deleted row
await database.remove('mongo_legacy', { id: created[2].id })

// simulate the legacy (< 3.7.0) `_fields` state: per-table entries keyed by
// `{ table, field }` with an ObjectId `_id`, carrying `virtual` and `autoInc`
const driver = database['getDriver']('mongo_legacy') as MongoDriver
const metaTable = driver.db.collection('_fields')
const { ObjectId } = await import('mongodb')
const current = await metaTable.findOne({ _id: 'mongo_legacy' })
await metaTable.deleteOne({ _id: 'mongo_legacy' })
await metaTable.insertOne({
_id: new ObjectId(),
table: 'mongo_legacy',
field: 'id',
virtual: false,
autoInc: current.autoInc,
})

// restart the driver: `_createFields` should initialize the new-format entry
// and inherit `virtual` / `autoInc` from the legacy entry instead of losing them
await reset()

const meta = await getMeta()
expect(meta.virtual).to.equal(false)
expect(meta.autoInc).to.equal(current.autoInc)

Check failure on line 101 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:latest (26)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should inherit virtual/autoInc from legacy-format _fields entries

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:101:29

Check failure on line 101 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:latest (24)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should inherit virtual/autoInc from legacy-format _fields entries

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:101:29

Check failure on line 101 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:6.0 (22)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should inherit virtual/autoInc from legacy-format _fields entries

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:101:29

// data must survive untouched (no destructive re-migration, ids still resolvable)
await expect(database.get('mongo_legacy', {})).to.eventually.have.deep.members(created.slice(0, 2))
// the next insert must NOT reuse the id of the deleted row
const next = await database.create('mongo_legacy', { text: 'row 3' })
expect(next.id).to.equal(current.autoInc + 1)
})

it('should not lose autoInc when _fields is dropped entirely', async () => {
// dropping `_fields` simulates a cold re-detect: virtual/autoInc are inferred
// from the data itself, which is the pre-existing behavior — this test guards
// that the inheritance path above does not interfere with it
const driver = database['getDriver']('mongo_legacy') as unknown as Driver
await driver.drop('_fields')
await reset()

const rows = await database.get('mongo_legacy', {})
expect(rows.length).to.equal(3)

Check failure on line 119 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:latest (26)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should not lose autoInc when _fields is dropped entirely

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:119:28

Check failure on line 119 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:latest (24)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should not lose autoInc when _fields is dropped entirely

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:119:28

Check failure on line 119 in packages/mongo/tests/meta-migration.spec.ts

View workflow job for this annotation

GitHub Actions / mongo:6.0 (22)

packages/mongo/tests/meta-migration.spec.ts > @cordisjs/plugin-database-mongo/meta-migration > should not lose autoInc when _fields is dropped entirely

AssertionError: expected 2 to equal 3 - Expected + Received - 3 + 2 ❯ packages/mongo/tests/meta-migration.spec.ts:119:28
const next = await database.create('mongo_legacy', { text: 'row 4' })
expect(rows.every(row => row.id !== next.id)).to.equal(true)
})
})
Loading