UNPKG

temporeest

Version:
105 lines (93 loc) 3.78 kB
import { BaseChunkIterable } from '../ChunkIterable.js'; import specAndOpsToQuery from './specAndOpsToQuery.js'; import { HoistedOperations } from './SQLExpression.js'; import { invariant } from '@strut/utils'; import { Context, IModel, SQLResolvedDB } from '@aphro/context-runtime-ts'; import { JunctionEdgeSpec, NodeSpec } from '@aphro/schema-api'; import { ModelFieldGetter } from '../Field.js'; import { SID_of } from '@strut/sid'; export default class SQLSourceChunkIterable<T extends IModel<Object>> extends BaseChunkIterable<T> { constructor( private ctx: Context, private spec: NodeSpec | JunctionEdgeSpec, private hoistedOperations: HoistedOperations, ) { super(); invariant(this.spec.storage.type === 'sql', 'SQL source used for non-SQL model!'); } // TODO: Should you even do direct load check here? // or should you just generate edge methods that check the cache and return a querified object if it is there? // also -- won't this be superseded by the query cache? async *[Symbol.asyncIterator](): AsyncIterator<readonly T[]> { // TODO: stronger types one day // e.g., exec should by parametrized and checked against T somehow. // Should probably allow a namespace too? // also... this is pretty generic and would apply to non-sql data sources too. // given the actual query execution happens in the resolver. // also -- should we chunk it at all? const resolvedDb = this.ctx.dbResolver .engine(this.spec.storage.engine) .db(this.spec.storage.db) as SQLResolvedDB; if (this.hoistedOperations.limit && this.hoistedOperations.limit.num < 1) { yield []; return; } // One may wonder how `directLoad` doesn't break follow-on expressions. // 1. all follow on expressions _must_ come after a modelLoad // 2. modelLoad checks the cache which can be checked wither either a `Model` or `Data` instance const directLoad = this.isDirectLoad(); if (directLoad !== null) { const cached = this.ctx.cache.get( directLoad, this.spec.storage.db, this.spec.storage.tablish, ); if (cached != null) { yield [cached]; return; } } const sql = specAndOpsToQuery(this.spec, this.hoistedOperations); yield await resolvedDb.read(sql); } /** * A direct load is when we are loading nodes directly by ID and performing no other operations. * These queries can be resolved directly from the key-value cache * * isDirectLoad optimization can likely happen in the planning layer. * Being here, each data source would have to implement similar logic. * @returns */ private isDirectLoad(): SID_of<any> | null { const spec = this.spec; if ( spec.type !== 'node' || this.hoistedOperations.filters?.length !== 1 || this.hoistedOperations.after != null || this.hoistedOperations.before != null || // if there is another hop then this isn't key-value cache resolvable this.hoistedOperations.hop != null || // Future: We could presumably also fulfill the ordering needs too this.hoistedOperations.orderBy != null ) { return null; } // select ids when provoding ids wouldn't make sense // Future: resolving a count could be done if cache has all ids if (this.hoistedOperations.what !== 'model') { return null; } const filter = this.hoistedOperations.filters[0]; if (!(filter.getter instanceof ModelFieldGetter)) { return null; } if (filter.getter.fieldName !== spec.primaryKey) { return null; } // future: maybe support `in` if (filter.predicate.type !== 'equal') { return null; } return filter.predicate.value as SID_of<any>; } }