Browse Source

Merge pull request #202 from zilliztech/partitionKey

Support Partition key
ryjiang 2 years ago
parent
commit
0380dd2919

+ 3 - 0
client/src/components/customDialog/DialogTemplate.tsx

@@ -17,6 +17,9 @@ const useStyles = makeStyles((theme: Theme) => ({
     '& form': {
     '& form': {
       display: 'flex',
       display: 'flex',
     },
     },
+    '& .MuiDialogContent-root': {
+      maxHeight: '60vh',
+    },
   },
   },
   block: {
   block: {
     borderRadius: 8,
     borderRadius: 8,

+ 7 - 2
client/src/http/Field.ts

@@ -7,6 +7,7 @@ export class FieldHttp extends BaseModel implements FieldData {
   fieldID!: string;
   fieldID!: string;
   type_params!: { key: string; value: string }[];
   type_params!: { key: string; value: string }[];
   is_primary_key!: true;
   is_primary_key!: true;
+  is_partition_key!: false;
   name!: string;
   name!: string;
   description!: string;
   description!: string;
   autoID!: boolean;
   autoID!: boolean;
@@ -35,6 +36,10 @@ export class FieldHttp extends BaseModel implements FieldData {
     return this.is_primary_key;
     return this.is_primary_key;
   }
   }
 
 
+  get _isPartitionKey() {
+    return this.is_partition_key;
+  }
+
   get _isAutoId() {
   get _isAutoId() {
     return this.autoID;
     return this.autoID;
   }
   }
@@ -52,12 +57,12 @@ export class FieldHttp extends BaseModel implements FieldData {
   }
   }
 
 
   get _dimension() {
   get _dimension() {
-    return this.type_params.find(item => item.key === 'dim')?.value || '--';
+    return this.type_params.find(item => item.key === 'dim')?.value || '';
   }
   }
 
 
   get _maxLength() {
   get _maxLength() {
     return (
     return (
-      this.type_params.find(item => item.key === 'max_length')?.value || '--'
+      this.type_params.find(item => item.key === 'max_length')?.value || ''
     );
     );
   }
   }
 }
 }

+ 12 - 7
client/src/i18n/en/collection.ts

