import { LRUCache } from 'lru-cache'; import { MaybeCancelablePromise, CacheMode, LoggerFn } from '@cubejs-backend/shared'; import { CubeStoreDriver } from '@cubejs-backend/cubestore-driver'; import { BaseDriver, InlineTables, CacheDriverInterface, TableStructure } from '@cubejs-backend/base-driver'; import { QueryQueue, QueryQueueOptions } from './QueryQueue'; import { DriverFactory, DriverFactoryByDataSource } from './DriverFactory'; import { LambdaQuery, LoadPreAggregationResult, PreAggregationDescription } from './PreAggregations'; import type { PreAggregationLoadCache } from './PreAggregationLoadCache'; import { CacheAndQueryDriverType } from './QueryOrchestrator'; export declare const REFRESH_KEY_CACHE_TTL_SECONDS: number; export type CacheQueryResultOptions = { /** Produces the result instead of executing the query. */ fetchResult?: () => Promise; renewalThreshold?: number; renewalKey?: any; priority?: number; external?: boolean; requestId?: string; dataSource: string; waitForRenew?: boolean; forceNoCache?: boolean; useInMemory?: boolean; useCsvQuery?: boolean; lambdaTypes?: TableStructure; persistent?: boolean; primaryQuery?: boolean; renewCycle?: boolean; }; /** * Deliberately narrow: the cache key, the renewal key and the renewal threshold are derived inside * `cacheRefreshKeyResult`, so no caller can store an entry under a key it later looks up by another. */ export type RefreshKeyCacheOptions = Pick; /** * Everything needed to evaluate an `every` based refreshKey without touching a * database: `FLOOR((utcOffset + unixTimestamp - dayOffset) / interval)`. */ export type LocalRefreshKeyDescriptor = { interval: number; utcOffset: number; dayOffset: number; cron?: boolean; }; type QueryOptions = { external?: boolean; renewalThreshold?: number; updateWindowSeconds?: number; renewalThresholdOutsideUpdateWindow?: number; incremental?: boolean; localRefreshKey?: LocalRefreshKeyDescriptor; }; export type QueryWithParams = [ sql: string, params: string[], options?: QueryOptions ]; export type LoadRefreshKeyOptions = { requestId?: string; skipRefreshKeyWaitForRenew?: boolean; /** Inherited from the query the keys are refreshed for: a blocked request waits on them too */ priority?: number; dataSource: string; }; export type Query = { requestId?: string; dataSource?: string; preAggregations?: PreAggregationDescription[]; groupedPartitionPreAggregations?: PreAggregationDescription[][]; preAggregationsLoadCacheByDataSource?: Record; cacheMode?: CacheMode; compilerCacheFn?: (subKey: string[], cacheFn: () => T) => T; }; export type QueryBody = { dataSource?: string; persistent?: boolean; query?: string; values?: string[]; loadRefreshKeysOnly?: boolean; scheduledRefresh?: boolean; cacheMode?: CacheMode; requestId?: string; external?: boolean; isJob?: boolean; forceNoCache?: boolean; preAggregations?: PreAggregationDescription[]; /** `null` streams rows with the SQL aliases as keys. */ aliasNameToMember?: { [alias: string]: string; } | null; preAggregationsLoadCacheByDataSource?: Record; queuePriority?: number; cacheKeyQueries?: QueryWithParams[] | { queries?: QueryWithParams[]; renewalThreshold?: number; }; expireSecs?: number; invalidate?: RefreshKeyIdentity | false; useCsvQuery?: boolean; lambdaTypes?: TableStructure; lambdaQueries?: Record; forceBuildPreAggregations?: boolean; orphanedTimeout?: number; metadata?: unknown; timezone?: string; context?: unknown; }; /** * Temp (partition/lambda) table definition. */ export type TempTable = LoadPreAggregationResult; /** * Pre-aggregation table (stored in the first element) to temp table * definition (stored in the second element) link. */ export type PreAggTableToTempTable = [ string, TempTable ]; export type PreAggTableToTempTableNames = [string, { targetTableName: string; }]; export type RefreshKeyIdentity = [sql: string, params: string[], external: boolean, dataSource: string]; export type CacheKeyItem = string | string[] | boolean | QueryWithParams | QueryWithParams[] | RefreshKeyIdentity | undefined; export type CacheKey = [ CacheKeyItem, CacheKeyItem ] | [ CacheKeyItem, CacheKeyItem, CacheKeyItem ] | [ CacheKeyItem, CacheKeyItem, CacheKeyItem, CacheKeyItem ]; export type CacheEntry = { time: number; result: any; renewalKey?: string; requestId?: string; }; export declare enum CacheAction { ServeCached = "serve-cached", RefreshSameRequest = "refresh-same-request", RefreshBackground = "refresh-background", WaitForRenew = "wait-for-renew" } type CacheOperationContext = { cacheKey: CacheKey; redisKey: string; renewalKey?: string; expiration: number; spanId: string; options: CacheQueryResultOptions; log: (message: string, extra?: Record) => void; }; export interface QueryCacheOptions { refreshKeyRenewalThreshold?: number; localRefreshKey?: boolean; externalQueueOptions?: any; externalDriverFactory?: DriverFactory; backgroundRenew?: Boolean; queueOptions?: (dataSource: string) => Promise<{ concurrency: number; continueWaitTimeout?: number; executionTimeout?: number; orphanedTimeout?: number; heartBeatInterval?: number; }>; cubeStoreDriverFactory?: () => Promise; continueWaitTimeout?: number; cacheAndQueueDriver: CacheAndQueryDriverType; maxInMemoryCacheEntries?: number; skipExternalCacheAndQueue?: boolean; } export declare class QueryCache { protected readonly cachePrefix: string; protected readonly driverFactory: DriverFactoryByDataSource; protected readonly logger: LoggerFn; readonly options: QueryCacheOptions; protected readonly cacheDriver: CacheDriverInterface; protected queue: { [dataSource: string]: QueryQueue; }; protected externalQueue: QueryQueue | null; protected memoryCache: LRUCache; protected static readonly IN_MEMORY_CACHE_DISABLE_PERIOD: number; protected readonly localRefreshKeyEnabled: boolean; constructor(cachePrefix: string, driverFactory: DriverFactoryByDataSource, logger: LoggerFn, options: QueryCacheOptions); /** Whether eligible local refresh keys have no shared cache entry to warm. */ usesUncachedLocalRefreshKey(): boolean; private localRefreshKeyFor; getCacheDriver(): CacheDriverInterface; getKey(catalog: string, key: string): string; /** * Generates from the `queryBody` the final `sql` query and push it to * the queue. Returns promise which will be resolved by the different * objects, depend on the original `queryBody` object. For the * persistent queries returns the `stream.Writable` instance. * * @throw Error */ cachedQueryResult(queryBody: QueryBody, preAggregationsTablesToTempTables: PreAggTableToTempTable[]): Promise; private getExpireSecs; private cacheKeyQueriesFrom; static queryCacheKey(queryBody: QueryBody): CacheKey; /** * Identity of a refresh key query: the SQL, its params, and where it runs. `external` and * `dataSource` are every dimension `cacheQueryResult` routes on; the rest of the options element * is policy applied to the result rather than part of it, and `replacePartitionSqlAndParams` * recomputes `renewalThreshold` from `new Date()`, so covering it would make the key drift within * a single request. */ static refreshKeyIdentity(sqlQuery: QueryWithParams, dataSource: string): RefreshKeyIdentity; /** * The `invalidate` discriminator of the partition build range cache, written by * `PreAggregationPartitionRangeLoader.loadRangeQuery` and read by * `PreAggregations.checkPartitionsBuildRangeCache`. Derived here so the two cannot drift apart — * when each spelled it out itself, the read stopped finding what the write had stored. */ static buildRangeInvalidateKey(preAggregation: { invalidateKeyQueries?: QueryWithParams[]; dataSource?: string; }): RefreshKeyIdentity | false; cacheRefreshKeyResult(sqlQuery: QueryWithParams, expiration: number, options: RefreshKeyCacheOptions): Promise; refreshKeyCacheKey(sqlQuery: QueryWithParams, dataSource: string): string; hasMemoryCacheEntry(redisKey: string): boolean; static extractRequestUUID(requestId: string): string; static replacePreAggregationTableNamesInSql(sql: string, preAggregationsTablesToTempTables: PreAggTableToTempTableNames[]): string; static replacePreAggregationTableNames(queryAndParams: QueryWithParams, preAggregationsTablesToTempTables: PreAggTableToTempTableNames[]): QueryWithParams; /** * Determines queue type, resolves `QueryQueue` instance and runs the * `executeInQueue` method passing incoming `query` into it. Resolves * promise with the `executeInQueue` method result for the not persistent * queries and with the `stream.Writable` instance for the persistent. */ queryWithRetryAndRelease(query: string | QueryWithParams, values: string[], { cacheKey, dataSource, external, priority, requestId, spanId, inlineTables, useCsvQuery, lambdaTypes, persistent, aliasNameToMember, }: { cacheKey: CacheKey; dataSource: string; external: boolean; priority?: number; requestId?: string; spanId?: string; inlineTables?: InlineTables; useCsvQuery?: boolean; lambdaTypes?: TableStructure; persistent?: boolean; aliasNameToMember?: { [alias: string]: string; } | null; }): Promise; /** * A queue payload as it may be logged: an inline table's rows are data source * content and have no place in a log line, so only its name and columns stay. */ private static payloadForLog; getQueue(dataSource?: string): Promise; protected csvQuery(client: any, q: any): Promise<{ types: any; csvRows: string; rowCount: number; }>; getExternalQueue(): QueryQueue; static createQueue(redisPrefix: string, clientFactory: DriverFactory, executeFn: (client: BaseDriver, req: any) => any, options: Omit): QueryQueue; /** * Returns registered queries queues hash table. */ getQueues(): { [dataSource: string]: QueryQueue; }; startRenewCycle(query: string, values: string[], cacheKeyQueries: QueryWithParams[], expireSecs: number, cacheKey: CacheKey, renewalThreshold: any, options: { requestId?: string; skipRefreshKeyWaitForRenew?: boolean; external?: boolean; dataSource: string; persistent?: boolean; }): void; renewQuery(query: string, values: string[], cacheKeyQueries: QueryWithParams[], expireSecs: number, cacheKey: CacheKey, renewalThreshold: any, options: { requestId?: string; skipRefreshKeyWaitForRenew?: boolean; priority?: number; external?: boolean; forceNoCache?: boolean; dataSource: string; useCsvQuery?: boolean; lambdaTypes?: TableStructure; persistent?: boolean; renewCycle?: boolean; }): Promise<{ data: any; refreshKeyValues: any[]; lastRefreshTime: Date; }>; loadRefreshKeysFromQuery(query: QueryBody): Promise; loadRefreshKeys(cacheKeyQueries: QueryWithParams[], expireSecs: number, options: LoadRefreshKeyOptions): Promise[]; loadRefreshKey(q: QueryWithParams, expireSecs: number, options: LoadRefreshKeyOptions): Promise; withLock: (key: string, ttl: number, callback: () => MaybeCancelablePromise) => import("@cubejs-backend/shared").CancelablePromise; protected static decideCacheAction(entry: CacheEntry, renewedAgo: number, options: CacheQueryResultOptions, renewalKey?: string): CacheAction; protected static isMemoryEntryUsable(entry: CacheEntry, renewedAgo: number, expiration: number, renewalThreshold?: number, renewalKey?: string): boolean; protected cacheOperationContext(cacheKey: CacheKey, expiration: number, options: CacheQueryResultOptions): CacheOperationContext; protected fetchAndCacheQuery(query: string | QueryWithParams, values: string[], ctx: CacheOperationContext): Promise; protected fetchAndCacheQueryInBackground(query: string | QueryWithParams, values: string[], ctx: CacheOperationContext): void; protected getFromMemoryCache(ctx: CacheOperationContext): CacheEntry | null; protected storeInMemoryCache(entry: CacheEntry, renewedAgo: number, ctx: CacheOperationContext): void; cacheQueryResult(query: string | QueryWithParams, values: string[], cacheKey: CacheKey, expiration: number, options: CacheQueryResultOptions): Promise; protected lastRefreshTime(cacheKey: any): Promise; resultFromCacheIfExists(queryBody: QueryBody): Promise<{ data: any; lastRefreshTime: Date; }>; queryCacheKey(cacheKey: CacheKey): string; cleanup(): Promise; testConnection(): Promise; } export {}; //# sourceMappingURL=QueryCache.d.ts.map