@memberjunction/generic-database-provider 5.36.0 → 5.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/GenericDatabaseProvider.d.ts +18 -26
- package/dist/GenericDatabaseProvider.d.ts.map +1 -1
- package/dist/GenericDatabaseProvider.js +85 -238
- package/dist/GenericDatabaseProvider.js.map +1 -1
- package/dist/queryCompositionEngine.d.ts +23 -4
- package/dist/queryCompositionEngine.d.ts.map +1 -1
- package/dist/queryCompositionEngine.js +27 -18
- package/dist/queryCompositionEngine.js.map +1 -1
- package/dist/queryPagingEngine.d.ts +69 -4
- package/dist/queryPagingEngine.d.ts.map +1 -1
- package/dist/queryPagingEngine.js +162 -55
- package/dist/queryPagingEngine.js.map +1 -1
- package/dist/renderPipeline.d.ts +34 -28
- package/dist/renderPipeline.d.ts.map +1 -1
- package/dist/renderPipeline.js +58 -130
- package/dist/renderPipeline.js.map +1 -1
- package/package.json +13 -13
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @module @memberjunction/generic-database-provider
|
|
17
17
|
*/
|
|
18
|
-
import { DatabaseProviderBase, EntityFieldTSType, EntityPermissionType, InMemoryLocalStorageProvider, Metadata, RunView,
|
|
18
|
+
import { DatabaseProviderBase, EntityFieldTSType, EntityPermissionType, InMemoryLocalStorageProvider, Metadata, RunView, LocalCacheManager, LogError, LogStatus, LogStatusEx, StripStopWords, AfterKeyNotSupportedError, IsKeysetPaginationOrderableType, } from '@memberjunction/core';
|
|
19
19
|
import { MJGlobal, SQLExpressionValidator, UUIDsEqual } from '@memberjunction/global';
|
|
20
20
|
import { QueryPagingEngine } from './queryPagingEngine.js';
|
|
21
21
|
// QueryParameterProcessor is now called internally by RenderPipeline
|
|
@@ -43,14 +43,6 @@ const GEO_EXTENDED_TYPES = new Set([
|
|
|
43
43
|
'GeoCountry', 'GeoPostalCode', 'GeoLatitude', 'GeoLongitude'
|
|
44
44
|
]);
|
|
45
45
|
export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
46
|
-
constructor() {
|
|
47
|
-
// Composition engine is now owned by RenderPipeline
|
|
48
|
-
super(...arguments);
|
|
49
|
-
/**************************************************************************/
|
|
50
|
-
// InternalRunQuery — Shared Pipeline Implementation
|
|
51
|
-
/**************************************************************************/
|
|
52
|
-
this._queryCacheInitialized = false;
|
|
53
|
-
}
|
|
54
46
|
/**
|
|
55
47
|
* Returns the active local storage provider, lazily creating an
|
|
56
48
|
* {@link InMemoryLocalStorageProvider} if none has been set.
|
|
@@ -1128,13 +1120,11 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
1128
1120
|
whereSQL = bHasWhere ? `${whereSQL} AND (${sExcludeSQL})` : `(${sExcludeSQL})`;
|
|
1129
1121
|
bHasWhere = true;
|
|
1130
1122
|
}
|
|
1131
|
-
// 5. Row-Level Security
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
bHasWhere = true;
|
|
1137
|
-
}
|
|
1123
|
+
// 5. Row-Level Security (exemption check is centralized in GetUserRowLevelSecurityWhereClause)
|
|
1124
|
+
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
|
|
1125
|
+
if (rlsWhereClause && rlsWhereClause.length > 0) {
|
|
1126
|
+
whereSQL = bHasWhere ? `${whereSQL} AND (${rlsWhereClause})` : `(${rlsWhereClause})`;
|
|
1127
|
+
bHasWhere = true;
|
|
1138
1128
|
}
|
|
1139
1129
|
// 6. Keyset (AfterKey) seek predicate — only on the data query, NOT the count query.
|
|
1140
1130
|
// The count is the total matching the user-visible filters; the seek predicate is
|
|
@@ -1750,11 +1740,9 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
1750
1740
|
bHasWhere = true;
|
|
1751
1741
|
}
|
|
1752
1742
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
whereSQL = bHasWhere ? `${whereSQL} AND (${rlsWhereClause})` : `(${rlsWhereClause})`;
|
|
1757
|
-
}
|
|
1743
|
+
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
|
|
1744
|
+
if (rlsWhereClause && rlsWhereClause.length > 0) {
|
|
1745
|
+
whereSQL = bHasWhere ? `${whereSQL} AND (${rlsWhereClause})` : `(${rlsWhereClause})`;
|
|
1758
1746
|
}
|
|
1759
1747
|
return whereSQL;
|
|
1760
1748
|
}
|
|
@@ -1960,45 +1948,45 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
1960
1948
|
const errorResults = [];
|
|
1961
1949
|
for (let i = 0; i < params.length; i++) {
|
|
1962
1950
|
const item = params[i];
|
|
1963
|
-
const
|
|
1964
|
-
if (!
|
|
1951
|
+
const query = this.resolveQuery(item.params);
|
|
1952
|
+
if (!query) {
|
|
1965
1953
|
errorResults.push({ queryIndex: i, queryId: item.params.QueryID || '', status: 'error', errorMessage: `Query not found: ${item.params.QueryID || item.params.QueryName}` });
|
|
1966
1954
|
continue;
|
|
1967
1955
|
}
|
|
1968
|
-
if (!
|
|
1969
|
-
errorResults.push({ queryIndex: i, queryId:
|
|
1956
|
+
if (!query.UserCanRun(user)) {
|
|
1957
|
+
errorResults.push({ queryIndex: i, queryId: query.ID, status: 'error', errorMessage: `User does not have permission to run query: ${query.Name}` });
|
|
1970
1958
|
continue;
|
|
1971
1959
|
}
|
|
1972
1960
|
if (!item.cacheStatus) {
|
|
1973
1961
|
itemsWithoutCacheCheck.push({ index: i, item });
|
|
1974
1962
|
continue;
|
|
1975
1963
|
}
|
|
1976
|
-
if (!
|
|
1977
|
-
itemsWithoutValidationSQL.push({ index: i, item,
|
|
1964
|
+
if (!query.CacheValidationSQL) {
|
|
1965
|
+
itemsWithoutValidationSQL.push({ index: i, item, query });
|
|
1978
1966
|
continue;
|
|
1979
1967
|
}
|
|
1980
|
-
itemsNeedingCacheCheck.push({ index: i, item,
|
|
1968
|
+
itemsNeedingCacheCheck.push({ index: i, item, query });
|
|
1981
1969
|
}
|
|
1982
1970
|
const cacheStatusResults = await this.getBatchedQueryCacheStatus(itemsNeedingCacheCheck, contextUser);
|
|
1983
1971
|
const staleItems = [];
|
|
1984
1972
|
const currentResults = [];
|
|
1985
|
-
for (const { index, item,
|
|
1973
|
+
for (const { index, item, query } of itemsNeedingCacheCheck) {
|
|
1986
1974
|
const serverStatus = cacheStatusResults.get(index);
|
|
1987
1975
|
if (!serverStatus || !serverStatus.success) {
|
|
1988
|
-
errorResults.push({ queryIndex: index, queryId:
|
|
1976
|
+
errorResults.push({ queryIndex: index, queryId: query.ID, status: 'error', errorMessage: serverStatus?.errorMessage || 'Failed to get cache status' });
|
|
1989
1977
|
continue;
|
|
1990
1978
|
}
|
|
1991
1979
|
if (this.isCacheCurrent(item.cacheStatus, serverStatus)) {
|
|
1992
|
-
currentResults.push({ queryIndex: index, queryId:
|
|
1980
|
+
currentResults.push({ queryIndex: index, queryId: query.ID, status: 'current' });
|
|
1993
1981
|
}
|
|
1994
1982
|
else {
|
|
1995
|
-
staleItems.push({ index, params: item.params,
|
|
1983
|
+
staleItems.push({ index, params: item.params, query });
|
|
1996
1984
|
}
|
|
1997
1985
|
}
|
|
1998
1986
|
const fullQueryPromises = [
|
|
1999
1987
|
...itemsWithoutCacheCheck.map(({ index, item }) => this.runFullQueryAndReturnForQuery(item.params, index, 'stale', contextUser)),
|
|
2000
|
-
...itemsWithoutValidationSQL.map(({ index, item,
|
|
2001
|
-
...staleItems.map(({ index, params: queryParams,
|
|
1988
|
+
...itemsWithoutValidationSQL.map(({ index, item, query }) => this.runFullQueryAndReturnForQuery(item.params, index, 'no_validation', contextUser, query.ID)),
|
|
1989
|
+
...staleItems.map(({ index, params: queryParams, query }) => this.runFullQueryAndReturnForQuery(queryParams, index, 'stale', contextUser, query.ID)),
|
|
2002
1990
|
];
|
|
2003
1991
|
const fullQueryResults = await Promise.all(fullQueryPromises);
|
|
2004
1992
|
const allResults = [...errorResults, ...currentResults, ...fullQueryResults];
|
|
@@ -2011,69 +1999,42 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2011
1999
|
}
|
|
2012
2000
|
}
|
|
2013
2001
|
/**
|
|
2014
|
-
* Resolves
|
|
2015
|
-
*
|
|
2016
|
-
*/
|
|
2017
|
-
resolveQueryInfo(params) {
|
|
2018
|
-
const freshEntity = this.findQueryInEngine(params.QueryID, params.QueryName, params.CategoryID, params.CategoryPath);
|
|
2019
|
-
if (freshEntity)
|
|
2020
|
-
return this.refreshQueryInfoFromEntity(freshEntity);
|
|
2021
|
-
if (params.QueryID)
|
|
2022
|
-
return this.Queries.find(q => UUIDsEqual(q.ID, params.QueryID));
|
|
2023
|
-
if (params.QueryName) {
|
|
2024
|
-
const matchingQueries = this.Queries.filter(q => q.Name.trim().toLowerCase() === params.QueryName?.trim().toLowerCase());
|
|
2025
|
-
if (matchingQueries.length === 0)
|
|
2026
|
-
return undefined;
|
|
2027
|
-
if (matchingQueries.length === 1)
|
|
2028
|
-
return matchingQueries[0];
|
|
2029
|
-
if (params.CategoryPath) {
|
|
2030
|
-
const byPath = matchingQueries.find(q => q.CategoryPath.toLowerCase() === params.CategoryPath?.toLowerCase());
|
|
2031
|
-
if (byPath)
|
|
2032
|
-
return byPath;
|
|
2033
|
-
}
|
|
2034
|
-
if (params.CategoryID) {
|
|
2035
|
-
const byId = matchingQueries.find(q => UUIDsEqual(q.CategoryID, params.CategoryID));
|
|
2036
|
-
if (byId)
|
|
2037
|
-
return byId;
|
|
2038
|
-
}
|
|
2039
|
-
return matchingQueries[0];
|
|
2040
|
-
}
|
|
2041
|
-
return undefined;
|
|
2042
|
-
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Searches QueryEngine for a fresh query entity.
|
|
2002
|
+
* Resolves a query from RunQueryParams (by ID or Name+CategoryPath).
|
|
2003
|
+
* Uses QueryEngine as the single source of truth for query metadata.
|
|
2045
2004
|
*/
|
|
2046
|
-
|
|
2005
|
+
resolveQuery(params) {
|
|
2047
2006
|
const engineQueries = QueryEngine.Instance?.Queries;
|
|
2048
2007
|
if (!engineQueries || engineQueries.length === 0)
|
|
2049
|
-
return
|
|
2050
|
-
if (QueryID) {
|
|
2051
|
-
|
|
2052
|
-
return engineQueries.find(q => q.ID.trim().toLowerCase() === lower) ?? null;
|
|
2008
|
+
return undefined;
|
|
2009
|
+
if (params.QueryID) {
|
|
2010
|
+
return engineQueries.find(q => UUIDsEqual(q.ID, params.QueryID));
|
|
2053
2011
|
}
|
|
2054
|
-
if (QueryName) {
|
|
2055
|
-
const lowerName = QueryName.trim().toLowerCase();
|
|
2012
|
+
if (params.QueryName) {
|
|
2013
|
+
const lowerName = params.QueryName.trim().toLowerCase();
|
|
2056
2014
|
const matches = engineQueries.filter(q => q.Name.trim().toLowerCase() === lowerName);
|
|
2057
2015
|
if (matches.length === 0)
|
|
2058
|
-
return
|
|
2016
|
+
return undefined;
|
|
2059
2017
|
if (matches.length === 1)
|
|
2060
2018
|
return matches[0];
|
|
2061
|
-
if (
|
|
2062
|
-
const
|
|
2019
|
+
if (params.CategoryPath) {
|
|
2020
|
+
const byPath = matches.find(q => q.CategoryPath.toLowerCase() === params.CategoryPath?.toLowerCase());
|
|
2021
|
+
if (byPath)
|
|
2022
|
+
return byPath;
|
|
2023
|
+
}
|
|
2024
|
+
if (params.CategoryID) {
|
|
2025
|
+
const byId = matches.find(q => UUIDsEqual(q.CategoryID, params.CategoryID));
|
|
2063
2026
|
if (byId)
|
|
2064
2027
|
return byId;
|
|
2065
|
-
|
|
2066
|
-
if (CategoryPath) {
|
|
2067
|
-
const resolvedCategoryId = this.resolveCategoryPath(CategoryPath);
|
|
2028
|
+
const resolvedCategoryId = this.resolveCategoryPath(params.CategoryPath ?? '');
|
|
2068
2029
|
if (resolvedCategoryId) {
|
|
2069
|
-
const
|
|
2070
|
-
if (
|
|
2071
|
-
return
|
|
2030
|
+
const byResolvedPath = matches.find(q => UUIDsEqual(q.CategoryID, resolvedCategoryId));
|
|
2031
|
+
if (byResolvedPath)
|
|
2032
|
+
return byResolvedPath;
|
|
2072
2033
|
}
|
|
2073
2034
|
}
|
|
2074
2035
|
return matches[0];
|
|
2075
2036
|
}
|
|
2076
|
-
return
|
|
2037
|
+
return undefined;
|
|
2077
2038
|
}
|
|
2078
2039
|
/**
|
|
2079
2040
|
* Validates that a query can be executed by the given user. Checks both permissions
|
|
@@ -2081,7 +2042,7 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2081
2042
|
* emits a console warning but allows execution to proceed, enabling query testing
|
|
2082
2043
|
* before formal approval.
|
|
2083
2044
|
*
|
|
2084
|
-
* @param query - The resolved
|
|
2045
|
+
* @param query - The resolved MJQueryEntityExtended to validate
|
|
2085
2046
|
* @param contextUser - The user attempting to execute the query
|
|
2086
2047
|
* @throws Error if the user does not have permission to run the query
|
|
2087
2048
|
*/
|
|
@@ -2094,20 +2055,6 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2094
2055
|
LogStatus(`WARNING: Executing query '${query.Name}' (ID: ${query.ID}) with status '${query.Status}'. Query has not been approved.`);
|
|
2095
2056
|
}
|
|
2096
2057
|
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Creates a fresh QueryInfo from a MJQueryEntity and patches the ProviderBase cache.
|
|
2099
|
-
*/
|
|
2100
|
-
refreshQueryInfoFromEntity(entity) {
|
|
2101
|
-
const freshInfo = new QueryInfo(entity.GetAll());
|
|
2102
|
-
const existingIndex = this.Queries.findIndex(q => UUIDsEqual(q.ID, freshInfo.ID));
|
|
2103
|
-
if (existingIndex >= 0) {
|
|
2104
|
-
this.Queries[existingIndex] = freshInfo;
|
|
2105
|
-
}
|
|
2106
|
-
else {
|
|
2107
|
-
this.Queries.push(freshInfo);
|
|
2108
|
-
}
|
|
2109
|
-
return freshInfo;
|
|
2110
|
-
}
|
|
2111
2058
|
/**
|
|
2112
2059
|
* Resolves a category path string to a QueryCategoryInfo ID.
|
|
2113
2060
|
*/
|
|
@@ -2134,9 +2081,9 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2134
2081
|
const results = new Map();
|
|
2135
2082
|
if (items.length === 0)
|
|
2136
2083
|
return results;
|
|
2137
|
-
const promises = items.map(async ({ index,
|
|
2084
|
+
const promises = items.map(async ({ index, query }) => {
|
|
2138
2085
|
try {
|
|
2139
|
-
const rows = await this.ExecuteSQL(
|
|
2086
|
+
const rows = await this.ExecuteSQL(query.CacheValidationSQL, undefined, undefined, contextUser);
|
|
2140
2087
|
if (rows && rows.length > 0) {
|
|
2141
2088
|
const row = rows[0];
|
|
2142
2089
|
results.set(index, {
|
|
@@ -2179,13 +2126,9 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2179
2126
|
rowCount: result.Results.length,
|
|
2180
2127
|
};
|
|
2181
2128
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
this._queryCacheInitialized = true;
|
|
2186
|
-
}
|
|
2187
|
-
return QueryCacheManager.Instance;
|
|
2188
|
-
}
|
|
2129
|
+
/**************************************************************************/
|
|
2130
|
+
// InternalRunQuery — Shared Pipeline Implementation
|
|
2131
|
+
/**************************************************************************/
|
|
2189
2132
|
/**
|
|
2190
2133
|
* Full query execution pipeline: resolve → validate → compose → template → cache check →
|
|
2191
2134
|
* execute → paginate → audit → cache store. Platform providers inherit this; only
|
|
@@ -2203,7 +2146,6 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2203
2146
|
const { finalSQL, appliedParameters } = this.processQueryParameters(query, params.Parameters, contextUser);
|
|
2204
2147
|
// Execute query — use SQL-level paging when requested, else fetch all rows
|
|
2205
2148
|
const useSQLPaging = QueryPagingEngine.ShouldPage(params.StartRow, params.MaxRows);
|
|
2206
|
-
const cacheConfig = query.CacheConfig;
|
|
2207
2149
|
let paginatedResult;
|
|
2208
2150
|
let totalRowCount;
|
|
2209
2151
|
let executionTime;
|
|
@@ -2215,43 +2157,19 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2215
2157
|
return pagedCacheHit;
|
|
2216
2158
|
}
|
|
2217
2159
|
const paging = QueryPagingEngine.WrapWithPaging(finalSQL, params.StartRow, params.MaxRows, this.PlatformKey);
|
|
2218
|
-
//
|
|
2219
|
-
const cachedCount = cacheConfig?.enabled
|
|
2220
|
-
? await this.QueryCacheMgr.GetTotalRowCount(query, params.Parameters || {})
|
|
2221
|
-
: null;
|
|
2160
|
+
// Execute data + count queries in parallel
|
|
2222
2161
|
const start = Date.now();
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
const [dataResult, countResult] = await Promise.all([
|
|
2235
|
-
this.ExecuteSQL(paging.DataSQL, undefined, undefined, contextUser),
|
|
2236
|
-
this.ExecuteSQL(paging.CountSQL, undefined, undefined, contextUser),
|
|
2237
|
-
]);
|
|
2238
|
-
executionTime = Date.now() - start;
|
|
2239
|
-
if (!dataResult)
|
|
2240
|
-
throw new Error('Error executing paged query SQL');
|
|
2241
|
-
paginatedResult = dataResult;
|
|
2242
|
-
totalRowCount = countResult?.[0]?.TotalRowCount != null
|
|
2243
|
-
? Number(countResult[0].TotalRowCount)
|
|
2244
|
-
: paginatedResult.length;
|
|
2245
|
-
// Cache the count for subsequent page requests (fire-and-forget)
|
|
2246
|
-
if (cacheConfig?.enabled) {
|
|
2247
|
-
void this.QueryCacheMgr.SetTotalRowCount(query, params.Parameters || {}, totalRowCount);
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
// Cache the paged results (fire-and-forget)
|
|
2251
|
-
if (cacheConfig?.enabled) {
|
|
2252
|
-
void this.QueryCacheMgr.SetPaged(query, params.Parameters || {}, params.StartRow, params.MaxRows, paginatedResult);
|
|
2253
|
-
void this.QueryCacheMgr.InvalidateWithDependents(query);
|
|
2254
|
-
}
|
|
2162
|
+
const [dataResult, countResult] = await Promise.all([
|
|
2163
|
+
this.ExecuteSQL(paging.DataSQL, undefined, undefined, contextUser),
|
|
2164
|
+
this.ExecuteSQL(paging.CountSQL, undefined, undefined, contextUser),
|
|
2165
|
+
]);
|
|
2166
|
+
executionTime = Date.now() - start;
|
|
2167
|
+
if (!dataResult)
|
|
2168
|
+
throw new Error('Error executing paged query SQL');
|
|
2169
|
+
paginatedResult = dataResult;
|
|
2170
|
+
totalRowCount = countResult?.[0]?.TotalRowCount != null
|
|
2171
|
+
? Number(countResult[0].TotalRowCount)
|
|
2172
|
+
: paginatedResult.length;
|
|
2255
2173
|
}
|
|
2256
2174
|
else {
|
|
2257
2175
|
// Check full-result cache before executing
|
|
@@ -2327,30 +2245,7 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2327
2245
|
ErrorMessage: validation.error || 'SQL validation failed',
|
|
2328
2246
|
};
|
|
2329
2247
|
}
|
|
2330
|
-
// Check ad-hoc cache if opt-in TTL is provided
|
|
2331
|
-
const adhocTTL = params.AdhocCacheTTLMinutes;
|
|
2332
|
-
if (adhocTTL != null && adhocTTL > 0) {
|
|
2333
|
-
const cached = await this.QueryCacheMgr.GetAdhoc(params.SQL, adhocTTL);
|
|
2334
|
-
if (cached) {
|
|
2335
|
-
const { paginatedResult, totalRowCount } = this.applyQueryPagination(cached.results, params);
|
|
2336
|
-
return {
|
|
2337
|
-
Success: true,
|
|
2338
|
-
QueryID: '',
|
|
2339
|
-
QueryName: 'Ad-Hoc Query',
|
|
2340
|
-
Results: paginatedResult,
|
|
2341
|
-
RowCount: paginatedResult.length,
|
|
2342
|
-
TotalRowCount: totalRowCount,
|
|
2343
|
-
ExecutionTime: 0,
|
|
2344
|
-
ErrorMessage: '',
|
|
2345
|
-
CacheHit: true,
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
2248
|
const { result, executionTime } = await this.executeQueryWithTiming(params.SQL, contextUser);
|
|
2350
|
-
// Store in ad-hoc cache if opt-in (fire-and-forget)
|
|
2351
|
-
if (adhocTTL != null && adhocTTL > 0) {
|
|
2352
|
-
void this.QueryCacheMgr.SetAdhoc(params.SQL, adhocTTL, result);
|
|
2353
|
-
}
|
|
2354
2249
|
const { paginatedResult, totalRowCount } = this.applyQueryPagination(result, params);
|
|
2355
2250
|
return {
|
|
2356
2251
|
Success: true,
|
|
@@ -2380,10 +2275,10 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2380
2275
|
}
|
|
2381
2276
|
/**
|
|
2382
2277
|
* Finds a query from RunQueryParams and validates user permissions.
|
|
2383
|
-
* Uses `
|
|
2278
|
+
* Uses `resolveQuery()` for lookup and `ValidateQueryForExecution()` for permissions.
|
|
2384
2279
|
*/
|
|
2385
2280
|
findAndValidateQuery(params, contextUser) {
|
|
2386
|
-
const query = this.
|
|
2281
|
+
const query = this.resolveQuery(params);
|
|
2387
2282
|
if (!query) {
|
|
2388
2283
|
let errorDetails = 'Query not found';
|
|
2389
2284
|
if (params.QueryName) {
|
|
@@ -2415,8 +2310,12 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2415
2310
|
Platform: this.PlatformKey,
|
|
2416
2311
|
ContextUser: contextUser,
|
|
2417
2312
|
Parameters: parameters,
|
|
2418
|
-
UsesTemplate: query.UsesTemplate,
|
|
2419
|
-
QueryInfo:
|
|
2313
|
+
UsesTemplate: query.UsesTemplate ?? false,
|
|
2314
|
+
QueryInfo: {
|
|
2315
|
+
SQL: query.SQL ?? '',
|
|
2316
|
+
UsesTemplate: query.UsesTemplate ?? false,
|
|
2317
|
+
Parameters: query.QueryParameters,
|
|
2318
|
+
},
|
|
2420
2319
|
});
|
|
2421
2320
|
if (!result.HasCompositions && !query.UsesTemplate && parameters && Object.keys(parameters).length > 0) {
|
|
2422
2321
|
LogStatus('Warning: Parameters provided but query does not use templates. Parameters will be ignored.');
|
|
@@ -2474,11 +2373,15 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2474
2373
|
* pipeline. Supports inline dependencies for transient query testing.
|
|
2475
2374
|
*/
|
|
2476
2375
|
resolveSpecParameters(spec, contextUser) {
|
|
2376
|
+
// QueryExecutionSpec.ParameterDefinitions is QueryParameterInfo[] (MJCore),
|
|
2377
|
+
// while RenderContext expects MJQueryParameterEntity[] (core-entities).
|
|
2378
|
+
// Both share the structural shape the processor needs (Name, DataType, IsRequired).
|
|
2379
|
+
const paramDefs = spec.ParameterDefinitions;
|
|
2477
2380
|
const result = RenderPipeline.Run(spec.SQL, {
|
|
2478
2381
|
Platform: this.PlatformKey,
|
|
2479
2382
|
ContextUser: contextUser,
|
|
2480
2383
|
Parameters: spec.Parameters,
|
|
2481
|
-
ParameterDefinitions:
|
|
2384
|
+
ParameterDefinitions: paramDefs,
|
|
2482
2385
|
UsesTemplate: spec.UsesTemplate,
|
|
2483
2386
|
Dependencies: spec.Dependencies,
|
|
2484
2387
|
OriginalSQL: spec.SQL,
|
|
@@ -2492,62 +2395,18 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2492
2395
|
// wrapWithMaxRows is now handled by RenderPipeline.applyMaxRows
|
|
2493
2396
|
/**
|
|
2494
2397
|
* Checks the query cache for existing results and returns them if valid.
|
|
2398
|
+
* Currently always returns null (query caching is not active).
|
|
2495
2399
|
*/
|
|
2496
|
-
async checkQueryCache(
|
|
2497
|
-
|
|
2498
|
-
if (!cacheConfig?.enabled) {
|
|
2499
|
-
return null;
|
|
2500
|
-
}
|
|
2501
|
-
const cached = await this.QueryCacheMgr.Get(query, params.Parameters || {});
|
|
2502
|
-
if (!cached) {
|
|
2503
|
-
return null;
|
|
2504
|
-
}
|
|
2505
|
-
LogStatus(`Cache hit for query ${query.Name} (${query.ID})`);
|
|
2506
|
-
const { paginatedResult, totalRowCount } = this.applyQueryPagination(cached.results, params);
|
|
2507
|
-
return {
|
|
2508
|
-
Success: true,
|
|
2509
|
-
QueryID: query.ID,
|
|
2510
|
-
QueryName: query.Name,
|
|
2511
|
-
Results: paginatedResult,
|
|
2512
|
-
RowCount: paginatedResult.length,
|
|
2513
|
-
TotalRowCount: totalRowCount,
|
|
2514
|
-
ExecutionTime: 0,
|
|
2515
|
-
ErrorMessage: '',
|
|
2516
|
-
AppliedParameters: appliedParameters,
|
|
2517
|
-
CacheHit: true,
|
|
2518
|
-
CacheTTLRemaining: cached.ttlRemainingMs,
|
|
2519
|
-
};
|
|
2400
|
+
async checkQueryCache(_query, _params, _appliedParameters) {
|
|
2401
|
+
return null;
|
|
2520
2402
|
}
|
|
2521
2403
|
/**
|
|
2522
2404
|
* Checks the paged cache for a specific page of query results.
|
|
2523
2405
|
* Returns a full RunQueryResult on hit, null on miss.
|
|
2406
|
+
* Currently always returns null (query caching is not active).
|
|
2524
2407
|
*/
|
|
2525
|
-
async checkPagedQueryCache(
|
|
2526
|
-
|
|
2527
|
-
if (!cacheConfig?.enabled)
|
|
2528
|
-
return null;
|
|
2529
|
-
const cached = await this.QueryCacheMgr.GetPaged(query, params.Parameters || {}, params.StartRow, params.MaxRows);
|
|
2530
|
-
if (!cached)
|
|
2531
|
-
return null;
|
|
2532
|
-
// Also try to get the cached count
|
|
2533
|
-
const cachedCount = await this.QueryCacheMgr.GetTotalRowCount(query, params.Parameters || {});
|
|
2534
|
-
const totalRowCount = cachedCount ?? cached.results.length;
|
|
2535
|
-
LogStatus(`Paged cache hit for query ${query.Name} (${query.ID}) page ${params.StartRow}+${params.MaxRows}`);
|
|
2536
|
-
return {
|
|
2537
|
-
Success: true,
|
|
2538
|
-
QueryID: query.ID,
|
|
2539
|
-
QueryName: query.Name,
|
|
2540
|
-
Results: cached.results,
|
|
2541
|
-
RowCount: cached.results.length,
|
|
2542
|
-
TotalRowCount: totalRowCount,
|
|
2543
|
-
PageNumber: Math.floor(params.StartRow / params.MaxRows) + 1,
|
|
2544
|
-
PageSize: params.MaxRows,
|
|
2545
|
-
ExecutionTime: 0,
|
|
2546
|
-
ErrorMessage: '',
|
|
2547
|
-
AppliedParameters: appliedParameters,
|
|
2548
|
-
CacheHit: true,
|
|
2549
|
-
CacheTTLRemaining: cached.ttlRemainingMs,
|
|
2550
|
-
};
|
|
2408
|
+
async checkPagedQueryCache(_query, _params, _appliedParameters) {
|
|
2409
|
+
return null;
|
|
2551
2410
|
}
|
|
2552
2411
|
/**
|
|
2553
2412
|
* Executes the query SQL and tracks execution time.
|
|
@@ -2603,16 +2462,10 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2603
2462
|
}
|
|
2604
2463
|
/**
|
|
2605
2464
|
* Caches query results if caching is enabled for the query.
|
|
2606
|
-
*
|
|
2465
|
+
* Currently a no-op (query caching is not active).
|
|
2607
2466
|
*/
|
|
2608
|
-
async cacheQueryResults(
|
|
2609
|
-
|
|
2610
|
-
if (!cacheConfig?.enabled) {
|
|
2611
|
-
return;
|
|
2612
|
-
}
|
|
2613
|
-
await this.QueryCacheMgr.Set(query, parameters, results);
|
|
2614
|
-
await this.QueryCacheMgr.InvalidateWithDependents(query);
|
|
2615
|
-
LogStatus(`Cached results for query ${query.Name} (${query.ID})`);
|
|
2467
|
+
async cacheQueryResults(_query, _parameters, _results) {
|
|
2468
|
+
// No-op: query caching has been removed
|
|
2616
2469
|
}
|
|
2617
2470
|
/**************************************************************************/
|
|
2618
2471
|
// Load — Shared Implementation
|
|
@@ -2631,9 +2484,9 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2631
2484
|
const quotes = pk.NeedsQuotes ? "'" : '';
|
|
2632
2485
|
return `${this.QuoteIdentifier(pk.CodeName)}=${quotes}${val.Value}${quotes}`;
|
|
2633
2486
|
}).join(' AND ');
|
|
2634
|
-
// Append Read RLS filter
|
|
2487
|
+
// Append Read RLS filter (exemption check is centralized in GetUserRowLevelSecurityWhereClause)
|
|
2635
2488
|
let fullWhere = where;
|
|
2636
|
-
if (user
|
|
2489
|
+
if (user) {
|
|
2637
2490
|
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
|
|
2638
2491
|
if (rlsWhereClause && rlsWhereClause.length > 0) {
|
|
2639
2492
|
fullWhere = `${where} AND (${rlsWhereClause})`;
|
|
@@ -2693,9 +2546,6 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2693
2546
|
*/
|
|
2694
2547
|
async CheckRecordRLS(entity, user, type) {
|
|
2695
2548
|
const entityInfo = entity.EntityInfo;
|
|
2696
|
-
if (entityInfo.UserExemptFromRowLevelSecurity(user, type)) {
|
|
2697
|
-
return true;
|
|
2698
|
-
}
|
|
2699
2549
|
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, type, '');
|
|
2700
2550
|
if (!rlsWhereClause || rlsWhereClause.length === 0) {
|
|
2701
2551
|
return true;
|
|
@@ -2715,9 +2565,6 @@ export class GenericDatabaseProvider extends DatabaseProviderBase {
|
|
|
2715
2565
|
*/
|
|
2716
2566
|
async CheckCreateRLS(entity, user) {
|
|
2717
2567
|
const entityInfo = entity.EntityInfo;
|
|
2718
|
-
if (entityInfo.UserExemptFromRowLevelSecurity(user, EntityPermissionType.Create)) {
|
|
2719
|
-
return true;
|
|
2720
|
-
}
|
|
2721
2568
|
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Create, '');
|
|
2722
2569
|
if (!rlsWhereClause || rlsWhereClause.length === 0) {
|
|
2723
2570
|
return true;
|