// Licensed to Cloudera, Inc. under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. Cloudera, Inc. licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { post } from '../../api/utils'; import { AUTOCOMPLETE_API_PREFIX } from '../../api/urls'; import { UdfArgument, UdfDetails } from '../../sql/reference/types'; import { Connector } from '../../config/types'; import I18n from '../../utils/i18n'; export interface ApiUdf { name: string; is_builtin?: string; is_persistent?: string; return_type?: string; signature?: string; description?: string; } const FUNCTION_OPERATION = 'function'; const FUNCTIONS_OPERATION = 'functions'; const DEFAULT_DESCRIPTION = I18n('No description available.'); const DEFAULT_RETURN_TYPES = ['T']; const DEFAULT_ARGUMENTS = [[{ type: 'T', multiple: true }]]; const SIGNATURE_REGEX = /([a-z]+(?:\.{3})?)/gi; const TYPE_REGEX = /(?[a-z]+)(?\.{3})?/i; const stripPrecision = (typeString: string): string => typeString.replace(/\(\*(,\*)?\)/g, ''); const adaptApiUdf = (apiUdf: ApiUdf): UdfDetails => { const signature = apiUdf.name + '()'; return { name: apiUdf.name, returnTypes: extractReturnTypes(apiUdf), arguments: extractArgumentTypes(apiUdf), signature: signature, draggable: signature, description: DEFAULT_DESCRIPTION, described: false }; }; const extractReturnTypes = (apiUdf: ApiUdf): string[] => apiUdf.return_type ? [stripPrecision(apiUdf.return_type)] : DEFAULT_RETURN_TYPES; export const extractArgumentTypes = (apiUdf: ApiUdf): UdfArgument[][] => { if (apiUdf.signature) { const cleanSignature = stripPrecision(apiUdf.signature); if (cleanSignature === '()') { return []; } const match = cleanSignature.match(SIGNATURE_REGEX); if (match) { return match.map(argString => { const typeMatch = argString.match(TYPE_REGEX); if (typeMatch && typeMatch.groups) { const arg: UdfArgument = { type: typeMatch.groups.type }; if (typeMatch.groups.multiple) { arg.multiple = true; } return [arg]; } else { return []; } }); } } return DEFAULT_ARGUMENTS; }; export const mergeArgumentTypes = (target: UdfArgument[][], additional: UdfArgument[][]): void => { for (let i = 0; i < target.length; i++) { if (i >= additional.length) { break; } if (target[i][0].type === 'T') { continue; } if (additional[i][0].type === 'T') { target[i] = additional[i]; continue; } target[i].push(...additional[i]); } }; export const adaptApiFunctions = (functions: ApiUdf[]): UdfDetails[] => { const udfs: UdfDetails[] = []; const adapted: { [attr: string]: UdfDetails } = {}; functions.forEach(apiUdf => { if (adapted[apiUdf.name]) { const adaptedUdf = adapted[apiUdf.name]; const additionalArgs = extractArgumentTypes(apiUdf); mergeArgumentTypes(adaptedUdf.arguments, additionalArgs); if (adaptedUdf.returnTypes[0] !== 'T') { const additionalReturnTypes = extractReturnTypes(apiUdf); if (additionalReturnTypes[0] !== 'T') { adaptedUdf.returnTypes.push(...additionalReturnTypes); } else { adaptedUdf.returnTypes = additionalReturnTypes; } } // Make sure the return types are unique adaptedUdf.returnTypes = [...new Set(adaptedUdf.returnTypes)]; } else { adapted[apiUdf.name] = adaptApiUdf(apiUdf); udfs.push(adapted[apiUdf.name]); } }); return udfs; }; const createUrl = (database?: string, udf?: UdfDetails): string => { if (database && udf) { return `${AUTOCOMPLETE_API_PREFIX}${database}/${udf.name}`; } if (database) { return `${AUTOCOMPLETE_API_PREFIX}${database}/`; } if (udf) { return `${AUTOCOMPLETE_API_PREFIX}${udf.name}`; } return AUTOCOMPLETE_API_PREFIX; }; const createRequestData = (connector: Connector, operation: string) => ({ notebook: {}, snippet: JSON.stringify({ type: connector.id }), operation: operation }); export const fetchUdfs = async ( connector: Connector, database?: string, silenceErrors = true ): Promise => { const url = createUrl(database); const data = createRequestData(connector, FUNCTIONS_OPERATION); try { const response = await post<{ functions?: ApiUdf[] }>(url, data, { silenceErrors: silenceErrors }); if (response?.functions) { return adaptApiFunctions(response.functions); } } catch (err) {} return []; }; export const fetchDescribe = async ( connector: Connector, udf: UdfDetails, database?: string, silenceErrors = true ): Promise => { const url = createUrl(database, udf); const data = createRequestData(connector, FUNCTION_OPERATION); try { const response = await post<{ function?: ApiUdf }>(url, data, { silenceErrors: silenceErrors }); if (response?.function) { return response.function; } } catch (err) {} };