diff --git a/README.md b/README.md index 6c09eca8ba7..9eef5e4a450 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ You can use InLong in the following ways: | | StarRocks | >= 2.0 | | | Kudu | >= 1.12.0 | | | Redis | >= 3.0 | +| | OceanBase | >= 1.0 | ## Build InLong More detailed instructions can be found at [Quick Start](https://inlong.apache.org/docs/next/quick_start/how_to_build) section in the documentation. diff --git a/inlong-dashboard/src/plugins/images/OceanBase.png b/inlong-dashboard/src/plugins/images/OceanBase.png new file mode 100644 index 00000000000..e67803d72fd Binary files /dev/null and b/inlong-dashboard/src/plugins/images/OceanBase.png differ diff --git a/inlong-dashboard/src/plugins/nodes/defaults/OceanBase.ts b/inlong-dashboard/src/plugins/nodes/defaults/OceanBase.ts new file mode 100644 index 00000000000..d73bcaab92c --- /dev/null +++ b/inlong-dashboard/src/plugins/nodes/defaults/OceanBase.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +import { DataWithBackend } from '@/plugins/DataWithBackend'; +import { RenderRow } from '@/plugins/RenderRow'; +import { RenderList } from '@/plugins/RenderList'; +import { NodeInfo } from '../common/NodeInfo'; + +const { I18n } = DataWithBackend; +const { FieldDecorator } = RenderRow; + +export default class OceanBaseNode + extends NodeInfo + implements DataWithBackend, RenderRow, RenderList +{ + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + }) + @I18n('meta.Nodes.OceanBase.Username') + username: string; + + @FieldDecorator({ + type: 'password', + rules: [{ required: true }], + }) + @I18n('meta.Nodes.OceanBase.Password') + token: string; + + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: { + placeholder: '127.0.0.1:2883', + }, + }) + @I18n('meta.Nodes.OceanBase.Url') + url: string; + + @FieldDecorator({ + type: 'input', + props: { + placeholder: '127.0.0.1:2883', + }, + }) + @I18n('meta.Nodes.OceanBase.BackupUrl') + backupUrl: string; +} diff --git a/inlong-dashboard/src/plugins/nodes/defaults/index.ts b/inlong-dashboard/src/plugins/nodes/defaults/index.ts index 212b0e53ef2..06209fd9409 100644 --- a/inlong-dashboard/src/plugins/nodes/defaults/index.ts +++ b/inlong-dashboard/src/plugins/nodes/defaults/index.ts @@ -66,6 +66,11 @@ export const allDefaultNodes: MetaExportWithBackendList = [ value: 'MYSQL', LoadEntity: () => import('./MySQL'), }, + { + label: 'OceanBase', + value: 'OCEANBASE', + LoadEntity: () => import('./OceanBase'), + }, { label: 'PostgreSQL', value: 'POSTGRESQL', diff --git a/inlong-dashboard/src/plugins/sinks/defaults/OceanBase.ts b/inlong-dashboard/src/plugins/sinks/defaults/OceanBase.ts new file mode 100644 index 00000000000..299ac99bda1 --- /dev/null +++ b/inlong-dashboard/src/plugins/sinks/defaults/OceanBase.ts @@ -0,0 +1,265 @@ +/* + * 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. + */ + +import { DataWithBackend } from '@/plugins/DataWithBackend'; +import { RenderRow } from '@/plugins/RenderRow'; +import { RenderList } from '@/plugins/RenderList'; +import i18n from '@/i18n'; +import EditableTable from '@/ui/components/EditableTable'; +import { sourceFields } from '../common/sourceFields'; +import { SinkInfo } from '../common/SinkInfo'; +import NodeSelect from '@/ui/components/NodeSelect'; +import CreateTable from '@/ui/components/CreateTable'; + +const { I18n } = DataWithBackend; +const { FieldDecorator, SyncField, SyncCreateTableField, IngestionField } = RenderRow; +const { ColumnDecorator } = RenderList; + +const fieldTypesConf = { + tinyint: (m, d) => (1 <= m && m <= 4 ? '' : '1<=M<=4'), + smallint: (m, d) => (1 <= m && m <= 6 ? '' : '1<=M<=6'), + mediumint: (m, d) => (1 <= m && m <= 9 ? '' : '1<=M<=9'), + int: (m, d) => (1 <= m && m <= 11 ? '' : '1<=M<=11'), + float: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + bigint: (m, d) => (1 <= m && m <= 20 ? '' : '1<=M<=20'), + double: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + numeric: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + decimal: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + boolean: () => '', + date: () => '', + time: () => '', + datetime: () => '', + char: (m, d) => (1 <= m && m <= 255 ? '' : '1<=M<=255'), + varchar: (m, d) => (1 <= m && m <= 16383 ? '' : '1<=M<=16383'), + text: () => '', + binary: (m, d) => (1 <= m && m <= 64 ? '' : '1<=M<=64'), + varbinary: (m, d) => (1 <= m && m <= 64 ? '' : '1<=M<=64'), + blob: () => '', +}; + +const fieldTypes = Object.keys(fieldTypesConf).reduce( + (acc, key) => + acc.concat({ + label: key, + value: key, + }), + [], +); + +export default class HiveSink extends SinkInfo implements DataWithBackend, RenderRow, RenderList { + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + }), + }) + @ColumnDecorator() + @I18n('meta.Sinks.OceanBase.DatabaseName') + @SyncField() + @IngestionField() + databaseName: string; + + @FieldDecorator({ + type: CreateTable, + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + sinkType: values.sinkType, + inlongGroupId: values.inlongGroupId, + inlongStreamId: values.inlongStreamId, + fieldName: 'tableName', + sinkObj: { + ...values, + }, + }), + }) + @ColumnDecorator() + @I18n('meta.Sinks.OceanBase.TableName') + @SyncField() + @IngestionField() + tableName: string; + + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + }), + }) + @ColumnDecorator() + @I18n('meta.Sinks.OceanBase.PrimaryKey') + @SyncField() + @IngestionField() + primaryKey: string; + + @FieldDecorator({ + type: 'radio', + rules: [{ required: true }], + initialValue: 1, + tooltip: i18n.t('meta.Sinks.EnableCreateResourceHelp'), + props: values => ({ + disabled: [110].includes(values?.status), + options: [ + { + label: i18n.t('basic.Yes'), + value: 1, + }, + { + label: i18n.t('basic.No'), + value: 0, + }, + ], + }), + }) + @IngestionField() + @I18n('meta.Sinks.EnableCreateResource') + enableCreateResource: number; + + @FieldDecorator({ + type: NodeSelect, + rules: [{ required: true }], + props: values => ({ + disabled: [110].includes(values?.status), + nodeType: 'OceanBase', + }), + }) + @SyncField() + @IngestionField() + @I18n('meta.Sinks.DataNodeName') + dataNodeName: string; + + @FieldDecorator({ + type: EditableTable, + props: values => ({ + size: 'small', + editing: ![110].includes(values?.status), + columns: getFieldListColumns(values), + canBatchAdd: true, + upsertByFieldKey: true, + }), + }) + @IngestionField() + sinkFieldList: Record[]; + + @FieldDecorator({ + type: EditableTable, + initialValue: [], + props: values => ({ + size: 'small', + editing: ![110].includes(values?.status), + columns: getFieldListColumns(values).filter( + item => item.dataIndex !== 'sourceFieldName' && item.dataIndex !== 'sourceFieldType', + ), + canBatchAdd: true, + upsertByFieldKey: true, + }), + }) + @SyncCreateTableField() + createTableField: Record[]; +} + +const getFieldListColumns = sinkValues => { + return [ + ...sourceFields, + { + title: i18n.t('meta.Sinks.SinkFieldName'), + dataIndex: 'fieldName', + initialValue: '', + rules: [ + { required: true }, + { + pattern: /^[a-zA-Z_][0-9a-z_]*$/, + message: i18n.t('meta.Sinks.SinkFieldNameRule'), + }, + ], + props: (text, record, idx, isNew) => ({ + disabled: [110].includes(sinkValues?.status as number) && !isNew, + }), + }, + { + title: i18n.t('meta.Sinks.SinkFieldType'), + dataIndex: 'fieldType', + initialValue: fieldTypes[0].value, + type: 'autocomplete', + props: (text, record, idx, isNew) => ({ + options: fieldTypes, + disabled: [110].includes(sinkValues?.status as number) && !isNew, + allowClear: true, + }), + rules: [ + { required: true, message: `${i18n.t('meta.Sinks.FieldTypeMessage')}` }, + () => ({ + validator(_, val) { + if (val) { + const [, type = val, typeLength = ''] = val.match(/^(.+)\((.+)\)$/) || []; + if (fieldTypesConf.hasOwnProperty(type)) { + const [m = -1, d = -1] = typeLength.split(','); + const errMsg = fieldTypesConf[type]?.(m, d); + if (typeLength && errMsg) return Promise.reject(new Error(errMsg)); + } else { + return Promise.reject(new Error('FieldType error')); + } + } + return Promise.resolve(); + }, + }), + ], + }, + { + title: i18n.t('meta.Sinks.OceanBase.IsMetaField'), + dataIndex: 'isMetaField', + initialValue: 0, + type: 'select', + props: (text, record, idx, isNew) => ({ + options: [ + { + label: i18n.t('basic.Yes'), + value: 1, + }, + { + label: i18n.t('basic.No'), + value: 0, + }, + ], + }), + }, + { + title: i18n.t('meta.Sinks.OceanBase.FieldFormat'), + dataIndex: 'fieldFormat', + initialValue: 0, + type: 'autocomplete', + props: (text, record, idx, isNew) => ({ + options: ['MICROSECONDS', 'MILLISECONDS', 'SECONDS', 'SQL', 'ISO_8601'].map(item => ({ + label: item, + value: item, + })), + }), + visible: (text, record) => + ['BIGINT', 'DATE', 'TIMESTAMP'].includes(record.fieldType as string), + }, + { + title: i18n.t('meta.Sinks.FieldDescription'), + dataIndex: 'fieldComment', + initialValue: '', + }, + ]; +}; diff --git a/inlong-dashboard/src/plugins/sinks/defaults/index.ts b/inlong-dashboard/src/plugins/sinks/defaults/index.ts index d9b5ef52203..0fe788c1034 100644 --- a/inlong-dashboard/src/plugins/sinks/defaults/index.ts +++ b/inlong-dashboard/src/plugins/sinks/defaults/index.ts @@ -81,6 +81,11 @@ export const allDefaultSinks: MetaExportWithBackendList = [ value: 'MYSQL', LoadEntity: () => import('./MySQL'), }, + { + label: 'OceanBase', + value: 'OCEANBASE', + LoadEntity: () => import('./OceanBase'), + }, { label: 'Oracle', value: 'ORACLE', diff --git a/inlong-dashboard/src/plugins/sources/defaults/OceanBaseBinlog.ts b/inlong-dashboard/src/plugins/sources/defaults/OceanBaseBinlog.ts new file mode 100644 index 00000000000..31645656c7e --- /dev/null +++ b/inlong-dashboard/src/plugins/sources/defaults/OceanBaseBinlog.ts @@ -0,0 +1,204 @@ +/* + * 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. + */ + +import { DataWithBackend } from '@/plugins/DataWithBackend'; +import { RenderRow } from '@/plugins/RenderRow'; +import { RenderList } from '@/plugins/RenderList'; +import i18n from '@/i18n'; +import { SourceInfo } from '../common/SourceInfo'; + +const { I18n } = DataWithBackend; +const { FieldDecorator, SyncField, SyncMoveDbField, IngestionField } = RenderRow; +const { ColumnDecorator } = RenderList; + +export default class TubeMqSource + extends SourceInfo + implements DataWithBackend, RenderRow, RenderList +{ + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + }), + }) + @SyncField() + @IngestionField() + @ColumnDecorator() + @SyncMoveDbField() + @I18n('meta.Sources.Db.Server') + hostname: string; + + @FieldDecorator({ + type: 'inputnumber', + initialValue: 3306, + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + min: 0, + max: 65535, + }), + }) + @SyncField() + @IngestionField() + @ColumnDecorator() + @SyncMoveDbField() + @I18n('meta.Sources.Db.Port') + port: number; + + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + }), + }) + @SyncField() + @IngestionField() + @SyncMoveDbField() + @I18n('meta.Sources.Db.User') + user: string; + + @FieldDecorator({ + type: 'password', + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + }), + }) + @SyncField() + @IngestionField() + @SyncMoveDbField() + @I18n('meta.Sources.Db.Password') + password: string; + + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + initialValue: '/data/inlong-agent/.history', + props: values => ({ + disabled: values?.status === 101, + }), + }) + @IngestionField() + @I18n('meta.Sources.Db.HistoryFilename') + historyFilename: string; + + @FieldDecorator({ + type: 'input', + tooltip: 'UTC, UTC+8, Asia/Shanghai, ...', + initialValue: 'UTC', + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + }), + }) + @IngestionField() + @I18n('meta.Sources.Db.ServerTimezone') + serverTimezone: string; + + @FieldDecorator({ + type: 'inputnumber', + initialValue: 1000, + rules: [{ required: true }], + suffix: 'ms', + props: values => ({ + disabled: values?.status === 101, + min: 1000, + max: 3600000, + }), + }) + @IngestionField() + @I18n('meta.Sources.Db.IntervalMs') + intervalMs: number; + + @FieldDecorator({ + type: 'radio', + rules: [{ required: true }], + initialValue: false, + props: values => ({ + disabled: values?.status === 101, + options: [ + { + label: i18n.t('basic.Yes'), + value: true, + }, + { + label: i18n.t('basic.No'), + value: false, + }, + ], + }), + }) + @IngestionField() + @I18n('meta.Sources.Db.AllMigration') + allMigration: boolean; + + @FieldDecorator({ + type: 'radio', + rules: [{ required: true }], + initialValue: true, + props: values => ({ + disabled: values?.status === 101, + options: [ + { + label: i18n.t('meta.Sources.Db.FullAmountAndIncremental'), + value: false, + }, + { + label: i18n.t('meta.Sources.Db.Incremental'), + value: true, + }, + ], + }), + }) + @SyncField() + @IngestionField() + @SyncMoveDbField() + @I18n('meta.Sources.Db.ReadMode') + onlyIncremental: boolean; + + @FieldDecorator({ + type: 'input', + rules: [{ required: true }], + tooltip: i18n.t('meta.Sources.Db.DatabaseWhiteListHelp'), + props: values => ({ + disabled: values?.status === 101, + }), + }) + @SyncField() + @IngestionField() + @SyncMoveDbField() + @I18n('meta.Sources.Db.DatabaseWhiteList') + databaseWhiteList: string; + + @FieldDecorator({ + type: 'input', + tooltip: i18n.t('meta.Sources.Db.TableWhiteListHelp'), + rules: [{ required: true }], + props: values => ({ + disabled: values?.status === 101, + }), + }) + @SyncField() + @IngestionField() + @SyncMoveDbField() + @I18n('meta.Sources.Db.TableWhiteList') + tableWhiteList: boolean; +} diff --git a/inlong-dashboard/src/plugins/sources/defaults/index.ts b/inlong-dashboard/src/plugins/sources/defaults/index.ts index 58d55b30cb9..416e7c310f5 100644 --- a/inlong-dashboard/src/plugins/sources/defaults/index.ts +++ b/inlong-dashboard/src/plugins/sources/defaults/index.ts @@ -50,6 +50,11 @@ export const allDefaultSources: MetaExportWithBackendList = [ value: 'MYSQL_BINLOG', LoadEntity: () => import('./MySQLBinlog'), }, + { + label: 'OceanBase', + value: 'OCEANBASE', + LoadEntity: () => import('./OceanBaseBinlog'), + }, { label: 'MongoDB', value: 'MONGODB', diff --git a/inlong-dashboard/src/ui/components/FieldList/FieldTypeConf.tsx b/inlong-dashboard/src/ui/components/FieldList/FieldTypeConf.tsx index d1b4a00c246..14fe2473a44 100644 --- a/inlong-dashboard/src/ui/components/FieldList/FieldTypeConf.tsx +++ b/inlong-dashboard/src/ui/components/FieldList/FieldTypeConf.tsx @@ -277,7 +277,31 @@ const mysqlTypesConf = { varbinary: (m, d) => (1 <= m && m <= 64 ? '' : '1<=M<=64'), blob: () => '', }; - +const oceanBaseTypesConf = { + tinyint: (m, d) => (1 <= m && m <= 4 ? '' : '1<=M<=4'), + smallint: (m, d) => (1 <= m && m <= 6 ? '' : '1<=M<=6'), + mediumint: (m, d) => (1 <= m && m <= 9 ? '' : '1<=M<=9'), + int: (m, d) => (1 <= m && m <= 11 ? '' : '1<=M<=11'), + float: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + bigint: (m, d) => (1 <= m && m <= 20 ? '' : '1<=M<=20'), + double: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + numeric: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + decimal: (m, d) => + 1 <= m && m <= 255 && 1 <= d && d <= 30 && d <= m - 2 ? '' : '1<=M<=255,1<=D<=30,D<=M-2', + boolean: () => '', + date: () => '', + time: () => '', + datetime: () => '', + char: (m, d) => (1 <= m && m <= 255 ? '' : '1<=M<=255'), + varchar: (m, d) => (1 <= m && m <= 16383 ? '' : '1<=M<=16383'), + text: () => '', + binary: (m, d) => (1 <= m && m <= 64 ? '' : '1<=M<=64'), + varbinary: (m, d) => (1 <= m && m <= 64 ? '' : '1<=M<=64'), + blob: () => '', +}; const oracleTypesConf = { binary_float: (m, d) => (1 <= m && m <= 6 ? '' : '1 <= M <= 6'), binary_double: (m, d) => (1 <= m && m <= 10 ? '' : '1 <= M <= 10'), @@ -394,6 +418,7 @@ export const fieldAllTypes = { ICEBERG: icebergFieldTypes, HUDI: hudiFieldTypes, MYSQL: getFieldTypes(mysqlTypesConf), + OCEANBASE: getFieldTypes(oceanBaseTypesConf), ORACLE: getFieldTypes(oracleTypesConf), POSTGRESQL: getFieldTypes(pgTypesConf), SQLSERVER: getFieldTypes(sqlServerTypesConf), diff --git a/inlong-dashboard/src/ui/locales/cn.json b/inlong-dashboard/src/ui/locales/cn.json index 88f10feb5d6..59c69445c7f 100644 --- a/inlong-dashboard/src/ui/locales/cn.json +++ b/inlong-dashboard/src/ui/locales/cn.json @@ -258,6 +258,11 @@ "meta.Sinks.MySQL.PrimaryKey": "主键", "meta.Sinks.MySQL.IsMetaField": "是否为元字段", "meta.Sinks.MySQL.FieldFormat": "字段格式", + "meta.Sinks.OceanBase.DatabaseName": "数据库名", + "meta.Sinks.OceanBase.TableName": "表名称", + "meta.Sinks.OceanBase.PrimaryKey": "主键", + "meta.Sinks.OceanBase.IsMetaField": "是否为元字段", + "meta.Sinks.OceanBase.FieldFormat": "字段格式", "meta.Sinks.Oracle.TableName": "表名称", "meta.Sinks.Oracle.PrimaryKey": "主键", "meta.Sinks.Oracle.IsMetaField": "是否为元字段", @@ -468,6 +473,10 @@ "meta.Nodes.MySQL.Password": "密码", "meta.Nodes.MySQL.Url": "地址", "meta.Nodes.MySQL.BackupUrl": "备份地址", + "meta.Nodes.OceanBase.Username": "用户名", + "meta.Nodes.OceanBase.Password": "密码", + "meta.Nodes.OceanBase.Url": "地址", + "meta.Nodes.OceanBase.BackupUrl": "备份地址", "meta.Nodes.PostgreSQL.Username": "用户名", "meta.Nodes.PostgreSQL.Password": "密码", "meta.Nodes.PostgreSQL.Url": "地址", diff --git a/inlong-dashboard/src/ui/locales/en.json b/inlong-dashboard/src/ui/locales/en.json index 8998a2d13f4..2cfb58d6de8 100644 --- a/inlong-dashboard/src/ui/locales/en.json +++ b/inlong-dashboard/src/ui/locales/en.json @@ -258,6 +258,11 @@ "meta.Sinks.MySQL.PrimaryKey": "Primary key", "meta.Sinks.MySQL.IsMetaField": "Is meta field", "meta.Sinks.MySQL.FieldFormat": "Field format", + "meta.Sinks.OceanBase.DatabaseName": "Database name", + "meta.Sinks.OceanBase.TableName": "Table name", + "meta.Sinks.OceanBase.PrimaryKey": "Primary key", + "meta.Sinks.OceanBase.IsMetaField": "Is meta field", + "meta.Sinks.OceanBase.FieldFormat": "Field format", "meta.Sinks.Oracle.TableName": "Table name", "meta.Sinks.Oracle.PrimaryKey": "Primary key", "meta.Sinks.Oracle.IsMetaField": "Is meta field", @@ -468,6 +473,10 @@ "meta.Nodes.MySQL.Password": "Password", "meta.Nodes.MySQL.Url": "URL", "meta.Nodes.MySQL.BackupUrl": "Backup url", + "meta.Nodes.OceanBase.Username": "Username", + "meta.Nodes.OceanBase.Password": "Password", + "meta.Nodes.OceanBase.Url": "URL", + "meta.Nodes.OceanBase.BackupUrl": "Backup url", "meta.Nodes.PostgreSQL.Username": "Username", "meta.Nodes.PostgreSQL.Password": "Password", "meta.Nodes.PostgreSQL.Url": "URL",