forge-sql-orm
Version:
Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.
731 lines • 28.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseDateTime = void 0;
exports.formatDateTime = formatDateTime;
exports.getPrimaryKeys = getPrimaryKeys;
exports.getTableMetadata = getTableMetadata;
exports.generateDropTableStatements = generateDropTableStatements;
exports.mapSelectAllFieldsToAlias = mapSelectAllFieldsToAlias;
exports.mapSelectFieldsWithAlias = mapSelectFieldsWithAlias;
exports.applyFromDriverTransform = applyFromDriverTransform;
exports.formatLimitOffset = formatLimitOffset;
exports.nextVal = nextVal;
exports.printQueriesWithPlan = printQueriesWithPlan;
exports.handleErrorsWithPlan = handleErrorsWithPlan;
exports.slowQueryPerHours = slowQueryPerHours;
exports.withTimeout = withTimeout;
exports.withTidbHint = withTidbHint;
exports.checkProductionEnvironment = checkProductionEnvironment;
const drizzle_orm_1 = require("drizzle-orm");
const luxon_1 = require("luxon");
const sql_1 = require("drizzle-orm/sql/sql");
const core_1 = require("../core");
const webtriggers_1 = require("../webtriggers");
const api_1 = require("@forge/api");
/**
* Parses a date string into a Date object using the specified format
* @param value - The date string to parse or Date
* @param format - The format to use for parsing
* @returns Date object
*/
const parseDateTime = (value, format) => {
let result;
if (value instanceof Date) {
result = value;
}
else {
// 1. Try to parse using the provided format (strict mode)
const dt = luxon_1.DateTime.fromFormat(value, format);
if (dt.isValid) {
result = dt.toJSDate();
}
else {
// 2. Try to parse as SQL string
const sqlDt = luxon_1.DateTime.fromSQL(value);
if (sqlDt.isValid) {
result = sqlDt.toJSDate();
}
else {
// 3. Try to parse as RFC2822 string
const isoDt = luxon_1.DateTime.fromRFC2822(value);
if (isoDt.isValid) {
result = isoDt.toJSDate();
}
else {
// 4. Fallback: use native Date constructor
result = new Date(value);
}
}
}
}
// 4. Ensure the result is a valid Date object
if (Number.isNaN(result.getTime())) {
result = new Date(value);
}
return result;
};
exports.parseDateTime = parseDateTime;
/**
* Parses a string value into DateTime using multiple format parsers
*/
function parseStringToDateTime(value) {
const parsers = [luxon_1.DateTime.fromISO, luxon_1.DateTime.fromRFC2822, luxon_1.DateTime.fromSQL, luxon_1.DateTime.fromHTTP];
for (const parser of parsers) {
const dt = parser(value);
if (dt.isValid) {
return dt;
}
}
// Try parsing as number string
const parsed = Number(value);
if (!Number.isNaN(parsed)) {
return luxon_1.DateTime.fromMillis(parsed);
}
return null;
}
/**
* Converts a value to DateTime
*/
function valueToDateTime(value) {
let dt = null;
if (value instanceof Date) {
dt = luxon_1.DateTime.fromJSDate(value);
}
else if (typeof value === "string") {
dt = parseStringToDateTime(value);
}
else if (typeof value === "number") {
dt = luxon_1.DateTime.fromMillis(value);
}
else {
throw new Error("Unsupported type");
}
if (!dt?.isValid) {
throw new Error("Invalid Date");
}
return dt;
}
/**
* Validates timestamp range for Atlassian Forge compatibility
*/
function validateTimestampRange(dt) {
const minDate = luxon_1.DateTime.fromSeconds(1);
const maxDate = luxon_1.DateTime.fromMillis(2147483647 * 1000); // 2038-01-19 03:14:07.999 UTC
if (dt < minDate) {
throw new Error("Atlassian Forge does not support zero or negative timestamps. Allowed range: from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'.");
}
if (dt > maxDate) {
throw new Error("Atlassian Forge does not support timestamps beyond 2038-01-19 03:14:07.999999. Please use a smaller date within the supported range.");
}
}
/**
* Helper function to validate and format a date-like value using Luxon DateTime.
* @param value - Date object, ISO/RFC2822/SQL/HTTP string, or timestamp (number|string).
* @param format - DateTime format string (Luxon format tokens).
* @param isTimeStamp - Whether to validate timestamp range
* @returns Formatted date string.
* @throws Error if value cannot be parsed as a valid date.
*/
function formatDateTime(value, format, isTimeStamp) {
const dt = valueToDateTime(value);
if (isTimeStamp) {
validateTimestampRange(dt);
}
return dt.toFormat(format);
}
/**
* Gets primary keys from the schema.
* @template T - The type of the table schema
* @param {T} table - The table schema
* @returns {[string, AnyColumn][]} Array of primary key name and column pairs
*/
function getPrimaryKeys(table) {
const { columns, primaryKeys } = getTableMetadata(table);
// First try to find primary keys in columns
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
if (columnPrimaryKeys.length > 0) {
return columnPrimaryKeys;
}
// If no primary keys found in columns, check primary key builders
if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
// Collect all primary key columns from all primary key builders
const primaryKeyColumns = new Set();
for (const primaryKeyBuilder of primaryKeys) {
// Get primary key columns from each builder
for (const [name, column1] of Object.entries(columns).filter(([, column]) => {
// @ts-ignore - PrimaryKeyBuilder has internal columns property
return primaryKeyBuilder.columns.includes(column);
})) {
primaryKeyColumns.add([name, column1]);
}
}
return Array.from(primaryKeyColumns);
}
return [];
}
/**
* Processes foreign keys from foreignKeysSymbol
*/
function processForeignKeysFromSymbol(table, foreignKeysSymbol) {
const foreignKeys = [];
// @ts-ignore
const fkArray = table[foreignKeysSymbol];
if (!fkArray) {
return foreignKeys;
}
for (const fk of fkArray) {
if (fk.reference) {
const item = fk.reference(fk);
foreignKeys.push(item);
}
}
return foreignKeys;
}
/**
* Extracts config builders from config builder data
*/
function extractConfigBuilders(configBuilderData) {
if (Array.isArray(configBuilderData)) {
return configBuilderData;
}
return Object.values(configBuilderData).map((item) => item.value ?? item);
}
/**
* Checks if a builder is a ForeignKeyBuilder
*/
function isForeignKeyBuilder(builder) {
if (!builder?.constructor) {
return false;
}
const builderName = builder.constructor.name.toLowerCase();
return builderName.includes("foreignkeybuilder");
}
/**
* Processes foreign keys from extraSymbol
*/
function processForeignKeysFromExtra(table, extraSymbol) {
const foreignKeys = [];
// @ts-ignore
const extraConfigBuilder = table[extraSymbol];
if (!extraConfigBuilder || typeof extraConfigBuilder !== "function") {
return foreignKeys;
}
const configBuilderData = extraConfigBuilder(table);
if (!configBuilderData) {
return foreignKeys;
}
const configBuilders = extractConfigBuilders(configBuilderData);
for (const builder of configBuilders) {
if (isForeignKeyBuilder(builder)) {
foreignKeys.push(builder);
}
}
return foreignKeys;
}
/**
* Processes foreign keys from both foreignKeysSymbol and extraSymbol
* @param table - The table schema
* @param foreignKeysSymbol - Symbol for foreign keys
* @param extraSymbol - Symbol for extra configuration
* @returns Array of foreign key builders
*/
function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
const foreignKeys = [];
// Process foreign keys from foreignKeysSymbol
if (foreignKeysSymbol) {
foreignKeys.push(...processForeignKeysFromSymbol(table, foreignKeysSymbol));
}
// Process foreign keys from extraSymbol
if (extraSymbol) {
foreignKeys.push(...processForeignKeysFromExtra(table, extraSymbol));
}
return foreignKeys;
}
/**
* Extracts symbols from table schema.
* @param table - The table schema
* @returns Object containing relevant symbols
*/
function extractTableSymbols(table) {
const symbols = Object.getOwnPropertySymbols(table);
return {
nameSymbol: symbols.find((s) => s.toString().includes("Name")),
columnsSymbol: symbols.find((s) => s.toString().includes("Columns")),
foreignKeysSymbol: symbols.find((s) => s.toString().includes("ForeignKeys)")),
extraSymbol: symbols.find((s) => s.toString().includes("ExtraConfigBuilder")),
};
}
/**
* Maps builder to appropriate array based on its type.
* @param builder - The builder object
* @param builders - The builders object containing all arrays
* @returns True if builder was added to a specific array, false otherwise
*/
function addBuilderToTypedArray(builder, builders) {
if (!builder?.constructor) {
return false;
}
const builderName = builder.constructor.name.toLowerCase();
const builderMap = {
indexbuilder: builders.indexes,
checkbuilder: builders.checks,
primarykeybuilder: builders.primaryKeys,
uniqueconstraintbuilder: builders.uniqueConstraints,
};
for (const [type, array] of Object.entries(builderMap)) {
if (builderName.includes(type)) {
array.push(builder);
return true;
}
}
return false;
}
/**
* Processes extra configuration builders and adds them to the builders object.
* @param table - The table schema
* @param extraSymbol - The extra symbol from table
* @param builders - The builders object to populate
*/
function processExtraConfigBuilders(table, extraSymbol, builders) {
if (!extraSymbol) {
return;
}
// @ts-ignore
const extraConfigBuilder = table[extraSymbol];
if (!extraConfigBuilder || typeof extraConfigBuilder !== "function") {
return;
}
const configBuilderData = extraConfigBuilder(table);
if (!configBuilderData) {
return;
}
const configBuilders = extractConfigBuilders(configBuilderData);
for (const builder of configBuilders) {
addBuilderToTypedArray(builder, builders);
builders.extras.push(builder);
}
}
/**
* Extracts table metadata from the schema.
* @param {AnyMySqlTable} table - The table schema
* @returns {MetadataInfo} Object containing table metadata
*/
function getTableMetadata(table) {
const { nameSymbol, columnsSymbol, foreignKeysSymbol, extraSymbol } = extractTableSymbols(table);
// Initialize builders arrays
const builders = {
indexes: [],
checks: [],
foreignKeys: [],
primaryKeys: [],
uniqueConstraints: [],
extras: [],
};
// Process foreign keys
builders.foreignKeys = processForeignKeys(table, foreignKeysSymbol, extraSymbol);
// Process extra configuration if available
processExtraConfigBuilders(table, extraSymbol, builders);
return {
tableName: nameSymbol ? table[nameSymbol] : "",
columns: columnsSymbol ? table[columnsSymbol] : {},
...builders,
};
}
/**
* Generates SQL statements for dropping tables and/or their sequences.
*
* @param tables - List of table names to generate DROP statements for.
* @param options - Configuration object:
* - sequence: whether to drop associated sequences (default: true)
* - table: whether to drop tables themselves (default: true)
* @returns Array of SQL statements for dropping the specified objects
*/
function generateDropTableStatements(tables, options) {
const dropStatements = [];
const validOptions = options ?? { sequence: true, table: true };
if (!validOptions.sequence && !validOptions.table) {
// eslint-disable-next-line no-console
console.warn('No drop operations requested: both "table" and "sequence" options are false');
return [];
}
for (const tableName of tables) {
if (validOptions.table) {
dropStatements.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
}
if (validOptions.sequence) {
dropStatements.push(`DROP SEQUENCE IF EXISTS \`${tableName}\`;`);
}
}
return dropStatements;
}
function mapSelectTableToAlias(table, uniqPrefix, aliasMap) {
const { columns, tableName } = getTableMetadata(table);
const selectionsTableFields = {};
for (const name of Object.keys(columns)) {
const column = columns[name];
const uniqName = `a_${uniqPrefix}_${tableName}_${column.name}`.toLowerCase();
const fieldAlias = drizzle_orm_1.sql.raw(uniqName);
selectionsTableFields[name] = (0, drizzle_orm_1.sql) `${column} as \`${fieldAlias}\``;
aliasMap[uniqName] = column;
}
return selectionsTableFields;
}
function isDrizzleColumn(column) {
return column && typeof column === "object" && "table" in column;
}
function mapSelectAllFieldsToAlias(selections, name, uniqName, fields, aliasMap) {
if ((0, drizzle_orm_1.isTable)(fields)) {
selections[name] = mapSelectTableToAlias(fields, uniqName, aliasMap);
}
else if (isDrizzleColumn(fields)) {
const column = fields;
const uniqAliasName = `a_${uniqName}_${column.name}`.toLowerCase();
let aliasName = drizzle_orm_1.sql.raw(uniqAliasName);
selections[name] = (0, drizzle_orm_1.sql) `${column} as \`${aliasName}\``;
aliasMap[uniqAliasName] = column;
}
else if ((0, sql_1.isSQLWrapper)(fields)) {
selections[name] = fields;
}
else {
const innerSelections = {};
for (const [iname, ifields] of Object.entries(fields)) {
mapSelectAllFieldsToAlias(innerSelections, iname, `${uniqName}_${iname}`, ifields, aliasMap);
}
selections[name] = innerSelections;
}
return selections;
}
function mapSelectFieldsWithAlias(fields) {
if (!fields) {
throw new Error("fields is empty");
}
const aliasMap = {};
const selections = {};
for (const [name, fields1] of Object.entries(fields)) {
mapSelectAllFieldsToAlias(selections, name, name, fields1, aliasMap);
}
return { selections, aliasMap };
}
/**
* Checks if value is a SQL object with queryChunks
*/
function isSQLValue(value) {
return (value !== null && typeof value === "object" && (0, sql_1.isSQLWrapper)(value) && "queryChunks" in value);
}
/**
* Extracts the alias name chunk from query chunks if it exists and is a SQL object
*/
function getAliasNameChunk(queryChunks) {
if (queryChunks.length <= 3) {
return undefined;
}
const aliasNameChunk = queryChunks.at(-2);
if ((0, sql_1.isSQLWrapper)(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
return aliasNameChunk;
}
return undefined;
}
/**
* Extracts string value from a SQL chunk if it contains a single string value
*/
function extractStringValueFromChunk(chunk) {
if (chunk.queryChunks?.length !== 1 || !chunk.queryChunks[0]) {
return undefined;
}
const stringChunk = chunk.queryChunks[0];
if (!("value" in stringChunk)) {
return undefined;
}
const values = stringChunk.value;
if (values?.length === 1) {
return values[0];
}
return undefined;
}
function getAliasFromDrizzleAlias(value) {
if (!isSQLValue(value)) {
return undefined;
}
const aliasNameChunk = getAliasNameChunk(value.queryChunks);
if (!aliasNameChunk) {
return undefined;
}
return extractStringValueFromChunk(aliasNameChunk);
}
function transformValue(value, alias, aliasMap) {
const column = aliasMap[alias];
if (!column)
return value;
let customColumn = column;
// @ts-ignore
const fromDriver = customColumn?.mapFrom;
if (fromDriver && value !== null && value !== undefined) {
return fromDriver(value);
}
return value;
}
function transformObject(obj, selections, aliasMap) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
const selection = selections[key];
const alias = getAliasFromDrizzleAlias(selection);
if (alias && aliasMap[alias]) {
result[key] = transformValue(value, alias, aliasMap);
}
else if (selection && typeof selection === "object" && !(0, sql_1.isSQLWrapper)(selection)) {
result[key] = transformObject(value, selection, aliasMap);
}
else {
result[key] = value;
}
}
return result;
}
function applyFromDriverTransform(rows, selections, aliasMap) {
return rows.map((row) => {
const transformed = transformObject(row, selections, aliasMap);
return processNullBranches(transformed);
});
}
function processNullBranches(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
// Skip built-in objects like Date, Array, etc.
if (obj.constructor && obj.constructor.name !== "Object") {
return obj;
}
const result = {};
let allNull = true;
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) {
result[key] = null;
continue;
}
if (typeof value === "object") {
const processed = processNullBranches(value);
result[key] = processed;
if (processed !== null) {
allNull = false;
}
}
else {
result[key] = value;
allNull = false;
}
}
return allNull ? null : result;
}
function formatLimitOffset(limitOrOffset) {
if (typeof limitOrOffset !== "number" || Number.isNaN(limitOrOffset)) {
throw new Error("limitOrOffset must be a valid number");
}
return drizzle_orm_1.sql.raw(`${limitOrOffset}`);
}
function nextVal(sequenceName) {
return drizzle_orm_1.sql.raw(`NEXTVAL(${sequenceName})`);
}
/**
* Helper function to build base query for CLUSTER_STATEMENTS_SUMMARY table
*/
function buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs) {
const statementsTable = core_1.clusterStatementsSummary;
return forgeSQLORM
.getDrizzleQueryBuilder()
.select({
digestText: withTidbHint(statementsTable.digestText),
avgLatency: statementsTable.avgLatency,
avgMem: statementsTable.avgMem,
execCount: statementsTable.execCount,
plan: statementsTable.plan,
stmtType: statementsTable.stmtType,
})
.from(statementsTable)
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(statementsTable.digest), (0, drizzle_orm_1.not)((0, drizzle_orm_1.ilike)(statementsTable.digestText, "%information_schema%")), (0, drizzle_orm_1.notInArray)(statementsTable.stmtType, ["Use", "Set", "Show", "Commit", "Rollback", "Begin"]), (0, drizzle_orm_1.gte)(statementsTable.lastSeen, (0, drizzle_orm_1.sql) `DATE_SUB
(NOW(), INTERVAL
${timeDiffMs * 1000}
MICROSECOND
)`)));
}
/**
* Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
*
* This function queries the CLUSTER_STATEMENTS_SUMMARY table to find queries that were executed
* within the specified time window and prints detailed performance information including:
* - SQL query text
* - Memory usage (average and max in MB)
* - Execution time (average in ms)
* - Number of executions
* - Execution plan
*
* @param forgeSQLORM - The ForgeSQL operation instance for database access
* @param timeDiffMs - Time window in milliseconds to look back for queries (e.g., 1500 for last 1.5 seconds)
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to 3000ms)
*
* @example
* ```typescript
* // Analyze queries from the last 2 seconds
* await printQueriesWithPlan(forgeSQLORM, 2000);
*
* // Analyze queries with custom timeout
* await printQueriesWithPlan(forgeSQLORM, 1000, 3000);
* ```
*
* @throws Does not throw - errors are logged to console.debug instead
*/
async function printQueriesWithPlan(forgeSQLORM, timeDiffMs, timeout) {
try {
const timeoutMs = timeout ?? 3000;
const results = await withTimeout(buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs), `Timeout ${timeoutMs}ms in printQueriesWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`, timeoutMs + 200);
for (const result of results) {
// Average execution time (convert from nanoseconds to milliseconds)
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
const avgMemMB = Number(result.avgMem) / 1_000_000;
// 1. Query info: SQL, memory, time, executions
// eslint-disable-next-line no-console
console.warn(`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`);
}
}
catch (error) {
// eslint-disable-next-line no-console
console.debug(`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`, error);
}
}
async function handleErrorsWithPlan(forgeSQLORM, timeDiffMs, type) {
try {
const statementsTable = core_1.clusterStatementsSummary;
const timeoutMs = 3000;
const baseQuery = buildClusterStatementsSummaryQuery(forgeSQLORM, timeDiffMs);
const orderColumn = type === "OOM" ? statementsTable.avgMem : statementsTable.avgLatency;
const query = baseQuery.orderBy((0, drizzle_orm_1.desc)(orderColumn)).limit(formatLimitOffset(1));
const results = await withTimeout(query, `Timeout ${timeoutMs}ms in handleErrorsWithPlan - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`, timeoutMs + 200);
for (const result of results) {
// Average execution time (convert from nanoseconds to milliseconds)
const avgTimeMs = Number(result.avgLatency) / 1_000_000;
const avgMemMB = Number(result.avgMem) / 1_000_000;
// 1. Query info: SQL, memory, time, executions
// eslint-disable-next-line no-console
console.warn(`SQL: ${result.digestText} | Memory: ${avgMemMB.toFixed(2)} MB | Time: ${avgTimeMs.toFixed(2)} ms | stmtType: ${result.stmtType} | Executions: ${result.execCount}\n Plan:${result.plan}`);
}
}
catch (error) {
// eslint-disable-next-line no-console
console.debug(`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`, error);
}
}
const SESSION_ALIAS_NAME_ORM = "orm";
/**
* Analyzes and logs slow queries from the last specified number of hours.
*
* This function queries the slow query system table to find queries that were executed
* within the specified time window and logs detailed performance information including:
* - SQL query text
* - Maximum memory usage (in MB)
* - Query execution time (in ms)
* - Execution count
* - Execution plan
*
* @param forgeSQLORM - The ForgeSQL operation instance for database access
* @param hours - Number of hours to look back for slow queries (e.g., 1 for last hour, 24 for last day)
* @param timeout - Optional timeout in milliseconds for the query execution (defaults to 1500ms)
*
* @example
* ```typescript
* // Analyze slow queries from the last hour
* await slowQueryPerHours(forgeSQLORM, 1);
*
* // Analyze slow queries from the last 24 hours with custom timeout
* await slowQueryPerHours(forgeSQLORM, 24, 3000);
*
* // Analyze slow queries from the last 6 hours
* await slowQueryPerHours(forgeSQLORM, 6);
* ```
*
* @throws Does not throw - errors are logged to console.debug instead
*/
async function slowQueryPerHours(forgeSQLORM, hours, timeout) {
try {
const timeoutMs = timeout ?? 1500;
const results = await withTimeout(forgeSQLORM
.getDrizzleQueryBuilder()
.select({
query: withTidbHint(core_1.slowQuery.query),
queryTime: core_1.slowQuery.queryTime,
memMax: core_1.slowQuery.memMax,
plan: core_1.slowQuery.plan,
})
.from(core_1.slowQuery)
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(core_1.slowQuery.digest), (0, drizzle_orm_1.ne)(core_1.slowQuery.sessionAlias, SESSION_ALIAS_NAME_ORM), (0, drizzle_orm_1.gte)(core_1.slowQuery.time, (0, drizzle_orm_1.sql) `DATE_SUB
(NOW(), INTERVAL
${hours}
HOUR
)`))), `Timeout ${timeoutMs}ms in slowQueryPerHours - transient timeouts are usually fine; repeated timeouts mean this diagnostic query is consistently slow and should be investigated`, timeoutMs);
const response = [];
for (const result of results) {
// Convert memory from bytes to MB and handle null values
const memMaxMB = result.memMax ? Number(result.memMax) / 1_000_000 : 0;
const message = `Found SlowQuery SQL: ${result.query} | Memory: ${memMaxMB.toFixed(2)} MB | Time: ${result.queryTime} ms\n Plan:${result.plan}`;
response.push(message);
// 1. Query info: SQL, memory, time, executions
// eslint-disable-next-line no-console
console.warn(message);
}
return response;
}
catch (error) {
// eslint-disable-next-line no-console
console.debug(`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}. Try again after some time`, error);
return [
`Error occurred while retrieving query execution plan: ${error instanceof Error ? error.message : "Unknown error"}`,
];
}
}
/**
* Executes a promise with a timeout.
*
* @param promise - The promise to execute
* @param timeoutMs - Timeout in milliseconds
* @returns Promise that resolves with the result or rejects on timeout
* @throws {Error} When the operation times out
*/
async function withTimeout(promise, message, timeoutMs) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(message));
}, timeoutMs);
});
try {
return await Promise.race([promise, timeoutPromise]);
}
finally {
if (timeoutId) {
clearTimeout(timeoutId);
}
}
}
function withTidbHint(column) {
// We lie a bit to TypeScript here: at runtime this is a new SQL fragment,
// but returning TExpr keeps the column type info in downstream inference.
return (0, drizzle_orm_1.sql) `/*+ SET_VAR(tidb_session_alias=${drizzle_orm_1.sql.raw(SESSION_ALIAS_NAME_ORM)}) */ ${column}`;
}
/**
* Checks if the current environment is production and returns an error response if so.
* This function is used to prevent development-only operations from running in production.
*
* @param functionName - The name of the function being checked (for logging purposes)
* @returns {TriggerResponse<string> | null} Returns error response if in production, null otherwise
*/
function checkProductionEnvironment(functionName) {
const environmentType = (0, api_1.getAppContext)()?.environmentType;
if (environmentType === "PRODUCTION") {
// eslint-disable-next-line no-console
console.log(`${functionName} is disabled in production environment`);
return (0, webtriggers_1.getHttpResponse)(500, `${functionName} is disabled in production environment`);
}
// eslint-disable-next-line no-console
console.log(`${functionName} triggered in ${environmentType}`);
return null;
}
//# sourceMappingURL=sqlUtils.js.map