Skip to content

Commit

Permalink
[apache#5453] subtask(web): support for table column types with param…
Browse files Browse the repository at this point in the history
…eters (apache#5592)

### What changes were proposed in this pull request?

Create and update column types `['char', 'varchar', 'fixed', `decimal`]`
with parameters

### Why are the changes needed?

Close: apache#5453 

### Does this PR introduce _any_ user-facing change?

four more table type column type on selection 

### How was this patch tested?

Create part 1

![chrome-capture-2024-11-16](https://github.com/user-attachments/assets/befec5c6-0afb-469a-928f-f6fa49a792f2)

Create part 2
![chrome-capture-2024-11-16
(1)](https://github.com/user-attachments/assets/efcacfdc-18e0-44b4-b80d-8caf65d71df2)

Update
![chrome-capture-2024-11-16
(2)](https://github.com/user-attachments/assets/cec70ac7-cbd5-411f-8970-27d854fbc4d5)
  • Loading branch information
orenccl authored Nov 19, 2024
1 parent b49a197 commit bcc4c42
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 42 deletions.
163 changes: 139 additions & 24 deletions web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 { relationalTypes } from '@/lib/utils/initial'
import { tableColumnTypes } from '@/lib/utils/initial'

// Default form values
const defaultFormValues = {
Expand Down Expand Up @@ -142,6 +142,7 @@ const CreateTableDialog = props => {
const [innerProps, setInnerProps] = useState([])
const [tableColumns, setTableColumns] = useState([{ name: '', type: '', nullable: true, comment: '' }])
const [initialTableData, setInitialTableData] = useState()
const [selectedColumnIndex, setSelectedColumnIndex] = useState(null)
const dispatch = useAppDispatch()

// Initialize form with react-hook-form
Expand Down Expand Up @@ -205,6 +206,34 @@ const CreateTableDialog = props => {
}
}

// reset type suffix and param errors
if (field === 'type') {
updatedColumns[index].typeSuffix = ''
updatedColumns[index].paramErrors = ''
if (tableColumnTypes.find(type => type.key === value)?.params) {
updatedColumns[index].paramValues = []
}
}

setTableColumns(updatedColumns)
setValue('columns', updatedColumns)
}

const transformParamValues = index => {
let updatedColumns = [...tableColumns]

const validateParams = tableColumnTypes.find(type => type.key === updatedColumns[index].type)?.validateParams
const paramValues = updatedColumns[index].paramValues.filter(param => param !== undefined).map(Number)
const validateResult = validateParams(paramValues)

if (validateResult.valid) {
updatedColumns[index].typeSuffix = `(${paramValues.join(',')})`
updatedColumns[index].paramErrors = ''
} else {
updatedColumns[index].paramErrors = validateResult.message
}

updatedColumns[index].paramValues = undefined
setTableColumns(updatedColumns)
setValue('columns', updatedColumns)
}
Expand Down Expand Up @@ -303,7 +332,9 @@ const CreateTableDialog = props => {
filteredCols.findIndex(otherCol => otherCol !== col && otherCol.name.trim() === col.name.trim()) !== -1
)

if (hasDuplicateKeys || hasInvalidKeys || hasDuplicateColumnNames) {
const hasInvalidColumnTypes = tableColumns.some(col => col.paramErrors)

if (hasDuplicateKeys || hasInvalidKeys || hasDuplicateColumnNames || hasInvalidColumnTypes) {
return
}

Expand All @@ -321,7 +352,14 @@ const CreateTableDialog = props => {
const tableData = {
name: formData.name,
comment: formData.comment,
columns: formData.columns.map(({ hasDuplicateName, ...rest }) => rest),

// remove redundant fields
columns: formData.columns.map(({ hasDuplicateName, paramErrors, typeSuffix, ...rest }) => {
return {
...rest,
type: rest.type + typeSuffix || '' // combine type and type suffix, like decimal(10,2)
}
}),
properties
}

Expand Down Expand Up @@ -381,6 +419,13 @@ const CreateTableDialog = props => {
// Set uniqueId to the column name to detect changes
column.uniqueId = column.name

// Extract type suffix for types with parameters
const match = column.type.match(/(\w+)(\([\d,]+\))/)
if (match && match.length === 3) {
column.typeSuffix = match[2]
column.type = match[1]
}

return {
...column
}
Expand All @@ -401,10 +446,32 @@ const CreateTableDialog = props => {
}
}, [open, data, setValue, type])

// Handle click outside of table rows
useEffect(() => {
const handleClickOutside = e => {
const selectElements = document.querySelectorAll('[role="listbox"]')
const isClickInsideSelect = Array.from(selectElements).some(el => el.contains(e.target))
if (isClickInsideSelect) {
return
}

const isClickInsideTableCell = e.target.closest('td')
if (isClickInsideTableCell) {
return
}

setSelectedColumnIndex(null)
}

document.addEventListener('click', handleClickOutside)

return () => document.removeEventListener('click', handleClickOutside)
}, [])

return (
<Dialog
fullWidth
maxWidth='md'
maxWidth='lg'
scroll='body'
TransitionComponent={Transition}
open={open}
Expand Down Expand Up @@ -484,10 +551,10 @@ const CreateTableDialog = props => {
<Table stickyHeader>
<TableHead>
<TableRow>
<TableCell sx={{ minWidth: 100 }}>Name</TableCell>
<TableCell sx={{ minWidth: 100, width: 200 }}>Name</TableCell>
<TableCell sx={{ minWidth: 100 }}>Type</TableCell>
<TableCell sx={{ minWidth: 100 }}>Nullable</TableCell>
<TableCell sx={{ minWidth: 200 }}>Comment</TableCell>
<TableCell sx={{ minWidth: 200, width: 550 }}>Comment</TableCell>
<TableCell sx={{ minWidth: 50 }}>Action</TableCell>
</TableRow>
</TableHead>
Expand All @@ -512,25 +579,73 @@ const CreateTableDialog = props => {
)}
</FormControl>
</TableCell>
<TableCell sx={{ verticalAlign: 'top' }}>
<TableCell sx={{ verticalAlign: 'top' }} onClick={() => setSelectedColumnIndex(index)}>
<FormControl fullWidth>
<Select
size='small'
fullWidth
value={column.type}
onChange={e => handleColumnChange({ index, field: 'type', value: e.target.value })}
error={!column.type.trim()}
data-refer={`column-type-${index}`}
>
{relationalTypes.map(type => (
<MenuItem key={type.value} value={type.value}>
{type.label}
</MenuItem>
))}
</Select>
{!column.type.trim() && (
<FormHelperText className={'twc-text-error-main'}>Type is required</FormHelperText>
)}
<Box sx={{ display: 'flex', gap: 1 }}>
<Box sx={{ minWidth: 120 }}>
<Select
size='small'
fullWidth
value={column.type}
onChange={e => handleColumnChange({ index, field: 'type', value: e.target.value })}
error={!column.type.trim() || column.paramErrors}
data-refer={`column-type-${index}`}
renderValue={selected => <Box>{`${selected}${column.typeSuffix || ''}`}</Box>}
>
{tableColumnTypes.map(type => (
<MenuItem key={type.key} value={type.key}>
{type.key}
</MenuItem>
))}
</Select>
{!column.type.trim() && (
<FormHelperText className={'twc-text-error-main'}>Type is required</FormHelperText>
)}
{column.paramErrors && (
<FormHelperText className={'twc-text-error-main'}>
{column.paramErrors}
</FormHelperText>
)}
</Box>
{selectedColumnIndex === index &&
column.type &&
(() => {
// Process typeSuffix before mapping
if (column.typeSuffix && !column.paramValues) {
const paramStr = column.typeSuffix.slice(1, -1) // Remove parentheses
const values = paramStr.split(',').map(v => v.trim())
handleColumnChange({
index,
field: 'paramValues',
value: values
})
}

return tableColumnTypes
.find(t => t.key === column.type)
?.params?.map((param, paramIndex) => (
<TextField
key={paramIndex}
size='small'
type='number'
sx={{ minWidth: 60 }}
value={column.paramValues?.[paramIndex] || ''}
onChange={e => {
const newParamValues = [...(column.paramValues || [])]
newParamValues[paramIndex] = e.target.value
handleColumnChange({ index, field: 'paramValues', value: newParamValues })
}}
placeholder={`${param}`}
data-refer={`column-param-${index}-${paramIndex}`}
inputProps={{ min: 0 }}
/>
))
})()}
{selectedColumnIndex !== index &&
tableColumnTypes.find(type => type.key === column.type)?.params &&
column.paramValues &&
transformParamValues(index)}
</Box>
</FormControl>
</TableCell>
<TableCell sx={{ verticalAlign: 'top' }}>
Expand Down
2 changes: 1 addition & 1 deletion web/web/src/lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const genUpdates = (originalData, newData) => {
newFieldName: newColumnsMap[key].name
})
}
if (originalColumnsMap[key].type !== newColumnsMap[key].type) {
if ((originalColumnsMap[key].type + originalColumnsMap[key].typeSuffix || '') !== newColumnsMap[key].type) {
updates.push({
'@type': 'updateColumnType',
fieldName: [newColumnsMap[key].name],
Expand Down
140 changes: 123 additions & 17 deletions web/web/src/lib/utils/initial.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,21 +347,127 @@ export const providers = [
}
]

export const relationalTypes = [
{ label: 'Boolean', value: 'boolean' },
{ label: 'Byte', value: 'byte' },
{ label: 'Short', value: 'short' },
{ label: 'Integer', value: 'integer' },
{ label: 'Long', value: 'long' },
{ label: 'Float', value: 'float' },
{ label: 'Double', value: 'double' },
{ label: 'Date', value: 'date' },
{ label: 'Time', value: 'time' },
{ label: 'Timestamp', value: 'timestamp' },
{ label: 'Timestamp_tz', value: 'timestamp_tz' },
{ label: 'String', value: 'string' },
{ label: 'Interval_day', value: 'interval_day' },
{ label: 'Interval_year', value: 'interval_year' },
{ label: 'Uuid', value: 'uuid' },
{ label: 'Binary', value: 'binary' }
export const tableColumnTypes = [
{ key: 'boolean' },
{ key: 'byte' },
{ key: 'short' },
{ key: 'integer' },
{ key: 'long' },
{ key: 'float' },
{ key: 'double' },
{
key: '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
}
}
},
{ key: 'date' },
{ key: 'time' },
{ key: 'timestamp' },
{ key: 'timestamp_tz' },
{ key: 'string' },
{
key: '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
}
}
},
{
key: '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
}
}
},
{ key: 'interval_day' },
{ key: 'interval_year' },
{
key: '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
}
}
},
{ key: 'uuid' },
{ key: 'binary' }
]

0 comments on commit bcc4c42

Please sign in to comment.