UNPKG

@itwin/presentation-hierarchies

Version:

A package for creating hierarchies based on data in iTwin.js iModels.

121 lines 4.88 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { Guid, StopWatch } from "@itwin/core-bentley"; import { createMainThreadReleaseOnTimePassedHandler, trimWhitespace, } from "@itwin/presentation-shared"; import { RowsLimitExceededError } from "../HierarchyErrors.js"; import { LOGGING_NAMESPACE as BASE_LOGGING_NAMESPACE, LOGGING_NAMESPACE_PERFORMANCE as BASE_LOGGING_NAMESPACE_PERFORMANCE, } from "../internal/Common.js"; import { doLog } from "../internal/LoggingUtils.js"; /** * Creates an `LimitingECSqlQueryExecutor` that throws `RowsLimitExceededError` if the query exceeds given amount of rows. * @public */ export function createLimitingECSqlQueryExecutor(baseExecutor, defaultLimit) { return { async *createQueryReader(query, config) { const { limit: configLimit, ...restConfig } = config ?? {}; const limit = configLimit ?? defaultLimit; const queryLogger = createQueryLogger(query); const releaseMainThread = createMainThreadReleaseOnTimePassedHandler(); // handle "unbounded" case without a buffer const reader = baseExecutor.createQueryReader({ ...query, ecsql: addLimit(query.ecsql, limit) }, restConfig); if (limit === "unbounded") { try { for await (const row of reader) { await releaseMainThread(); queryLogger.onStep(); yield row; } } finally { queryLogger.onComplete(); } return; } // avoid streaming until we know the number of rows is okay const buffer = []; try { for await (const row of reader) { await releaseMainThread(); queryLogger.onStep(); buffer.push(row); if (buffer.length > limit) { throw new RowsLimitExceededError(limit); } } } finally { queryLogger.onComplete(); } for (const row of buffer) { await releaseMainThread(); yield row; } }, }; } function addLimit(ecsql, limit) { if (limit === "unbounded") { return ecsql; } return ` SELECT * FROM (${ecsql}) LIMIT ${limit + 1} `; } const LOGGING_NAMESPACE = `${BASE_LOGGING_NAMESPACE}.Queries`; const LOGGING_NAMESPACE_PERFORMANCE = `${BASE_LOGGING_NAMESPACE_PERFORMANCE}.Queries`; function createQueryLogger(query, firstStepWarningThreshold = 3000, allRowsWarningThreshold = 5000) { const queryId = Guid.createValue(); /* v8 ignore next -- @preserve */ doLog({ category: LOGGING_NAMESPACE, message: () => `Executing query [${queryId}]: ${createQueryLogMessage(query)}`, }); let firstStep = true; let rowsCount = 0; const timer = new StopWatch(undefined, true); return { onStep() { if (firstStep) { /* v8 ignore start */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE, severity: timer.current.milliseconds >= firstStepWarningThreshold ? "warning" : "trace", message: () => `[${queryId}] First step took ${timer.currentSeconds} s.`, }); /* v8 ignore stop */ firstStep = false; } ++rowsCount; }, onComplete() { /* v8 ignore start */ doLog({ category: LOGGING_NAMESPACE_PERFORMANCE, severity: timer.current.milliseconds >= allRowsWarningThreshold ? "warning" : "trace", message: () => `[${queryId}] Query took ${timer.currentSeconds} s. for ${rowsCount} rows.`, }); /* v8 ignore stop */ }, }; } /* v8 ignore start */ function createQueryLogMessage(query) { const ctes = query.ctes?.map((cte) => ` ${trimWhitespace(cte)}`).join(", \n"); const bindings = query.bindings?.map((b) => JSON.stringify(b.value)).join(", "); let output = "{\n"; if (ctes) { output += ` ctes: [ \n${ctes} \n], \n`; } output += ` ecsql: ${trimWhitespace(query.ecsql)}, \n`; if (bindings) { output += ` bindings: [${bindings}], \n`; } output += "}"; return output; } /* v8 ignore stop */ //# sourceMappingURL=LimitingECSqlQueryExecutor.js.map