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
+ }
+}