Skip to content
Merged
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
2 changes: 2 additions & 0 deletions packages/core/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export class Database extends Service {
const driver = this.getDriver(name)
if (!driver) return

driver.tables.add(name)

const { fields } = driver.model(name)
Object.values(fields).forEach(field => field?.transformers?.forEach(x => driver.define(x)))

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export abstract class Driver<T = any> {
abstract dropIndex(table: string, name: string): Promise<void>

public database!: Database

public tables: Set<string> = new Set()
public types: Dict<Driver.Transformer> = Object.create(null)

constructor(public ctx: Context, public config: T) {}
Expand Down
5 changes: 3 additions & 2 deletions packages/mongo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,15 @@ export class MongoDriver extends Driver<MongoDriver.Config> {
}

async dropAll() {
const tables = [...this.tables]
await Promise.all([
'_fields',
...Object.keys(this.database.tables),
...tables,
].map(name => this.db.dropCollection(name, { session: this.session })))
}

private async _collStats() {
const tables = Object.keys(this.database.tables)
const tables = [...this.tables]
const entries = await Promise.all(tables.map(async (name) => {
const coll = this.db.collection(name)
const [{ storageStats: { count, size } }] = await coll.aggregate([{
Expand Down
94 changes: 48 additions & 46 deletions packages/mongo/tests/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,20 @@ interface RawDoc {
value?: number
}

interface Tables {
temp1: Foo
temp2: Bar
temp3: Baz
temp4: Qux
declare module 'minato' {
interface Tables {
mongo1: Foo
mongo2: Bar
mongo3: Baz
mongo4: Qux
}
}

describe('@minatojs/driver-mongo/migrate-virtualKey', () => {
const ctx = new Context()

let database: Database
let fiber: Fiber<Context> | undefined
let fiber: Fiber | undefined

const resetConfig = async (optimizeIndex: boolean) => {
await fiber?.dispose()
Expand Down Expand Up @@ -90,13 +92,13 @@ describe('@minatojs/driver-mongo/migrate-virtualKey', () => {
await fiber?.dispose()
})

const getCollection = (table: keyof Tables) => {
const driver = database['getDriver'](table as string) as MongoDriver
return driver.db.collection<RawDoc>(table as string)
const getCollection = (table: string) => {
const driver = database['getDriver'](table) as MongoDriver
return driver.db.collection<RawDoc>(table)
}

it('reset optimizeIndex', async () => {
database.extend('temp1', {
database.extend('mongo1', {
id: 'unsigned',
text: 'string',
value: 'integer',
Expand All @@ -112,33 +114,33 @@ describe('@minatojs/driver-mongo/migrate-virtualKey', () => {
})

const table: Foo[] = []
table.push(await database.create('temp1', {
table.push(await database.create('mongo1', {
text: 'awesome foo',
timestamp: new Date('2000-01-01'),
date: new Date('2020-01-01'),
time: new Date('2020-01-01 12:00:00'),
}))
table.push(await database.create('temp1', { text: 'awesome bar' }))
table.push(await database.create('temp1', { text: 'awesome baz' }))
await expect(database.get('temp1', {})).to.eventually.deep.eq(table)
table.push(await database.create('mongo1', { text: 'awesome bar' }))
table.push(await database.create('mongo1', { text: 'awesome baz' }))
await expect(database.get('mongo1', {})).to.eventually.deep.eq(table)

await resetConfig(true)
await expect(database.get('temp1', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo1', {})).to.eventually.deep.eq(table)

await resetConfig(false)
await expect(database.get('temp1', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo1', {})).to.eventually.deep.eq(table)

await (Object.values(database.drivers)[0] as Driver).drop('_fields')
await resetConfig(true)
await expect(database.get('temp1', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo1', {})).to.eventually.deep.eq(table)

await (Object.values(database.drivers)[0] as Driver).drop('_fields')
await resetConfig(false)
await expect(database.get('temp1', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo1', {})).to.eventually.deep.eq(table)
})

it('using primary', async () => {
database.extend('temp2', {
database.extend('mongo2', {
id: 'primary',
text: 'string',
value: 'integer',
Expand All @@ -151,63 +153,63 @@ describe('@minatojs/driver-mongo/migrate-virtualKey', () => {
foreign: 'primary',
})

await database.remove('temp2', {})
await database.remove('mongo2', {})

const table: Bar[] = []
table.push(await database.create('temp2', {
table.push(await database.create('mongo2', {
text: 'awesome foo',
timestamp: new Date('2000-01-01'),
date: new Date('2020-01-01'),
time: new Date('2020-01-01 12:00:00'),
}))
table.push(await database.create('temp2', { text: 'awesome bar' }))
table.push(await database.create('temp2', { text: 'awesome baz' }))
await expect(database.get('temp2', {})).to.eventually.deep.eq(table)
table.push(await database.create('mongo2', { text: 'awesome bar' }))
table.push(await database.create('mongo2', { text: 'awesome baz' }))
await expect(database.get('mongo2', {})).to.eventually.deep.eq(table)

await expect(database.get('temp2', table[0].id?.toString() as any)).to.eventually.deep.eq([table[0]])
await expect(database.get('temp2', { id: table[0].id?.toString() as any })).to.eventually.deep.eq([table[0]])
await expect(database.get('temp2', row => $.eq(row.id, $.literal(table[0].id?.toString(), 'primary') as any))).to.eventually.deep.eq([table[0]])
await expect(database.get('mongo2', table[0].id?.toString() as any)).to.eventually.deep.eq([table[0]])
await expect(database.get('mongo2', { id: table[0].id?.toString() as any })).to.eventually.deep.eq([table[0]])
await expect(database.get('mongo2', row => $.eq(row.id, $.literal(table[0].id?.toString(), 'primary') as any))).to.eventually.deep.eq([table[0]])

await (Object.values(database.drivers)[0] as Driver).drop('_fields')
await resetConfig(true)
await expect(database.get('temp2', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo2', {})).to.eventually.deep.eq(table)

await (Object.values(database.drivers)[0] as Driver).drop('_fields')
await resetConfig(false)
await expect(database.get('temp2', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo2', {})).to.eventually.deep.eq(table)

// query & eval
table.push(await database.create('temp2', { foreign: table[0].id }))
await expect(database.get('temp2', {})).to.eventually.deep.eq(table)
await expect(database.get('temp2', { foreign: table[0].id })).to.eventually.deep.eq([table[3]])
await expect(database.get('temp2', row => $.eq(row.foreign, table[0].id!))).to.eventually.deep.eq([table[3]])
table.push(await database.create('mongo2', { foreign: table[0].id }))
await expect(database.get('mongo2', {})).to.eventually.deep.eq(table)
await expect(database.get('mongo2', { foreign: table[0].id })).to.eventually.deep.eq([table[3]])
await expect(database.get('mongo2', row => $.eq(row.foreign, table[0].id!))).to.eventually.deep.eq([table[3]])
})

it('upsert for ensurePrimary path', async () => {
await resetConfig(true)
database.extend('temp3', {
database.extend('mongo3', {
id: 'unsigned',
text: 'string',
value: 'integer',
}, {
autoInc: true,
})

await database.remove('temp3', {})
await database.remove('mongo3', {})

const existing = await database.create('temp3', { text: 'before', value: 1 })
const existing = await database.create('mongo3', { text: 'before', value: 1 })
const insertedId = existing.id! + 1
await expect(database.upsert('temp3', [
await expect(database.upsert('mongo3', [
{ id: existing.id, text: 'after', value: 2 },
{ id: insertedId, text: 'inserted', value: 3 },
])).to.eventually.have.shape({ inserted: 1, matched: 1 })

await expect(database.get('temp3', {})).to.eventually.have.deep.members([
await expect(database.get('mongo3', {})).to.eventually.have.deep.members([
{ id: existing.id, text: 'after', value: 2 },
{ id: insertedId, text: 'inserted', value: 3 },
])

const docs = await getCollection('temp3')
const docs = await getCollection('mongo3')
.find({}, { projection: { _id: 1, id: 1, text: 1, value: 1 } })
.sort({ _id: 1 })
.toArray()
Expand All @@ -221,30 +223,30 @@ describe('@minatojs/driver-mongo/migrate-virtualKey', () => {
})

it('upsert for pipeline path', async () => {
database.extend('temp4', {
database.extend('mongo4', {
id: 'primary',
text: 'string',
value: 'integer',
})

await database.remove('temp4', {})
await database.remove('mongo4', {})

const existing = await database.create('temp4', { text: 'before', value: 1 })
const existing = await database.create('mongo4', { text: 'before', value: 1 })
const insertedId = new ObjectId() as unknown as Primary
await expect(database.upsert('temp4', [
await expect(database.upsert('mongo4', [
{ id: existing.id, text: 'after', value: 2 },
{ id: insertedId, text: 'inserted', value: 3 },
])).to.eventually.have.shape({ inserted: 1, matched: 1 })

const [updated] = await database.get('temp4', { id: existing.id?.toString() as any })
const [updated] = await database.get('mongo4', { id: existing.id?.toString() as any })
expect(updated).to.have.shape({ text: 'after', value: 2 })
expect(updated.id?.toString()).to.equal(existing.id?.toString())

const [inserted] = await database.get('temp4', { id: insertedId.toString() as any })
const [inserted] = await database.get('mongo4', { id: insertedId.toString() as any })
expect(inserted).to.have.shape({ text: 'inserted', value: 3 })
expect(inserted.id?.toString()).to.equal(insertedId.toString())

const docs = await getCollection('temp4')
const docs = await getCollection('mongo4')
.find({}, { projection: { _id: 1, id: 1, text: 1, value: 1 } })
.toArray()

Expand Down
12 changes: 8 additions & 4 deletions packages/mysql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,18 +354,22 @@ INSERT INTO mtt VALUES(json_extract(j, concat('$[', i, ']'))); SET i=i+1; END WH
}

async dropAll() {
const data = await this._select('information_schema.tables', ['TABLE_NAME'], 'TABLE_SCHEMA = ? AND TABLE_TYPE = ?', [this.config.database, 'BASE TABLE'])
if (!data.length) return
const tables = [...this.tables]
if (!tables.length) return
await this.query([
'SET foreign_key_checks = 0',
...data.map(({ TABLE_NAME }) => `DROP TABLE ${escapeId(TABLE_NAME)}`),
...tables.map(table => `DROP TABLE ${escapeId(table)}`),
'SET foreign_key_checks = 1',
].join('; '))
}

async stats() {
const tables = [...this.tables]
if (!tables.length) return { size: 0, tables: {} }
const data = await this._select(
'information_schema.tables', ['TABLE_NAME', 'TABLE_ROWS', 'DATA_LENGTH'], 'TABLE_SCHEMA = ? AND TABLE_TYPE = ?', [this.config.database, 'BASE TABLE'],
'information_schema.tables', ['TABLE_NAME', 'TABLE_ROWS', 'DATA_LENGTH'],
'TABLE_SCHEMA = ? AND TABLE_TYPE = ? AND TABLE_NAME IN (?)',
[this.config.database, 'BASE TABLE', tables],
)
const stats: Partial<Driver.Stats> = { size: 0 }
stats.tables = Object.fromEntries(data.map(({ TABLE_NAME: name, TABLE_ROWS: count, DATA_LENGTH: size }) => {
Expand Down
24 changes: 4 additions & 20 deletions packages/postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,6 @@ interface IndexInfo {
indexdef: string
}

interface TableInfo {
table_catalog: string
table_schema: string
table_name: string
table_type: string
self_referencing_column_name: null
reference_generation: null
user_defined_type_catalog: null
user_defined_type_schema: null
user_defined_type_name: null
is_insertable_into: string
is_typed: string
commit_action: null
}

interface QueryTask {
sql: string
resolve: (value: any) => void
Expand Down Expand Up @@ -276,15 +261,14 @@ export class PostgresDriver extends Driver<PostgresDriver.Config> {
}

async dropAll() {
const tables: TableInfo[] = await this.queue(`SELECT * FROM information_schema.tables WHERE table_schema = 'public'`)
const tables = [...this.tables]
if (!tables.length) return
await this.query(`DROP TABLE IF EXISTS ${tables.map(t => escapeId(t.table_name)).join(',')} CASCADE`)
await this.query(`DROP TABLE IF EXISTS ${tables.map(t => escapeId(t)).join(',')} CASCADE`)
}

async stats(): Promise<Partial<Driver.Stats>> {
const names = Object.keys(this.database.tables)
const tables = (await this.queue<TableInfo[]>(`SELECT * FROM information_schema.tables WHERE table_schema = 'public'`))
.map(t => t.table_name).filter(name => names.includes(name))
const tables = [...this.tables]
if (!tables.length) return { size: 0, tables: {} }
const tableStats = await this.queue(
tables.map(
(name) => `SELECT '${name}' AS name, pg_total_relation_size('${escapeId(name)}') AS size, COUNT(*) AS count FROM ${escapeId(name)}`,
Expand Down
10 changes: 5 additions & 5 deletions packages/sqlite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,24 +258,24 @@ export class SQLiteDriver extends Driver<SQLiteDriver.Config> {
}

async dropAll() {
const tables = Object.keys(this.database.tables)
const tables = [...this.tables]
for (const table of tables) {
this._run(`DROP TABLE ${escapeId(table)}`)
}
}

async stats() {
const tables = [...this.tables]
const pageCount = this._get(`PRAGMA page_count`) as { page_count?: number | bigint }
const pageSize = this._get(`PRAGMA page_size`) as { page_size?: number | bigint }
const stats: Driver.Stats = {
size: Number(pageCount?.page_count ?? 0) * Number(pageSize?.page_size ?? 0),
tables: {},
}
const tableNames: { name: string }[] = this._all("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
const dbstats: { name: string; size: number }[] = this._all('SELECT name, pgsize as size FROM "dbstat" WHERE aggregate=TRUE;')
tableNames.forEach(tbl => {
stats.tables[tbl.name] = this._get(`SELECT COUNT(*) as count FROM ${escapeId(tbl.name)};`)
stats.tables[tbl.name].size = dbstats.find(o => o.name === tbl.name)!.size
tables.forEach(name => {
stats.tables[name] = this._get(`SELECT COUNT(*) as count FROM ${escapeId(name)};`)
stats.tables[name].size = dbstats.find(o => o.name === name)?.size ?? 0
})
return stats
}
Expand Down