From db3cdfc78d9a194ce5c8136e4e494e9a69cb9ddd Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Mon, 6 May 2024 16:23:18 +0800 Subject: [PATCH 1/8] feat: impl index apis --- packages/core/src/database.ts | 12 +++++++++ packages/core/src/driver.ts | 8 ++++++ packages/memory/src/index.ts | 17 +++++++++++++ packages/mongo/src/index.ts | 31 +++++++++++++++++++++-- packages/mysql/src/index.ts | 30 ++++++++++++++++++++++- packages/postgres/src/index.ts | 43 ++++++++++++++++++++++++++++++++ packages/sqlite/src/index.ts | 45 +++++++++++++++++++++++++++++++++- packages/tests/src/update.ts | 24 +++++++++++++++++- 8 files changed, 205 insertions(+), 5 deletions(-) diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 0d0d367c..4016e957 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -393,6 +393,18 @@ export class Database extends Servi await Promise.all(Object.values(this.drivers).map(driver => driver.dropAll())) } + getIndexes>(table: K): Promise>> { + return this.getDriver(table).getIndexes(table) + } + + createIndex>(table: K, index: Driver.Index) { + return this.getDriver(table).createIndex(table, index) + } + + dropIndex>(table: K, name: string) { + return this.getDriver(table).dropIndex(table, name) + } + async stats() { await this.prepared() const stats: Driver.Stats = { size: 0, tables: {} } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index d104888e..3beda9a1 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -33,6 +33,11 @@ export namespace Driver { removed?: number } + export interface Index { + unique?: boolean + keys: { [P in keyof S ]?: 'asc' | 'desc' } + } + export interface Transformer { types: Field.Type[] dump: (value: S | null) => T | null | void @@ -60,6 +65,9 @@ export abstract class Driver { abstract create(sel: Selection.Mutable, data: any): Promise abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise abstract withTransaction(callback: (session?: any) => Promise): Promise + abstract getIndexes(table: string): Promise> + abstract createIndex(table: string, index: Driver.Index): Promise + abstract dropIndex(table: string, name: string): Promise public database: Database public logger: Logger diff --git a/packages/memory/src/index.ts b/packages/memory/src/index.ts index 454f4e86..6ceb199f 100644 --- a/packages/memory/src/index.ts +++ b/packages/memory/src/index.ts @@ -8,6 +8,8 @@ export class MemoryDriver extends Driver { _fields: [], } + _indexes: Dict> = {} + async prepare(name: string) {} async start() { @@ -198,6 +200,21 @@ export class MemoryDriver extends Driver { throw e }) } + + async getIndexes(table: string): Promise> { + return this._indexes[table] ?? {} + } + + async createIndex(table: string, index: Driver.Index) { + const name = 'index:' + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction}`).join('+') + this._indexes[table] ??= {} + this._indexes[table][name] = { unique: false, ...index } + } + + async dropIndex(table: string, name: string) { + this._indexes[table] ??= {} + delete this._indexes[table][name] + } } export namespace MemoryDriver { diff --git a/packages/mongo/src/index.ts b/packages/mongo/src/index.ts index b8acb552..45ad5998 100644 --- a/packages/mongo/src/index.ts +++ b/packages/mongo/src/index.ts @@ -72,7 +72,7 @@ export class MongoDriver extends Driver { * https://www.mongodb.com/docs/manual/indexes/ */ private async _createIndexes(table: string) { - const { primary, unique } = this.model(table) + const { fields, primary, unique } = this.model(table) const coll = this.db.collection(table) const newSpecs: IndexDescription[] = [] const oldSpecs = await coll.indexes() @@ -86,6 +86,7 @@ export class MongoDriver extends Driver { const name = (index ? 'unique:' : 'primary:') + keys.join('+') if (oldSpecs.find(spec => spec.name === name)) return + const nullable = Object.entries(fields).filter(([key]) => keys.includes(key)).every(([, field]) => field?.nullable) newSpecs.push({ name, key: Object.fromEntries(keys.map(key => [key, 1])), @@ -94,7 +95,7 @@ export class MongoDriver extends Driver { // mongodb seems to not support $ne in partialFilterExpression // so we cannot simply use `{ $ne: null }` to filter out null values // below is a workaround for https://github.com/koishijs/koishi/issues/893 - partialFilterExpression: Object.fromEntries(keys.map((key) => [key, { + partialFilterExpression: nullable ? undefined : Object.fromEntries(keys.map((key) => [key, { $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], }])), }) @@ -488,6 +489,32 @@ See https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-s } } + async getIndexes(table: string): Promise> { + const indexes = await this.db.collection(table).listIndexes().toArray() + return Object.fromEntries(indexes.map(({ name, key, unique }) => [name, { + unique: !!unique, + keys: mapValues(key, value => value === 1 ? 'asc' : value === -1 ? 'desc' : value), + }])) + } + + async createIndex(table: string, index: Driver.Index) { + const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') + const keys = mapValues(index.keys, (value) => value === 'asc' ? 1 : value === 'desc' ? -1 : isNullable(value) ? 1 : value) + const { fields } = this.model(table) + const nullable = Object.keys(index.keys).every(key => fields[key]?.nullable) + await this.db.collection(table).createIndex(keys, { + name, + unique: !!index.unique, + partialFilterExpression: nullable ? undefined : Object.fromEntries(Object.keys(index.keys).map((key) => [key, { + $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], + }])), + }) + } + + async dropIndex(table: string, name: string) { + await this.db.collection(table).dropIndex(name) + } + logPipeline(table: string, pipeline: any) { this.logger.debug('%s %s', table, JSON.stringify(pipeline, (_, value) => typeof value === 'bigint' ? `${value}n` : value)) } diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index 25260848..b77dd51f 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -1,4 +1,4 @@ -import { Binary, Dict, difference, isNullable, makeArray, pick } from 'cosmokit' +import { Binary, Dict, difference, isNullable, makeArray, noop, pick } from 'cosmokit' import { createPool, format } from '@vlasky/mysql' import type { OkPacket, Pool, PoolConfig, PoolConnection } from 'mysql' import { Driver, Eval, executeUpdate, Field, RuntimeError, Selection, z } from 'minato' @@ -30,6 +30,10 @@ interface ColumnInfo { interface IndexInfo { INDEX_NAME: string COLUMN_NAME: string + SEQ_IN_INDEX: string + COLLATION: 'A' | 'D' + NULLABLE: string + NON_UNIQUE: string } interface QueryTask { @@ -506,6 +510,30 @@ INSERT INTO mtt VALUES(json_extract(j, concat('$[', i, ']'))); SET i=i+1; END WH }) } + async getIndexes(table: string): Promise> { + const indexes = await this.queue([ + `SELECT *`, + `FROM information_schema.statistics`, + `WHERE TABLE_SCHEMA = ? && TABLE_NAME = ?`, + ].join(' '), [this.config.database, table]) + const result = {} + for (const { INDEX_NAME: name, COLUMN_NAME: key, COLLATION: direction, NON_UNIQUE: unique } of indexes) { + if (!result[name]) result[name] = { unique: unique !== '1', keys: {} } + result[name].keys[key] = direction === 'A' ? 'asc' : direction === 'D' ? 'desc' : direction + } + return result + } + + async createIndex(table: string, index: Driver.Index) { + const name = 'index:' + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') + const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') + await this.query(`ALTER TABLE ${escapeId(table)} ADD ${index.unique ? 'UNIQUE' : ''} INDEX ${escapeId(name)} (${keyFields})`).catch(noop) + } + + async dropIndex(table: string, name: string) { + await this.query(`DROP INDEX ${escapeId(name)} ON ${escapeId(table)}`) + } + private getTypeDef({ deftype: type, length, precision, scale }: Field) { const getIntegerType = (length = 4) => { if (length <= 1) return 'tinyint' diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts index 911094e3..dbafa61e 100644 --- a/packages/postgres/src/index.ts +++ b/packages/postgres/src/index.ts @@ -34,6 +34,14 @@ interface ConstraintInfo { nulls_distinct: string } +interface IndexInfo { + schemaname: string + tablename: string + indexname: string + tablespace: string + indexdef: string +} + interface TableInfo { table_catalog: string table_schema: string @@ -414,6 +422,41 @@ export class PostgresDriver extends Driver { return this.postgres.begin((conn) => callback(conn)) } + async getIndexes(table: string): Promise> { + const indexes = await this.queue(`SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = ${this.sql.escape(table)}`) + const result = {} + for (const { indexname: name, indexdef: sql } of indexes) { + result[name] = { + unique: sql.toUpperCase().startsWith('CREATE UNIQUE'), + keys: this._parseIndexDef(sql), + } + } + return result + } + + async createIndex(table: string, index: Driver.Index) { + const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') + const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') + await this.query(`CREATE ${index.unique ? 'UNIQUE' : ''} INDEX IF NOT EXISTS ${escapeId(name)} ON ${escapeId(table)} (${keyFields})`) + } + + async dropIndex(table: string, name: string) { + await this.query(`DROP INDEX ${escapeId(name)}`) + } + + _parseIndexDef(def: string) { + try { + const keys = {}, matches = def.match(/\((.*)\)/)! + matches[1].split(',').forEach((key) => { + const [name, direction] = key.trim().split(' ') + keys[name.startsWith('"') ? name.slice(1, -1).replace(/""/g, '"') : name] = direction?.toLowerCase() === 'desc' ? 'desc' : 'asc' + }) + return keys + } catch { + return {} + } + } + private getTypeDef(field: Field & { autoInc?: boolean }) { let { deftype: type, length, precision, scale, autoInc } = field switch (type) { diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 07c449fc..5cea00fc 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -40,7 +40,7 @@ function getTypeDef({ deftype: type }: Field) { } } -export interface SQLiteFieldInfo { +interface SQLiteFieldInfo { cid: number name: string type: string @@ -49,6 +49,13 @@ export interface SQLiteFieldInfo { pk: boolean } +interface SQLiteMasterInfo { + type: string + name: string + tbl_name: string + sql: string +} + export class SQLiteDriver extends Driver { static name = 'sqlite' @@ -420,6 +427,42 @@ export class SQLiteDriver extends Driver { ) }) } + + async getIndexes(table: string): Promise> { + const indexes = this._all(`SELECT type,name,tbl_name,sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?`, [table]) as SQLiteMasterInfo[] + const result = {} + for (const { name, sql } of indexes) { + result[name] = { + unique: !sql || sql.toUpperCase().startsWith('CREATE UNIQUE'), + keys: this._parseIndexDef(sql), + } + } + return result + } + + async createIndex(table: string, index: Driver.Index) { + const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') + const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') + await this._run(`create ${index.unique ? 'UNIQUE' : ''} index ${escapeId(name)} ON ${escapeId(table)} (${keyFields})`) + } + + async dropIndex(table: string, name: string) { + await this._run(`DROP INDEX ${escapeId(name)}`) + } + + _parseIndexDef(def: string) { + if (!def) return {} + try { + const keys = {}, matches = def.match(/\((.*)\)/)! + matches[1].split(',').forEach((key) => { + const [name, direction] = key.trim().split(' ') + keys[name.startsWith('`') ? name.slice(1, -1) : name] = direction?.toLowerCase() === 'desc' ? 'desc' : 'asc' + }) + return keys + } catch { + return {} + } + } } export namespace SQLiteDriver { diff --git a/packages/tests/src/update.ts b/packages/tests/src/update.ts index 5653f57d..f977a0d1 100644 --- a/packages/tests/src/update.ts +++ b/packages/tests/src/update.ts @@ -1,5 +1,5 @@ import { $, Database } from 'minato' -import { omit } from 'cosmokit' +import { deepEqual, omit } from 'cosmokit' import { expect } from 'chai' interface Bar { @@ -427,6 +427,28 @@ namespace OrmOperations { }) } + export const index = function Index(database: Database) { + it('basic support', async () => { + const index = { + unique: false, + keys: { + num: 'asc', + timestamp: 'asc', + }, + } as const + + await database.createIndex('temp2', index) + let indexes = await database.getIndexes('temp2') + let added = Object.entries(indexes).find(([, ind]) => deepEqual(ind, index)) + expect(added).to.not.be.undefined + + await database.dropIndex('temp2', added![0]) + indexes = await database.getIndexes('temp2') + added = Object.entries(indexes).find(([, ind]) => deepEqual(ind, index)) + expect(added).to.be.undefined + }) + } + export const drop = function Drop(database: Database) { it('make coverage happy', async () => { // @ts-expect-error From 971de800d7c17c7d35ae46d9521231b86cb50844 Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Sun, 14 Jul 2024 20:46:21 +0800 Subject: [PATCH 2/8] fix(mongo): init virtual in migratePrimary --- packages/mongo/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mongo/src/index.ts b/packages/mongo/src/index.ts index db28ad88..071c0c18 100644 --- a/packages/mongo/src/index.ts +++ b/packages/mongo/src/index.ts @@ -208,6 +208,7 @@ export class MongoDriver extends Driver { const [latest] = await coll.find().sort(this.getVirtualKey(table) ? '_id' : primary, -1).limit(1).toArray() await fields.updateOne(meta, { $set: { autoInc: latest ? +latest[this.getVirtualKey(table) ? '_id' : primary] : 0 }, + $setOnInsert: { virtual: !!this.getVirtualKey(table) }, }, { upsert: true }) } From c1cbb91e5422043fdc958b550610b203bba6d21a Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Tue, 30 Jul 2024 15:58:27 +0800 Subject: [PATCH 3/8] feat: impl --- packages/core/src/database.ts | 22 +++----- packages/core/src/driver.ts | 23 ++++++-- packages/core/src/model.ts | 31 ++++++++-- packages/memory/src/index.ts | 8 +-- packages/memory/tests/index.spec.ts | 3 + packages/mongo/src/index.ts | 26 +++++---- packages/mysql/src/index.ts | 13 +++-- packages/postgres/src/index.ts | 16 +++--- packages/sqlite/src/index.ts | 12 ++-- packages/tests/src/migration.ts | 88 +++++++++++++++++++++++++++++ packages/tests/src/relation.ts | 3 + packages/tests/src/update.ts | 51 +++++++++++++++-- 12 files changed, 232 insertions(+), 64 deletions(-) diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index bfe7548a..405595ad 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -129,6 +129,7 @@ export class Database extends Servi Object.values(fields).forEach(field => field?.transformers?.forEach(x => driver.define(x))) await driver.prepare(name) + await driver.prepareIndexes(name) } extend>(name: K, fields: Field.Extension, config: Partial>> = {}) { @@ -189,9 +190,12 @@ export class Database extends Servi } }) // use relation field as primary - if (Array.isArray(model.primary) && model.primary.every(key => model.fields[key]?.relation)) { - model.primary = deduplicate(model.primary.map(key => model.fields[key]!.relation!.fields).flat()) + if (Array.isArray(model.primary)) { + model.primary = deduplicate(model.primary.map(key => model.fields[key]!.relation?.fields || key).flat()) } + model.unique = model.unique.map(keys => typeof keys === 'string' ? model.fields[keys]!.relation?.fields || keys + : keys.map(key => model.fields[key]!.relation?.fields || key).flat()) + this.prepareTasks[name] = this.prepare(name) ;(this.ctx as Context).emit('model', name) } @@ -495,7 +499,7 @@ export class Database extends Servi return sel._action('remove').execute() } - async create>(table: K, data: Create): Promise + async create>(table: K, data: CreateMap): Promise async create>(table: K, data: any): Promise { const sel = this.select(table) @@ -597,18 +601,6 @@ export class Database extends Servi await Promise.all(Object.values(this.drivers).map(driver => driver.dropAll())) } - getIndexes>(table: K): Promise>> { - return this.getDriver(table).getIndexes(table) - } - - createIndex>(table: K, index: Driver.Index) { - return this.getDriver(table).createIndex(table, index) - } - - dropIndex>(table: K, name: string) { - return this.getDriver(table).dropIndex(table, name) - } - async stats() { await this.prepared() const stats: Driver.Stats = { size: 0, tables: {} } diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 5114d53c..2c385210 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -1,4 +1,4 @@ -import { Awaitable, defineProperty, Dict, mapValues, remove } from 'cosmokit' +import { Awaitable, deepEqual, defineProperty, Dict, mapValues, remove } from 'cosmokit' import { Context, Logger, Service } from 'cordis' import { Eval, Update } from './eval.ts' import { Direction, Modifier, Selection } from './selection.ts' @@ -35,9 +35,10 @@ export namespace Driver { removed?: number } - export interface Index { + export interface Index { + name?: string unique?: boolean - keys: { [P in keyof S ]?: 'asc' | 'desc' } + keys: { [P in K]?: 'asc' | 'desc' } } export interface Transformer { @@ -67,7 +68,7 @@ export abstract class Driver { abstract create(sel: Selection.Mutable, data: any): Promise abstract upsert(sel: Selection.Mutable, data: any[], keys: string[]): Promise abstract withTransaction(callback: (session?: any) => Promise): Promise - abstract getIndexes(table: string): Promise> + abstract getIndexes(table: string): Promise abstract createIndex(table: string, index: Driver.Index): Promise abstract dropIndex(table: string, name: string): Promise @@ -159,6 +160,20 @@ export abstract class Driver { } async _ensureSession() {} + + async prepareIndexes(table: string) { + const oldIndexes = await this.getIndexes(table) + const { indexes } = this.model(table) + for (const index of indexes) { + const oldIndex = oldIndexes.find(info => info.name === index.name) + if (!oldIndex) { + await this.createIndex(table, index) + } else if (!deepEqual(oldIndex, index)) { + await this.dropIndex(table, index.name!) + await this.createIndex(table, index) + } + } + } } export interface MigrationHooks { diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 3b4e11f2..6c82db6e 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -71,8 +71,8 @@ export namespace Relation { : typeof def.shared === 'string' ? { [def.shared]: def.shared } : Array.isArray(def.shared) ? Object.fromEntries(def.shared.map(x => [x, x])) : def.shared - const fields = def.fields ?? ((subprimary || model.name === relmodel.name || def.type === 'manyToOne' - || (def.type === 'oneToOne' && !makeArray(relmodel.primary).every(key => !relmodel.fields[key]?.nullable))) + const fields = def.fields ?? ((subprimary || def.type === 'manyToOne' + || (def.type === 'oneToOne' && (model.name === relmodel.name || !makeArray(relmodel.primary).every(key => !relmodel.fields[key]?.nullable)))) ? makeArray(relmodel.primary).map(x => `${key}.${x}`) : model.primary) const relation: Config = { type: def.type, @@ -247,6 +247,7 @@ export namespace Model { autoInc: boolean primary: MaybeArray unique: MaybeArray[] + indexes: (MaybeArray | Driver.Index)[] foreign: { [P in K]?: [string, string] } @@ -257,6 +258,7 @@ export interface Model extends Model.Config {} export class Model { declare ctx?: Context + declare indexes: Driver.Index>[] fields: Field.Config = {} migrations = new Map() @@ -266,16 +268,18 @@ export class Model { this.autoInc = false this.primary = 'id' as never this.unique = [] + this.indexes = [] this.foreign = {} } extend(fields: Field.Extension, config?: Partial): void extend(fields = {}, config: Partial = {}) { - const { primary, autoInc, unique = [] as [], foreign, callback } = config + const { primary, autoInc, unique = [], indexes = [], foreign, callback } = config this.primary = primary || this.primary this.autoInc = autoInc || this.autoInc unique.forEach(key => this.unique.includes(key) || this.unique.push(key)) + indexes.map(x => this.parseIndex(x)).forEach(index => (this.indexes.some((ind: Driver.Index) => ind.name === index.name)) || this.indexes.push(index)) Object.assign(this.foreign, foreign) if (callback) this.migrations.set(callback, Object.keys(fields)) @@ -292,10 +296,27 @@ export class Model { // check index this.checkIndex(this.primary) this.unique.forEach(index => this.checkIndex(index)) + this.indexes.forEach(index => this.checkIndex(index)) } - private checkIndex(index: MaybeArray) { - for (const key of makeArray(index)) { + private parseIndex(index: MaybeArray | Driver.Index): Driver.Index { + if (typeof index === 'string' || Array.isArray(index)) { + return { + name: `index:${this.name}:` + makeArray(index).join('+'), + unique: false, + keys: Object.fromEntries(makeArray(index).map(key => [key, 'asc'])), + } + } else { + return { + name: index.name ?? `index:${this.name}:` + Object.keys(index.keys).join('+'), + unique: index.unique ?? false, + keys: index.keys, + } + } + } + + private checkIndex(index: MaybeArray | Driver.Index) { + for (const key of typeof index === 'string' || Array.isArray(index) ? makeArray(index) : Object.keys(index.keys)) { if (!this.fields[key]) { throw new TypeError(`missing field definition for index key "${key}"`) } diff --git a/packages/memory/src/index.ts b/packages/memory/src/index.ts index 6f8aecc2..80d7f156 100644 --- a/packages/memory/src/index.ts +++ b/packages/memory/src/index.ts @@ -200,14 +200,14 @@ export class MemoryDriver extends Driver { }) } - async getIndexes(table: string): Promise> { - return this._indexes[table] ?? {} + async getIndexes(table: string) { + return Object.values(this._indexes[table] ?? {}) } async createIndex(table: string, index: Driver.Index) { - const name = 'index:' + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction}`).join('+') + const name = index.name ?? 'index:' + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction}`).join('+') this._indexes[table] ??= {} - this._indexes[table][name] = { unique: false, ...index } + this._indexes[table][name] = { name, unique: false, ...index } } async dropIndex(table: string, name: string) { diff --git a/packages/memory/tests/index.spec.ts b/packages/memory/tests/index.spec.ts index 0049ea6f..3e88880e 100644 --- a/packages/memory/tests/index.spec.ts +++ b/packages/memory/tests/index.spec.ts @@ -16,6 +16,9 @@ describe('@minatojs/driver-memory', () => { test(database, { migration: false, + update: { + index: false, + }, model: { fields: { cast: false, diff --git a/packages/mongo/src/index.ts b/packages/mongo/src/index.ts index f7dc0891..f0d991dd 100644 --- a/packages/mongo/src/index.ts +++ b/packages/mongo/src/index.ts @@ -105,9 +105,11 @@ export class MongoDriver extends Driver { // mongodb seems to not support $ne in partialFilterExpression // so we cannot simply use `{ $ne: null }` to filter out null values // below is a workaround for https://github.com/koishijs/koishi/issues/893 - partialFilterExpression: nullable ? undefined : Object.fromEntries(keys.map((key) => [key, { - $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], - }])), + ...(nullable || index > unique.length) ? {} : { + partialFilterExpression: Object.fromEntries(keys.map((key) => [key, { + $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], + }])), + }, }) }) @@ -493,25 +495,27 @@ export class MongoDriver extends Driver { } } - async getIndexes(table: string): Promise> { + async getIndexes(table: string) { const indexes = await this.db.collection(table).listIndexes().toArray() - return Object.fromEntries(indexes.map(({ name, key, unique }) => [name, { + return indexes.map(({ name, key, unique }) => ({ + name, unique: !!unique, keys: mapValues(key, value => value === 1 ? 'asc' : value === -1 ? 'desc' : value), - }])) + } as Driver.Index)) } async createIndex(table: string, index: Driver.Index) { - const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') const keys = mapValues(index.keys, (value) => value === 'asc' ? 1 : value === 'desc' ? -1 : isNullable(value) ? 1 : value) const { fields } = this.model(table) const nullable = Object.keys(index.keys).every(key => fields[key]?.nullable) await this.db.collection(table).createIndex(keys, { - name, + name: index.name, unique: !!index.unique, - partialFilterExpression: nullable ? undefined : Object.fromEntries(Object.keys(index.keys).map((key) => [key, { - $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], - }])), + ...nullable ? {} : { + partialFilterExpression: Object.fromEntries(Object.keys(index.keys).map((key) => [key, { + $type: [BSONType.date, BSONType.int, BSONType.long, BSONType.string, BSONType.objectId], + }])), + }, }) } diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index fb05ea28..d8c04380 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -511,24 +511,25 @@ INSERT INTO mtt VALUES(json_extract(j, concat('$[', i, ']'))); SET i=i+1; END WH }) } - async getIndexes(table: string): Promise> { + async getIndexes(table: string) { const indexes = await this.queue([ `SELECT *`, `FROM information_schema.statistics`, `WHERE TABLE_SCHEMA = ? && TABLE_NAME = ?`, ].join(' '), [this.config.database, table]) - const result = {} + const result: Dict = {} for (const { INDEX_NAME: name, COLUMN_NAME: key, COLLATION: direction, NON_UNIQUE: unique } of indexes) { - if (!result[name]) result[name] = { unique: unique !== '1', keys: {} } + if (!result[name]) result[name] = { name, unique: unique !== '1', keys: {} } result[name].keys[key] = direction === 'A' ? 'asc' : direction === 'D' ? 'desc' : direction } - return result + return Object.values(result) } async createIndex(table: string, index: Driver.Index) { - const name = 'index:' + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') - await this.query(`ALTER TABLE ${escapeId(table)} ADD ${index.unique ? 'UNIQUE' : ''} INDEX ${escapeId(name)} (${keyFields})`).catch(noop) + await this.query( + `ALTER TABLE ${escapeId(table)} ADD ${index.unique ? 'UNIQUE' : ''} INDEX ${index.name ? escapeId(index.name) : ''} (${keyFields})`, + ).catch(noop) } async dropIndex(table: string, name: string) { diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts index 4af82ec0..d9f67508 100644 --- a/packages/postgres/src/index.ts +++ b/packages/postgres/src/index.ts @@ -423,22 +423,24 @@ export class PostgresDriver extends Driver { return this.postgres.begin((conn) => callback(conn)) } - async getIndexes(table: string): Promise> { + async getIndexes(table: string) { const indexes = await this.queue(`SELECT * FROM pg_indexes WHERE schemaname = 'public' AND tablename = ${this.sql.escape(table)}`) - const result = {} + const result: Driver.Index[] = [] for (const { indexname: name, indexdef: sql } of indexes) { - result[name] = { + result.push({ + name, unique: sql.toUpperCase().startsWith('CREATE UNIQUE'), keys: this._parseIndexDef(sql), - } + }) } - return result + return Object.values(result) } async createIndex(table: string, index: Driver.Index) { - const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') - await this.query(`CREATE ${index.unique ? 'UNIQUE' : ''} INDEX IF NOT EXISTS ${escapeId(name)} ON ${escapeId(table)} (${keyFields})`) + await this.query( + `CREATE ${index.unique ? 'UNIQUE' : ''} INDEX ${index.name ? `IF NOT EXISTS ${escapeId(index.name)}` : ''} ON ${escapeId(table)} (${keyFields})`, + ) } async dropIndex(table: string, name: string) { diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 7fc5c799..fb0325be 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -434,22 +434,22 @@ export class SQLiteDriver extends Driver { }) } - async getIndexes(table: string): Promise> { + async getIndexes(table: string) { const indexes = this._all(`SELECT type,name,tbl_name,sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?`, [table]) as SQLiteMasterInfo[] - const result = {} + const result: Driver.Index[] = [] for (const { name, sql } of indexes) { - result[name] = { + result.push({ + name, unique: !sql || sql.toUpperCase().startsWith('CREATE UNIQUE'), keys: this._parseIndexDef(sql), - } + }) } return result } async createIndex(table: string, index: Driver.Index) { - const name = `index:${table}:` + Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') - await this._run(`create ${index.unique ? 'UNIQUE' : ''} index ${escapeId(name)} ON ${escapeId(table)} (${keyFields})`) + await this._run(`create ${index.unique ? 'UNIQUE' : ''} index ${index.name ? escapeId(index.name) : ''} ON ${escapeId(table)} (${keyFields})`) } async dropIndex(table: string, name: string) { diff --git a/packages/tests/src/migration.ts b/packages/tests/src/migration.ts index 3a1ac834..2e10bef3 100644 --- a/packages/tests/src/migration.ts +++ b/packages/tests/src/migration.ts @@ -1,5 +1,6 @@ import { Database } from 'minato' import { expect } from 'chai' +import { deepEqual, noop, omit } from 'cosmokit' interface Qux { id: number @@ -21,6 +22,10 @@ interface Tables { } function MigrationTests(database: Database) { + beforeEach(async () => { + await database.drop('qux').catch(noop) + }) + it('alter field', async () => { Reflect.deleteProperty(database.tables, 'qux') @@ -149,6 +154,89 @@ function MigrationTests(database: Database) { { id: 2, text: 'bar', obj: {} }, ]) }) + + it('indexes', async () => { + const driver = Object.values(database.drivers)[0] + Reflect.deleteProperty(database.tables, 'qux') + + database.extend('qux', { + id: 'unsigned', + number: 'unsigned', + }) + + await database.upsert('qux', [ + { id: 1, number: 1 }, + { id: 2, number: 2 }, + ]) + + await expect(database.get('qux', {})).to.eventually.have.deep.members([ + { id: 1, number: 1 }, + { id: 2, number: 2 }, + ]) + + database.extend('qux', { + id: 'unsigned', + number: 'unsigned', + }, { + indexes: ['number'], + }) + + await expect(database.get('qux', {})).to.eventually.have.deep.members([ + { id: 1, number: 1 }, + { id: 2, number: 2 }, + ]) + + let indexes = await driver.getIndexes('qux') + expect(indexes.find(ind => deepEqual(omit(ind, ['name']), { + unique: false, + keys: { + number: 'asc', + }, + }))).to.not.be.undefined + + Reflect.deleteProperty(database.tables, 'qux') + + database.extend('qux', { + id: 'unsigned', + value: { + type: 'unsigned', + legacy: ['number'], + }, + }, { + indexes: ['value'], + }) + + await expect(database.get('qux', {})).to.eventually.have.deep.members([ + { id: 1, value: 1 }, + { id: 2, value: 2 }, + ]) + + indexes = await driver.getIndexes('qux') + expect(indexes.find(ind => deepEqual(omit(ind, ['name']), { + unique: false, + keys: { + value: 'asc', + }, + }))).to.not.be.undefined + + database.extend('qux', {}, { + indexes: [['id', 'value']], + }) + + await expect(database.get('qux', {})).to.eventually.have.deep.members([ + { id: 1, value: 1 }, + { id: 2, value: 2 }, + ]) + + indexes = await driver.getIndexes('qux') + expect(indexes.find(ind => deepEqual(omit(ind, ['name']), { + unique: false, + keys: { + id: 'asc', + value: 'asc', + }, + }))).to.not.be.undefined + }) } export default MigrationTests diff --git a/packages/tests/src/relation.ts b/packages/tests/src/relation.ts index 347ce702..09723fe1 100644 --- a/packages/tests/src/relation.ts +++ b/packages/tests/src/relation.ts @@ -9,6 +9,7 @@ interface User { posts?: Post[] successor?: Record & { id: number } predecessor?: Record & { id: number } + friends?: Record & { id: number } } interface Profile { @@ -94,6 +95,8 @@ function RelationTests(database: Database) { table: 'user', target: 'profile', }, + }, { + unique: [['user', 'name']] }) database.extend('post', { diff --git a/packages/tests/src/update.ts b/packages/tests/src/update.ts index f977a0d1..bbe521b1 100644 --- a/packages/tests/src/update.ts +++ b/packages/tests/src/update.ts @@ -48,6 +48,7 @@ function OrmOperations(database: Database) { }, }, { autoInc: true, + indexes: ['text'], }) database.extend('temp3', { @@ -429,6 +430,7 @@ namespace OrmOperations { export const index = function Index(database: Database) { it('basic support', async () => { + const driver = Object.values(database.drivers)[0] const index = { unique: false, keys: { @@ -437,16 +439,53 @@ namespace OrmOperations { }, } as const - await database.createIndex('temp2', index) - let indexes = await database.getIndexes('temp2') - let added = Object.entries(indexes).find(([, ind]) => deepEqual(ind, index)) + await driver.createIndex('temp2', index) + let indexes = await driver.getIndexes('temp2') + console.log(indexes) + let added = indexes.find(ind => deepEqual(omit(ind, ['name']), index)) expect(added).to.not.be.undefined - await database.dropIndex('temp2', added![0]) - indexes = await database.getIndexes('temp2') - added = Object.entries(indexes).find(([, ind]) => deepEqual(ind, index)) + await driver.dropIndex('temp2', added!.name!) + indexes = await driver.getIndexes('temp2') + added = indexes.find(ind => deepEqual(omit(ind, ['name']), index)) expect(added).to.be.undefined }) + + it('named', async () => { + const driver = Object.values(database.drivers)[0] + const index = { + name: 'index_unique:temp2:num_asc+timestamp_asc', + unique: true, + keys: { + num: 'asc', + timestamp: 'asc', + }, + } as const + + await driver.createIndex('temp2', index) + let indexes = await driver.getIndexes('temp2') + let added = indexes.find(ind => deepEqual(ind, index)) + expect(added).to.not.be.undefined + + await driver.dropIndex('temp2', added!.name!) + indexes = await driver.getIndexes('temp2') + added = indexes.find(ind => deepEqual(ind, index)) + expect(added).to.be.undefined + }) + + it('extend', async () => { + const driver = Object.values(database.drivers)[0] + const index = { + unique: false, + keys: { + text: 'asc', + }, + } as const + + const indexes = await driver.getIndexes('temp2') + const existed = indexes.find(ind => deepEqual(omit(ind, ['name']), index)) + expect(existed).to.not.be.undefined + }) } export const drop = function Drop(database: Database) { From 8f21639eb69b2317c22f443c0ecf4800a50eb64c Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Tue, 30 Jul 2024 16:06:22 +0800 Subject: [PATCH 4/8] fix: prev --- packages/mysql/src/index.ts | 4 ++-- packages/sqlite/src/index.ts | 3 ++- packages/tests/src/migration.ts | 7 ++++++- packages/tests/src/relation.ts | 1 - packages/tests/src/update.ts | 1 - 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index d8c04380..073b54f0 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -1,4 +1,4 @@ -import { Binary, Dict, difference, isNullable, makeArray, noop, pick } from 'cosmokit' +import { Binary, Dict, difference, isNullable, makeArray, pick } from 'cosmokit' import { createPool, format } from '@vlasky/mysql' import type { OkPacket, Pool, PoolConfig, PoolConnection } from 'mysql' import { Driver, Eval, executeUpdate, Field, RuntimeError, Selection, z } from 'minato' @@ -529,7 +529,7 @@ INSERT INTO mtt VALUES(json_extract(j, concat('$[', i, ']'))); SET i=i+1; END WH const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') await this.query( `ALTER TABLE ${escapeId(table)} ADD ${index.unique ? 'UNIQUE' : ''} INDEX ${index.name ? escapeId(index.name) : ''} (${keyFields})`, - ).catch(noop) + ) } async dropIndex(table: string, name: string) { diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index fb0325be..83d41364 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -448,8 +448,9 @@ export class SQLiteDriver extends Driver { } async createIndex(table: string, index: Driver.Index) { + const name = index.name ?? Object.entries(index.keys).map(([key, direction]) => `${key}_${direction ?? 'asc'}`).join('+') const keyFields = Object.entries(index.keys).map(([key, direction]) => `${escapeId(key)} ${direction ?? 'asc'}`).join(', ') - await this._run(`create ${index.unique ? 'UNIQUE' : ''} index ${index.name ? escapeId(index.name) : ''} ON ${escapeId(table)} (${keyFields})`) + await this._run(`create ${index.unique ? 'UNIQUE' : ''} index ${escapeId(name)} ON ${escapeId(table)} (${keyFields})`) } async dropIndex(table: string, name: string) { diff --git a/packages/tests/src/migration.ts b/packages/tests/src/migration.ts index 2e10bef3..2eb02bdc 100644 --- a/packages/tests/src/migration.ts +++ b/packages/tests/src/migration.ts @@ -220,7 +220,12 @@ function MigrationTests(database: Database) { }))).to.not.be.undefined database.extend('qux', {}, { - indexes: [['id', 'value']], + indexes: [{ + keys: { + id: 'asc', + value: 'asc', + } + }], }) await expect(database.get('qux', {})).to.eventually.have.deep.members([ diff --git a/packages/tests/src/relation.ts b/packages/tests/src/relation.ts index 09723fe1..f2f7556e 100644 --- a/packages/tests/src/relation.ts +++ b/packages/tests/src/relation.ts @@ -9,7 +9,6 @@ interface User { posts?: Post[] successor?: Record & { id: number } predecessor?: Record & { id: number } - friends?: Record & { id: number } } interface Profile { diff --git a/packages/tests/src/update.ts b/packages/tests/src/update.ts index bbe521b1..bd00182f 100644 --- a/packages/tests/src/update.ts +++ b/packages/tests/src/update.ts @@ -441,7 +441,6 @@ namespace OrmOperations { await driver.createIndex('temp2', index) let indexes = await driver.getIndexes('temp2') - console.log(indexes) let added = indexes.find(ind => deepEqual(omit(ind, ['name']), index)) expect(added).to.not.be.undefined From 4956ee0ff1ffddc63f13f913293f514b04a920ed Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Tue, 30 Jul 2024 17:41:13 +0800 Subject: [PATCH 5/8] fix: migrate named index, fix default values of postgres field --- packages/core/src/model.ts | 4 ++-- packages/mysql/src/index.ts | 4 ++-- packages/postgres/src/index.ts | 4 ++-- packages/tests/src/migration.ts | 31 ++++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 6c82db6e..3df8c1f7 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -1,4 +1,4 @@ -import { clone, filterKeys, isNullable, makeArray, mapValues, MaybeArray } from 'cosmokit' +import { clone, deepEqual, filterKeys, isNullable, makeArray, mapValues, MaybeArray } from 'cosmokit' import { Context } from 'cordis' import { Eval, Update } from './eval.ts' import { DeepPartial, FlatKeys, Flatten, isFlat, Keys, Row, unravel } from './utils.ts' @@ -279,7 +279,7 @@ export class Model { this.primary = primary || this.primary this.autoInc = autoInc || this.autoInc unique.forEach(key => this.unique.includes(key) || this.unique.push(key)) - indexes.map(x => this.parseIndex(x)).forEach(index => (this.indexes.some((ind: Driver.Index) => ind.name === index.name)) || this.indexes.push(index)) + indexes.map(x => this.parseIndex(x)).forEach(index => (this.indexes.some(ind => deepEqual(ind, index))) || this.indexes.push(index)) Object.assign(this.foreign, foreign) if (callback) this.migrations.set(callback, Object.keys(fields)) diff --git a/packages/mysql/src/index.ts b/packages/mysql/src/index.ts index 073b54f0..75ddc333 100644 --- a/packages/mysql/src/index.ts +++ b/packages/mysql/src/index.ts @@ -185,11 +185,11 @@ export class MySQLDriver extends Driver { def += (nullable ? ' ' : ' not ') + 'null' } // blob, text, geometry or json columns cannot have default values - if (initial && !typedef.startsWith('text') && !typedef.endsWith('blob')) { + if (!isNullable(initial) && !typedef.startsWith('text') && !typedef.endsWith('blob')) { def += ' default ' + this.sql.escape(initial, fields[key]) } - if (!column && initial && (typedef.startsWith('text') || typedef.endsWith('blob'))) { + if (!column && !isNullable(initial) && (typedef.startsWith('text') || typedef.endsWith('blob'))) { alterInit[key] = this.sql.escape(initial, fields[key]) } } diff --git a/packages/postgres/src/index.ts b/packages/postgres/src/index.ts index d9f67508..47aabda2 100644 --- a/packages/postgres/src/index.ts +++ b/packages/postgres/src/index.ts @@ -196,12 +196,12 @@ export class PostgresDriver extends Driver { if (!column) { create.push(`${escapeId(key)} ${typedef} ${makeArray(primary).includes(key) || !nullable ? 'not null' : 'null'}` - + (initial ? ' DEFAULT ' + this.sql.escape(initial, fields[key]) : '')) + + (!primary.includes(key) && !isNullable(initial) ? ' DEFAULT ' + this.sql.escape(initial, fields[key]) : '')) } else if (shouldUpdate) { if (column.column_name !== key) rename.push(`RENAME ${escapeId(column.column_name)} TO ${escapeId(key)}`) update.push(`ALTER ${escapeId(key)} TYPE ${typedef}`) update.push(`ALTER ${escapeId(key)} ${makeArray(primary).includes(key) || !nullable ? 'SET' : 'DROP'} NOT NULL`) - if (initial) update.push(`ALTER ${escapeId(key)} SET DEFAULT ${this.sql.escape(initial, fields[key])}`) + if (!isNullable(initial)) update.push(`ALTER ${escapeId(key)} SET DEFAULT ${this.sql.escape(initial, fields[key])}`) } } diff --git a/packages/tests/src/migration.ts b/packages/tests/src/migration.ts index 2eb02bdc..80bfd670 100644 --- a/packages/tests/src/migration.ts +++ b/packages/tests/src/migration.ts @@ -221,6 +221,7 @@ function MigrationTests(database: Database) { database.extend('qux', {}, { indexes: [{ + name: 'named-index', keys: { id: 'asc', value: 'asc', @@ -234,13 +235,41 @@ function MigrationTests(database: Database) { ]) indexes = await driver.getIndexes('qux') - expect(indexes.find(ind => deepEqual(omit(ind, ['name']), { + expect(indexes.find(ind => deepEqual(ind, { + name: 'named-index', unique: false, keys: { id: 'asc', value: 'asc', }, }))).to.not.be.undefined + + database.extend('qux', { + text: 'string', + }, { + indexes: [{ + name: 'named-index', + keys: { + text: 'asc', + value: 'asc', + } + }], + }) + + await expect(database.get('qux', {})).to.eventually.have.deep.members([ + { id: 1, value: 1, text: '' }, + { id: 2, value: 2, text: '' }, + ]) + + indexes = await driver.getIndexes('qux') + expect(indexes.find(ind => deepEqual(ind, { + name: 'named-index', + unique: false, + keys: { + text: 'asc', + value: 'asc', + }, + }))).to.not.be.undefined }) } From d8511c6e6471e35e79e6ea7103473adb4ce658af Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Fri, 2 Aug 2024 03:51:08 +0800 Subject: [PATCH 6/8] chore(core): create argument typing --- packages/core/src/database.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 405595ad..b323dc06 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -44,28 +44,28 @@ export namespace Join2 { export type Predicate> = (args: Parameters) => Eval.Expr } -type CreateMap = { [K in keyof T]?: Create } - -export type Create = +type CreateUnit = | T extends Values ? T - : T extends (infer I extends Values)[] ? CreateMap[] | + : T extends (infer I extends Values)[] ? Create[] | { $literal?: DeepPartial - $create?: MaybeArray> - $upsert?: MaybeArray> + $create?: MaybeArray> + $upsert?: MaybeArray> $connect?: Query.Expr> } - : T extends Values ? CreateMap | + : T extends Values ? Create | { $literal?: DeepPartial - $create?: CreateMap - $upsert?: CreateMap + $create?: Create + $upsert?: Create $connect?: Query.Expr> } : T extends (infer U)[] ? DeepPartial[] - : T extends object ? CreateMap + : T extends object ? Create : T +export type Create = { [K in keyof T]?: CreateUnit } + function mergeQuery(base: Query.FieldExpr, query: Query.Expr> | ((row: Row) => Query.Expr>)): Selection.Callback { if (typeof query === 'function') { return (row: any) => { @@ -499,7 +499,7 @@ export class Database extends Servi return sel._action('remove').execute() } - async create>(table: K, data: CreateMap): Promise + async create>(table: K, data: Create): Promise async create>(table: K, data: any): Promise { const sel = this.select(table) From f6f85298d8aaa665a8d0975929cfb7bb1d75cde6 Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Sun, 11 Aug 2024 20:52:51 +0800 Subject: [PATCH 7/8] feat: emphasis indexdef type --- packages/core/src/driver.ts | 7 +++++-- packages/core/src/model.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 2c385210..838ef8c7 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -35,12 +35,15 @@ export namespace Driver { removed?: number } - export interface Index { + export interface IndexDef { name?: string - unique?: boolean keys: { [P in K]?: 'asc' | 'desc' } } + export interface Index extends IndexDef { + unique?: boolean + } + export interface Transformer { types: Field.Type[] dump: (value: S | null) => T | null | void diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 3df8c1f7..4f0a7b62 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -247,7 +247,7 @@ export namespace Model { autoInc: boolean primary: MaybeArray unique: MaybeArray[] - indexes: (MaybeArray | Driver.Index)[] + indexes: (MaybeArray | Driver.IndexDef)[] foreign: { [P in K]?: [string, string] } From 03b6b3b278085e16089a42a66d7e70a72a631c5e Mon Sep 17 00:00:00 2001 From: Hieuzest Date: Tue, 13 Aug 2024 17:13:39 +0800 Subject: [PATCH 8/8] fix(sqlite): decode primary to number --- packages/sqlite/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 83d41364..df83e5b7 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -221,7 +221,7 @@ export class SQLiteDriver extends Driver { }) this.define({ - types: Field.number as any, + types: ['primary', ...Field.number as any], dump: value => value, load: value => isNullable(value) ? value : Number(value), })