@@ -41,14 +41,16 @@ const collectionTrans = {
   consistency: 'Consistency Level',
   consistency: 'Consistency Level',
   consistencyLevel: 'Consistency Level',
   consistencyLevel: 'Consistency Level',
   description: 'Description',
   description: 'Description',
-  fieldType: 'Field Type',
+  fieldType: 'Type',
   vectorFieldType: 'Vector Field Type',
   vectorFieldType: 'Vector Field Type',
-  fieldName: 'Field Name',
-  idFieldName: 'ID Name',
-  vectorFieldName: 'Vector Name',
+  fieldName: 'Field',
+  idFieldName: 'Primary Key Field',
+  vectorFieldName: 'Vector Field',
   autoId: 'Auto ID',
   autoId: 'Auto ID',
-  vectorType: 'Vector Type',
-  idType: 'ID Type',
+  autoIdToggleTip:
+    'Whether the primary key is automatically generated by Milvus, only suppport INT64.',
+  vectorType: 'Type',
+  idType: 'Type',
   dimension: 'Dimension',
   dimension: 'Dimension',
   dimensionTooltip: 'Only vector type has dimension',
   dimensionTooltip: 'Only vector type has dimension',
   dimensionMutipleWarning: 'Dimension should be 8 multiple',
   dimensionMutipleWarning: 'Dimension should be 8 multiple',
@@ -58,6 +60,9 @@ const collectionTrans = {
   nameContentWarning: 'Only numbers, letters, and underscores are allowed.',
   nameContentWarning: 'Only numbers, letters, and underscores are allowed.',
   nameFirstLetterWarning:
   nameFirstLetterWarning:
     'Name first character must be underscore or character(a~z, A~Z)',
     'Name first character must be underscore or character(a~z, A~Z)',
+  partitionKey: 'Partition Key',
+  partitionKeyTooltip:
+    ' Milvus will store entities in a partition according to the values in the partition key field. Only one Int64 or VarChar field is suppported.',
 
 
   // load dialog
   // load dialog
   loadTitle: 'Load Collection',
   loadTitle: 'Load Collection',
@@ -84,7 +89,7 @@ const collectionTrans = {
   schemaTab: 'Schema',
   schemaTab: 'Schema',
   queryTab: 'Data Query',
   queryTab: 'Data Query',
   previewTab: 'Data Preview',
   previewTab: 'Data Preview',
-  startTip: 'Start your data query.',
+  startTip: 'Start your data query',
   dataQuerylimits:
   dataQuerylimits:
     ' Please note that the maximum number of results for your data query is 16384.',
     ' Please note that the maximum number of results for your data query is 16384.',
   exprPlaceHolder: 'Please enter your data query, for example id > 0',
   exprPlaceHolder: 'Please enter your data query, for example id > 0',

+ 97 - 23
client/src/pages/collections/CreateFields.tsx

@@ -1,8 +1,16 @@
-import { makeStyles, Theme, TextField, IconButton } from '@material-ui/core';
+import {
+  makeStyles,
+  Theme,
+  TextField,
+  IconButton,
+  Switch,
+  FormControlLabel,
+} from '@material-ui/core';
 import { FC, Fragment, ReactElement, useMemo } from 'react';
 import { FC, Fragment, ReactElement, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import CustomSelector from '../../components/customSelector/CustomSelector';
 import icons from '../../components/icons/Icons';
 import icons from '../../components/icons/Icons';
+import CustomToolTip from '../../components/customToolTip/CustomToolTip';
 import { generateId } from '../../utils/Common';
 import { generateId } from '../../utils/Common';
 import { getCreateFieldType } from '../../utils/Format';
 import { getCreateFieldType } from '../../utils/Format';
 import {
 import {
@@ -12,7 +20,6 @@ import {
 } from '../../utils/Validation';
 } from '../../utils/Validation';
 import {
 import {
   ALL_OPTIONS,
   ALL_OPTIONS,
-  AUTO_ID_OPTIONS,
   PRIMARY_FIELDS_OPTIONS,
   PRIMARY_FIELDS_OPTIONS,
   VECTOR_FIELDS_OPTIONS,
   VECTOR_FIELDS_OPTIONS,
 } from './Constants';
 } from './Constants';
@@ -27,7 +34,6 @@ const useStyles = makeStyles((theme: Theme) => ({
   optionalWrapper: {
   optionalWrapper: {
     width: '100%',
     width: '100%',
     paddingRight: theme.spacing(1),
     paddingRight: theme.spacing(1),
-    maxHeight: '240px',
     overflowY: 'auto',
     overflowY: 'auto',
   },
   },
   rowWrapper: {
   rowWrapper: {
@@ -41,7 +47,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     fontSize: '14px',
     fontSize: '14px',
   },
   },
   fieldInput: {
   fieldInput: {
-    width: '160px',
+    width: '170px',
   },
   },
   select: {
   select: {
     width: '140px',
     width: '140px',
@@ -72,6 +78,8 @@ const useStyles = makeStyles((theme: Theme) => ({
     padding: 0,
     padding: 0,
     width: '16px',
     width: '16px',
     height: '16px',
     height: '16px',
+    position: 'relative',
+    top: '-8px',
   },
   },
   helperText: {
   helperText: {
     lineHeight: '20px',
     lineHeight: '20px',
@@ -79,6 +87,15 @@ const useStyles = makeStyles((theme: Theme) => ({
     margin: theme.spacing(0),
     margin: theme.spacing(0),
     marginLeft: '11px',
     marginLeft: '11px',
   },
   },
+  toggle: {
+    marginBottom: theme.spacing(2),
+    marginLeft: theme.spacing(0.5),
+    marginRight: theme.spacing(0.5),
+  },
+  icon: {
+    fontSize: '20px',
+    marginLeft: theme.spacing(0.5),
+  },
 }));
 }));
 
 
 type inputType = {
 type inputType = {
@@ -106,6 +123,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
 
 
   const AddIcon = icons.addOutline;
   const AddIcon = icons.addOutline;
   const RemoveIcon = icons.remove;
   const RemoveIcon = icons.remove;
+  const InfoIcon = icons.info;
 
 
   const { requiredFields, optionalFields } = useMemo(
   const { requiredFields, optionalFields } = useMemo(
     () =>
     () =>
@@ -326,6 +344,41 @@ const CreateFields: FC<CreateFieldsProps> = ({
     });
     });
   };
   };
 
 
+  const generateParitionKeyToggle = (field: Field, fields: Field[]) => {
+    return (
+      <FormControlLabel
+        control={
+          <Switch
+            checked={!!field.is_partition_key}
+            disabled={
+              fields.some(f => f.is_partition_key) && !field.is_partition_key
+            }
+            size="small"
+            onChange={() => {
+              changeFields(
+                field.id!,
+                'is_partition_key',
+                !field.is_partition_key
+              );
+            }}
+          />
+        }
+        label={
+          <CustomToolTip
+            title={collectionTrans('partitionKeyTooltip')}
+            placement="top"
+          >
+            <>
+              {collectionTrans('partitionKey')}
+              {/* <InfoIcon classes={{ root: classes.icon }} /> */}
+            </>
+          </CustomToolTip>
+        }
+        className={classes.toggle}
+      />
+    );
+  };
+
   const changeFields = (id: string, key: string, value: any) => {
   const changeFields = (id: string, key: string, value: any) => {
     const newFields = fields.map(f => {
     const newFields = fields.map(f => {
       if (f.id !== id) {
       if (f.id !== id) {
@@ -373,7 +426,6 @@ const CreateFields: FC<CreateFieldsProps> = ({
     autoID: boolean
     autoID: boolean
   ): ReactElement => {
   ): ReactElement => {
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
-    const autoIdOff = isVarChar ? 'false' : autoID ? 'true' : 'false';
     return (
     return (
       <div className={`${classes.rowWrapper}`}>
       <div className={`${classes.rowWrapper}`}>
         {generateFieldName(field, collectionTrans('idFieldName'))}
         {generateFieldName(field, collectionTrans('idFieldName'))}
@@ -391,21 +443,29 @@ const CreateFields: FC<CreateFieldsProps> = ({
         )}
         )}
         {generateDesc(field)}
         {generateDesc(field)}
 
 
-        <CustomSelector
-          label={collectionTrans('autoId')}
-          options={AUTO_ID_OPTIONS}
-          value={autoIdOff}
-          onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
-            const autoId = e.target.value === 'true';
-            setAutoID(autoId);
-          }}
-          variant="filled"
-          wrapperClass={classes.autoIdSelect}
-          disabled={isVarChar}
-          size="small"
-        />
-
         {isVarChar && generateMaxLength(field)}
         {isVarChar && generateMaxLength(field)}
+
+        <FormControlLabel
+          control={
+            <Switch
+              checked={autoID}
+              disabled={isVarChar}
+              size="small"
+              onChange={() => {
+                setAutoID(!autoID);
+              }}
+            />
+          }
+          label={
+            <CustomToolTip
+              title={collectionTrans('autoIdToggleTip')}
+              placement="top"
+            >
+              <>{collectionTrans('autoId')}</>
+            </CustomToolTip>
+          }
+          className={classes.toggle}
+        />
       </div>
       </div>
     );
     );
   };
   };
@@ -441,8 +501,13 @@ const CreateFields: FC<CreateFieldsProps> = ({
     );
     );
   };
   };
 
 
-  const generateNumberRow = (field: Field, index: number): ReactElement => {
+  const generateNumberRow = (
+    field: Field,
+    index: number,
+    fields: Field[]
+  ): ReactElement => {
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
     const isVarChar = field.data_type === DataTypeEnum.VarChar;
+    const isInt64 = field.data_type === DataTypeEnum.Int64;
     return (
     return (
       <div className={`${classes.rowWrapper}`}>
       <div className={`${classes.rowWrapper}`}>
         {generateFieldName(field)}
         {generateFieldName(field)}
@@ -455,6 +520,7 @@ const CreateFields: FC<CreateFieldsProps> = ({
         {generateDesc(field)}
         {generateDesc(field)}
 
 
         {isVarChar && generateMaxLength(field)}
         {isVarChar && generateMaxLength(field)}
+        {(isVarChar || isInt64) && generateParitionKeyToggle(field, fields)}
         <IconButton
         <IconButton
           onClick={() => {
           onClick={() => {
             handleAddNewField(index);
             handleAddNewField(index);
@@ -511,14 +577,18 @@ const CreateFields: FC<CreateFieldsProps> = ({
     return generateDefaultVectorRow(field, index);
     return generateDefaultVectorRow(field, index);
   };
   };
 
 
-  const generateOptionalFieldRow = (field: Field, index: number) => {
+  const generateOptionalFieldRow = (
+    field: Field,
+    index: number,
+    fields: Field[]
+  ) => {
     // optional type is vector or number
     // optional type is vector or number
     if (field.createType === 'vector') {
     if (field.createType === 'vector') {
       return generateVectorRow(field);
       return generateVectorRow(field);
     }
     }
 
 
     // use number as default createType
     // use number as default createType
-    return generateNumberRow(field, index);
+    return generateNumberRow(field, index, fields);
   };
   };
 
 
   return (
   return (
@@ -531,7 +601,11 @@ const CreateFields: FC<CreateFieldsProps> = ({
       <div className={classes.optionalWrapper}>
       <div className={classes.optionalWrapper}>
         {optionalFields.map((field, index) => (
         {optionalFields.map((field, index) => (
           <Fragment key={field.id}>
           <Fragment key={field.id}>
-            {generateOptionalFieldRow(field, index + requiredFields.length)}
+            {generateOptionalFieldRow(
+              field,
+              index + requiredFields.length,
+              optionalFields
+            )}
           </Fragment>
           </Fragment>
         ))}
         ))}
       </div>
       </div>

+ 2 - 0
client/src/pages/collections/Types.ts

@@ -91,6 +91,7 @@ export interface Field {
   name: string | null;
   name: string | null;
   data_type: DataTypeEnum;
   data_type: DataTypeEnum;
   is_primary_key: boolean;
   is_primary_key: boolean;
+  is_partition_key?: boolean;
   description: string;
   description: string;
   dimension?: number | string;
   dimension?: number | string;
   isDefault?: boolean;
   isDefault?: boolean;
@@ -102,6 +103,7 @@ export interface Field {
   createType?: CreateFieldType;
   createType?: CreateFieldType;
   max_length?: string | null;
   max_length?: string | null;
   autoID?: boolean;
   autoID?: boolean;
+  
 }
 }
 
 
 export type CreateFieldType =
 export type CreateFieldType =

+ 2 - 2
client/src/pages/dialogs/CreateCollectionDialog.tsx

@@ -184,10 +184,11 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     const param: CollectionCreateParam = {
     const param: CollectionCreateParam = {
       ...form,
       ...form,
       fields: fields.map(v => {
       fields: fields.map(v => {
-        const data: any = {
+        const data: Field = {
           name: v.name,
           name: v.name,
           description: v.description,
           description: v.description,
           is_primary_key: v.is_primary_key,
           is_primary_key: v.is_primary_key,
+          is_partition_key: v.is_partition_key,
           data_type: v.data_type,
           data_type: v.data_type,
           dimension: vectorType.includes(v.data_type) ? v.dimension : undefined,
           dimension: vectorType.includes(v.data_type) ? v.dimension : undefined,
           max_length: v.max_length,
           max_length: v.max_length,
@@ -236,7 +237,6 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     >
     >
       <>
       <>
         <fieldset className={classes.fieldset}>
         <fieldset className={classes.fieldset}>
-          <legend>{collectionTrans('general')}</legend>
           {generalInfoConfigs.map(config => (
           {generalInfoConfigs.map(config => (
             <CustomInput
             <CustomInput
               key={config.key}
               key={config.key}

+ 86 - 29
client/src/pages/schema/Schema.tsx

@@ -1,15 +1,15 @@
-import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { makeStyles, Theme, Typography, Chip } from '@material-ui/core';
 import { FC, useCallback, useEffect, useState } from 'react';
 import { FC, useCallback, useEffect, useState } from 'react';
 import AttuGrid from '../../components/grid/Grid';
 import AttuGrid from '../../components/grid/Grid';
 import { ColDefinitionsType } from '../../components/grid/Types';
 import { ColDefinitionsType } from '../../components/grid/Types';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { usePaginationHook } from '../../hooks/Pagination';
 import { usePaginationHook } from '../../hooks/Pagination';
 import icons from '../../components/icons/Icons';
 import icons from '../../components/icons/Icons';
-import CustomToolTip from '../../components/customToolTip/CustomToolTip';
 import { FieldHttp } from '../../http/Field';
 import { FieldHttp } from '../../http/Field';
 import { FieldView } from './Types';
 import { FieldView } from './Types';
 import IndexTypeElement from './IndexTypeElement';
 import IndexTypeElement from './IndexTypeElement';
 import { IndexHttp } from '../../http/Index';
 import { IndexHttp } from '../../http/Index';
+import { DataTypeStringEnum } from '../collections/Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
@@ -19,6 +19,16 @@ const useStyles = makeStyles((theme: Theme) => ({
     fontSize: '20px',
     fontSize: '20px',
     marginLeft: theme.spacing(0.5),
     marginLeft: theme.spacing(0.5),
   },
   },
+  iconTitle: {
+    fontSize: '8px',
+    position: 'relative',
+    top: '3px',
+    color: 'grey',
+  },
+  chip: {
+    marginLeft: theme.spacing(0.5),
+    marginRight: theme.spacing(0.5),
+  },
   nameWrapper: {
   nameWrapper: {
     display: 'flex',
     display: 'flex',
     alignItems: 'center',
     alignItems: 'center',
@@ -56,7 +66,6 @@ const Schema: FC<{
   const classes = useStyles();
   const classes = useStyles();
   const { t: collectionTrans } = useTranslation('collection');
   const { t: collectionTrans } = useTranslation('collection');
   const { t: indexTrans } = useTranslation('index');
   const { t: indexTrans } = useTranslation('index');
-  const InfoIcon = icons.info;
 
 
   const [fields, setFields] = useState<FieldView[]>([]);
   const [fields, setFields] = useState<FieldView[]>([]);
   const [loading, setLoading] = useState<boolean>(true);
   const [loading, setLoading] = useState<boolean>(true);
@@ -106,7 +115,51 @@ const Schema: FC<{
             _fieldNameElement: (
             _fieldNameElement: (
               <div className={classes.nameWrapper}>
               <div className={classes.nameWrapper}>
                 {f._fieldName}
                 {f._fieldName}
-                {f._isPrimaryKey && <KeyIcon classes={{ root: 'key' }} />}
+                {f._isPrimaryKey ? (
+                  <div
+                    className={classes.iconTitle}
+                    title={collectionTrans('idFieldName')}
+                  >
+                    <KeyIcon classes={{ root: 'key' }} />
+                  </div>
+                ) : null}
+                {f.is_partition_key ? (
+                  <Chip
+                    className={classes.chip}
+                    size="small"
+                    label="Partition key"
+                    variant="outlined"
+                  />
+                ) : null}
+                {f._isAutoId ? (
+                  <Chip
+                    className={classes.chip}
+                    size="small"
+                    label="auto id"
+                    variant="outlined"
+                  />
+                ) : null}
+              </div>
+            ),
+            _fieldTypeElement: (
+              <div className={classes.nameWrapper}>
+                {f._fieldType}
+                {f._dimension ? (
+                  <Chip
+                    className={classes.chip}
+                    size="small"
+                    label={`dim: ${f._dimension}`}
+                    variant="outlined"
+                  />
+                ) : null}
+                {f._maxLength && f._maxLength !== 'null' ? (
+                  <Chip
+                    className={classes.chip}
+                    size="small"
+                    label={`max: ${f._maxLength}`}
+                    variant="outlined"
+                  />
+                ) : null}
               </div>
               </div>
             ),
             ),
             _indexParamElement: (
             _indexParamElement: (
@@ -128,11 +181,15 @@ const Schema: FC<{
               </div>
               </div>
             ),
             ),
             _indexTypeElement: (
             _indexTypeElement: (
-              <IndexTypeElement
-                data={f}
-                collectionName={collectionName}
-                cb={fetchFields}
-              />
+              <>
+                {f._fieldType !== DataTypeStringEnum.JSON ? (
+                  <IndexTypeElement
+                    data={f}
+                    collectionName={collectionName}
+                    cb={fetchFields}
+                  />
+                ) : null}
+              </>
             ),
             ),
           })
           })
         );
         );
@@ -160,30 +217,30 @@ const Schema: FC<{
       sortBy: '_fieldName',
       sortBy: '_fieldName',
     },
     },
     {
     {
-      id: '_fieldType',
+      id: '_fieldTypeElement',
       align: 'left',
       align: 'left',
       disablePadding: false,
       disablePadding: false,
       label: collectionTrans('fieldType'),
       label: collectionTrans('fieldType'),
     },
     },
-    {
-      id: '_dimension',
-      align: 'left',
-      disablePadding: false,
-      label: (
-        <span className="flex-center">
-          {collectionTrans('dimension')}
-          <CustomToolTip title={collectionTrans('dimensionTooltip')}>
-            <InfoIcon classes={{ root: classes.icon }} />
-          </CustomToolTip>
-        </span>
-      ),
-    },
-    {
-      id: '_maxLength',
-      align: 'left',
-      disablePadding: true,
-      label: collectionTrans('maxLength'),
-    },
+    // {
+    //   id: '_dimension',
+    //   align: 'left',
+    //   disablePadding: false,
+    //   label: (
+    //     <span className="flex-center">
+    //       {collectionTrans('dimension')}
+    //       <CustomToolTip title={collectionTrans('dimensionTooltip')}>
+    //         <InfoIcon classes={{ root: classes.icon }} />
+    //       </CustomToolTip>
+    //     </span>
+    //   ),
+    // },
+    // {
+    //   id: '_maxLength',
+    //   align: 'left',
+    //   disablePadding: true,
+    //   label: collectionTrans('maxLength'),
+    // },
     {
     {
       id: '_indexName',
       id: '_indexName',
       align: 'left',
       align: 'left',

+ 1 - 0
client/src/pages/schema/Types.ts

@@ -30,6 +30,7 @@ export interface Field {
 export interface FieldData {
 export interface FieldData {
   _fieldId: string;
   _fieldId: string;
   _isPrimaryKey: boolean;
   _isPrimaryKey: boolean;
+  is_partition_key: boolean;
   _isAutoId: boolean;
   _isAutoId: boolean;
   _fieldName: string;
   _fieldName: string;
   _fieldNameElement?: ReactElement;
   _fieldNameElement?: ReactElement;