2022-06-30 15:38:48 -05:00
|
|
|
import { lastValueFrom, of } from 'rxjs';
|
|
|
|
import { catchError, map } from 'rxjs/operators';
|
|
|
|
|
|
|
|
import {
|
|
|
|
DataFrame,
|
|
|
|
DataFrameView,
|
2022-07-12 07:11:54 -05:00
|
|
|
DataQuery,
|
2022-06-30 15:38:48 -05:00
|
|
|
DataSourceInstanceSettings,
|
|
|
|
DataSourceRef,
|
|
|
|
MetricFindValue,
|
|
|
|
ScopedVars,
|
2022-07-12 07:11:54 -05:00
|
|
|
TimeRange,
|
2022-06-30 15:38:48 -05:00
|
|
|
} from '@grafana/data';
|
|
|
|
import {
|
|
|
|
BackendDataSourceResponse,
|
|
|
|
DataSourceWithBackend,
|
|
|
|
FetchResponse,
|
|
|
|
getBackendSrv,
|
|
|
|
getTemplateSrv,
|
|
|
|
TemplateSrv,
|
|
|
|
} from '@grafana/runtime';
|
2022-07-12 07:11:54 -05:00
|
|
|
import { toDataQueryResponse, toTestingStatus } from '@grafana/runtime/src/utils/queryResponse';
|
|
|
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
2022-06-30 15:38:48 -05:00
|
|
|
|
|
|
|
import { VariableWithMultiSupport } from '../../../variables/types';
|
2022-07-12 07:11:54 -05:00
|
|
|
import { getSearchFilterScopedVar, SearchFilterOptions } from '../../../variables/utils';
|
2022-11-01 23:30:35 -05:00
|
|
|
import { ResponseParser } from '../ResponseParser';
|
2022-07-12 07:11:54 -05:00
|
|
|
import { MACRO_NAMES } from '../constants';
|
2022-11-01 23:30:35 -05:00
|
|
|
import { DB, SQLQuery, SQLOptions, SqlQueryModel, QueryFormat } from '../types';
|
2022-06-30 15:38:48 -05:00
|
|
|
|
|
|
|
export abstract class SqlDatasource extends DataSourceWithBackend<SQLQuery, SQLOptions> {
|
|
|
|
id: number;
|
2022-11-01 23:30:35 -05:00
|
|
|
responseParser: ResponseParser;
|
2022-06-30 15:38:48 -05:00
|
|
|
name: string;
|
|
|
|
interval: string;
|
|
|
|
db: DB;
|
2022-07-12 07:11:54 -05:00
|
|
|
annotations = {};
|
2022-06-30 15:38:48 -05:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
instanceSettings: DataSourceInstanceSettings<SQLOptions>,
|
|
|
|
protected readonly templateSrv: TemplateSrv = getTemplateSrv()
|
|
|
|
) {
|
|
|
|
super(instanceSettings);
|
|
|
|
this.name = instanceSettings.name;
|
2022-11-01 23:30:35 -05:00
|
|
|
this.responseParser = new ResponseParser();
|
2022-06-30 15:38:48 -05:00
|
|
|
this.id = instanceSettings.id;
|
|
|
|
const settingsData = instanceSettings.jsonData || {};
|
|
|
|
this.interval = settingsData.timeInterval || '1m';
|
|
|
|
this.db = this.getDB();
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract getDB(dsID?: number): DB;
|
|
|
|
|
|
|
|
abstract getQueryModel(target?: SQLQuery, templateSrv?: TemplateSrv, scopedVars?: ScopedVars): SqlQueryModel;
|
|
|
|
|
2022-11-01 23:30:35 -05:00
|
|
|
getResponseParser() {
|
|
|
|
return this.responseParser;
|
|
|
|
}
|
2022-06-30 15:38:48 -05:00
|
|
|
|
|
|
|
interpolateVariable = (value: string | string[] | number, variable: VariableWithMultiSupport) => {
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
if (variable.multi || variable.includeAll) {
|
2022-10-14 03:52:08 -05:00
|
|
|
return this.getQueryModel().quoteLiteral(value);
|
2022-06-30 15:38:48 -05:00
|
|
|
} else {
|
2022-10-14 03:52:08 -05:00
|
|
|
return String(value).replace(/'/g, "''");
|
2022-06-30 15:38:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof value === 'number') {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
const quotedValues = value.map((v) => this.getQueryModel().quoteLiteral(v));
|
|
|
|
return quotedValues.join(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
};
|
|
|
|
|
2022-07-15 12:51:28 -05:00
|
|
|
interpolateVariablesInQueries(queries: SQLQuery[], scopedVars: ScopedVars): SQLQuery[] {
|
2022-06-30 15:38:48 -05:00
|
|
|
let expandedQueries = queries;
|
|
|
|
if (queries && queries.length > 0) {
|
|
|
|
expandedQueries = queries.map((query) => {
|
|
|
|
const expandedQuery = {
|
|
|
|
...query,
|
|
|
|
datasource: this.getRef(),
|
|
|
|
rawSql: this.templateSrv.replace(query.rawSql, scopedVars, this.interpolateVariable),
|
|
|
|
rawQuery: true,
|
|
|
|
};
|
|
|
|
return expandedQuery;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return expandedQueries;
|
|
|
|
}
|
|
|
|
|
|
|
|
filterQuery(query: SQLQuery): boolean {
|
|
|
|
return !query.hide;
|
|
|
|
}
|
|
|
|
|
|
|
|
applyTemplateVariables(
|
|
|
|
target: SQLQuery,
|
|
|
|
scopedVars: ScopedVars
|
|
|
|
): Record<string, string | DataSourceRef | SQLQuery['format']> {
|
|
|
|
return {
|
|
|
|
refId: target.refId,
|
|
|
|
datasource: this.getRef(),
|
2022-10-14 03:52:08 -05:00
|
|
|
rawSql: this.templateSrv.replace(target.rawSql, scopedVars, this.interpolateVariable),
|
2022-06-30 15:38:48 -05:00
|
|
|
format: target.format,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
async metricFindQuery(query: string, optionalOptions?: MetricFindQueryOptions): Promise<MetricFindValue[]> {
|
2022-10-14 03:52:12 -05:00
|
|
|
let refId = 'tempvar';
|
|
|
|
if (optionalOptions && optionalOptions.variable && optionalOptions.variable.name) {
|
|
|
|
refId = optionalOptions.variable.name;
|
|
|
|
}
|
|
|
|
|
2022-06-30 15:38:48 -05:00
|
|
|
const rawSql = this.templateSrv.replace(
|
|
|
|
query,
|
|
|
|
getSearchFilterScopedVar({ query, wildcardChar: '%', options: optionalOptions }),
|
|
|
|
this.interpolateVariable
|
|
|
|
);
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
const interpolatedQuery: SQLQuery = {
|
2022-10-14 03:52:12 -05:00
|
|
|
refId: refId,
|
2022-06-30 15:38:48 -05:00
|
|
|
datasource: this.getRef(),
|
|
|
|
rawSql,
|
|
|
|
format: QueryFormat.Table,
|
|
|
|
};
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
const response = await this.runMetaQuery(interpolatedQuery, optionalOptions);
|
2022-06-30 15:38:48 -05:00
|
|
|
return this.getResponseParser().transformMetricFindResponse(response);
|
|
|
|
}
|
|
|
|
|
2022-07-15 12:51:28 -05:00
|
|
|
async runSql<T>(query: string, options?: RunSQLOptions) {
|
|
|
|
const frame = await this.runMetaQuery({ rawSql: query, format: QueryFormat.Table, refId: options?.refId }, options);
|
2022-06-30 15:38:48 -05:00
|
|
|
return new DataFrameView<T>(frame);
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
private runMetaQuery(request: Partial<SQLQuery>, options?: MetricFindQueryOptions): Promise<DataFrame> {
|
|
|
|
const range = getTimeSrv().timeRange();
|
|
|
|
const refId = request.refId || 'meta';
|
|
|
|
const queries: DataQuery[] = [{ ...request, datasource: request.datasource || this.getRef(), refId }];
|
|
|
|
|
|
|
|
return lastValueFrom(
|
|
|
|
getBackendSrv()
|
|
|
|
.fetch<BackendDataSourceResponse>({
|
|
|
|
url: '/api/ds/query',
|
|
|
|
method: 'POST',
|
2022-11-14 18:35:50 -06:00
|
|
|
headers: this.getRequestHeaders(),
|
2022-07-12 07:11:54 -05:00
|
|
|
data: {
|
|
|
|
from: options?.range?.from.valueOf().toString() || range.from.valueOf().toString(),
|
|
|
|
to: options?.range?.to.valueOf().toString() || range.to.valueOf().toString(),
|
|
|
|
queries,
|
|
|
|
},
|
|
|
|
requestId: refId,
|
|
|
|
})
|
|
|
|
.pipe(
|
|
|
|
map((res: FetchResponse<BackendDataSourceResponse>) => {
|
|
|
|
const rsp = toDataQueryResponse(res, queries);
|
|
|
|
return rsp.data[0];
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
2022-06-30 15:38:48 -05:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
testDatasource(): Promise<{ status: string; message: string }> {
|
2022-11-11 15:41:38 -06:00
|
|
|
const refId = 'A';
|
2022-06-30 15:38:48 -05:00
|
|
|
return lastValueFrom(
|
|
|
|
getBackendSrv()
|
2022-11-11 15:41:38 -06:00
|
|
|
.fetch<BackendDataSourceResponse>({
|
2022-06-30 15:38:48 -05:00
|
|
|
url: '/api/ds/query',
|
|
|
|
method: 'POST',
|
2022-11-14 18:35:50 -06:00
|
|
|
headers: this.getRequestHeaders(),
|
2022-06-30 15:38:48 -05:00
|
|
|
data: {
|
|
|
|
from: '5m',
|
|
|
|
to: 'now',
|
|
|
|
queries: [
|
|
|
|
{
|
2022-11-11 15:41:38 -06:00
|
|
|
refId: refId,
|
2022-06-30 15:38:48 -05:00
|
|
|
intervalMs: 1,
|
|
|
|
maxDataPoints: 1,
|
|
|
|
datasource: this.getRef(),
|
|
|
|
datasourceId: this.id,
|
|
|
|
rawSql: 'SELECT 1',
|
|
|
|
format: 'table',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.pipe(
|
2022-11-11 15:41:38 -06:00
|
|
|
map((r) => {
|
|
|
|
const error = r.data.results[refId].error;
|
|
|
|
if (error) {
|
|
|
|
return { status: 'error', message: error };
|
|
|
|
}
|
|
|
|
return { status: 'success', message: 'Database Connection OK' };
|
|
|
|
}),
|
2022-06-30 15:38:48 -05:00
|
|
|
catchError((err) => {
|
|
|
|
return of(toTestingStatus(err));
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
targetContainsTemplate(target: SQLQuery) {
|
|
|
|
let queryWithoutMacros = target.rawSql;
|
|
|
|
MACRO_NAMES.forEach((value) => {
|
|
|
|
queryWithoutMacros = queryWithoutMacros?.replace(value, '') || '';
|
|
|
|
});
|
|
|
|
return this.templateSrv.containsTemplate(queryWithoutMacros);
|
2022-06-30 15:38:48 -05:00
|
|
|
}
|
|
|
|
}
|
2022-07-12 07:11:54 -05:00
|
|
|
|
2022-07-15 12:51:28 -05:00
|
|
|
interface RunSQLOptions extends MetricFindQueryOptions {
|
|
|
|
refId?: string;
|
|
|
|
}
|
2022-07-15 15:49:24 -05:00
|
|
|
|
2022-07-12 07:11:54 -05:00
|
|
|
interface MetricFindQueryOptions extends SearchFilterOptions {
|
|
|
|
range?: TimeRange;
|
2022-10-14 03:52:12 -05:00
|
|
|
variable?: VariableWithMultiSupport;
|
2022-07-12 07:11:54 -05:00
|
|
|
}
|