|
@@ -18,26 +18,73 @@ import { matchesType } from './typeUtils';
|
|
|
import I18n from 'utils/i18n';
|
|
import I18n from 'utils/i18n';
|
|
|
import huePubSub from 'utils/huePubSub';
|
|
import huePubSub from 'utils/huePubSub';
|
|
|
import { clearUdfCache, getCachedApiUdfs, setCachedApiUdfs } from './apiCache';
|
|
import { clearUdfCache, getCachedApiUdfs, setCachedApiUdfs } from './apiCache';
|
|
|
-import { adaptApiUdf, fetchUdfs } from './apiUtils';
|
|
|
|
|
|
|
+import { fetchUdfs } from './apiUtils';
|
|
|
|
|
+
|
|
|
|
|
+export interface Connector {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ dialect: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface Argument {
|
|
|
|
|
+ type: string;
|
|
|
|
|
+ multiple?: boolean;
|
|
|
|
|
+ keywords?: string[];
|
|
|
|
|
+ optional?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface UdfDetails {
|
|
|
|
|
+ returnTypes: string[];
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ arguments: Argument[][];
|
|
|
|
|
+ signature: string;
|
|
|
|
|
+ draggable: string;
|
|
|
|
|
+ description?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface UdfCategoryFunctions {
|
|
|
|
|
+ [attr: string]: UdfDetails;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface UdfCategory {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ functions: UdfCategoryFunctions;
|
|
|
|
|
+ isAnalytic?: boolean;
|
|
|
|
|
+ isAggregate?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface SetOptions {
|
|
|
|
|
+ [attr: string]: SetDetails;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface SetDetails {
|
|
|
|
|
+ default: string;
|
|
|
|
|
+ type: string;
|
|
|
|
|
+ details: string;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
export const CLEAR_UDF_CACHE_EVENT = 'hue.clear.udf.cache';
|
|
export const CLEAR_UDF_CACHE_EVENT = 'hue.clear.udf.cache';
|
|
|
|
|
|
|
|
-const SET_REFS = {
|
|
|
|
|
|
|
+const SET_REFS: { [attr: string]: () => Promise<{ SET_OPTIONS?: SetOptions }> } = {
|
|
|
|
|
+ // @ts-ignore
|
|
|
impala: async () => import(/* webpackChunkName: "impala-ref" */ './impala/setReference')
|
|
impala: async () => import(/* webpackChunkName: "impala-ref" */ './impala/setReference')
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const UDF_REFS = {
|
|
|
|
|
|
|
+const UDF_REFS: { [attr: string]: () => Promise<{ UDF_CATEGORIES?: UdfCategory[] }> } = {
|
|
|
|
|
+ // @ts-ignore
|
|
|
generic: async () => import(/* webpackChunkName: "generic-ref" */ './generic/udfReference'),
|
|
generic: async () => import(/* webpackChunkName: "generic-ref" */ './generic/udfReference'),
|
|
|
|
|
+ // @ts-ignore
|
|
|
hive: async () => import(/* webpackChunkName: "hive-ref" */ './hive/udfReference'),
|
|
hive: async () => import(/* webpackChunkName: "hive-ref" */ './hive/udfReference'),
|
|
|
|
|
+ // @ts-ignore
|
|
|
impala: async () => import(/* webpackChunkName: "impala-ref" */ './impala/udfReference'),
|
|
impala: async () => import(/* webpackChunkName: "impala-ref" */ './impala/udfReference'),
|
|
|
|
|
+ // @ts-ignore
|
|
|
pig: async () => import(/* webpackChunkName: "pig-ref" */ './pig/udfReference')
|
|
pig: async () => import(/* webpackChunkName: "pig-ref" */ './pig/udfReference')
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const IGNORED_UDF_REGEX = /^[!=$%&*+-/<>^|~]+$/;
|
|
const IGNORED_UDF_REGEX = /^[!=$%&*+-/<>^|~]+$/;
|
|
|
|
|
|
|
|
-const mergedUdfPromises = {};
|
|
|
|
|
|
|
+const mergedUdfPromises: { [attr: string]: Promise<UdfCategory[]> } = {};
|
|
|
|
|
|
|
|
-const getMergedUdfKey = (connector, database) => {
|
|
|
|
|
|
|
+const getMergedUdfKey = (connector: Connector, database?: string): string => {
|
|
|
let key = connector.id;
|
|
let key = connector.id;
|
|
|
if (database) {
|
|
if (database) {
|
|
|
key += '_' + database;
|
|
key += '_' + database;
|
|
@@ -45,9 +92,13 @@ const getMergedUdfKey = (connector, database) => {
|
|
|
return key;
|
|
return key;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const hasUdfCategories = connector => typeof UDF_REFS[connector.dialect] !== 'undefined';
|
|
|
|
|
|
|
+export const hasUdfCategories = (connector: Connector): boolean =>
|
|
|
|
|
+ typeof UDF_REFS[connector.dialect] !== 'undefined';
|
|
|
|
|
|
|
|
-const findUdfsToAdd = (apiUdfs, existingCategories) => {
|
|
|
|
|
|
|
+const findUdfsToAdd = (
|
|
|
|
|
+ apiUdfs: UdfDetails[],
|
|
|
|
|
+ existingCategories: UdfCategory[]
|
|
|
|
|
+): UdfCategoryFunctions => {
|
|
|
const existingUdfNames = new Set();
|
|
const existingUdfNames = new Set();
|
|
|
existingCategories.forEach(category => {
|
|
existingCategories.forEach(category => {
|
|
|
Object.keys(category.functions).forEach(udfName => {
|
|
Object.keys(category.functions).forEach(udfName => {
|
|
@@ -55,7 +106,7 @@ const findUdfsToAdd = (apiUdfs, existingCategories) => {
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const result = {};
|
|
|
|
|
|
|
+ const result: UdfCategoryFunctions = {};
|
|
|
|
|
|
|
|
apiUdfs.forEach(apiUdf => {
|
|
apiUdfs.forEach(apiUdf => {
|
|
|
// TODO: Impala reports the same UDF multiple times, once per argument type.
|
|
// TODO: Impala reports the same UDF multiple times, once per argument type.
|
|
@@ -64,14 +115,18 @@ const findUdfsToAdd = (apiUdfs, existingCategories) => {
|
|
|
!existingUdfNames.has(apiUdf.name.toUpperCase()) &&
|
|
!existingUdfNames.has(apiUdf.name.toUpperCase()) &&
|
|
|
!IGNORED_UDF_REGEX.test(apiUdf.name)
|
|
!IGNORED_UDF_REGEX.test(apiUdf.name)
|
|
|
) {
|
|
) {
|
|
|
- result[apiUdf.name] = adaptApiUdf(apiUdf);
|
|
|
|
|
|
|
+ result[apiUdf.name] = apiUdf;
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-const mergeWithApiUdfs = async (categories, connector, database) => {
|
|
|
|
|
|
|
+const mergeWithApiUdfs = async (
|
|
|
|
|
+ categories: UdfCategory[],
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ database?: string
|
|
|
|
|
+) => {
|
|
|
let apiUdfs = await getCachedApiUdfs(connector, database);
|
|
let apiUdfs = await getCachedApiUdfs(connector, database);
|
|
|
if (!apiUdfs) {
|
|
if (!apiUdfs) {
|
|
|
apiUdfs = await fetchUdfs({
|
|
apiUdfs = await fetchUdfs({
|
|
@@ -94,11 +149,14 @@ const mergeWithApiUdfs = async (categories, connector, database) => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const getUdfCategories = async (connector, database) => {
|
|
|
|
|
|
|
+export const getUdfCategories = async (
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ database?: string
|
|
|
|
|
+): Promise<UdfCategory[]> => {
|
|
|
const promiseKey = getMergedUdfKey(connector, database);
|
|
const promiseKey = getMergedUdfKey(connector, database);
|
|
|
if (!mergedUdfPromises[promiseKey]) {
|
|
if (!mergedUdfPromises[promiseKey]) {
|
|
|
mergedUdfPromises[promiseKey] = new Promise(async resolve => {
|
|
mergedUdfPromises[promiseKey] = new Promise(async resolve => {
|
|
|
- let categories = [];
|
|
|
|
|
|
|
+ let categories: UdfCategory[] = [];
|
|
|
if (UDF_REFS[connector.dialect]) {
|
|
if (UDF_REFS[connector.dialect]) {
|
|
|
const module = await UDF_REFS[connector.dialect]();
|
|
const module = await UDF_REFS[connector.dialect]();
|
|
|
if (module.UDF_CATEGORIES) {
|
|
if (module.UDF_CATEGORIES) {
|
|
@@ -118,9 +176,12 @@ export const getUdfCategories = async (connector, database) => {
|
|
|
return await mergedUdfPromises[promiseKey];
|
|
return await mergedUdfPromises[promiseKey];
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const findUdf = async (connector, functionName) => {
|
|
|
|
|
|
|
+export const findUdf = async (
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ functionName: string
|
|
|
|
|
+): Promise<UdfDetails[]> => {
|
|
|
const categories = await getUdfCategories(connector);
|
|
const categories = await getUdfCategories(connector);
|
|
|
- const found = [];
|
|
|
|
|
|
|
+ const found: UdfDetails[] = [];
|
|
|
categories.forEach(category => {
|
|
categories.forEach(category => {
|
|
|
if (category.functions[functionName]) {
|
|
if (category.functions[functionName]) {
|
|
|
found.push(category.functions[functionName]);
|
|
found.push(category.functions[functionName]);
|
|
@@ -129,14 +190,17 @@ export const findUdf = async (connector, functionName) => {
|
|
|
return found;
|
|
return found;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const getReturnTypesForUdf = async (connector, functionName) => {
|
|
|
|
|
|
|
+export const getReturnTypesForUdf = async (
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ functionName: string
|
|
|
|
|
+): Promise<string[]> => {
|
|
|
if (!functionName) {
|
|
if (!functionName) {
|
|
|
return ['T'];
|
|
return ['T'];
|
|
|
}
|
|
}
|
|
|
const udfs = await findUdf(connector, functionName);
|
|
const udfs = await findUdf(connector, functionName);
|
|
|
if (!udfs.length) {
|
|
if (!udfs.length) {
|
|
|
let returnTypesPresent = false;
|
|
let returnTypesPresent = false;
|
|
|
- const returnTypes = new Set();
|
|
|
|
|
|
|
+ const returnTypes = new Set<string>();
|
|
|
udfs.forEach(udf => {
|
|
udfs.forEach(udf => {
|
|
|
if (udf.returnTypes) {
|
|
if (udf.returnTypes) {
|
|
|
returnTypesPresent = true;
|
|
returnTypesPresent = true;
|
|
@@ -144,7 +208,7 @@ export const getReturnTypesForUdf = async (connector, functionName) => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
if (returnTypesPresent) {
|
|
if (returnTypesPresent) {
|
|
|
- return Array.from(returnTypes.entries());
|
|
|
|
|
|
|
+ return [...returnTypes];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -152,13 +216,13 @@ export const getReturnTypesForUdf = async (connector, functionName) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
export const getUdfsWithReturnTypes = async (
|
|
export const getUdfsWithReturnTypes = async (
|
|
|
- connector,
|
|
|
|
|
- returnTypes,
|
|
|
|
|
- includeAggregate,
|
|
|
|
|
- includeAnalytic
|
|
|
|
|
-) => {
|
|
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ returnTypes: string[],
|
|
|
|
|
+ includeAggregate?: boolean,
|
|
|
|
|
+ includeAnalytic?: boolean
|
|
|
|
|
+): Promise<UdfDetails[]> => {
|
|
|
const categories = await getUdfCategories(connector);
|
|
const categories = await getUdfCategories(connector);
|
|
|
- const result = [];
|
|
|
|
|
|
|
+ const result: UdfDetails[] = [];
|
|
|
categories.forEach(category => {
|
|
categories.forEach(category => {
|
|
|
if (
|
|
if (
|
|
|
(!category.isAnalytic && !category.isAggregate) ||
|
|
(!category.isAnalytic && !category.isAggregate) ||
|
|
@@ -167,7 +231,7 @@ export const getUdfsWithReturnTypes = async (
|
|
|
) {
|
|
) {
|
|
|
Object.keys(category.functions).forEach(udfName => {
|
|
Object.keys(category.functions).forEach(udfName => {
|
|
|
const udf = category.functions[udfName];
|
|
const udf = category.functions[udfName];
|
|
|
- if (!returnTypes || matchesType(connector, returnTypes, udf.returnTypes)) {
|
|
|
|
|
|
|
+ if (!returnTypes || matchesType(connector.dialect, returnTypes, udf.returnTypes)) {
|
|
|
result.push(udf);
|
|
result.push(udf);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
@@ -177,13 +241,17 @@ export const getUdfsWithReturnTypes = async (
|
|
|
return result;
|
|
return result;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const getArgumentDetailsForUdf = async (connector, functionName, argumentPosition) => {
|
|
|
|
|
|
|
+export const getArgumentDetailsForUdf = async (
|
|
|
|
|
+ connector: Connector,
|
|
|
|
|
+ functionName: string,
|
|
|
|
|
+ argumentPosition: number
|
|
|
|
|
+): Promise<Argument[]> => {
|
|
|
const foundFunctions = await findUdf(connector, functionName);
|
|
const foundFunctions = await findUdf(connector, functionName);
|
|
|
if (!foundFunctions.length) {
|
|
if (!foundFunctions.length) {
|
|
|
return [{ type: 'T' }];
|
|
return [{ type: 'T' }];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const possibleArguments = [];
|
|
|
|
|
|
|
+ const possibleArguments: Argument[] = [];
|
|
|
foundFunctions.forEach(foundFunction => {
|
|
foundFunctions.forEach(foundFunction => {
|
|
|
const args = foundFunction.arguments;
|
|
const args = foundFunction.arguments;
|
|
|
if (argumentPosition > args.length) {
|
|
if (argumentPosition > args.length) {
|
|
@@ -195,7 +263,7 @@ export const getArgumentDetailsForUdf = async (connector, functionName, argument
|
|
|
return possibleArguments;
|
|
return possibleArguments;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const getSetOptions = async connector => {
|
|
|
|
|
|
|
+export const getSetOptions = async (connector: Connector): Promise<SetOptions> => {
|
|
|
if (SET_REFS[connector.dialect]) {
|
|
if (SET_REFS[connector.dialect]) {
|
|
|
const module = await SET_REFS[connector.dialect]();
|
|
const module = await SET_REFS[connector.dialect]();
|
|
|
if (module.SET_OPTIONS) {
|
|
if (module.SET_OPTIONS) {
|
|
@@ -205,14 +273,17 @@ export const getSetOptions = async connector => {
|
|
|
return {};
|
|
return {};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-huePubSub.subscribe(CLEAR_UDF_CACHE_EVENT, async details => {
|
|
|
|
|
- await clearUdfCache(details.connector);
|
|
|
|
|
- Object.keys(mergedUdfPromises).forEach(key => {
|
|
|
|
|
- if (key === details.connector.id || key.indexOf(details.connector.id + '_') === 0) {
|
|
|
|
|
- delete mergedUdfPromises[key];
|
|
|
|
|
|
|
+huePubSub.subscribe(
|
|
|
|
|
+ CLEAR_UDF_CACHE_EVENT,
|
|
|
|
|
+ async (details: { connector: Connector; callback: () => void }) => {
|
|
|
|
|
+ await clearUdfCache(details.connector);
|
|
|
|
|
+ Object.keys(mergedUdfPromises).forEach(key => {
|
|
|
|
|
+ if (key === details.connector.id || key.indexOf(details.connector.id + '_') === 0) {
|
|
|
|
|
+ delete mergedUdfPromises[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (details.callback) {
|
|
|
|
|
+ details.callback();
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
- if (details.callback) {
|
|
|
|
|
- details.callback();
|
|
|
|
|
}
|
|
}
|
|
|
-});
|
|
|
|
|
|
|
+);
|