@querypanel/node-sdk 1.3.7 → 1.3.9
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 +34 -0
- package/dist/index.cjs +318 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -3
- package/dist/index.d.ts +50 -3
- package/dist/index.js +313 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -230,6 +230,29 @@ declare class ClickHouseAdapter implements DatabaseAdapter {
|
|
|
230
230
|
private extractRows;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/** Attach option for schema-per-tenant isolation. */
|
|
234
|
+
interface TenantSchemaConfig {
|
|
235
|
+
/** Schema name with one `{tenant}` placeholder, e.g. `group_{tenant}_indexed`. */
|
|
236
|
+
template: string;
|
|
237
|
+
/** Extra schemas every tenant may read (lookup tables etc.). */
|
|
238
|
+
sharedSchemas?: string[];
|
|
239
|
+
}
|
|
240
|
+
declare class TenantSchemaError extends Error {
|
|
241
|
+
constructor(message: string);
|
|
242
|
+
}
|
|
243
|
+
interface ParsedTemplate {
|
|
244
|
+
prefix: string;
|
|
245
|
+
suffix: string;
|
|
246
|
+
}
|
|
247
|
+
declare function parseTenantSchemaTemplate(template: string): ParsedTemplate;
|
|
248
|
+
/** The schema a tenant's queries run against. Throws for unsafe tenant ids. */
|
|
249
|
+
declare function resolveTenantSchema(template: string, tenantId: string): string;
|
|
250
|
+
/**
|
|
251
|
+
* Rewrite every template-schema qualifier to the tenant's schema. Fails closed when
|
|
252
|
+
* another tenant's schema is still named anywhere (strings and comments included).
|
|
253
|
+
*/
|
|
254
|
+
declare function rewriteTenantSchema(sql: string, template: string, tenantId: string): string;
|
|
255
|
+
|
|
233
256
|
interface PostgresQueryResult {
|
|
234
257
|
rows: Array<Record<string, unknown>>;
|
|
235
258
|
fields: Array<{
|
|
@@ -257,6 +280,12 @@ interface PostgresAdapterOptions {
|
|
|
257
280
|
* to these tables only.
|
|
258
281
|
*/
|
|
259
282
|
allowedTables?: string[];
|
|
283
|
+
/**
|
|
284
|
+
* Schema-per-tenant connection: SQL has been rewritten to the caller's tenant
|
|
285
|
+
* schema, so queries may read any schema the template produces (or the tables
|
|
286
|
+
* `allowedTables` names in it) plus `sharedSchemas`, and nothing else.
|
|
287
|
+
*/
|
|
288
|
+
tenantSchema?: TenantSchemaConfig;
|
|
260
289
|
}
|
|
261
290
|
/**
|
|
262
291
|
* Simplified PostgreSQL adapter following IngestRequest format
|
|
@@ -270,6 +299,8 @@ declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
270
299
|
private readonly kind;
|
|
271
300
|
private readonly dialect;
|
|
272
301
|
private readonly allowedTables?;
|
|
302
|
+
/** Undefined when queries are unrestricted. */
|
|
303
|
+
private readonly allowsRelation?;
|
|
273
304
|
constructor(clientFn: PostgresClientFn, options?: PostgresAdapterOptions);
|
|
274
305
|
execute(sql: string, params?: Record<string, string | number | boolean | string[] | number[]>): Promise<DatabaseExecutionResult>;
|
|
275
306
|
private validateQueryTables;
|
|
@@ -867,6 +898,12 @@ interface AskOptions {
|
|
|
867
898
|
previousSql?: string;
|
|
868
899
|
/** Maximum number of retry attempts on execution failure. */
|
|
869
900
|
maxRetry?: number;
|
|
901
|
+
/**
|
|
902
|
+
* Also retry (within `maxRetry`) when the query returns no rows, or one row of only zeros/NULLs
|
|
903
|
+
* (what `COUNT(*) ... WHERE status = 'approved'` returns when the data stores 'APPROVE').
|
|
904
|
+
* Only that fact is sent to QueryPanel, never rows or values. Default false.
|
|
905
|
+
*/
|
|
906
|
+
retryOnEmptyResult?: boolean;
|
|
870
907
|
/** Maximum number of retries for chart generation. */
|
|
871
908
|
chartMaxRetries?: number;
|
|
872
909
|
/** Choose chart generation method. */
|
|
@@ -1321,6 +1358,16 @@ declare function mapGeneratedParams(params: Array<Record<string, unknown>>): Par
|
|
|
1321
1358
|
*/
|
|
1322
1359
|
declare function normalizeSqlParamsInput(params: unknown[] | Record<string, unknown> | null | undefined): ParamRecord;
|
|
1323
1360
|
|
|
1361
|
+
/** Schema-per-tenant option for attachPostgres / attachRedshift (instead of tenantFieldName). */
|
|
1362
|
+
interface TenantSchemaAttachOptions {
|
|
1363
|
+
/**
|
|
1364
|
+
* Each tenant has its own schema, e.g. `{ template: "group_{tenant}_indexed" }`.
|
|
1365
|
+
* SQL naming any schema the template produces runs against the caller's tenant
|
|
1366
|
+
* schema, and only that schema (plus `sharedSchemas`) may be read.
|
|
1367
|
+
*/
|
|
1368
|
+
tenantSchema?: TenantSchemaConfig;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1324
1371
|
/** Options when constructing the SDK with HTTP (default) or with a custom API implementation. */
|
|
1325
1372
|
interface QueryPanelSdkAPIOptions {
|
|
1326
1373
|
defaultTenantId?: string;
|
|
@@ -1400,7 +1447,7 @@ declare class QueryPanelSdkAPI {
|
|
|
1400
1447
|
tenantFieldName?: string;
|
|
1401
1448
|
tenantFieldType?: string;
|
|
1402
1449
|
enforceTenantIsolation?: boolean;
|
|
1403
|
-
}): void;
|
|
1450
|
+
} & TenantSchemaAttachOptions): void;
|
|
1404
1451
|
/**
|
|
1405
1452
|
* Attach an Amazon Redshift database. Redshift speaks the Postgres wire
|
|
1406
1453
|
* protocol, so `clientFn` can wrap a `pg` client; introspection uses
|
|
@@ -1412,7 +1459,7 @@ declare class QueryPanelSdkAPI {
|
|
|
1412
1459
|
tenantFieldName?: string;
|
|
1413
1460
|
tenantFieldType?: string;
|
|
1414
1461
|
enforceTenantIsolation?: boolean;
|
|
1415
|
-
}): void;
|
|
1462
|
+
} & TenantSchemaAttachOptions): void;
|
|
1416
1463
|
attachBigQuery(name: string, clientFn: BigQueryClientFn, options: BigQueryAdapterOptions & {
|
|
1417
1464
|
description?: string;
|
|
1418
1465
|
tags?: string[];
|
|
@@ -2187,4 +2234,4 @@ declare class QueryPanelSdkAPI {
|
|
|
2187
2234
|
}, signal?: AbortSignal): Promise<SdkDashboard[]>;
|
|
2188
2235
|
}
|
|
2189
2236
|
|
|
2190
|
-
export { ALL_VIZ_CHART_TYPES, type ActiveChartBulkGetResponse, type ActiveChartCreateInput, type ActiveChartListOptions, type ActiveChartUpdateInput, type AggregateOp, type AskOptions, type AskResponse, type AxisField, type AxisFieldInput, BigQueryAdapter, type BigQueryAdapterOptions, type BigQueryClientFn, type BigQueryQueryRequest, type BigQueryQueryResult, CallbackApiClient, type CatalogSchema, type CatalogTable, type CatalogTableType, type ChartBulkGetResponse, type ChartCreateInput, type ChartEncoding, type ChartEnvelope, type ChartListAllOptions, type ChartListOptions, type ChartModifyInput, type ChartModifyOptions, type ChartModifyResponse, type ChartSpec, type ChartType, type ChartUpdateInput, ClickHouseAdapter, type ClickHouseAdapterOptions, type ClickHouseClientFn, type ContextDocument, type DashboardCreateInput, type DashboardForkInput, type DashboardForkUpdateInput, type DashboardListOptions, type DashboardUpdateInput, type DatabaseAdapter, type DatabaseDialect, type DateRangeInput, type EncodingHints, type FieldRef, type FieldRefInput, type FieldType, type IQueryPanelApi, type IngestResponse, type IntentResult, type ListTablesOptions, type MapEncoding, type MapLocation, type MapLocationMode, type MapSpec, type MetricEncoding, type MetricField, type MetricSpec, type PaginatedResponse$1 as PaginatedResponse, type PaginationInfo$1 as PaginationInfo, type PaginationQuery$1 as PaginationQuery, type ParamRecord, type ParamValue, type PipelineTrace, PostgresAdapter, type PostgresAdapterOptions, type PostgresClientFn, type PostgresWireDialect, QueryErrorCode, QueryErrorCode as QueryErrorCodeType, QueryPanelSdkAPI, type QueryPanelSdkAPIOptions, QueryPipelineError, type RequestHandler, type RequestHandlerOptions, type RequestHandlerResult, type SchemaIngestBatchEmbedResponse, type SchemaIngestRequestOptions, type SchemaIntrospection, type SchemaSyncOptions, type SchemaSyncProgressEvent, SchemaTooLargeError, type SdkActiveChart, type SdkChart, type SdkDashboard, type SdkSession, type SdkSessionTurn, type SessionGetOptions, type SessionListOptions, type SessionUpdateInput, type SqlModifications, type StackingMode, type StageSchemaIngestOptions, type StageSchemaIngestResult, type SyncSchemaInBatchesOptions, type TableColumn, type TableEncoding, type TableSpec, type TimeUnit, type ValueFormat, type VizModifications, type VizSpec, type VizSpecGenerateInput, type VizSpecGenerateOptions, type VizSpecGeneratorInput, type VizSpecKind, type VizSpecResponse, type VizSpecResult, anonymizeResults, mapGeneratedParams, normalizeSqlParamsInput, resolveQueryPipeline };
|
|
2237
|
+
export { ALL_VIZ_CHART_TYPES, type ActiveChartBulkGetResponse, type ActiveChartCreateInput, type ActiveChartListOptions, type ActiveChartUpdateInput, type AggregateOp, type AskOptions, type AskResponse, type AxisField, type AxisFieldInput, BigQueryAdapter, type BigQueryAdapterOptions, type BigQueryClientFn, type BigQueryQueryRequest, type BigQueryQueryResult, CallbackApiClient, type CatalogSchema, type CatalogTable, type CatalogTableType, type ChartBulkGetResponse, type ChartCreateInput, type ChartEncoding, type ChartEnvelope, type ChartListAllOptions, type ChartListOptions, type ChartModifyInput, type ChartModifyOptions, type ChartModifyResponse, type ChartSpec, type ChartType, type ChartUpdateInput, ClickHouseAdapter, type ClickHouseAdapterOptions, type ClickHouseClientFn, type ContextDocument, type DashboardCreateInput, type DashboardForkInput, type DashboardForkUpdateInput, type DashboardListOptions, type DashboardUpdateInput, type DatabaseAdapter, type DatabaseDialect, type DateRangeInput, type EncodingHints, type FieldRef, type FieldRefInput, type FieldType, type IQueryPanelApi, type IngestResponse, type IntentResult, type ListTablesOptions, type MapEncoding, type MapLocation, type MapLocationMode, type MapSpec, type MetricEncoding, type MetricField, type MetricSpec, type PaginatedResponse$1 as PaginatedResponse, type PaginationInfo$1 as PaginationInfo, type PaginationQuery$1 as PaginationQuery, type ParamRecord, type ParamValue, type PipelineTrace, PostgresAdapter, type PostgresAdapterOptions, type PostgresClientFn, type PostgresWireDialect, QueryErrorCode, QueryErrorCode as QueryErrorCodeType, QueryPanelSdkAPI, type QueryPanelSdkAPIOptions, QueryPipelineError, type RequestHandler, type RequestHandlerOptions, type RequestHandlerResult, type SchemaIngestBatchEmbedResponse, type SchemaIngestRequestOptions, type SchemaIntrospection, type SchemaSyncOptions, type SchemaSyncProgressEvent, SchemaTooLargeError, type SdkActiveChart, type SdkChart, type SdkDashboard, type SdkSession, type SdkSessionTurn, type SessionGetOptions, type SessionListOptions, type SessionUpdateInput, type SqlModifications, type StackingMode, type StageSchemaIngestOptions, type StageSchemaIngestResult, type SyncSchemaInBatchesOptions, type TableColumn, type TableEncoding, type TableSpec, type TenantSchemaAttachOptions, type TenantSchemaConfig, TenantSchemaError, type TimeUnit, type ValueFormat, type VizModifications, type VizSpec, type VizSpecGenerateInput, type VizSpecGenerateOptions, type VizSpecGeneratorInput, type VizSpecKind, type VizSpecResponse, type VizSpecResult, anonymizeResults, mapGeneratedParams, normalizeSqlParamsInput, parseTenantSchemaTemplate, resolveQueryPipeline, resolveTenantSchema, rewriteTenantSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -230,6 +230,29 @@ declare class ClickHouseAdapter implements DatabaseAdapter {
|
|
|
230
230
|
private extractRows;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/** Attach option for schema-per-tenant isolation. */
|
|
234
|
+
interface TenantSchemaConfig {
|
|
235
|
+
/** Schema name with one `{tenant}` placeholder, e.g. `group_{tenant}_indexed`. */
|
|
236
|
+
template: string;
|
|
237
|
+
/** Extra schemas every tenant may read (lookup tables etc.). */
|
|
238
|
+
sharedSchemas?: string[];
|
|
239
|
+
}
|
|
240
|
+
declare class TenantSchemaError extends Error {
|
|
241
|
+
constructor(message: string);
|
|
242
|
+
}
|
|
243
|
+
interface ParsedTemplate {
|
|
244
|
+
prefix: string;
|
|
245
|
+
suffix: string;
|
|
246
|
+
}
|
|
247
|
+
declare function parseTenantSchemaTemplate(template: string): ParsedTemplate;
|
|
248
|
+
/** The schema a tenant's queries run against. Throws for unsafe tenant ids. */
|
|
249
|
+
declare function resolveTenantSchema(template: string, tenantId: string): string;
|
|
250
|
+
/**
|
|
251
|
+
* Rewrite every template-schema qualifier to the tenant's schema. Fails closed when
|
|
252
|
+
* another tenant's schema is still named anywhere (strings and comments included).
|
|
253
|
+
*/
|
|
254
|
+
declare function rewriteTenantSchema(sql: string, template: string, tenantId: string): string;
|
|
255
|
+
|
|
233
256
|
interface PostgresQueryResult {
|
|
234
257
|
rows: Array<Record<string, unknown>>;
|
|
235
258
|
fields: Array<{
|
|
@@ -257,6 +280,12 @@ interface PostgresAdapterOptions {
|
|
|
257
280
|
* to these tables only.
|
|
258
281
|
*/
|
|
259
282
|
allowedTables?: string[];
|
|
283
|
+
/**
|
|
284
|
+
* Schema-per-tenant connection: SQL has been rewritten to the caller's tenant
|
|
285
|
+
* schema, so queries may read any schema the template produces (or the tables
|
|
286
|
+
* `allowedTables` names in it) plus `sharedSchemas`, and nothing else.
|
|
287
|
+
*/
|
|
288
|
+
tenantSchema?: TenantSchemaConfig;
|
|
260
289
|
}
|
|
261
290
|
/**
|
|
262
291
|
* Simplified PostgreSQL adapter following IngestRequest format
|
|
@@ -270,6 +299,8 @@ declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
270
299
|
private readonly kind;
|
|
271
300
|
private readonly dialect;
|
|
272
301
|
private readonly allowedTables?;
|
|
302
|
+
/** Undefined when queries are unrestricted. */
|
|
303
|
+
private readonly allowsRelation?;
|
|
273
304
|
constructor(clientFn: PostgresClientFn, options?: PostgresAdapterOptions);
|
|
274
305
|
execute(sql: string, params?: Record<string, string | number | boolean | string[] | number[]>): Promise<DatabaseExecutionResult>;
|
|
275
306
|
private validateQueryTables;
|
|
@@ -867,6 +898,12 @@ interface AskOptions {
|
|
|
867
898
|
previousSql?: string;
|
|
868
899
|
/** Maximum number of retry attempts on execution failure. */
|
|
869
900
|
maxRetry?: number;
|
|
901
|
+
/**
|
|
902
|
+
* Also retry (within `maxRetry`) when the query returns no rows, or one row of only zeros/NULLs
|
|
903
|
+
* (what `COUNT(*) ... WHERE status = 'approved'` returns when the data stores 'APPROVE').
|
|
904
|
+
* Only that fact is sent to QueryPanel, never rows or values. Default false.
|
|
905
|
+
*/
|
|
906
|
+
retryOnEmptyResult?: boolean;
|
|
870
907
|
/** Maximum number of retries for chart generation. */
|
|
871
908
|
chartMaxRetries?: number;
|
|
872
909
|
/** Choose chart generation method. */
|
|
@@ -1321,6 +1358,16 @@ declare function mapGeneratedParams(params: Array<Record<string, unknown>>): Par
|
|
|
1321
1358
|
*/
|
|
1322
1359
|
declare function normalizeSqlParamsInput(params: unknown[] | Record<string, unknown> | null | undefined): ParamRecord;
|
|
1323
1360
|
|
|
1361
|
+
/** Schema-per-tenant option for attachPostgres / attachRedshift (instead of tenantFieldName). */
|
|
1362
|
+
interface TenantSchemaAttachOptions {
|
|
1363
|
+
/**
|
|
1364
|
+
* Each tenant has its own schema, e.g. `{ template: "group_{tenant}_indexed" }`.
|
|
1365
|
+
* SQL naming any schema the template produces runs against the caller's tenant
|
|
1366
|
+
* schema, and only that schema (plus `sharedSchemas`) may be read.
|
|
1367
|
+
*/
|
|
1368
|
+
tenantSchema?: TenantSchemaConfig;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1324
1371
|
/** Options when constructing the SDK with HTTP (default) or with a custom API implementation. */
|
|
1325
1372
|
interface QueryPanelSdkAPIOptions {
|
|
1326
1373
|
defaultTenantId?: string;
|
|
@@ -1400,7 +1447,7 @@ declare class QueryPanelSdkAPI {
|
|
|
1400
1447
|
tenantFieldName?: string;
|
|
1401
1448
|
tenantFieldType?: string;
|
|
1402
1449
|
enforceTenantIsolation?: boolean;
|
|
1403
|
-
}): void;
|
|
1450
|
+
} & TenantSchemaAttachOptions): void;
|
|
1404
1451
|
/**
|
|
1405
1452
|
* Attach an Amazon Redshift database. Redshift speaks the Postgres wire
|
|
1406
1453
|
* protocol, so `clientFn` can wrap a `pg` client; introspection uses
|
|
@@ -1412,7 +1459,7 @@ declare class QueryPanelSdkAPI {
|
|
|
1412
1459
|
tenantFieldName?: string;
|
|
1413
1460
|
tenantFieldType?: string;
|
|
1414
1461
|
enforceTenantIsolation?: boolean;
|
|
1415
|
-
}): void;
|
|
1462
|
+
} & TenantSchemaAttachOptions): void;
|
|
1416
1463
|
attachBigQuery(name: string, clientFn: BigQueryClientFn, options: BigQueryAdapterOptions & {
|
|
1417
1464
|
description?: string;
|
|
1418
1465
|
tags?: string[];
|
|
@@ -2187,4 +2234,4 @@ declare class QueryPanelSdkAPI {
|
|
|
2187
2234
|
}, signal?: AbortSignal): Promise<SdkDashboard[]>;
|
|
2188
2235
|
}
|
|
2189
2236
|
|
|
2190
|
-
export { ALL_VIZ_CHART_TYPES, type ActiveChartBulkGetResponse, type ActiveChartCreateInput, type ActiveChartListOptions, type ActiveChartUpdateInput, type AggregateOp, type AskOptions, type AskResponse, type AxisField, type AxisFieldInput, BigQueryAdapter, type BigQueryAdapterOptions, type BigQueryClientFn, type BigQueryQueryRequest, type BigQueryQueryResult, CallbackApiClient, type CatalogSchema, type CatalogTable, type CatalogTableType, type ChartBulkGetResponse, type ChartCreateInput, type ChartEncoding, type ChartEnvelope, type ChartListAllOptions, type ChartListOptions, type ChartModifyInput, type ChartModifyOptions, type ChartModifyResponse, type ChartSpec, type ChartType, type ChartUpdateInput, ClickHouseAdapter, type ClickHouseAdapterOptions, type ClickHouseClientFn, type ContextDocument, type DashboardCreateInput, type DashboardForkInput, type DashboardForkUpdateInput, type DashboardListOptions, type DashboardUpdateInput, type DatabaseAdapter, type DatabaseDialect, type DateRangeInput, type EncodingHints, type FieldRef, type FieldRefInput, type FieldType, type IQueryPanelApi, type IngestResponse, type IntentResult, type ListTablesOptions, type MapEncoding, type MapLocation, type MapLocationMode, type MapSpec, type MetricEncoding, type MetricField, type MetricSpec, type PaginatedResponse$1 as PaginatedResponse, type PaginationInfo$1 as PaginationInfo, type PaginationQuery$1 as PaginationQuery, type ParamRecord, type ParamValue, type PipelineTrace, PostgresAdapter, type PostgresAdapterOptions, type PostgresClientFn, type PostgresWireDialect, QueryErrorCode, QueryErrorCode as QueryErrorCodeType, QueryPanelSdkAPI, type QueryPanelSdkAPIOptions, QueryPipelineError, type RequestHandler, type RequestHandlerOptions, type RequestHandlerResult, type SchemaIngestBatchEmbedResponse, type SchemaIngestRequestOptions, type SchemaIntrospection, type SchemaSyncOptions, type SchemaSyncProgressEvent, SchemaTooLargeError, type SdkActiveChart, type SdkChart, type SdkDashboard, type SdkSession, type SdkSessionTurn, type SessionGetOptions, type SessionListOptions, type SessionUpdateInput, type SqlModifications, type StackingMode, type StageSchemaIngestOptions, type StageSchemaIngestResult, type SyncSchemaInBatchesOptions, type TableColumn, type TableEncoding, type TableSpec, type TimeUnit, type ValueFormat, type VizModifications, type VizSpec, type VizSpecGenerateInput, type VizSpecGenerateOptions, type VizSpecGeneratorInput, type VizSpecKind, type VizSpecResponse, type VizSpecResult, anonymizeResults, mapGeneratedParams, normalizeSqlParamsInput, resolveQueryPipeline };
|
|
2237
|
+
export { ALL_VIZ_CHART_TYPES, type ActiveChartBulkGetResponse, type ActiveChartCreateInput, type ActiveChartListOptions, type ActiveChartUpdateInput, type AggregateOp, type AskOptions, type AskResponse, type AxisField, type AxisFieldInput, BigQueryAdapter, type BigQueryAdapterOptions, type BigQueryClientFn, type BigQueryQueryRequest, type BigQueryQueryResult, CallbackApiClient, type CatalogSchema, type CatalogTable, type CatalogTableType, type ChartBulkGetResponse, type ChartCreateInput, type ChartEncoding, type ChartEnvelope, type ChartListAllOptions, type ChartListOptions, type ChartModifyInput, type ChartModifyOptions, type ChartModifyResponse, type ChartSpec, type ChartType, type ChartUpdateInput, ClickHouseAdapter, type ClickHouseAdapterOptions, type ClickHouseClientFn, type ContextDocument, type DashboardCreateInput, type DashboardForkInput, type DashboardForkUpdateInput, type DashboardListOptions, type DashboardUpdateInput, type DatabaseAdapter, type DatabaseDialect, type DateRangeInput, type EncodingHints, type FieldRef, type FieldRefInput, type FieldType, type IQueryPanelApi, type IngestResponse, type IntentResult, type ListTablesOptions, type MapEncoding, type MapLocation, type MapLocationMode, type MapSpec, type MetricEncoding, type MetricField, type MetricSpec, type PaginatedResponse$1 as PaginatedResponse, type PaginationInfo$1 as PaginationInfo, type PaginationQuery$1 as PaginationQuery, type ParamRecord, type ParamValue, type PipelineTrace, PostgresAdapter, type PostgresAdapterOptions, type PostgresClientFn, type PostgresWireDialect, QueryErrorCode, QueryErrorCode as QueryErrorCodeType, QueryPanelSdkAPI, type QueryPanelSdkAPIOptions, QueryPipelineError, type RequestHandler, type RequestHandlerOptions, type RequestHandlerResult, type SchemaIngestBatchEmbedResponse, type SchemaIngestRequestOptions, type SchemaIntrospection, type SchemaSyncOptions, type SchemaSyncProgressEvent, SchemaTooLargeError, type SdkActiveChart, type SdkChart, type SdkDashboard, type SdkSession, type SdkSessionTurn, type SessionGetOptions, type SessionListOptions, type SessionUpdateInput, type SqlModifications, type StackingMode, type StageSchemaIngestOptions, type StageSchemaIngestResult, type SyncSchemaInBatchesOptions, type TableColumn, type TableEncoding, type TableSpec, type TenantSchemaAttachOptions, type TenantSchemaConfig, TenantSchemaError, type TimeUnit, type ValueFormat, type VizModifications, type VizSpec, type VizSpecGenerateInput, type VizSpecGenerateOptions, type VizSpecGeneratorInput, type VizSpecKind, type VizSpecResponse, type VizSpecResult, anonymizeResults, mapGeneratedParams, normalizeSqlParamsInput, parseTenantSchemaTemplate, resolveQueryPipeline, resolveTenantSchema, rewriteTenantSchema };
|
package/dist/index.js
CHANGED
|
@@ -581,6 +581,248 @@ function toNumber(value) {
|
|
|
581
581
|
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
+
// src/core/sql-relations.ts
|
|
585
|
+
function tokenize(sql) {
|
|
586
|
+
const tokens = [];
|
|
587
|
+
let i = 0;
|
|
588
|
+
while (i < sql.length) {
|
|
589
|
+
const ch = sql[i];
|
|
590
|
+
const next = sql[i + 1];
|
|
591
|
+
if (/\s/.test(ch)) {
|
|
592
|
+
i++;
|
|
593
|
+
} else if (ch === "-" && next === "-") {
|
|
594
|
+
while (i < sql.length && sql[i] !== "\n") i++;
|
|
595
|
+
} else if (ch === "/" && next === "*") {
|
|
596
|
+
const end = sql.indexOf("*/", i + 2);
|
|
597
|
+
i = end < 0 ? sql.length : end + 2;
|
|
598
|
+
} else if (ch === "'") {
|
|
599
|
+
i++;
|
|
600
|
+
while (i < sql.length) {
|
|
601
|
+
if (sql[i] === "\\") i += 2;
|
|
602
|
+
else if (sql[i] === "'" && sql[i + 1] === "'") i += 2;
|
|
603
|
+
else if (sql[i] === "'") break;
|
|
604
|
+
else i++;
|
|
605
|
+
}
|
|
606
|
+
i++;
|
|
607
|
+
} else if (ch === "$" && /[A-Za-z_$]/.test(next ?? "")) {
|
|
608
|
+
const tag = /^\$[A-Za-z_]*\$/.exec(sql.slice(i))?.[0];
|
|
609
|
+
if (tag) {
|
|
610
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
611
|
+
i = end < 0 ? sql.length : end + tag.length;
|
|
612
|
+
} else {
|
|
613
|
+
i++;
|
|
614
|
+
}
|
|
615
|
+
} else if (ch === '"') {
|
|
616
|
+
const start = i;
|
|
617
|
+
let text = "";
|
|
618
|
+
i++;
|
|
619
|
+
while (i < sql.length) {
|
|
620
|
+
if (sql[i] === '"' && sql[i + 1] === '"') {
|
|
621
|
+
text += '"';
|
|
622
|
+
i += 2;
|
|
623
|
+
} else if (sql[i] === '"') break;
|
|
624
|
+
else text += sql[i++];
|
|
625
|
+
}
|
|
626
|
+
i++;
|
|
627
|
+
tokens.push({ kind: "word", text, quoted: true, start, end: i });
|
|
628
|
+
} else if (/[A-Za-z_]/.test(ch)) {
|
|
629
|
+
const start = i;
|
|
630
|
+
while (i < sql.length && /[A-Za-z0-9_$]/.test(sql[i])) i++;
|
|
631
|
+
tokens.push({ kind: "word", text: sql.slice(start, i), quoted: false, start, end: i });
|
|
632
|
+
} else if (/[0-9]/.test(ch)) {
|
|
633
|
+
while (i < sql.length && /[0-9.eE]/.test(sql[i])) i++;
|
|
634
|
+
} else {
|
|
635
|
+
tokens.push({ kind: "punct", text: ch, start: i, end: i + 1 });
|
|
636
|
+
i++;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return tokens;
|
|
640
|
+
}
|
|
641
|
+
var identifier = (t) => t?.kind === "word" ? t.quoted ? t.text : t.text.toLowerCase() : void 0;
|
|
642
|
+
var isKeyword = (t, word) => t?.kind === "word" && !t.quoted && t.text.toLowerCase() === word;
|
|
643
|
+
var isPunct = (t, p) => t?.kind === "punct" && t.text === p;
|
|
644
|
+
var CLAUSE_WORDS = /* @__PURE__ */ new Set([
|
|
645
|
+
"where",
|
|
646
|
+
"group",
|
|
647
|
+
"order",
|
|
648
|
+
"limit",
|
|
649
|
+
"having",
|
|
650
|
+
"union",
|
|
651
|
+
"intersect",
|
|
652
|
+
"except",
|
|
653
|
+
"join",
|
|
654
|
+
"inner",
|
|
655
|
+
"left",
|
|
656
|
+
"right",
|
|
657
|
+
"full",
|
|
658
|
+
"cross",
|
|
659
|
+
"natural",
|
|
660
|
+
"on",
|
|
661
|
+
"using",
|
|
662
|
+
"window",
|
|
663
|
+
"qualify",
|
|
664
|
+
"offset",
|
|
665
|
+
"fetch",
|
|
666
|
+
"for",
|
|
667
|
+
"lateral",
|
|
668
|
+
"as"
|
|
669
|
+
]);
|
|
670
|
+
var FROM_CLAUSE_END = /* @__PURE__ */ new Set([
|
|
671
|
+
"where",
|
|
672
|
+
"group",
|
|
673
|
+
"having",
|
|
674
|
+
"order",
|
|
675
|
+
"limit",
|
|
676
|
+
"offset",
|
|
677
|
+
"fetch",
|
|
678
|
+
"union",
|
|
679
|
+
"intersect",
|
|
680
|
+
"except",
|
|
681
|
+
"window",
|
|
682
|
+
"qualify",
|
|
683
|
+
"for",
|
|
684
|
+
"returning"
|
|
685
|
+
]);
|
|
686
|
+
function cteNames(tokens) {
|
|
687
|
+
const names = /* @__PURE__ */ new Set();
|
|
688
|
+
for (let k = 0; k + 2 < tokens.length; k++) {
|
|
689
|
+
const value = identifier(tokens[k]);
|
|
690
|
+
if (!value) continue;
|
|
691
|
+
if (isKeyword(tokens[k + 1], "as") && isPunct(tokens[k + 2], "(")) names.add(value);
|
|
692
|
+
if (isPunct(tokens[k + 1], "(")) {
|
|
693
|
+
let j = k + 2;
|
|
694
|
+
while (j < tokens.length && !isPunct(tokens[j], ")")) j++;
|
|
695
|
+
if (isKeyword(tokens[j + 1], "as") && isPunct(tokens[j + 2], "(")) names.add(value);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return names;
|
|
699
|
+
}
|
|
700
|
+
function findRelations(sql) {
|
|
701
|
+
const tokens = tokenize(sql);
|
|
702
|
+
const ctes = cteNames(tokens);
|
|
703
|
+
const relations = [];
|
|
704
|
+
const readItem = (start) => {
|
|
705
|
+
let k = start;
|
|
706
|
+
if (isKeyword(tokens[k], "lateral")) k++;
|
|
707
|
+
const first = identifier(tokens[k]);
|
|
708
|
+
if (!first) return k;
|
|
709
|
+
const parts = [first];
|
|
710
|
+
k++;
|
|
711
|
+
while (isPunct(tokens[k], ".") && identifier(tokens[k + 1])) {
|
|
712
|
+
parts.push(identifier(tokens[k + 1]));
|
|
713
|
+
k += 2;
|
|
714
|
+
}
|
|
715
|
+
if (isPunct(tokens[k], "(")) return k;
|
|
716
|
+
if (!(parts.length === 1 && ctes.has(first))) relations.push({ parts });
|
|
717
|
+
if (isKeyword(tokens[k], "as")) k++;
|
|
718
|
+
const alias = tokens[k];
|
|
719
|
+
if (alias?.kind === "word" && (alias.quoted || !CLAUSE_WORDS.has(alias.text.toLowerCase()))) k++;
|
|
720
|
+
return k;
|
|
721
|
+
};
|
|
722
|
+
const selectAtDepth = [false];
|
|
723
|
+
const inFromAtDepth = [false];
|
|
724
|
+
const top = () => selectAtDepth.length - 1;
|
|
725
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
726
|
+
const token = tokens[k];
|
|
727
|
+
if (isPunct(token, "(")) {
|
|
728
|
+
selectAtDepth.push(false);
|
|
729
|
+
inFromAtDepth.push(false);
|
|
730
|
+
} else if (isPunct(token, ")")) {
|
|
731
|
+
if (selectAtDepth.length > 1) {
|
|
732
|
+
selectAtDepth.pop();
|
|
733
|
+
inFromAtDepth.pop();
|
|
734
|
+
}
|
|
735
|
+
} else if (isKeyword(token, "select")) {
|
|
736
|
+
selectAtDepth[top()] = true;
|
|
737
|
+
inFromAtDepth[top()] = false;
|
|
738
|
+
} else if (isKeyword(token, "join")) {
|
|
739
|
+
k = readItem(k + 1) - 1;
|
|
740
|
+
} else if (isKeyword(token, "from") && selectAtDepth[top()] && !isKeyword(tokens[k - 1], "distinct")) {
|
|
741
|
+
inFromAtDepth[top()] = true;
|
|
742
|
+
k = readItem(k + 1) - 1;
|
|
743
|
+
} else if (isPunct(token, ",") && inFromAtDepth[top()]) {
|
|
744
|
+
k = readItem(k + 1) - 1;
|
|
745
|
+
} else if (token?.kind === "word" && !token.quoted && FROM_CLAUSE_END.has(token.text.toLowerCase())) {
|
|
746
|
+
inFromAtDepth[top()] = false;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return relations;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/core/tenant-schema.ts
|
|
753
|
+
var TenantSchemaError = class extends Error {
|
|
754
|
+
constructor(message) {
|
|
755
|
+
super(message);
|
|
756
|
+
this.name = "TenantSchemaError";
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
var PLACEHOLDER = "{tenant}";
|
|
760
|
+
var TEMPLATE_PART = /^[a-z0-9_]*$/;
|
|
761
|
+
var TENANT_ID = /^[A-Za-z0-9_]{1,64}$/;
|
|
762
|
+
function parseTenantSchemaTemplate(template) {
|
|
763
|
+
const value = template.trim().toLowerCase();
|
|
764
|
+
const index = value.indexOf(PLACEHOLDER);
|
|
765
|
+
if (index < 0 || value.indexOf(PLACEHOLDER, index + 1) >= 0) {
|
|
766
|
+
throw new TenantSchemaError(
|
|
767
|
+
`Tenant schema template must contain ${PLACEHOLDER} exactly once`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
const prefix = value.slice(0, index);
|
|
771
|
+
const suffix = value.slice(index + PLACEHOLDER.length);
|
|
772
|
+
if (!TEMPLATE_PART.test(prefix) || !TEMPLATE_PART.test(suffix) || !(prefix || suffix)) {
|
|
773
|
+
throw new TenantSchemaError(
|
|
774
|
+
"Tenant schema template may only use a-z, 0-9 and _ around {tenant}"
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return { prefix, suffix };
|
|
778
|
+
}
|
|
779
|
+
function matchesTemplate({ prefix, suffix }, schema) {
|
|
780
|
+
const value = schema.toLowerCase();
|
|
781
|
+
return value.length > prefix.length + suffix.length && value.startsWith(prefix) && value.endsWith(suffix) && /^[a-z0-9_]+$/.test(value.slice(prefix.length, value.length - suffix.length));
|
|
782
|
+
}
|
|
783
|
+
function resolveTenantSchema(template, tenantId) {
|
|
784
|
+
const { prefix, suffix } = parseTenantSchemaTemplate(template);
|
|
785
|
+
const id = tenantId.trim();
|
|
786
|
+
if (!TENANT_ID.test(id)) {
|
|
787
|
+
throw new TenantSchemaError(
|
|
788
|
+
"tenantId must be 1-64 characters of letters, digits or _ for schema-per-tenant isolation"
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return `${prefix}${id.toLowerCase()}${suffix}`;
|
|
792
|
+
}
|
|
793
|
+
function rewriteTenantSchema(sql, template, tenantId) {
|
|
794
|
+
const parsed = parseTenantSchemaTemplate(template);
|
|
795
|
+
const tenantSchema = resolveTenantSchema(template, tenantId);
|
|
796
|
+
const tokens = tokenize(sql);
|
|
797
|
+
let rewritten = "";
|
|
798
|
+
let cursor = 0;
|
|
799
|
+
for (let k = 0; k < tokens.length; k++) {
|
|
800
|
+
const token = tokens[k];
|
|
801
|
+
const value = identifier(token);
|
|
802
|
+
if (!token || !value || !isPunct(tokens[k + 1], ".")) continue;
|
|
803
|
+
if (!matchesTemplate(parsed, value) || value.toLowerCase() === tenantSchema) continue;
|
|
804
|
+
const replacement = token.kind === "word" && token.quoted ? `"${tenantSchema}"` : tenantSchema;
|
|
805
|
+
rewritten += sql.slice(cursor, token.start) + replacement;
|
|
806
|
+
cursor = token.end;
|
|
807
|
+
}
|
|
808
|
+
rewritten += sql.slice(cursor);
|
|
809
|
+
const pattern = new RegExp(`${escapeRegex(parsed.prefix)}[a-z0-9_]+${escapeRegex(parsed.suffix)}`, "gi");
|
|
810
|
+
for (const match of rewritten.matchAll(pattern)) {
|
|
811
|
+
if (match[0].toLowerCase() !== tenantSchema) {
|
|
812
|
+
throw new TenantSchemaError("Query references another tenant's schema");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return rewritten;
|
|
816
|
+
}
|
|
817
|
+
var TENANT_SCHEMA_KEY = PLACEHOLDER;
|
|
818
|
+
function tenantSchemaKey(template) {
|
|
819
|
+
const parsed = parseTenantSchemaTemplate(template);
|
|
820
|
+
return (schema) => matchesTemplate(parsed, schema) ? TENANT_SCHEMA_KEY : schema.toLowerCase();
|
|
821
|
+
}
|
|
822
|
+
function escapeRegex(value) {
|
|
823
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
824
|
+
}
|
|
825
|
+
|
|
584
826
|
// src/adapters/postgres.ts
|
|
585
827
|
var WILDCARD = "*";
|
|
586
828
|
function maxPostgresPlaceholderIndex(sql) {
|
|
@@ -627,6 +869,11 @@ var PostgresAdapter = class {
|
|
|
627
869
|
this.defaultSchema
|
|
628
870
|
);
|
|
629
871
|
}
|
|
872
|
+
this.allowsRelation = buildRelationAllowlist(
|
|
873
|
+
this.allowedTables,
|
|
874
|
+
options.tenantSchema,
|
|
875
|
+
this.defaultSchema
|
|
876
|
+
);
|
|
630
877
|
}
|
|
631
878
|
clientFn;
|
|
632
879
|
databaseName;
|
|
@@ -634,9 +881,11 @@ var PostgresAdapter = class {
|
|
|
634
881
|
kind;
|
|
635
882
|
dialect;
|
|
636
883
|
allowedTables;
|
|
884
|
+
/** Undefined when queries are unrestricted. */
|
|
885
|
+
allowsRelation;
|
|
637
886
|
async execute(sql, params) {
|
|
638
|
-
if (this.
|
|
639
|
-
this.validateQueryTables(sql);
|
|
887
|
+
if (this.allowsRelation) {
|
|
888
|
+
this.validateQueryTables(sql, this.allowsRelation);
|
|
640
889
|
}
|
|
641
890
|
const boundParams = filterParamsForPostgresSql(sql, params);
|
|
642
891
|
let paramArray;
|
|
@@ -647,29 +896,11 @@ var PostgresAdapter = class {
|
|
|
647
896
|
const fields = result.fields.map((f) => f.name);
|
|
648
897
|
return { fields, rows: result.rows };
|
|
649
898
|
}
|
|
650
|
-
validateQueryTables(sql) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
this.allowedTables.map((t) => tableKey2(t.schema, t.table))
|
|
656
|
-
);
|
|
657
|
-
const allowedSchemas = new Set(
|
|
658
|
-
this.allowedTables.filter((t) => t.table === WILDCARD).map((t) => t.schema)
|
|
659
|
-
);
|
|
660
|
-
const neutralizedSql = sql.replace(/EXTRACT\s*\([^)]*FROM\s+[^)]+\)/gi, "EXTRACT(/*neutralized*/)").replace(/SUBSTRING\s*\([^)]*FROM\s+[^)]+\)/gi, "SUBSTRING(/*neutralized*/)").replace(/TRIM\s*\([^)]*FROM\s+[^)]+\)/gi, "TRIM(/*neutralized*/)").replace(/POSITION\s*\([^)]*FROM\s+[^)]+\)/gi, "POSITION(/*neutralized*/)");
|
|
661
|
-
const tablePattern = /(?:FROM|JOIN)\s+(?:ONLY\s+)?(?:([a-zA-Z_][a-zA-Z0-9_]*)\.)?(["']?[a-zA-Z_][a-zA-Z0-9_]*["']?)/gi;
|
|
662
|
-
const matches = neutralizedSql.matchAll(tablePattern);
|
|
663
|
-
for (const match of matches) {
|
|
664
|
-
const schema = match[1] ?? this.defaultSchema;
|
|
665
|
-
const table = match[2]?.replace(/['"]/g, "");
|
|
666
|
-
if (table) {
|
|
667
|
-
const key = tableKey2(schema, table);
|
|
668
|
-
if (!allowedSet.has(key) && !allowedSchemas.has(schema)) {
|
|
669
|
-
throw new Error(
|
|
670
|
-
`Query references table "${schema}.${table}" which is not in the allowed tables list`
|
|
671
|
-
);
|
|
672
|
-
}
|
|
899
|
+
validateQueryTables(sql, allows) {
|
|
900
|
+
for (const relation of findRelations(sql)) {
|
|
901
|
+
if (!allows(relation)) {
|
|
902
|
+
const name = relation.parts.length === 1 ? `${this.defaultSchema}.${relation.parts[0]}` : relation.parts.join(".");
|
|
903
|
+
throw new Error(`Query references table "${name}" which is not in the allowed tables list`);
|
|
673
904
|
}
|
|
674
905
|
}
|
|
675
906
|
}
|
|
@@ -707,6 +938,9 @@ var PostgresAdapter = class {
|
|
|
707
938
|
return positionalParams;
|
|
708
939
|
}
|
|
709
940
|
async validate(sql, params) {
|
|
941
|
+
if (this.allowsRelation) {
|
|
942
|
+
this.validateQueryTables(sql, this.allowsRelation);
|
|
943
|
+
}
|
|
710
944
|
const boundParams = filterParamsForPostgresSql(sql, params);
|
|
711
945
|
let paramArray;
|
|
712
946
|
if (boundParams) {
|
|
@@ -996,6 +1230,27 @@ function buildListTablesQuery(dialect, options) {
|
|
|
996
1230
|
LIMIT ${limit};`;
|
|
997
1231
|
return { sql, params };
|
|
998
1232
|
}
|
|
1233
|
+
function buildRelationAllowlist(allowedTables, tenantSchema, defaultSchema) {
|
|
1234
|
+
if (!allowedTables?.length && !tenantSchema) return void 0;
|
|
1235
|
+
const schemaKey = tenantSchema ? tenantSchemaKey(tenantSchema.template) : (schema) => schema.toLowerCase();
|
|
1236
|
+
const entries = [
|
|
1237
|
+
...allowedTables?.length ? allowedTables : [{ schema: TENANT_SCHEMA_KEY, table: WILDCARD }],
|
|
1238
|
+
...(tenantSchema?.sharedSchemas ?? []).map((schema) => ({ schema, table: WILDCARD }))
|
|
1239
|
+
];
|
|
1240
|
+
const tables = /* @__PURE__ */ new Set();
|
|
1241
|
+
const wildcardSchemas = /* @__PURE__ */ new Set();
|
|
1242
|
+
for (const entry of entries) {
|
|
1243
|
+
const schema = entry.schema === TENANT_SCHEMA_KEY ? entry.schema : schemaKey(entry.schema);
|
|
1244
|
+
if (entry.table === WILDCARD) wildcardSchemas.add(schema);
|
|
1245
|
+
else tables.add(tableKey2(schema, entry.table.toLowerCase()));
|
|
1246
|
+
}
|
|
1247
|
+
return ({ parts }) => {
|
|
1248
|
+
if (parts.length > 2) return false;
|
|
1249
|
+
const table = parts[parts.length - 1].toLowerCase();
|
|
1250
|
+
const schema = schemaKey(parts.length === 2 ? parts[0] : defaultSchema);
|
|
1251
|
+
return wildcardSchemas.has(schema) || tables.has(tableKey2(schema, table));
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
999
1254
|
function tableKey2(schema, table) {
|
|
1000
1255
|
return `${schema}.${table}`;
|
|
1001
1256
|
}
|
|
@@ -1600,7 +1855,9 @@ var QueryEngine = class {
|
|
|
1600
1855
|
const adapter = this.getDatabase(databaseName);
|
|
1601
1856
|
const metadata = this.getDatabaseMetadata(databaseName);
|
|
1602
1857
|
let finalSql = sql;
|
|
1603
|
-
if (metadata) {
|
|
1858
|
+
if (metadata?.tenantSchema) {
|
|
1859
|
+
finalSql = rewriteTenantSchema(sql, metadata.tenantSchema.template, tenantId);
|
|
1860
|
+
} else if (metadata) {
|
|
1604
1861
|
finalSql = this.ensureTenantIsolation(sql, params, metadata, tenantId);
|
|
1605
1862
|
}
|
|
1606
1863
|
await adapter.validate(finalSql, params);
|
|
@@ -2444,6 +2701,15 @@ function resolveQueryPipeline(pipeline) {
|
|
|
2444
2701
|
}
|
|
2445
2702
|
return "v2";
|
|
2446
2703
|
}
|
|
2704
|
+
var EMPTY_RESULT_ERROR = "The query ran but returned no rows (or only zero/NULL values). A filter value probably does not match how the data stores it (case, spelling, codes such as 'APPROVE' vs 'approved'), or a filter is narrower than the question. Check each filter value against the column's documented values and fix it; keep every filter the question asks for.";
|
|
2705
|
+
function isEmptyLikeResult(rows) {
|
|
2706
|
+
if (rows.length === 0) return true;
|
|
2707
|
+
if (rows.length > 1) return false;
|
|
2708
|
+
const row = rows[0];
|
|
2709
|
+
if (!row || typeof row !== "object") return false;
|
|
2710
|
+
const values = Object.values(row);
|
|
2711
|
+
return values.length > 0 && values.every((v) => v === null || v === 0 || v === "0" || v === 0n);
|
|
2712
|
+
}
|
|
2447
2713
|
async function ask(client, queryEngine, question, options, signal) {
|
|
2448
2714
|
const tenantId = resolveTenantId5(client, options.tenantId);
|
|
2449
2715
|
const sessionId = crypto3.randomUUID();
|
|
@@ -2526,6 +2792,17 @@ async function ask(client, queryEngine, question, options, signal) {
|
|
|
2526
2792
|
tenantId
|
|
2527
2793
|
);
|
|
2528
2794
|
const rows = execution.rows ?? [];
|
|
2795
|
+
if (options.retryOnEmptyResult && attempt < maxRetry && isEmptyLikeResult(rows)) {
|
|
2796
|
+
attempt++;
|
|
2797
|
+
lastError = EMPTY_RESULT_ERROR;
|
|
2798
|
+
previousSql = sql;
|
|
2799
|
+
console.warn("Query returned no data, retrying query generation", {
|
|
2800
|
+
attempt,
|
|
2801
|
+
maxAttempts: maxRetry + 1,
|
|
2802
|
+
sql
|
|
2803
|
+
});
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2529
2806
|
const chartType = options.chartType ?? "vega-lite";
|
|
2530
2807
|
let chart = {
|
|
2531
2808
|
specType: chartType,
|
|
@@ -3329,6 +3606,9 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
|
|
|
3329
3606
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
3330
3607
|
}
|
|
3331
3608
|
attachPostgres(name, clientFn, options) {
|
|
3609
|
+
if (options?.tenantSchema && options.tenantFieldName) {
|
|
3610
|
+
throw new Error("Use either tenantSchema or tenantFieldName for tenant isolation, not both");
|
|
3611
|
+
}
|
|
3332
3612
|
const adapter = new PostgresAdapter(clientFn, options);
|
|
3333
3613
|
const metadata = {
|
|
3334
3614
|
name,
|
|
@@ -3340,7 +3620,8 @@ var QueryPanelSdkAPI = class _QueryPanelSdkAPI {
|
|
|
3340
3620
|
enforceTenantIsolation: resolveAttachEnforceTenantIsolation(
|
|
3341
3621
|
options?.tenantFieldName,
|
|
3342
3622
|
options?.enforceTenantIsolation
|
|
3343
|
-
)
|
|
3623
|
+
),
|
|
3624
|
+
tenantSchema: options?.tenantSchema
|
|
3344
3625
|
};
|
|
3345
3626
|
this.queryEngine.attachDatabase(name, adapter, metadata);
|
|
3346
3627
|
}
|
|
@@ -4394,9 +4675,13 @@ export {
|
|
|
4394
4675
|
QueryPanelSdkAPI,
|
|
4395
4676
|
QueryPipelineError,
|
|
4396
4677
|
SchemaTooLargeError,
|
|
4678
|
+
TenantSchemaError,
|
|
4397
4679
|
anonymizeResults,
|
|
4398
4680
|
mapGeneratedParams,
|
|
4399
4681
|
normalizeSqlParamsInput,
|
|
4400
|
-
|
|
4682
|
+
parseTenantSchemaTemplate,
|
|
4683
|
+
resolveQueryPipeline,
|
|
4684
|
+
resolveTenantSchema,
|
|
4685
|
+
rewriteTenantSchema
|
|
4401
4686
|
};
|
|
4402
4687
|
//# sourceMappingURL=index.js.map
|