diff --git a/CHANGELOG.md b/CHANGELOG.md index 33046a189a01c..86e9054e563d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ and be accessible according to the permissions that were configured for the role - console: misc bug fixes (close #4785, #6330, #6288) - console: allow setting table custom name (#212) - console: support tracking VOLATILE functions as mutations or queries (close #6228) +- console: show only compatible postgres functions in computed fields section (close #5155) (#5978) - console: added export data option on browse rows page (close #1438 #5158) - cli: add missing global flags for seed command (#5565) - cli: allow seeds as alias for seed command (#5693) diff --git a/console/cypress/helpers/dataHelpers.ts b/console/cypress/helpers/dataHelpers.ts index 9dbefd7d0df6e..820fac5de59b5 100644 --- a/console/cypress/helpers/dataHelpers.ts +++ b/console/cypress/helpers/dataHelpers.ts @@ -146,6 +146,39 @@ export const testCustomFunctionSQLWithSessArg = ( }; }; +export const createUntrackedFunctionSQL = ( + fnName: string, + tableName: string +) => { + return { + type: 'run_sql', + args: { + sql: ` + CREATE OR REPLACE FUNCTION ${fnName}(table_row "${tableName}") + RETURNS int + LANGUAGE sql + STABLE + AS $function$ + SELECT table_row.id + $function$ + `, + cascade: false, + }, + }; +}; + +export const dropUntrackedFunctionSQL = (fnName: string) => { + return { + type: 'run_sql', + args: { + sql: ` + DROP FUNCTION public.${fnName}; + `, + cascade: false, + }, + }; +}; + export const getTrackFnPayload = (name = 'customfunctionwithsessionarg') => ({ type: 'pg_track_function', args: { diff --git a/console/cypress/integration/data/modify/spec.ts b/console/cypress/integration/data/modify/spec.ts index feeb8b4406fb0..cc5a478ff06ae 100644 --- a/console/cypress/integration/data/modify/spec.ts +++ b/console/cypress/integration/data/modify/spec.ts @@ -4,6 +4,8 @@ import { getTableName, getColName, getElementFromAlias, + createUntrackedFunctionSQL, + dropUntrackedFunctionSQL, } from '../../../helpers/dataHelpers'; import { @@ -11,11 +13,37 @@ import { validateCT, validateColumn, ResultType, + dataRequest, } from '../../validators/validators'; import { setPromptValue } from '../../../helpers/common'; const testName = 'mod'; +export const passMTFunctionList = () => { + const tableName = getTableName(0, testName); + dataRequest( + createUntrackedFunctionSQL(`${tableName}_id_fn`, tableName), + ResultType.SUCCESS + ); + cy.wait(5000); + cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click(); + + cy.get(getElementFromAlias('functions-dropdown')).click(); + + cy.get('[data-test^="data_test_column_type_value_"]').should( + 'have.length', + 1 + ); + + cy.get('[data-test^="data_test_column_type_value_"]') + .first() + .should('have.text', `${getTableName(0, testName)}_id_fn`.toLowerCase()); + dataRequest( + dropUntrackedFunctionSQL(`${tableName}_id_fn`), + ResultType.SUCCESS + ); +}; + export const passMTCreateTable = () => { cy.get(getElementFromAlias('data-create-table')).click(); cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`); @@ -30,7 +58,10 @@ export const passMTCreateTable = () => { cy.wait(7000); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateCT(getTableName(0, testName), ResultType.SUCCESS); }; @@ -41,7 +72,10 @@ export const passMTCheckRoute = () => { // Match the URL cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); }; @@ -92,7 +126,10 @@ export const passMTMoveToTable = () => { cy.get(getElementFromAlias(getTableName(0, testName))).click(); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/browse` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/browse` ); }; @@ -101,7 +138,10 @@ export const failMTWithoutColName = () => { cy.get(getElementFromAlias('modify-table-add-new-column-save')).click(); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateColumn( @@ -116,7 +156,10 @@ export const failMTWithoutColType = () => { cy.get(getElementFromAlias('modify-table-add-new-column-save')).click(); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateColumn( getTableName(0, testName), @@ -137,7 +180,10 @@ export const Addcolumnnullable = () => { cy.wait(2500); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateColumn( getTableName(0, testName), @@ -266,7 +312,10 @@ export const passMTDeleteCol = () => { cy.wait(5000); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateColumn( getTableName(0, testName), @@ -281,7 +330,10 @@ export const passMTDeleteTableCancel = () => { cy.window().its('prompt').should('be.called'); cy.url().should( 'eq', - `${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify` + `${baseUrl}/data/default/schema/public/tables/${getTableName( + 0, + testName + )}/modify` ); validateCT(getTableName(0, testName), ResultType.SUCCESS); @@ -292,8 +344,7 @@ export const passMTDeleteTable = () => { cy.get(getElementFromAlias('delete-table')).click(); cy.window().its('prompt').should('be.called'); cy.wait(5000); - // FIXME: change this later - // cy.url().should('eq', `${baseUrl}/data/default/schema/public`); + cy.url().should('eq', `${baseUrl}/data/default/schema/public`); validateCT(getTableName(0, testName), ResultType.FAILURE); }; diff --git a/console/cypress/integration/data/modify/test.ts b/console/cypress/integration/data/modify/test.ts index 31d832b7e159c..81dc0ffedae8e 100644 --- a/console/cypress/integration/data/modify/test.ts +++ b/console/cypress/integration/data/modify/test.ts @@ -19,6 +19,7 @@ import { passModifyUniqueKey, passRemoveUniqueKey, passMTChangeDefaultValueForPKey, + passMTFunctionList, } from './spec'; import { testMode } from '../../../helpers/common'; @@ -42,6 +43,10 @@ export const runModifyTableTests = () => { it('Creating a table', passMTCreateTable); it('Moving to the table', passMTMoveToTable); it('Modify table button opens the correct route', passMTCheckRoute); + it( + 'Can create computed field with compatible functions', + passMTFunctionList + ); it('Pass renaming table', passMTRenameTable); it('Pass renaming column', passMTRenameColumn); it('Fails to add column without column name', failMTWithoutColName); diff --git a/console/src/components/Services/Data/TableModify/ComputedFieldsEditor.js b/console/src/components/Services/Data/TableModify/ComputedFieldsEditor.js index 61c421ac584e3..b57db37683281 100644 --- a/console/src/components/Services/Data/TableModify/ComputedFieldsEditor.js +++ b/console/src/components/Services/Data/TableModify/ComputedFieldsEditor.js @@ -311,10 +311,18 @@ const ComputedFieldsEditor = ({ Create new -
+
fn.function_name)} onChange={handleFnNameChange} value={computedFieldFunctionName} diff --git a/console/src/dataSources/index.ts b/console/src/dataSources/index.ts index 3c52dfa8c3e1d..7d7229423eeb4 100644 --- a/console/src/dataSources/index.ts +++ b/console/src/dataSources/index.ts @@ -31,7 +31,12 @@ export interface DataSourcesAPI { // todo: replace with function type getFunctionSchema(func: PGFunction): string; getFunctionDefinition(func: PGFunction): string; - getSchemaFunctions(func: PGFunction[], schemaName: string): PGFunction[]; + getSchemaFunctions( + func: PGFunction[], + schemaName: string, + tableName: string, + tableSchema: string + ): PGFunction[]; findFunction( func: PGFunction[], fName: string, diff --git a/console/src/dataSources/services/postgresql/index.tsx b/console/src/dataSources/services/postgresql/index.tsx index 43d496cfc64eb..8149dbd29d778 100644 --- a/console/src/dataSources/services/postgresql/index.tsx +++ b/console/src/dataSources/services/postgresql/index.tsx @@ -135,11 +135,42 @@ export const getFunctionDefinition = (pgFunction: PGFunction) => { return pgFunction.function_definition; }; +export const isFunctionCompatibleToTable = ( + pgFunction: PGFunction, + tableName: string, + tableSchema: string +) => { + const inputArgTypes = pgFunction?.input_arg_types || []; + + let hasTableRowInArguments = false; + let hasUnsupportedArguments = false; + + inputArgTypes.forEach(inputArgType => { + if (!hasTableRowInArguments) { + hasTableRowInArguments = + inputArgType.name === tableName && inputArgType.schema === tableSchema; + } + + if (!hasUnsupportedArguments) { + hasUnsupportedArguments = + inputArgType.type !== 'c' && inputArgType.type !== 'b'; + } + }); + + return hasTableRowInArguments && !hasUnsupportedArguments; +}; + export const getSchemaFunctions = ( allFunctions: PGFunction[], - fnSchema: string + fnSchema: string, + tableName: string, + tableSchema: string ) => { - return allFunctions.filter(fn => getFunctionSchema(fn) === fnSchema); + return allFunctions.filter( + fn => + getFunctionSchema(fn) === fnSchema && + isFunctionCompatibleToTable(fn, tableName, tableSchema) + ); }; export const findFunction = ( diff --git a/console/src/dataSources/services/postgresql/types.ts b/console/src/dataSources/services/postgresql/types.ts index 9b734a96f6e3d..20ff05a94bce6 100644 --- a/console/src/dataSources/services/postgresql/types.ts +++ b/console/src/dataSources/services/postgresql/types.ts @@ -1,9 +1,16 @@ +export type PGInputArgType = { + schema: string; + name: string; + type: string; +}; + export type PGFunction = { function_name: string; function_schema: string; function_definition: string; return_type_type: string; function_type: string; + input_arg_types?: PGInputArgType[]; }; export interface PostgresTable {