Skip to content

Commit

Permalink
fix(minato): fix relation issues (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hieuzest authored Jun 17, 2024
1 parent 08540c0 commit 60e474f
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 147 deletions.
169 changes: 85 additions & 84 deletions packages/core/src/database.ts

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/core/src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Direction, Modifier, Selection } from './selection.ts'
import { Field, Model, Relation } from './model.ts'
import { Database } from './database.ts'
import { Type } from './type.ts'
import { FlatKeys } from './utils.ts'
import { Keys, Values } from './utils.ts'

export namespace Driver {
export interface Stats {
Expand All @@ -18,14 +18,14 @@ export namespace Driver {
size: number
}

export type Cursor<T = any, S = any, K extends FlatKeys<T> = any> = K[] | CursorOptions<T, S, K>
export type Cursor<K extends string = string, S = any, T extends Keys<S> = any> = K[] | CursorOptions<K, S, T>

export interface CursorOptions<T = any, S = any, K extends FlatKeys<T> = any> {
export interface CursorOptions<K extends string = string, S = any, T extends Keys<S> = any> {
limit?: number
offset?: number
fields?: K[]
sort?: Dict<Direction, K>
include?: Relation.Include<T, S>
include?: Relation.Include<S[T], Values<S>>
}

export interface WriteResult {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export namespace Eval {

ignoreNull<T, A extends boolean>(value: Eval.Expr<T, A>): Eval.Expr<T, A>
select(...args: Any[]): Expr<any[], false>
query<T extends object>(row: Row<T>, query: Query.Expr<T>): Expr<boolean, false>
query<T extends object>(row: Row<T>, query: Query.Expr<T>, expr?: Term<boolean>): Expr<boolean, false>

// univeral
if<T extends Comparable, A extends boolean>(cond: Any<A>, vThen: Term<T, A>, vElse: Term<T, A>): Expr<T, A>
Expand Down Expand Up @@ -194,7 +194,7 @@ operators.$switch = (args, data) => {

Eval.ignoreNull = (expr) => (expr[Type.kType]!.ignoreNull = true, expr)
Eval.select = multary('select', (args, table) => args.map(arg => executeEval(table, arg)), Type.Array())
Eval.query = (row, query) => ({ $expr: true, ...query }) as any
Eval.query = (row, query, expr = true) => ({ $expr: expr, ...query }) as any

// univeral
Eval.if = multary('if', ([cond, vThen, vElse], data) => executeEval(data, cond) ? executeEval(data, vThen)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export namespace Model {
export interface Model extends Model.Config {}

export class Model<S = any> {
ctx?: Context
declare ctx?: Context
fields: Field.Config<S> = {}
migrations = new Map<Model.Migration, string[]>()

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export class Selection<S = any> extends Executable<S, S[]> {
}

execute(): Promise<S[]>
execute<K extends FlatKeys<S> = any>(cursor?: Driver.Cursor<S, any, K>): Promise<FlatPick<S, K>[]>
execute<K extends FlatKeys<S> = any>(cursor?: Driver.Cursor<K>): Promise<FlatPick<S, K>[]>
execute<T>(callback: Selection.Callback<S, T, true>): Promise<T>
async execute(cursor?: any) {
if (typeof cursor === 'function') {
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ export function flatten(source: object, prefix = '', ignore: (value: any) => boo
return result
}

export function getCell(row: any, key: any): any {
if (key in row) return row[key]
return key.split('.').reduce((r, k) => r === undefined ? undefined : r[k], row)
export function getCell(row: any, path: any): any {
if (path in row) return row[path]
if (path.includes('.')) {
const index = path.indexOf('.')
return getCell(row[path.slice(0, index)] ?? {}, path.slice(index + 1))
} else {
return row[path]
}
}

export function isEmpty(value: any) {
Expand Down
2 changes: 1 addition & 1 deletion packages/sql-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export class Builder {
const flattenQuery = isFlat(query[key]) ? { [key]: query[key] } : flatten(query[key], `${key}.`)
for (const key in flattenQuery) {
const model = this.state.tables![this.state.table!] ?? Object.values(this.state.tables!)[0]
const expr = Eval('', [Object.keys(this.state.tables!)[0], key], model.getType(key)!)
const expr = Eval('', [this.state.table ?? Object.keys(this.state.tables!)[0], key], model.getType(key)!)
conditions.push(this.parseFieldQuery(this.parseEval(expr), flattenQuery[key]))
}
}
Expand Down
27 changes: 11 additions & 16 deletions packages/sqlite/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Binary, deepEqual, Dict, difference, isNullable, makeArray, mapValues } from 'cosmokit'
import { Driver, Eval, executeUpdate, Field, hasSubquery, isEvalExpr, Selection, z } from 'minato'
import { Driver, Eval, executeUpdate, Field, getCell, hasSubquery, isEvalExpr, Selection, z } from 'minato'
import { escapeId } from '@minatojs/sql-utils'
import { resolve } from 'node:path'
import { readFile, writeFile } from 'node:fs/promises'
Expand All @@ -10,15 +10,6 @@ import zhCN from './locales/zh-CN.yml'
import { SQLiteBuilder } from './builder'
import { pathToFileURL } from 'node:url'

function getValue(obj: any, path: string) {
if (path.includes('.')) {
const index = path.indexOf('.')
return getValue(obj[path.slice(0, index)] ?? {}, path.slice(index + 1))
} else {
return obj[path]
}
}

function getTypeDef({ deftype: type }: Field) {
switch (type) {
case 'primary':
Expand Down Expand Up @@ -349,16 +340,20 @@ export class SQLiteDriver extends Driver<SQLiteDriver.Config> {
return Object.keys(fields).find(field => field === key || key.startsWith(field + '.'))!
}))]
const primaryFields = makeArray(primary)
if ((Object.keys(query).length === 1 && query.$expr) || hasSubquery(sel.query) || Object.values(update).some(x => hasSubquery(x))) {
if (query.$expr || hasSubquery(sel.query) || Object.values(update).some(x => hasSubquery(x))) {
const sel2 = this.database.select(table as never, query)
sel2.tables[sel.ref] = sel2.table[sel2.ref]
delete sel2.table[sel2.ref]
sel2.tables[sel.ref] = sel2.tables[sel2.ref]
delete sel2.tables[sel2.ref]
sel2.ref = sel.ref
const project = mapValues(update as any, (value, key) => () => (isEvalExpr(value) ? value : Eval.literal(value, model.getType(key))))
const rawUpsert = await sel2.project({ ...project, ...Object.fromEntries(primaryFields.map(x => [x, x])) } as any).execute()
const rawUpsert = await sel2.project({
...project,
// do not touch sel2.row since it is not patched
...Object.fromEntries(primaryFields.map(x => [x, () => Eval('', [sel.ref, x], sel2.model.getType(x)!)])),
}).execute()
const upsert = rawUpsert.map(row => ({
...mapValues(update, (_, key) => getValue(row, key)),
...Object.fromEntries(primaryFields.map(x => [x, getValue(row, x)])),
...mapValues(update, (_, key) => getCell(row, key)),
...Object.fromEntries(primaryFields.map(x => [x, getCell(row, x)])),
}))
return this.database.upsert(table, upsert)
} else {
Expand Down
23 changes: 7 additions & 16 deletions packages/tests/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mapValues, isNullable, deduplicate, omit } from 'cosmokit'
import { $, Database, Field, Type, unravel } from 'minato'
import { $, Database, Field, getCell, Type, unravel } from 'minato'
import { expect } from 'chai'

interface DType {
Expand Down Expand Up @@ -265,15 +265,6 @@ function ModelOperations(database: Database<Tables, Types>) {
}, { autoInc: true })
}

function getValue(obj: any, path: string) {
if (path.includes('.')) {
const index = path.indexOf('.')
return getValue(obj[path.slice(0, index)] ?? {}, path.slice(index + 1))
} else {
return obj[path]
}
}

namespace ModelOperations {
const magicBorn = new Date('1970/08/17')

Expand Down Expand Up @@ -450,7 +441,7 @@ namespace ModelOperations {
it('$.array encoding', async () => {
const table = await setup(database, 'dtypes', dtypeTable)
await Promise.all(Object.keys(database.tables['dtypes'].fields).map(
key => expect(database.eval('dtypes', row => $.array(row[key]))).to.eventually.have.deep.members(table.map(x => getValue(x, key)))
key => expect(database.eval('dtypes', row => $.array(row[key]))).to.eventually.have.deep.members(table.map(x => getCell(x, key)))
))
})

Expand All @@ -462,7 +453,7 @@ namespace ModelOperations {
x: row => database.select('dtypes').evaluate(key as any)
})
.execute()
).to.eventually.have.shape([{ x: table.map(x => getValue(x, key)) }])
).to.eventually.have.shape([{ x: table.map(x => getCell(x, key)) }])
))
})

Expand Down Expand Up @@ -602,14 +593,14 @@ namespace ModelOperations {
aggregateNull && it('$.array encoding', async () => {
const table = await setup(database, 'dobjects', dobjectTable)
await Promise.all(Object.keys(database.tables['dobjects'].fields).map(
key => expect(database.eval('dobjects', row => $.array(row[key]))).to.eventually.have.deep.members(table.map(x => getValue(x, key)))
key => expect(database.eval('dobjects', row => $.array(row[key]))).to.eventually.have.deep.members(table.map(x => getCell(x, key)))
))
})

it('$.array encoding boxed', async () => {
const table = await setup(database, 'dobjects', dobjectTable)
await Promise.all(Object.keys(database.tables['dobjects'].fields).map(
key => expect(database.eval('dobjects', row => $.array($.object({ x: row[key] })))).to.eventually.have.deep.members(table.map(x => ({ x: getValue(x, key) })))
key => expect(database.eval('dobjects', row => $.array($.object({ x: row[key] })))).to.eventually.have.deep.members(table.map(x => ({ x: getCell(x, key) })))
))
})

Expand All @@ -621,7 +612,7 @@ namespace ModelOperations {
x: row => database.select('dobjects').evaluate(key as any)
})
.execute()
).to.eventually.have.shape([{ x: table.map(x => getValue(x, key)) }])
).to.eventually.have.shape([{ x: table.map(x => getCell(x, key)) }])
))
})

Expand All @@ -633,7 +624,7 @@ namespace ModelOperations {
...Object.keys(database.tables['dobjects'].fields).flatMap(k => k.split('.').reduce((arr, c) => arr.length ? [`${arr[0]}.${c}`, ...arr] : [c], [])),
])
await Promise.all(keys.map(key =>
expect(database.select('dobjects').project([key as any]).execute()).to.eventually.have.deep.members(table.map(row => unravel({ [key]: getValue(row, key) })))
expect(database.select('dobjects').project([key as any]).execute()).to.eventually.have.deep.members(table.map(row => unravel({ [key]: getCell(row, key) })))
))
})

Expand Down
9 changes: 9 additions & 0 deletions packages/tests/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,15 @@ namespace ObjectOperations {
y: database.select('object').where(row => $.lt(row.meta.embed.b, 100)),
}).execute(row => $.sum(1))).to.eventually.deep.equal(4)
})

it('switch model in object query', async () => {
const table = await setup(database)
await expect(database.select('object', {
'meta.a': '666',
}).project({
t: 'meta',
}).execute()).to.eventually.have.deep.members([{ t: table[1].meta }])
})
}
}

Expand Down
Loading

0 comments on commit 60e474f

Please sign in to comment.