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 4ddd885b4 [#5455] feat(web): disable to update the reserved fields of 
table properties (#5643)
4ddd885b4 is described below

commit 4ddd885b46b5d5b5043db3578c1a4b580de0b852
Author: JUN <[email protected]>
AuthorDate: Tue Nov 26 11:59:50 2024 +0800

    [#5455] feat(web): disable to update the reserved fields of table 
properties (#5643)
    
    ### What changes were proposed in this pull request?
    
    Disable the creation or update of reserved fields in table properties.
    
    ### Why are the changes needed?
    
    Fix #5455.
    
    ### Does this PR introduce any user-facing changes?
    
    Users will no longer be able to create or update reserved fields in
    table properties.
    
    ### How was this patch tested?
    
    Tested by attempting to create or update reserved fields in table
    properties to ensure it is not allowed.
---
 .../metalake/rightContent/CreateTableDialog.js     | 76 ++++++++---------
 web/web/src/lib/utils/initial.js                   | 99 ++++++++++++++++++++--
 2 files changed, 130 insertions(+), 45 deletions(-)

diff --git 
a/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js 
b/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
index 547382dcd..b810c541f 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
@@ -85,7 +85,7 @@ import { groupBy } from 'lodash-es'
 import { genUpdates } from '@/lib/utils'
 import { nameRegex, nameRegexDesc, keyRegex } from '@/lib/utils/regex'
 import { useSearchParams } from 'next/navigation'
-import { getRelationalColumnTypeMap, getParameterizedColumnType } from 
'@/lib/utils/initial'
+import { getRelationalColumnType, getParameterizedColumnType, 
getRelationalTablePropInfo } from '@/lib/utils/initial'
 
 // Default form values
 const defaultFormValues = {
@@ -140,9 +140,9 @@ const CreateTableDialog = props => {
 
   const store = useAppSelector(state => state.metalakes)
   const currentCatalog = store.catalogs.find(ca => ca.name === catalog)
-  const columnTypes = getRelationalColumnTypeMap(currentCatalog?.provider)
+  const columnTypes = getRelationalColumnType(currentCatalog?.provider)
+  const propInfo = getRelationalTablePropInfo(currentCatalog?.provider)
 
-  // Component state
   const [innerProps, setInnerProps] = useState([])
   const [tableColumns, setTableColumns] = useState([{ name: '', type: '', 
nullable: true, comment: '' }])
   const [initialTableData, setInitialTableData] = useState()
@@ -187,6 +187,9 @@ const CreateTableDialog = props => {
       updatedProps.forEach(item => (item.hasDuplicateKey = false))
     }
 
+    const isReserved = propInfo.reserved.includes(updatedProps[index].key)
+    updatedProps[index].isReserved = isReserved
+
     setInnerProps(updatedProps)
     setValue('propItems', updatedProps)
   }
@@ -267,14 +270,8 @@ const CreateTableDialog = props => {
    * Checks for duplicate keys before adding
    */
   const addProperty = () => {
-    const hasDuplicateKeys = innerProps
-      .filter(item => item.key.trim() !== '')
-      .some(
-        (item, index, filteredItems) =>
-          filteredItems.findIndex(otherItem => otherItem !== item && 
otherItem.key.trim() === item.key.trim()) !== -1
-      )
-
-    if (hasDuplicateKeys) {
+    const hasError = innerProps.some(prop => prop.hasDuplicateKey || 
prop.isReserved || prop.invalid)
+    if (hasError) {
       return
     }
 
@@ -321,14 +318,7 @@ const CreateTableDialog = props => {
    * Validates data and dispatches create/update actions
    */
   const submitForm = formData => {
-    const hasDuplicateKeys = innerProps
-      .filter(item => item.key.trim() !== '')
-      .some(
-        (item, index, filteredItems) =>
-          filteredItems.findIndex(otherItem => otherItem !== item && 
otherItem.key.trim() === item.key.trim()) !== -1
-      )
-
-    const hasInvalidKeys = innerProps.some(prop => prop.invalid)
+    const hasErrorProperties = innerProps.some(prop => prop.hasDuplicateKey || 
prop.isReserved || prop.invalid)
 
     const hasDuplicateColumnNames = tableColumns
       .filter(col => col.name.trim() !== '')
@@ -339,7 +329,7 @@ const CreateTableDialog = props => {
 
     const hasInvalidColumnTypes = tableColumns.some(col => col.paramErrors)
 
-    if (hasDuplicateKeys || hasInvalidKeys || hasDuplicateColumnNames || 
hasInvalidColumnTypes) {
+    if (hasErrorProperties || hasDuplicateColumnNames || 
hasInvalidColumnTypes) {
       return
     }
 
@@ -442,14 +432,15 @@ const CreateTableDialog = props => {
       const propertyItems = Object.entries(properties).map(([key, value]) => {
         return {
           key,
-          value
+          value,
+          disabled: propInfo.reserved.includes(key) || 
propInfo.immutable.includes(key)
         }
       })
 
       setInnerProps(propertyItems)
       setValue('propItems', propertyItems)
     }
-  }, [open, data, setValue, type])
+  }, [open, data, setValue, type, propInfo])
 
   // Handle click outside of table rows
   useEffect(() => {
@@ -694,9 +685,11 @@ const CreateTableDialog = props => {
             </Grid>
 
             <Grid item xs={12} data-refer='table-props-layout'>
-              <Typography sx={{ mb: 2 }} variant='body2'>
-                Properties
-              </Typography>
+              {(propInfo.allowAdd || innerProps.length > 0) && (
+                <Typography sx={{ mb: 2 }} variant='body2'>
+                  Properties
+                </Typography>
+              )}
               {innerProps.map((item, index) => {
                 return (
                   <Fragment key={index}>
@@ -707,20 +700,22 @@ const CreateTableDialog = props => {
                             sx={{ display: 'flex', alignItems: 'center', 
justifyContent: 'space-between' }}
                             data-refer={`table-props-${index}`}
                           >
-                            <Box>
+                            <Box sx={{ width: '30%' }}>
                               <TextField
+                                sx={{ width: '95%' }}
                                 size='small'
                                 name='key'
                                 label='Key'
                                 value={item.key}
                                 disabled={item.disabled}
                                 onChange={event => handlePropertyChange({ 
index, event })}
-                                error={item.hasDuplicateKey || item.invalid || 
!item.key.trim()}
+                                error={item.hasDuplicateKey || item.isReserved 
|| item.invalid || !item.key.trim()}
                                 data-refer={`props-key-${index}`}
                               />
                             </Box>
-                            <Box>
+                            <Box sx={{ width: '65%' }}>
                               <TextField
+                                sx={{ width: '95%' }}
                                 size='small'
                                 name='value'
                                 label='Value'
@@ -733,7 +728,7 @@ const CreateTableDialog = props => {
                               />
                             </Box>
 
-                            {!item.disabled ? (
+                            {!item.disabled && (propInfo.allowDelete || type 
=== 'create') ? (
                               <Box sx={{ minWidth: 40 }}>
                                 <IconButton onClick={() => 
removeProperty(index)}>
                                   <Icon icon='mdi:minus-circle-outline' />
@@ -755,6 +750,9 @@ const CreateTableDialog = props => {
                         {item.hasDuplicateKey && (
                           <FormHelperText 
className={'twc-text-error-main'}>Key already exists</FormHelperText>
                         )}
+                        {item.isReserved && (
+                          <FormHelperText 
className={'twc-text-error-main'}>Key is reserved</FormHelperText>
+                        )}
                         {item.key && item.invalid && (
                           <FormHelperText className={'twc-text-error-main'}>
                             Invalid key, matches strings starting with a 
letter/underscore, followed by alphanumeric
@@ -772,15 +770,17 @@ const CreateTableDialog = props => {
             </Grid>
 
             <Grid item xs={12}>
-              <Button
-                size='small'
-                onClick={addProperty}
-                variant='outlined'
-                startIcon={<Icon icon='mdi:plus-circle-outline' />}
-                data-refer='add-table-props'
-              >
-                Add Property
-              </Button>
+              {propInfo.allowAdd && (
+                <Button
+                  size='small'
+                  onClick={addProperty}
+                  variant='outlined'
+                  startIcon={<Icon icon='mdi:plus-circle-outline' />}
+                  data-refer='add-table-props'
+                >
+                  Add Property
+                </Button>
+              )}
             </Grid>
           </Grid>
         </DialogContent>
diff --git a/web/web/src/lib/utils/initial.js b/web/web/src/lib/utils/initial.js
index 6d633f765..445b2403b 100644
--- a/web/web/src/lib/utils/initial.js
+++ b/web/web/src/lib/utils/initial.js
@@ -455,7 +455,7 @@ const parameterizedColumnTypes = {
 }
 
 export const getParameterizedColumnType = type => {
-  if (Object.keys(parameterizedColumnTypes).includes(type)) {
+  if (Object.hasOwn(parameterizedColumnTypes, type)) {
     return parameterizedColumnTypes[type]
   }
 }
@@ -477,7 +477,6 @@ const relationalColumnTypeMap = {
     'timestamp_tz',
     'uuid'
   ],
-
   hive: [
     'binary',
     'boolean',
@@ -496,7 +495,6 @@ const relationalColumnTypeMap = {
     'timestamp',
     'varchar'
   ],
-
   'jdbc-mysql': [
     'binary',
     'byte',
@@ -534,7 +532,6 @@ const relationalColumnTypeMap = {
     'timestamp_tz',
     'varchar'
   ],
-
   'jdbc-doris': [
     'boolean',
     'byte',
@@ -550,7 +547,6 @@ const relationalColumnTypeMap = {
     'timestamp',
     'varchar'
   ],
-
   'lakehouse-paimon': [
     'binary',
     'boolean',
@@ -572,10 +568,99 @@ const relationalColumnTypeMap = {
   ]
 }
 
-export const getRelationalColumnTypeMap = catalog => {
-  if (Object.keys(relationalColumnTypeMap).includes(catalog)) {
+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-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
+  }
+}
+
+export const getRelationalTablePropInfo = catalog => {
+  if (Object.hasOwn(relationalTablePropInfoMap, catalog)) {
+    return relationalTablePropInfoMap[catalog]
+  }
+
+  return {
+    reserved: [],
+    immutable: [],
+    allowDelete: true,
+    allowAdd: true
+  }
+}

Reply via email to