Files
Joshen Lim 097f220c5c Add support for managing stored procedures under database functions (#46977)
## Context

Dashboard currently doesn't have any support for managing stored
procedures. In the event that the security advisor surfaces a warning
about a stored procedure, users hence run into a dead-end as there's
currently no way to self-remediate via the dashboard

## Changes involved

We're hence adding support for managing stored procedures within
Database Functions
<img width="1082" height="546" alt="image"
src="https://github.com/user-attachments/assets/2598a5fe-e58f-4e8a-ad2f-9cb6d0eb2f53"
/>

Creating a function now shows a dropdown to select the type
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/acc9249d-7b25-4416-aae8-89c630e1c62b"
/>

In which if stored procedure is selected, the following fields will be
hidden since they're irrelevant for stored procedures
- Return type
- Behaviour (Under advanced settings)

Some other minor UI changes as well:
- Field inputs are re-ordered a little, opting to group "Schema" and
"Name" into one section, followed by "Type" and "Return type"
- Opting to show "Return type" when editing a function but disabled
- Add schema filter for fetching database functions to reduce
unnecessary load on the database

## To test
- [ ] Can create, update, delete, read stored procedures via database
functions page

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary

- **New Features**
- Added PostgreSQL **procedure** support alongside functions, including
a **Type** selector in the create/edit flow.
- Updated Functions UI with a new **Type** column and procedure-aware
return/argument details.

- **Improvements**
- Refreshed create/edit headers and language help text for clearer
context.
- Improved argument parsing/display, including better handling of
procedure argument modes.

- **Bug Fixes**
- Corrected routine-type handling during function/procedure delete and
update SQL operations.

- **Tests**
- Updated unit snapshots and end-to-end UI flows/labels for the new “New
function” control.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-17 19:15:54 +08:00

69 lines
2.1 KiB
TypeScript

import { isEmpty } from 'lodash'
/**
* Procedures expose their arguments with explicit `IN` mode prefixes
* (e.g. "IN a integer, IN b integer"); strip them so they read like functions.
* `INOUT`/`VARIADIC` are left intact since `IN\s+` requires whitespace after `IN`.
*/
export function stripInArgModePrefixes(value: string) {
return value?.replace(/(^|,)\s*IN\s+/gi, '$1')
}
/**
* convert argument_types = "a integer, b integer"
* to args = {value: [{name:'a', type:'integer'}, {name:'b', type:'integer'}]}
*/
export function convertArgumentTypes({
type,
value,
}: {
type: 'function' | 'procedure'
value: string
}) {
const normalizedValue = type === 'procedure' ? stripInArgModePrefixes(value) : value
const items = normalizedValue?.split(',').map((item) => item.trim())
if (isEmpty(value) || !items || items.length === 0) return { value: [] }
const temp = items
.map((x) => {
const regex = /(\w+)\s+([\w\[\]]+)(?:\s+DEFAULT\s+(.*))?/i
const match = x.match(regex)
if (match) {
const [, name, type, defaultValue] = match
let parsedDefaultValue = defaultValue ? defaultValue.trim() : undefined
if (
['timestamp', 'time', 'timetz', 'timestamptz'].includes(type.toLowerCase()) &&
parsedDefaultValue
) {
parsedDefaultValue = `'${parsedDefaultValue}'`
}
return { name, type, defaultValue: parsedDefaultValue }
} else {
console.error('Error while trying to parse function arguments', x)
return null
}
})
.filter(Boolean) as { name: string; type: string; defaultValue?: string }[]
return { value: temp }
}
/**
* convert config_params = {search_path: "auth, public"}
* to {value: [{name: 'search_path', value: 'auth, public'}]}
*/
export function convertConfigParams(value: Record<string, string> | null | undefined) {
const temp = []
if (value) {
for (var key in value) {
temp.push({ name: key, value: value[key] })
}
}
return { value: temp }
}
export function hasWhitespace(value: string) {
return /\s/.test(value)
}