This is an automated email from the ASF dual-hosted git repository.

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new a89e394b5c [#10027][#10028][#10044][#10045][#10048][#10056] fix and 
improve function related issue (#10114)
a89e394b5c is described below

commit a89e394b5c249ad722c4f1ae292cefe8f08bfe48
Author: Qian Xia <[email protected]>
AuthorDate: Tue Mar 3 20:35:48 2026 +0800

    [#10027][#10028][#10044][#10045][#10048][#10056] fix and improve function 
related issue (#10114)
    
    ### What changes were proposed in this pull request?
    <img width="2762" height="1866" alt="image"
    
src="https://github.com/user-attachments/assets/19bb4725-8c09-4e1a-a23b-1c48cd6421fa";
    />
    
    ### Why are the changes needed?
    N/A
    
    Fix: #10027
    Fix: #10028
    Fix: #10044
    Fix: #10045
    Fix: #10048
    Fix: #10056
    
    ### Does this PR introduce _any_ user-facing change?
    N/A
    
    ### How was this patch tested?
    manually
---
 web-v2/web/src/app/catalogs/TreeComponent.js       | 113 +--
 web-v2/web/src/app/catalogs/page.js                | 245 ++++---
 .../entitiesContent/FunctionDetailsPage.js         |  84 ++-
 .../rightContent/entitiesContent/ListFiles.js      |   1 -
 web-v2/web/src/lib/store/metalakes/index.js        |  70 +-
 web-v2/web/src/lib/utils/initial.js                | 775 ---------------------
 6 files changed, 308 insertions(+), 980 deletions(-)

diff --git a/web-v2/web/src/app/catalogs/TreeComponent.js 
b/web-v2/web/src/app/catalogs/TreeComponent.js
index 4d1b80cdf4..c023f5d3ef 100644
--- a/web-v2/web/src/app/catalogs/TreeComponent.js
+++ b/web-v2/web/src/app/catalogs/TreeComponent.js
@@ -21,10 +21,9 @@
 
 import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, 
useState } from 'react'
 
-import { usePathname, useRouter, useSearchParams } from 'next/navigation'
+import { useRouter, useSearchParams } from 'next/navigation'
 
 import { Empty, Input, Spin, Tabs, Tooltip, Tree } from 'antd'
-import { useTheme } from 'next-themes'
 
 import Icons from '@/components/Icons'
 import { checkCatalogIcon } from '@/config/catalog'
@@ -34,7 +33,6 @@ import {
   setExpanded,
   setExpandedNodes,
   setIntoTreeNodeWithFetch,
-  removeExpandedNode,
   setSelectedNodes,
   setLoadedNodes,
   getTableDetails,
@@ -52,18 +50,12 @@ const { Search } = Input
 export const TreeComponent = forwardRef(function TreeComponent(props, ref) {
   const [treeData, setTreeData] = useState([])
   const [searchValue, setSearchValue] = useState('')
-  const [expandedKeys, setExpandedKeys] = useState([])
-  const [loadedKeys, setLoadedKeys] = useState([])
   const [autoExpandParent, setAutoExpandParent] = useState(false)
-  const [selectedKeys, setSelectedKeys] = useState([])
   const router = useRouter()
-  const pathname = usePathname()
   const searchParams = useSearchParams()
   const currentMetalake = searchParams.get('metalake')
   const [currentCatalogType, setCatalogType] = 
useState(searchParams.get('catalogType') || 'relational')
-  const [, locale, , catalog, schema, entity] = pathname.split('/')
   const treeRef = useRef(null)
-  const { theme } = useTheme()
   const [isHover, setIsHover] = useState('')
   const dispatch = useAppDispatch()
   const store = useAppSelector(state => state.metalakes)
@@ -75,26 +67,6 @@ export const TreeComponent = forwardRef(function 
TreeComponent(props, ref) {
     { label: 'Model', key: 'model' }
   ]
 
-  const updateTreeData = (list, key, children) =>
-    list.map(node => {
-      if (node.key === key) {
-        return {
-          ...node,
-          isLeaf: children?.length === 0,
-          children
-        }
-      }
-      if (node.children) {
-        return {
-          ...node,
-          isLeaf: node.children.length === 0,
-          children: updateTreeData(node.children, key, children)
-        }
-      }
-
-      return node
-    })
-
   const handleDoubleClick = event => {
     event.preventDefault()
     event.stopPropagation()
@@ -323,29 +295,62 @@ export const TreeComponent = forwardRef(function 
TreeComponent(props, ref) {
     if (searchParams.get('catalogType')) {
       setCatalogType(searchParams.get('catalogType'))
     } else {
-      setExpandedKeys([])
+      dispatch(setExpanded([]))
       setCatalogType('relational')
     }
-  }, [searchParams.get('catalogType')])
+  }, [dispatch, searchParams])
 
   useEffect(() => {
-    setTimeout(() => {
-      if (entity) {
-        setExpandedKeys(
-          Array.from(new Set([...expandedKeys, catalog, 
`${catalog}/${schema}`, `${catalog}/${schema}/${entity}`]))
-        )
-        setSelectedKeys([`${catalog}/${schema}/${entity}`])
-      } else if (schema && !entity) {
-        setExpandedKeys(Array.from(new Set([...expandedKeys, catalog, 
`${catalog}/${schema}`])))
-        setSelectedKeys([`${catalog}/${schema}`])
-      } else if (catalog && !schema && !entity) {
-        setExpandedKeys(Array.from(new Set([...expandedKeys, catalog])))
-        setSelectedKeys([catalog])
-      } else if (!catalog && !schema && !entity) {
-        setExpandedKeys(Array.from(new Set([...expandedKeys])))
+    const timerId = setTimeout(() => {
+      const metalake = searchParams.get('metalake')
+      const catalog = searchParams.get('catalog')
+      const catalogType = searchParams.get('catalogType')
+      const schema = searchParams.get('schema')
+      const table = searchParams.get('table')
+      const fileset = searchParams.get('fileset')
+      const topic = searchParams.get('topic')
+      const model = searchParams.get('model')
+      const func = searchParams.get('function')
+      const entity = table || fileset || topic || model || func
+
+      if (!metalake) {
+        dispatch(setExpanded([]))
+        dispatch(setSelectedNodes([]))
+
+        return
+      }
+
+      const metalakeKey = `{{${metalake}}}`
+      const nextExpandedKeys = [metalakeKey]
+      let nextSelectedKey = ''
+
+      if (catalog && catalogType) {
+        const catalogKey = `${metalakeKey}{{${catalog}}}{{${catalogType}}}`
+        nextExpandedKeys.push(catalogKey)
+        nextSelectedKey = catalogKey
+
+        if (schema) {
+          const schemaKey = `${catalogKey}{{${schema}}}`
+          nextExpandedKeys.push(schemaKey)
+          nextSelectedKey = schemaKey
+
+          if (entity) {
+            nextSelectedKey = `${schemaKey}{{${entity}}}`
+          }
+        }
+      }
+
+      dispatch(setExpandedNodes(Array.from(new Set(nextExpandedKeys))))
+
+      if (nextSelectedKey) {
+        dispatch(setSelectedNodes([nextSelectedKey]))
       }
     }, 1000)
-  }, [pathname])
+
+    return () => {
+      clearTimeout(timerId)
+    }
+  }, [dispatch, searchParams])
 
   const renderTitle = title => {
     return (
@@ -493,12 +498,16 @@ export const TreeComponent = forwardRef(function 
TreeComponent(props, ref) {
       }
     }
     if (inUseStr === 'false') {
+      const catalogPrefix = 
`{{${currentMetalake}}}{{${name}}}{{${currentCatalogType}}}`
+
       // Remove from expanded and loaded nodes
-      dispatch(setExpandedNodes([...store.expandedNodes.filter(key => 
!key.includes(name))]))
-      dispatch(setLoadedNodes([...store.loadedNodes.filter(key => 
!key.includes(name))]))
+      dispatch(setExpandedNodes([...store.expandedNodes.filter(key => 
!key.startsWith(catalogPrefix))]))
+      dispatch(setLoadedNodes([...store.loadedNodes.filter(key => 
!key.startsWith(catalogPrefix))]))
     } else {
+      const catalogKey = 
`{{${currentMetalake}}}{{${name}}}{{${currentCatalogType}}}`
+
       // Remove from loaded nodes to allow re-fetching children when expanded
-      dispatch(setLoadedNodes([...store.loadedNodes.filter(key => key !== 
name)]))
+      dispatch(setLoadedNodes([...store.loadedNodes.filter(key => key !== 
catalogKey)]))
     }
     setTreeData(tree)
   }
@@ -614,9 +623,9 @@ export const TreeComponent = forwardRef(function 
TreeComponent(props, ref) {
 
   const onChangeType = key => {
     setCatalogType(key)
-    setExpandedKeys([])
-    setLoadedKeys([])
-    setSelectedKeys([])
+    dispatch(setExpanded([]))
+    dispatch(setLoadedNodes([]))
+    dispatch(setSelectedNodes([]))
     setTreeData([])
     router.push(`/catalogs?metalake=${currentMetalake}&catalogType=${key}`)
   }
diff --git a/web-v2/web/src/app/catalogs/page.js 
b/web-v2/web/src/app/catalogs/page.js
index 2231c9d6eb..e4af25ad55 100644
--- a/web-v2/web/src/app/catalogs/page.js
+++ b/web-v2/web/src/app/catalogs/page.js
@@ -40,6 +40,7 @@ import {
   fetchModels,
   fetchModelVersions,
   fetchFunctions,
+  getFunctionDetails,
   getMetalakeDetails,
   getCatalogDetails,
   getSchemaDetails,
@@ -52,7 +53,8 @@ import {
   getCurrentEntityTags,
   getCurrentEntityPolicies,
   resetActivatedDetails,
-  setActivatedDetailsLoading
+  setActivatedDetailsLoading,
+  setLoadedNodes
 } from '@/lib/store/metalakes'
 
 import { fetchTags } from '@/lib/store/tags'
@@ -67,7 +69,7 @@ const CatalogsListPage = () => {
   const treeRef = useRef()
 
   const buildNodePath = routeParams => {
-    const keys = ['metalake', 'catalog', 'catalogType', 'schema', 'table', 
'fileset', 'topic', 'model']
+    const keys = ['metalake', 'catalog', 'catalogType', 'schema', 'table', 
'fileset', 'topic', 'model', 'function']
 
     return keys.map(key => (routeParams[key] ? `{{${routeParams[key]}}}` : 
'')).join('')
   }
@@ -82,11 +84,23 @@ const CatalogsListPage = () => {
       fileset: searchParams.get('fileset'),
       topic: searchParams.get('topic'),
       model: searchParams.get('model'),
+      function: searchParams.get('function'),
       version: searchParams.get('version')
     }
     async function fetchDependsData() {
       if ([...searchParams.keys()].length) {
-        const { metalake, catalog, catalogType, schema, table, fileset, topic, 
model, version } = routeParams
+        const {
+          metalake,
+          catalog,
+          catalogType,
+          schema,
+          table,
+          fileset,
+          topic,
+          model,
+          function: func,
+          version
+        } = routeParams
 
         if (metalake) {
           dispatch(fetchTags({ metalake, details: true }))
@@ -99,6 +113,13 @@ const CatalogsListPage = () => {
         }
 
         if (paramsSize === 3 && catalog) {
+          if (catalogType) {
+            const catalogKey = 
`{{${metalake}}}{{${catalog}}}{{${catalogType}}}`
+            if (!store.loadedNodes.includes(catalogKey)) {
+              await dispatch(setLoadedNodes([...new Set([...store.loadedNodes, 
catalogKey])]))
+            }
+          }
+
           if (!store.catalogs.length) {
             await dispatch(fetchCatalogs({ metalake }))
           }
@@ -171,115 +192,123 @@ const CatalogsListPage = () => {
           )
         }
 
-        if (paramsSize === 5 && catalog && catalogType && schema && (table || 
fileset || topic || model)) {
+        if (paramsSize === 5 && catalog && catalogType && schema) {
           if (!store.catalogs.length) {
             await dispatch(fetchCatalogs({ metalake }))
             await dispatch(fetchSchemas({ metalake, catalog, catalogType }))
           }
-          switch (catalogType) {
-            case 'relational':
-              store.tables.length === 0 &&
-                (await dispatch(fetchTables({ init: true, page: 'schemas', 
metalake, catalog, schema })))
-              await dispatch(resetActivatedDetails())
-              await dispatch(setActivatedDetailsLoading(true))
-              await dispatch(getTableDetails({ init: true, metalake, catalog, 
schema, table }))
-              await dispatch(setActivatedDetailsLoading(false))
-              dispatch(
-                getCurrentEntityTags({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'table',
-                  metadataObjectFullName: `${catalog}.${schema}.${table}`
-                })
-              )
-              dispatch(
-                getCurrentEntityPolicies({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'table',
-                  metadataObjectFullName: `${catalog}.${schema}.${table}`,
-                  details: true
-                })
-              )
-              break
-            case 'fileset':
-              store.filesets.length === 0 &&
-                (await dispatch(fetchFilesets({ init: true, page: 'schemas', 
metalake, catalog, schema })))
-              await dispatch(setActivatedDetailsLoading(true))
-              await dispatch(getFilesetDetails({ init: true, metalake, 
catalog, schema, fileset }))
-              await dispatch(setActivatedDetailsLoading(false))
-              dispatch(
-                getCurrentEntityTags({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'fileset',
-                  metadataObjectFullName: `${catalog}.${schema}.${fileset}`,
-                  details: true
-                })
-              )
-              dispatch(
-                getCurrentEntityPolicies({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'fileset',
-                  metadataObjectFullName: `${catalog}.${schema}.${fileset}`,
-                  details: true
-                })
-              )
-              break
-            case 'messaging':
-              store.topics.length === 0 &&
-                (await dispatch(fetchTopics({ init: true, page: 'schemas', 
metalake, catalog, schema })))
-              await dispatch(setActivatedDetailsLoading(true))
-              await dispatch(getTopicDetails({ init: true, metalake, catalog, 
schema, topic }))
-              await dispatch(setActivatedDetailsLoading(false))
-              dispatch(
-                getCurrentEntityTags({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'topic',
-                  metadataObjectFullName: `${catalog}.${schema}.${topic}`,
-                  details: true
-                })
-              )
-              dispatch(
-                getCurrentEntityPolicies({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'topic',
-                  metadataObjectFullName: `${catalog}.${schema}.${topic}`,
-                  details: true
-                })
-              )
-              break
-            case 'model':
-              store.models.length === 0 &&
-                (await dispatch(fetchModels({ init: true, page: 'schemas', 
metalake, catalog, schema })))
-              await dispatch(fetchModelVersions({ init: true, metalake, 
catalog, schema, model }))
-              await dispatch(setActivatedDetailsLoading(true))
-              await dispatch(getModelDetails({ init: true, metalake, catalog, 
schema, model }))
-              await dispatch(setActivatedDetailsLoading(false))
-              dispatch(
-                getCurrentEntityTags({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'model',
-                  metadataObjectFullName: `${catalog}.${schema}.${model}`,
-                  details: true
-                })
-              )
-              dispatch(
-                getCurrentEntityPolicies({
-                  init: true,
-                  metalake,
-                  metadataObjectType: 'model',
-                  metadataObjectFullName: `${catalog}.${schema}.${model}`,
-                  details: true
-                })
-              )
-              break
-            default:
-              break
+          if (table || fileset || topic || model) {
+            switch (catalogType) {
+              case 'relational':
+                store.tables.length === 0 &&
+                  (await dispatch(fetchTables({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+                await dispatch(resetActivatedDetails())
+                await dispatch(setActivatedDetailsLoading(true))
+                await dispatch(getTableDetails({ init: true, metalake, 
catalog, schema, table }))
+                await dispatch(setActivatedDetailsLoading(false))
+                dispatch(
+                  getCurrentEntityTags({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'table',
+                    metadataObjectFullName: `${catalog}.${schema}.${table}`
+                  })
+                )
+                dispatch(
+                  getCurrentEntityPolicies({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'table',
+                    metadataObjectFullName: `${catalog}.${schema}.${table}`,
+                    details: true
+                  })
+                )
+                break
+              case 'fileset':
+                store.filesets.length === 0 &&
+                  (await dispatch(fetchFilesets({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+                await dispatch(setActivatedDetailsLoading(true))
+                await dispatch(getFilesetDetails({ init: true, metalake, 
catalog, schema, fileset }))
+                await dispatch(setActivatedDetailsLoading(false))
+                dispatch(
+                  getCurrentEntityTags({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'fileset',
+                    metadataObjectFullName: `${catalog}.${schema}.${fileset}`,
+                    details: true
+                  })
+                )
+                dispatch(
+                  getCurrentEntityPolicies({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'fileset',
+                    metadataObjectFullName: `${catalog}.${schema}.${fileset}`,
+                    details: true
+                  })
+                )
+                break
+              case 'messaging':
+                store.topics.length === 0 &&
+                  (await dispatch(fetchTopics({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+                await dispatch(setActivatedDetailsLoading(true))
+                await dispatch(getTopicDetails({ init: true, metalake, 
catalog, schema, topic }))
+                await dispatch(setActivatedDetailsLoading(false))
+                dispatch(
+                  getCurrentEntityTags({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'topic',
+                    metadataObjectFullName: `${catalog}.${schema}.${topic}`,
+                    details: true
+                  })
+                )
+                dispatch(
+                  getCurrentEntityPolicies({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'topic',
+                    metadataObjectFullName: `${catalog}.${schema}.${topic}`,
+                    details: true
+                  })
+                )
+                break
+              case 'model':
+                store.models.length === 0 &&
+                  (await dispatch(fetchModels({ init: true, page: 'schemas', 
metalake, catalog, schema })))
+                await dispatch(fetchModelVersions({ init: true, metalake, 
catalog, schema, model }))
+                await dispatch(setActivatedDetailsLoading(true))
+                await dispatch(getModelDetails({ init: true, metalake, 
catalog, schema, model }))
+                await dispatch(setActivatedDetailsLoading(false))
+                dispatch(
+                  getCurrentEntityTags({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'model',
+                    metadataObjectFullName: `${catalog}.${schema}.${model}`,
+                    details: true
+                  })
+                )
+                dispatch(
+                  getCurrentEntityPolicies({
+                    init: true,
+                    metalake,
+                    metadataObjectType: 'model',
+                    metadataObjectFullName: `${catalog}.${schema}.${model}`,
+                    details: true
+                  })
+                )
+                break
+              default:
+                break
+            }
+          }
+          if (func) {
+            store.functions.length === 0 && (await dispatch(fetchFunctions({ 
init: true, metalake, catalog, schema })))
+            await dispatch(setActivatedDetailsLoading(true))
+            await dispatch(getFunctionDetails({ init: true, metalake, catalog, 
schema, functionName: func }))
+            await dispatch(setActivatedDetailsLoading(false))
           }
         }
         if (paramsSize === 6 && version) {
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
index 6101684da6..221d0971ae 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
@@ -23,9 +23,8 @@ import { useEffect, useState } from 'react'
 import { Collapse, Descriptions, Space, Spin, Tag, Typography, Flex, Tooltip, 
Divider, Tabs } from 'antd'
 import { useSearchParams } from 'next/navigation'
 import { formatToDateTime, isValidDate } from '@/lib/utils/date'
-import { getFunctionDetailsApi } from '@/lib/api/functions'
-import { to } from '@/lib/utils'
 import Icons from '@/components/Icons'
+import { useAppSelector } from '@/lib/hooks/useStore'
 
 const { Title, Paragraph } = Typography
 
@@ -37,11 +36,30 @@ const formatType = type => {
   return JSON.stringify(type)
 }
 
+const formatDefaultValue = defaultValue => {
+  if (defaultValue == null) return ''
+
+  if (typeof defaultValue === 'string' || typeof defaultValue === 'number' || 
typeof defaultValue === 'boolean') {
+    return `${defaultValue}`
+  }
+
+  if (typeof defaultValue === 'object') {
+    if (Object.prototype.hasOwnProperty.call(defaultValue, 'value')) {
+      return `${defaultValue.value}`
+    }
+
+    return JSON.stringify(defaultValue)
+  }
+
+  return ''
+}
+
 const renderParameters = parameters => {
   if (!parameters?.length) return null
 
   return parameters.map((param, index) => {
     const comment = typeof param?.comment === 'string' ? param.comment.trim() 
: ''
+    const defaultText = formatDefaultValue(param?.defaultValue)
 
     return (
       <span key={`${param.name || 'param'}-${index}`}>
@@ -53,7 +71,7 @@ const renderParameters = parameters => {
             </span>
           </Tooltip>
         )}
-        <span>{`: ${formatType(param.dataType)}`}</span>
+        <span>{`: ${formatType(param.dataType)}${defaultText ? ` = 
${defaultText}` : ''}`}</span>
         {index < parameters.length - 1 && <span>, </span>}
       </span>
     )
@@ -118,6 +136,10 @@ const buildImplDetails = impl => {
 const FunctionImplTabs = ({ impls }) => {
   const [activeKey, setActiveKey] = useState(impls[0]?.runtime || 'runtime-0')
 
+  useEffect(() => {
+    setActiveKey(impls[0]?.runtime || 'runtime-0')
+  }, [impls])
+
   const tabItems = impls.map((impl, implIndex) => {
     const runtimeKey = impl?.runtime || `runtime-${implIndex}`
     const tabLabel = impl?.runtime || `Runtime ${implIndex + 1}`
@@ -138,7 +160,14 @@ const FunctionImplTabs = ({ impls }) => {
   return (
     <div className='flex flex-col gap-2 w-full'>
       <Tabs size='small' className='w-full' items={tabItems} 
activeKey={activeKey} onChange={setActiveKey} />
-      <Descriptions layout='horizontal' column={1} size='small' bordered 
style={{ width: '100%' }}>
+      <Descriptions
+        layout='horizontal'
+        column={1}
+        size='small'
+        bordered
+        style={{ width: '100%' }}
+        labelStyle={{ width: '40%' }}
+      >
         {activeDetails.map(detail => (
           <Descriptions.Item key={`${activeKey}-${detail.label}`} 
label={detail.label}>
             {detail.value}
@@ -151,37 +180,14 @@ const FunctionImplTabs = ({ impls }) => {
 
 export default function FunctionDetailsPage() {
   const searchParams = useSearchParams()
-  const metalake = searchParams.get('metalake')
-  const catalog = searchParams.get('catalog')
-  const schema = searchParams.get('schema')
   const functionName = searchParams.get('function')
-  const [loading, setLoading] = useState(false)
-  const [functionData, setFunctionData] = useState(null)
+  const store = useAppSelector(state => state.metalakes)
+  const functionData = store.activatedDetails
+  const [activeDefinitionKeys, setActiveDefinitionKeys] = useState([])
 
   useEffect(() => {
-    const loadDetails = async () => {
-      if (!metalake || !catalog || !schema || !functionName) return
-      setLoading(true)
-
-      const [err, res] = await to(
-        getFunctionDetailsApi({
-          metalake,
-          catalog,
-          schema,
-          functionName
-        })
-      )
-      setLoading(false)
-      if (err || !res) {
-        setFunctionData(null)
-
-        return
-      }
-      setFunctionData(res.function)
-    }
-
-    loadDetails()
-  }, [metalake, catalog, schema, functionName])
+    setActiveDefinitionKeys([])
+  }, [functionName])
 
   const definitions = functionData?.definitions || []
   const createdAt = functionData?.audit?.createTime
@@ -211,7 +217,7 @@ export default function FunctionDetailsPage() {
   })
 
   return (
-    <Spin spinning={loading}>
+    <Spin spinning={store.activatedDetailsLoading}>
       <div className='bg-white min-h-[calc(100vh-24rem)]'>
         <Space direction='vertical' size='small' style={{ width: '100%' }}>
           <Flex className='mb-2' gap='small' align='flex-start'>
@@ -260,11 +266,19 @@ export default function FunctionDetailsPage() {
             </Space>
           </Space>
           <div className='max-h-[60vh] overflow-auto'>
-            <div className='mb-2 font-medium'>Definitions</div>
+            <Tabs
+              data-refer='details-tabs'
+              defaultActiveKey={'Definitions'}
+              items={[{ label: 'Definitions', key: 'Definitions' }]}
+            />
             {definitions.length === 0 ? (
               <span className='text-slate-400'>No definitions</span>
             ) : (
-              <Collapse items={definitionItems} defaultActiveKey={[]} />
+              <Collapse
+                items={definitionItems}
+                activeKey={activeDefinitionKeys}
+                onChange={keys => setActiveDefinitionKeys(Array.isArray(keys) 
? keys : [keys])}
+              />
             )}
           </div>
         </Space>
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ListFiles.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ListFiles.js
index 849e1409c3..3fd84ba3f9 100644
--- a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ListFiles.js
+++ b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/ListFiles.js
@@ -56,7 +56,6 @@ const ListFiles = ({ metalake, catalog, schema, fileset, 
storageLocations, defau
   }, [sub_path])
 
   useEffect(() => {
-    console.log('sub_path changed:', sub_path)
     if (metalake && catalog && schema && fileset) {
       dispatch(
         getFilesetFiles({
diff --git a/web-v2/web/src/lib/store/metalakes/index.js 
b/web-v2/web/src/lib/store/metalakes/index.js
index d0f2c318f1..78cc3719d0 100644
--- a/web-v2/web/src/lib/store/metalakes/index.js
+++ b/web-v2/web/src/lib/store/metalakes/index.js
@@ -77,7 +77,7 @@ import {
   updateVersionApi,
   deleteVersionApi
 } from '@/lib/api/models'
-import { getFunctionsApi } from '@/lib/api/functions'
+import { getFunctionsApi, getFunctionDetailsApi } from '@/lib/api/functions'
 
 const remapExpandedAndLoadedNodes = ({ getState, mapNode }) => {
   const expandedNodes = getState().metalakes.expandedNodes.map(mapNode)
@@ -89,6 +89,13 @@ const remapExpandedAndLoadedNodes = ({ getState, mapNode }) 
=> {
   }
 }
 
+const mergeWithFunctionNodes = ({ tree, key, entities }) => {
+  const existingNode = findInTree(tree, 'key', key)
+  const functions = existingNode?.children?.filter(child => child?.node === 
'function') || []
+
+  return _.uniqBy([...entities, ...functions], 'key')
+}
+
 export const fetchMetalakes = createAsyncThunk('appMetalakes/fetchMetalakes', 
async (params, { getState }) => {
   const [err, res] = await to(getMetalakesApi())
 
@@ -799,6 +806,24 @@ export const fetchSchemas = createAsyncThunk(
     await dispatch(setTableLoading(false))
 
     if (err || !res) {
+      const isCanceledRequest =
+        err?.name === 'CanceledError' ||
+        err?.code === 'ERR_CANCELED' ||
+        err?.message?.includes?.('canceled') ||
+        err?.message?.includes?.('aborted')
+
+      if (isCanceledRequest) {
+        const catalogKey = `{{${metalake}}}{{${catalog}}}{{${catalogType}}}`
+        const catalogNode = findInTree(getState().metalakes.metalakeTree, 
'key', catalogKey)
+        const cachedSchemas = (catalogNode?.children || []).filter(item => 
item?.node === 'schema')
+
+        if (cachedSchemas.length > 0) {
+          dispatch(setExpandedNodes([`{{${metalake}}}`, catalogKey]))
+
+          return { schemas: cachedSchemas, page, init }
+        }
+      }
+
       throw new Error(err)
     }
 
@@ -1035,10 +1060,11 @@ export const fetchTables = createAsyncThunk(
       init &&
       
getState().metalakes.loadedNodes.includes(`{{${metalake}}}{{${catalog}}}{{${'relational'}}}{{${schema}}}`)
     ) {
+      const tableKey = 
`{{${metalake}}}{{${catalog}}}{{${'relational'}}}{{${schema}}}`
       dispatch(
         setIntoTreeNodes({
-          key: `{{${metalake}}}{{${catalog}}}{{${'relational'}}}{{${schema}}}`,
-          data: tables
+          key: tableKey,
+          data: mergeWithFunctionNodes({ tree: 
getState().metalakes.metalakeTree, key: tableKey, entities: tables })
         })
       )
     }
@@ -1348,10 +1374,11 @@ export const fetchFilesets = createAsyncThunk(
       init &&
       
getState().metalakes.loadedNodes.includes(`{{${metalake}}}{{${catalog}}}{{${'fileset'}}}{{${schema}}}`)
     ) {
+      const filesetKey = 
`{{${metalake}}}{{${catalog}}}{{${'fileset'}}}{{${schema}}}`
       dispatch(
         setIntoTreeNodes({
-          key: `{{${metalake}}}{{${catalog}}}{{${'fileset'}}}{{${schema}}}`,
-          data: filesets
+          key: filesetKey,
+          data: mergeWithFunctionNodes({ tree: 
getState().metalakes.metalakeTree, key: filesetKey, entities: filesets })
         })
       )
     }
@@ -1588,10 +1615,11 @@ export const fetchTopics = createAsyncThunk(
       init &&
       
getState().metalakes.loadedNodes.includes(`{{${metalake}}}{{${catalog}}}{{${'messaging'}}}{{${schema}}}`)
     ) {
+      const topicKey = 
`{{${metalake}}}{{${catalog}}}{{${'messaging'}}}{{${schema}}}`
       await dispatch(
         setIntoTreeNodes({
-          key: `{{${metalake}}}{{${catalog}}}{{${'messaging'}}}{{${schema}}}`,
-          data: topics
+          key: topicKey,
+          data: mergeWithFunctionNodes({ tree: 
getState().metalakes.metalakeTree, key: topicKey, entities: topics })
         })
       )
     }
@@ -1803,10 +1831,11 @@ export const fetchModels = createAsyncThunk(
     })
 
     if (init && 
getState().metalakes.loadedNodes.includes(`{{${metalake}}}{{${catalog}}}{{${'model'}}}{{${schema}}}`))
 {
+      const modelKey = 
`{{${metalake}}}{{${catalog}}}{{${'model'}}}{{${schema}}}`
       await dispatch(
         setIntoTreeNodes({
-          key: `{{${metalake}}}{{${catalog}}}{{${'model'}}}{{${schema}}}`,
-          data: models
+          key: modelKey,
+          data: mergeWithFunctionNodes({ tree: 
getState().metalakes.metalakeTree, key: modelKey, entities: models })
         })
       )
     }
@@ -2105,6 +2134,19 @@ export const fetchFunctions = createAsyncThunk(
   }
 )
 
+export const getFunctionDetails = createAsyncThunk(
+  'appMetalakes/getFunctionDetails',
+  async ({ init, metalake, catalog, schema, functionName }) => {
+    const [err, res] = await to(getFunctionDetailsApi({ metalake, catalog, 
schema, functionName }))
+
+    if (err || !res) {
+      throw new Error(err)
+    }
+
+    return { function: res.function || res, init }
+  }
+)
+
 export const appMetalakesSlice = createSlice({
   name: 'appMetalakes',
   initialState: {
@@ -2804,11 +2846,21 @@ export const appMetalakesSlice = createSlice({
     builder.addCase(fetchFunctions.fulfilled, (state, action) => {
       state.functions = action.payload.functions
     })
+    builder.addCase(getFunctionDetails.fulfilled, (state, action) => {
+      if (action.payload.init) {
+        state.activatedDetails = action.payload.function
+      }
+    })
     builder.addCase(fetchFunctions.rejected, (state, action) => {
       if (!action.error.message.includes('CanceledError')) {
         toast.error(action.error.message)
       }
     })
+    builder.addCase(getFunctionDetails.rejected, (state, action) => {
+      if (!action.error.message.includes('CanceledError')) {
+        toast.error(action.error.message)
+      }
+    })
     builder.addCase(getVersionDetails.rejected, (state, action) => {
       if (!action.error.message.includes('CanceledError')) {
         toast.error(action.error.message)
diff --git a/web-v2/web/src/lib/utils/initial.js 
b/web-v2/web/src/lib/utils/initial.js
deleted file mode 100644
index 1690c3573b..0000000000
--- a/web-v2/web/src/lib/utils/initial.js
+++ /dev/null
@@ -1,775 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF 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.
- */
-
-export const filesetProviders = [
-  {
-    label: 'Apache Hadoop',
-    value: 'hadoop',
-    defaultProps: [
-      {
-        key: 'location',
-        value: '',
-        required: false,
-        description: 'The storage location of the fileset'
-      }
-    ]
-  }
-]
-
-export const messagingProviders = [
-  {
-    label: 'Apache Kafka',
-    value: 'kafka',
-    defaultProps: [
-      {
-        key: 'bootstrap.servers',
-        value: '',
-        required: true,
-        description: 'The Apache Kafka brokers to connect to, allowing for 
multiple brokers separated by commas'
-      }
-    ]
-  }
-]
-
-export const providers = [
-  {
-    label: 'Apache Doris',
-    value: 'jdbc-doris',
-    defaultProps: [
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        description: 'e.g. com.mysql.jdbc.Driver'
-      },
-      {
-        key: 'jdbc-url',
-        value: '',
-        required: true,
-        description: 'e.g. jdbc:mysql://localhost:9030'
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true
-      }
-    ]
-  },
-  {
-    label: 'Apache Hive',
-    value: 'hive',
-    defaultProps: [
-      {
-        key: 'metastore.uris',
-        value: '',
-        required: true,
-        description: 'The Apache Hive metastore URIs'
-      }
-    ]
-  },
-  {
-    label: 'Apache Hudi',
-    value: 'lakehouse-hudi',
-    defaultProps: [
-      {
-        key: 'catalog-backend',
-        value: 'hms',
-        defaultValue: 'hms',
-        required: true,
-        select: ['hms'],
-        description: 'Apache Hudi catalog type choose properties'
-      },
-      {
-        key: 'uri',
-        value: '',
-        required: true,
-        description: 'Apache Hudi catalog uri config'
-      }
-    ]
-  },
-  {
-    label: 'Apache Iceberg',
-    value: 'lakehouse-iceberg',
-    defaultProps: [
-      {
-        key: 'catalog-backend',
-        value: 'hive',
-        defaultValue: 'hive',
-        required: true,
-        description: 'Apache Iceberg catalog type choose properties',
-        select: ['hive', 'jdbc', 'rest']
-      },
-      {
-        key: 'uri',
-        value: '',
-        required: true,
-        description: 'Apache Iceberg catalog uri config'
-      },
-      {
-        key: 'warehouse',
-        value: '',
-        description: 'Apache Iceberg catalog warehouse config'
-      },
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'rest'],
-        description: `"com.mysql.jdbc.Driver" or "com.mysql.cj.jdbc.Driver" 
for MySQL, "org.postgresql.Driver" for PostgreSQL`
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'rest']
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'rest']
-      },
-      {
-        key: 'authentication.type',
-        value: 'simple',
-        defaultValue: 'simple',
-        required: false,
-        description:
-          'The type of authentication for Paimon catalog backend, currently 
Gravitino only supports Kerberos and simple',
-        select: ['simple', 'Kerberos']
-      },
-      {
-        key: 'authentication.kerberos.principal',
-        value: '',
-        required: true,
-        description: 'The principal of the Kerberos authentication.',
-        parentField: 'authentication.type',
-        hide: ['simple']
-      },
-      {
-        key: 'authentication.kerberos.keytab-uri',
-        value: '',
-        required: true,
-        description: 'The URI of The keytab for the Kerberos authentication.',
-        parentField: 'authentication.type',
-        hide: ['simple']
-      }
-    ]
-  },
-  {
-    label: 'Apache Paimon',
-    value: 'lakehouse-paimon',
-    defaultProps: [
-      {
-        key: 'catalog-backend',
-        value: 'filesystem',
-        defaultValue: 'filesystem',
-        required: true,
-        select: ['filesystem', 'hive', 'jdbc']
-      },
-      {
-        key: 'uri',
-        value: '',
-        required: true,
-        description:
-          'e.g. thrift://127.0.0.1:9083 or 
jdbc:postgresql://127.0.0.1:5432/db_name or 
jdbc:mysql://127.0.0.1:3306/metastore_db',
-        parentField: 'catalog-backend',
-        hide: ['filesystem']
-      },
-      {
-        key: 'warehouse',
-        value: '',
-        required: true,
-        description: 'e.g. file:///user/hive/warehouse-paimon/ or 
hdfs://namespace/hdfs/path'
-      },
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'filesystem'],
-        description: `"com.mysql.jdbc.Driver" or "com.mysql.cj.jdbc.Driver" 
for MySQL, "org.postgresql.Driver" for PostgreSQL`
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'filesystem']
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true,
-        parentField: 'catalog-backend',
-        hide: ['hive', 'filesystem']
-      },
-      {
-        key: 'authentication.type',
-        value: 'simple',
-        defaultValue: 'simple',
-        required: false,
-        description:
-          'The type of authentication for Paimon catalog backend, currently 
Gravitino only supports Kerberos and simple',
-        select: ['simple', 'Kerberos']
-      },
-      {
-        key: 'authentication.kerberos.principal',
-        value: '',
-        required: true,
-        description: 'The principal of the Kerberos authentication.',
-        parentField: 'authentication.type',
-        hide: ['simple']
-      },
-      {
-        key: 'authentication.kerberos.keytab-uri',
-        value: '',
-        required: true,
-        description: 'The URI of The keytab for the Kerberos authentication.',
-        parentField: 'authentication.type',
-        hide: ['simple']
-      }
-    ]
-  },
-  {
-    label: 'Lakehouse Generic',
-    value: 'lakehouse-generic',
-    defaultProps: []
-  },
-  {
-    label: 'MySQL',
-    value: 'jdbc-mysql',
-    defaultProps: [
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        description: 'e.g. com.mysql.jdbc.Driver or com.mysql.cj.jdbc.Driver'
-      },
-      {
-        key: 'jdbc-url',
-        value: '',
-        required: true,
-        description: 'e.g. jdbc:mysql://localhost:3306'
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true
-      }
-    ]
-  },
-  {
-    label: 'OceanBase',
-    value: 'jdbc-oceanbase',
-    defaultProps: [
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        description: 'e.g. com.mysql.jdbc.Driver or com.mysql.cj.jdbc.Driver 
or com.oceanbase.jdbc.Driver'
-      },
-      {
-        key: 'jdbc-url',
-        value: '',
-        required: true,
-        description: 'e.g. jdbc:mysql://localhost:2881 or 
jdbc:oceanbase://localhost:2881'
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true
-      }
-    ]
-  },
-  {
-    label: 'PostgreSQL',
-    value: 'jdbc-postgresql',
-    defaultProps: [
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        description: 'e.g. org.postgresql.Driver'
-      },
-      {
-        key: 'jdbc-url',
-        value: '',
-        required: true,
-        description: 'e.g. jdbc:postgresql://localhost:5432/your_database'
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-database',
-        value: '',
-        required: true
-      }
-    ]
-  },
-  {
-    label: 'StarRocks',
-    value: 'jdbc-starrocks',
-    defaultProps: [
-      {
-        key: 'jdbc-driver',
-        value: '',
-        required: true,
-        description: 'e.g. com.mysql.jdbc.Driver'
-      },
-      {
-        key: 'jdbc-url',
-        value: '',
-        required: true,
-        description: 'e.g. jdbc:mysql://localhost:9030'
-      },
-      {
-        key: 'jdbc-user',
-        value: '',
-        required: true
-      },
-      {
-        key: 'jdbc-password',
-        value: '',
-        required: true
-      }
-    ]
-  }
-]
-
-const parameterizedColumnTypes = {
-  char: {
-    params: ['length'],
-    validateParams: params => {
-      if (params.length !== 1) {
-        return {
-          valid: false,
-          message: 'Please set length'
-        }
-      }
-
-      const length = params[0]
-
-      if (length <= 0) {
-        return {
-          valid: false,
-          message: 'The length must be greater than 0'
-        }
-      }
-
-      return {
-        valid: true
-      }
-    }
-  },
-  decimal: {
-    params: ['precision', 'scale'],
-    validateParams: params => {
-      if (params.length !== 2) {
-        return {
-          valid: false,
-          message: 'Please set precision and scale'
-        }
-      }
-
-      const [param1, param2] = params
-      if (param1 <= 0 || param1 > 38) {
-        return {
-          valid: false,
-          message: 'The precision must be between 1 and 38'
-        }
-      }
-
-      if (param2 < 0 || param2 > param1) {
-        return {
-          valid: false,
-          message: 'The scale must be between 0 and the precision'
-        }
-      }
-
-      return {
-        valid: true
-      }
-    }
-  },
-  fixed: {
-    params: ['length'],
-    validateParams: params => {
-      if (params.length !== 1) {
-        return {
-          valid: false,
-          message: 'Please set length'
-        }
-      }
-
-      const length = params[0]
-
-      if (length <= 0) {
-        return {
-          valid: false,
-          message: 'The length must be greater than 0'
-        }
-      }
-
-      return {
-        valid: true
-      }
-    }
-  },
-  varchar: {
-    params: ['length'],
-    validateParams: params => {
-      if (params.length !== 1) {
-        return {
-          valid: false,
-          message: 'Please set length'
-        }
-      }
-
-      const length = params[0]
-
-      if (length <= 0) {
-        return {
-          valid: false,
-          message: 'The length must be greater than 0'
-        }
-      }
-
-      return {
-        valid: true
-      }
-    }
-  }
-}
-
-export const getParameterizedColumnType = type => {
-  if (Object.hasOwn(parameterizedColumnTypes, type)) {
-    return parameterizedColumnTypes[type]
-  }
-}
-
-const relationalColumnTypeMap = {
-  'lakehouse-iceberg': [
-    'binary',
-    'boolean',
-    'date',
-    'decimal',
-    'double',
-    'fixed',
-    'float',
-    'integer',
-    'long',
-    'string',
-    'time',
-    'timestamp',
-    'timestamp_tz',
-    'uuid'
-  ],
-  hive: [
-    'binary',
-    'boolean',
-    'byte',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'interval_day',
-    'interval_year',
-    'long',
-    'short',
-    'string',
-    'timestamp',
-    'varchar'
-  ],
-  'jdbc-mysql': [
-    'binary',
-    'byte',
-    'byte unsigned',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'integer unsigned',
-    'long',
-    'long unsigned',
-    'short',
-    'short unsigned',
-    'string',
-    'time',
-    'timestamp',
-    'varchar'
-  ],
-  'jdbc-postgresql': [
-    'binary',
-    'boolean',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'long',
-    'short',
-    'string',
-    'time',
-    'timestamp',
-    'timestamp_tz',
-    'varchar'
-  ],
-  'jdbc-starrocks': [
-    'binary',
-    'boolean',
-    'byte',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'long',
-    'short',
-    'string',
-    'timestamp',
-    'varchar'
-  ],
-  'jdbc-doris': [
-    'boolean',
-    'byte',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'long',
-    'short',
-    'string',
-    'timestamp',
-    'varchar'
-  ],
-  'lakehouse-paimon': [
-    'binary',
-    'boolean',
-    'byte',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'fixed',
-    'float',
-    'integer',
-    'long',
-    'short',
-    'string',
-    'time',
-    'timestamp',
-    'timestamp_tz',
-    'varchar'
-  ],
-  'lakehouse-generic': [
-    'binary',
-    'boolean',
-    'byte',
-    'date',
-    'decimal',
-    'double',
-    'fixed',
-    'float',
-    'integer',
-    'interval_day',
-    'interval_year',
-    'list',
-    'long',
-    'map',
-    'short',
-    'struct',
-    'string',
-    'time',
-    'timestamp',
-    'union'
-  ],
-  'jdbc-oceanbase': [
-    'binary',
-    'byte',
-    'byte unsigned',
-    'char',
-    'date',
-    'decimal',
-    'double',
-    'float',
-    'integer',
-    'integer unsigned',
-    'long',
-    'long unsigned',
-    'short',
-    'short unsigned',
-    'string',
-    'time',
-    'timestamp',
-    'varchar'
-  ]
-}
-
-export const getRelationalColumnType = catalog => {
-  if (Object.hasOwn(relationalColumnTypeMap, catalog)) {
-    return relationalColumnTypeMap[catalog]
-  }
-
-  return []
-}
-
-const relationalTablePropInfoMap = {
-  hive: {
-    reserved: ['comment', 'EXTERNAL', 'numFiles', 'totalSize', 
'transient_lastDdlTime'],
-    immutable: [
-      'format',
-      'input-format',
-      'location',
-      'output-format',
-      'serde-name',
-      'serde-lib',
-      'serde.parameter',
-      'table-type'
-    ],
-    allowDelete: true,
-    allowAdd: true
-  },
-  'jdbc-starrocks': {
-    reserved: [],
-    allowDelete: true,
-    allowAdd: true
-  },
-  'jdbc-doris': {
-    reserved: [],
-    allowDelete: true,
-    allowAdd: true
-  },
-  'jdbc-mysql': {
-    reserved: [],
-    immutable: ['auto-increment-offset', 'engine'],
-    allowDelete: false,
-    allowAdd: true
-  },
-  'jdbc-oceanbase': {
-    reserved: [],
-    immutable: [],
-    allowDelete: false,
-    allowAdd: false
-  },
-  'jdbc-postgresql': {
-    reserved: [],
-    immutable: [],
-    allowDelete: false,
-    allowAdd: false
-  },
-  'lakehouse-hudi': {
-    reserved: [],
-    immutable: [],
-    allowDelete: true,
-    allowAdd: true
-  },
-  'lakehouse-iceberg': {
-    reserved: [
-      'cherry-pick-snapshot-id',
-      'comment',
-      'creator',
-      'current-snapshot-id',
-      'identifier-fields',
-      'sort-order',
-      'write.distribution-mode'
-    ], // Can't be set or modified
-    immutable: ['location', 'provider', 'format', 'format-version'], // Can't 
be modified after creation
-    allowDelete: true,
-    allowAdd: true
-  },
-  'lakehouse-paimon': {
-    reserved: [
-      'bucket-key',
-      'comment',
-      'merge-engine',
-      'owner',
-      'partition',
-      'primary-key',
-      'rowkind.field',
-      'sequence.field'
-    ],
-    immutable: ['merge-engine', 'rowkind.field', 'sequence.field'],
-    allowDelete: true,
-    allowAdd: true
-  },
-  'lakehouse-generic': {
-    reserved: [],
-    immutable: ['format', 'location'],
-    allowDelete: true,
-    allowAdd: true
-  },
-  kafka: {
-    reserved: [],
-    immutable: ['replication-factor'],
-    allowAdd: true
-  },
-  fileset: {
-    reserved: [],
-    immutable: ['default-location-name'],
-    allowAdd: true
-  }
-}
-
-export const getRelationalTablePropInfo = catalog => {
-  if (Object.hasOwn(relationalTablePropInfoMap, catalog)) {
-    return relationalTablePropInfoMap[catalog]
-  }
-
-  return {
-    reserved: [],
-    immutable: [],
-    allowDelete: true,
-    allowAdd: true
-  }
-}

Reply via email to