Repository: CodePhiliaX/Chat2DB Branch: main Commit: 15ec66005e92 Files: 1633 Total size: 4.7 MB Directory structure: gitextract_docf9pkb/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── optimized.md │ │ └── suggest.md │ └── workflows/ │ ├── pushdocker.yml │ ├── release.yml │ ├── release_test.yml │ └── release_test_2.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── CHANGELOG_CN.md ├── CHAT2DB_AI_SQL.md ├── Chat2DB_LICENSE ├── LICENSE ├── README.md ├── README_CN.md ├── README_JA.md ├── chat2db-client/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .umirc.prod.desktop.ts │ ├── .umirc.prod.ts │ ├── .umirc.ts │ ├── .vscode/ │ │ └── settings.json │ ├── mock/ │ │ └── sqlResult.json │ ├── package.json │ ├── readme.md │ ├── src/ │ │ ├── assets/ │ │ │ ├── font/ │ │ │ │ ├── demo.css │ │ │ │ ├── demo_index.html │ │ │ │ ├── iconfont.css │ │ │ │ ├── iconfont.js │ │ │ │ └── iconfont.json │ │ │ └── logo/ │ │ │ └── logo.icns │ │ ├── blocks/ │ │ │ ├── AppTitleBar/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── CreateConnection/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── DatabaseTableEditor/ │ │ │ │ ├── BaseInfo/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── ColumnList/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── IncludeCol/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── IndexList/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── RealTimeSQL/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── SequenceEditor/ │ │ │ │ ├── BaseInfo/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── IncludeCol/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── RealTimeSQL/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Setting/ │ │ │ │ ├── About/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── AiSetting/ │ │ │ │ │ ├── aiTypeConfig.ts │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── BaseSetting/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── ProxySetting/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── UpdateDetection/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── Tree/ │ │ │ ├── functions/ │ │ │ │ ├── deleteSequence.tsx │ │ │ │ ├── deleteTable.less │ │ │ │ ├── deleteTable.tsx │ │ │ │ ├── openAsyncSql.ts │ │ │ │ ├── pinTable.ts │ │ │ │ ├── refresh.ts │ │ │ │ ├── viewDDL.less │ │ │ │ └── viewDDL.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useGetRightClickMenu.ts │ │ │ │ └── useTreeNodeFocus.ts │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── treeConfig.tsx │ │ │ └── treeStore.ts │ │ ├── components/ │ │ │ ├── BrandLogo/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── CascaderDB/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ConnectionEdit/ │ │ │ │ ├── components/ │ │ │ │ │ └── Driver/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── config/ │ │ │ │ │ ├── dataSource.ts │ │ │ │ │ ├── enum.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ConsoleEditor/ │ │ │ │ ├── components/ │ │ │ │ │ ├── ChatInput/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── OperationLine/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── SelectBoundInfo/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── hooks/ │ │ │ │ │ ├── useModuleData.ts │ │ │ │ │ └── useSaveEditorData.ts │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── CreateDatabase/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── CustomLayout/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── CustomSelect/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── DraggableContainer/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── EditDialog/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ExecuteSQL/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Iconfont/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ImportBlock/ │ │ │ │ └── index.tsx │ │ │ ├── ImportConnection/ │ │ │ │ └── index.tsx │ │ │ ├── LayoutBasic/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Loading/ │ │ │ │ ├── LazyLoading/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── Loading/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── LoadingContent/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── LoadingGracile/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ └── LoadingLiquid/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── MenuLabel/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Modal/ │ │ │ │ ├── BaseModal/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── MonacoEditorModal/ │ │ │ │ │ └── index.tsx │ │ │ │ └── TriggeredModal/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── MonacoEditor/ │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ ├── monacoEditorConfig.ts │ │ │ │ ├── syntax-parser/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── lexer/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── token.ts │ │ │ │ │ ├── parser/ │ │ │ │ │ │ ├── chain.ts │ │ │ │ │ │ ├── define.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── match.ts │ │ │ │ │ │ ├── scanner.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ └── plugin/ │ │ │ │ │ ├── monaco-plugin/ │ │ │ │ │ │ ├── default-opts.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── parser.worker.ts │ │ │ │ │ └── sql-parser/ │ │ │ │ │ ├── base/ │ │ │ │ │ │ ├── define.ts │ │ │ │ │ │ ├── four-operations.ts │ │ │ │ │ │ ├── parser.ts │ │ │ │ │ │ ├── reader.ts │ │ │ │ │ │ ├── reserve-keys.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── mysql/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── lexer.ts │ │ │ │ │ └── parser.ts │ │ │ │ └── useMonacoTheme.ts │ │ │ ├── MyNotification/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Output/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Popularize/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── RefreshLoadingButton/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ScrollLoading/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── SearchResult/ │ │ │ │ ├── components/ │ │ │ │ │ ├── OperationalDataBar/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Pagination/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── RightClickMenu/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ScreeningResult/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── StatusBar/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── TableBox/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── hooks/ │ │ │ │ │ ├── useCurdTableData.ts │ │ │ │ │ ├── useMultipleSelect.ts │ │ │ │ │ └── usePasteData.ts │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ └── utils.tsx │ │ │ ├── ShortcutKey/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── SingleFileMonacoEditor/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── StateIndicator/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Tabs/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── UploadDriver/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ViewDDL/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── XXXX_FN/ │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── constants/ │ │ │ ├── IntelliSense/ │ │ │ │ ├── index.ts │ │ │ │ ├── mysql.ts │ │ │ │ ├── oracle.ts │ │ │ │ ├── pgsql.ts │ │ │ │ ├── redis.ts │ │ │ │ └── sqlserver.ts │ │ │ ├── appConfig.ts │ │ │ ├── chat.ts │ │ │ ├── common.ts │ │ │ ├── console.ts │ │ │ ├── database.ts │ │ │ ├── editTable.ts │ │ │ ├── environment.ts │ │ │ ├── index.ts │ │ │ ├── table.ts │ │ │ ├── theme.ts │ │ │ ├── tree.ts │ │ │ └── workspace.ts │ │ ├── hooks/ │ │ │ ├── getConnection.ts │ │ │ ├── index.ts │ │ │ ├── useClickAndDoubleClick.ts │ │ │ ├── useEventSource.ts │ │ │ ├── useFocusData.ts │ │ │ ├── usePollRequestService.ts │ │ │ ├── useTheme.ts │ │ │ └── useUpdateEffect.ts │ │ ├── i18n/ │ │ │ ├── en-us/ │ │ │ │ ├── chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── connection.ts │ │ │ │ ├── dashboard.ts │ │ │ │ ├── editSequence.ts │ │ │ │ ├── editTable.ts │ │ │ │ ├── editTableData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── setting.ts │ │ │ │ ├── sqlEditor.ts │ │ │ │ ├── team.ts │ │ │ │ └── workspace.ts │ │ │ ├── index.tsx │ │ │ ├── ja-jp/ │ │ │ │ ├── chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── connection.ts │ │ │ │ ├── dashboard.ts │ │ │ │ ├── editSequence.ts │ │ │ │ ├── editTable.ts │ │ │ │ ├── editTableData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── setting.ts │ │ │ │ ├── sqlEditor.ts │ │ │ │ ├── team.ts │ │ │ │ └── workspace.ts │ │ │ ├── tr-tr/ │ │ │ │ ├── chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── connection.ts │ │ │ │ ├── dashboard.ts │ │ │ │ ├── editSequence.ts │ │ │ │ ├── editTable.ts │ │ │ │ ├── editTableData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── setting.ts │ │ │ │ ├── sqlEditor.ts │ │ │ │ ├── team.ts │ │ │ │ └── workspace.ts │ │ │ └── zh-cn/ │ │ │ ├── chat.ts │ │ │ ├── common.ts │ │ │ ├── connection.ts │ │ │ ├── dashboard.ts │ │ │ ├── editSequence.ts │ │ │ ├── editTable.ts │ │ │ ├── editTableData.ts │ │ │ ├── index.ts │ │ │ ├── login.ts │ │ │ ├── menu.ts │ │ │ ├── setting.ts │ │ │ ├── sqlEditor.ts │ │ │ ├── team.ts │ │ │ └── workspace.ts │ │ ├── indexedDB/ │ │ │ ├── index.ts │ │ │ └── table.ts │ │ ├── layouts/ │ │ │ ├── GlobalLayout/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── init/ │ │ │ ├── GlobalComponent.tsx │ │ │ ├── init.ts │ │ │ ├── initIndexedDB.ts │ │ │ ├── registerElectronApi.ts │ │ │ ├── registerMessage.ts │ │ │ └── registerNotification.ts │ │ ├── main/ │ │ │ ├── analysis.js │ │ │ ├── constants.js │ │ │ ├── ga4.js │ │ │ ├── i18n/ │ │ │ │ ├── en/ │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ └── zh-cn/ │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── main.js │ │ │ ├── main.js.LICENSE.txt │ │ │ ├── menu.js │ │ │ ├── package.json │ │ │ ├── preload.js │ │ │ ├── store.js │ │ │ ├── utils.js │ │ │ └── webpack.config.js │ │ ├── pages/ │ │ │ ├── demo/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── document.ejs │ │ │ ├── login/ │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── main/ │ │ │ │ ├── connection/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── chart/ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── line/ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── pie/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── chart-item/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── left-block/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── functions/ │ │ │ │ │ └── getConnection.ts │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ ├── store/ │ │ │ │ │ ├── connection/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── main/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── monaco/ │ │ │ │ │ └── index.ts │ │ │ │ ├── team/ │ │ │ │ │ ├── datasource-management/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── team-management/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── universal-add-modal/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── universal-drawer/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── user-management/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ └── workspace/ │ │ │ │ ├── components/ │ │ │ │ │ ├── OperationLine/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── SQLExecute/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── SaveList/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── TableList/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ViewAllTable/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── WorkspaceExtend/ │ │ │ │ │ │ ├── GlobalExtendComponents/ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── WorkspaceExtendBody/ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── WorkspaceExtendNav/ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── config.tsx │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── WorkspaceLeft/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── WorkspaceLeftHeader/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── WorkspaceRight/ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── WorkspaceTabs/ │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── functions/ │ │ │ │ │ └── shortcutKeyCreateConsole.ts │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ └── store/ │ │ │ │ ├── common.ts │ │ │ │ ├── config.ts │ │ │ │ ├── console.ts │ │ │ │ ├── index.ts │ │ │ │ └── modal.ts │ │ │ └── test/ │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── service/ │ │ │ ├── ai.ts │ │ │ ├── base.ts │ │ │ ├── config.ts │ │ │ ├── connection.ts │ │ │ ├── dashboard.ts │ │ │ ├── history.ts │ │ │ ├── misc.tsx │ │ │ ├── outside.ts │ │ │ ├── sql.ts │ │ │ ├── team.ts │ │ │ └── user.ts │ │ ├── store/ │ │ │ ├── common/ │ │ │ │ ├── appTitleBarConfig.ts │ │ │ │ ├── components.ts │ │ │ │ ├── copyFocusedContent.ts │ │ │ │ └── index.ts │ │ │ ├── config/ │ │ │ │ └── index.ts │ │ │ ├── monaco/ │ │ │ │ └── index.ts │ │ │ ├── setting/ │ │ │ │ └── index.ts │ │ │ └── user/ │ │ │ └── index.ts │ │ ├── styles/ │ │ │ ├── antd.less │ │ │ ├── common.less │ │ │ ├── global.less │ │ │ └── var.less │ │ ├── theme/ │ │ │ ├── abandon/ │ │ │ │ ├── demo/ │ │ │ │ │ ├── dark.less │ │ │ │ │ └── light.less │ │ │ │ └── primaryColor.less │ │ │ ├── background/ │ │ │ │ ├── dark.ts │ │ │ │ ├── darkDimmed.ts │ │ │ │ └── light.ts │ │ │ ├── common.ts │ │ │ ├── custom/ │ │ │ │ ├── dark.less │ │ │ │ ├── darkDimmed.less │ │ │ │ └── light.less │ │ │ └── index.ts │ │ ├── typings/ │ │ │ ├── ai.ts │ │ │ ├── common.ts │ │ │ ├── connection.ts │ │ │ ├── console.ts │ │ │ ├── dashboard.ts │ │ │ ├── database.ts │ │ │ ├── editSequence.ts │ │ │ ├── editTable.ts │ │ │ ├── index.ts │ │ │ ├── main.ts │ │ │ ├── resultTable.ts │ │ │ ├── setting.ts │ │ │ ├── team.ts │ │ │ ├── theme.ts │ │ │ ├── tree.ts │ │ │ ├── user.ts │ │ │ └── workspace.ts │ │ └── utils/ │ │ ├── IntelliSense/ │ │ │ ├── database.ts │ │ │ ├── field.ts │ │ │ ├── index.ts │ │ │ ├── keyword.ts │ │ │ ├── table.ts │ │ │ └── view.ts │ │ ├── check.ts │ │ ├── database.ts │ │ ├── date.ts │ │ ├── eventSource.ts │ │ ├── file.ts │ │ ├── getTree.ts │ │ ├── index.ts │ │ ├── localStorage.ts │ │ ├── lodash.ts │ │ ├── sort.ts │ │ ├── sql.ts │ │ ├── timezone.ts │ │ ├── url.ts │ │ └── webpack.ts │ ├── tsconfig.json │ └── typings.d.ts ├── chat2db-server/ │ ├── .apifox-helper.properties │ ├── .easy.api.config │ ├── .gitignore │ ├── README.md │ ├── chat2db-plugins/ │ │ ├── chat2db-clickhouse/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── clickhouse/ │ │ │ │ ├── ClickHouseDBManage.java │ │ │ │ ├── ClickHouseMetaData.java │ │ │ │ ├── ClickHousePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── ClickHouseSqlBuilder.java │ │ │ │ ├── clickhouse.json │ │ │ │ └── type/ │ │ │ │ ├── ClickHouseColumnTypeEnum.java │ │ │ │ ├── ClickHouseEngineTypeEnum.java │ │ │ │ └── ClickHouseIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-db2/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── db2/ │ │ │ │ ├── DB2DBManage.java │ │ │ │ ├── DB2MetaData.java │ │ │ │ ├── DB2Plugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── DB2SqlBuilder.java │ │ │ │ ├── constant/ │ │ │ │ │ └── SQLConstant.java │ │ │ │ ├── db2.json │ │ │ │ └── type/ │ │ │ │ ├── DB2ColumnTypeEnum.java │ │ │ │ ├── DB2DefaultValueEnum.java │ │ │ │ └── DB2IndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-dm/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── dm/ │ │ │ │ ├── DMDBManage.java │ │ │ │ ├── DMMetaData.java │ │ │ │ ├── DMPlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── DMSqlBuilder.java │ │ │ │ ├── dm.json │ │ │ │ └── type/ │ │ │ │ ├── DMColumnTypeEnum.java │ │ │ │ ├── DMDefaultValueEnum.java │ │ │ │ └── DMIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-h2/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── h2/ │ │ │ │ ├── H2DBManage.java │ │ │ │ ├── H2Meta.java │ │ │ │ ├── H2Plugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── H2SqlBuilder.java │ │ │ │ └── h2.json │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-hive/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── hive/ │ │ │ │ ├── HiveCommandExecutor.java │ │ │ │ ├── HiveDBManage.java │ │ │ │ ├── HiveMetaData.java │ │ │ │ ├── HivePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── HiveSqlBuilder.java │ │ │ │ ├── hive.json │ │ │ │ └── type/ │ │ │ │ ├── HiveColumnTypeEnum.java │ │ │ │ └── HiveIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-kingbase/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── kingbase/ │ │ │ │ ├── KingBaseDBManage.java │ │ │ │ ├── KingBaseMetaData.java │ │ │ │ ├── KingBasePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── KingBaseSqlBuilder.java │ │ │ │ ├── kingbase.json │ │ │ │ └── type/ │ │ │ │ ├── KingBaseColumnTypeEnum.java │ │ │ │ ├── KingBaseDefaultValueEnum.java │ │ │ │ └── KingBaseIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-mariadb/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── mariadb/ │ │ │ │ ├── MariaDBManage.java │ │ │ │ ├── MariaDBMetaData.java │ │ │ │ ├── MariaDBPlugin.java │ │ │ │ ├── mariadb.json │ │ │ │ └── value/ │ │ │ │ ├── MariaDBValueProcessor.java │ │ │ │ ├── factory/ │ │ │ │ │ └── MariaDBValueProcessorFactory.java │ │ │ │ └── sub/ │ │ │ │ ├── MariaDBBitProcessor.java │ │ │ │ ├── MariaDBGeometryProcessor.java │ │ │ │ ├── MariaDBTimestampProcessor.java │ │ │ │ └── MariaDBYearProcessor.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-mongodb/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── mongodb/ │ │ │ │ ├── MongodbCommandExecutor.java │ │ │ │ ├── MongodbManage.java │ │ │ │ ├── MongodbMetaData.java │ │ │ │ ├── MongodbPlugin.java │ │ │ │ └── mongodb.json │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-mysql/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── mysql/ │ │ │ │ ├── MysqlDBManage.java │ │ │ │ ├── MysqlMetaData.java │ │ │ │ ├── MysqlPlugin.java │ │ │ │ ├── MysqlValueHandler.java │ │ │ │ ├── builder/ │ │ │ │ │ ├── MysqlSqlBuilder.java │ │ │ │ │ └── form.json │ │ │ │ ├── mysql.json │ │ │ │ ├── type/ │ │ │ │ │ ├── MysqlCharsetEnum.java │ │ │ │ │ ├── MysqlCollationEnum.java │ │ │ │ │ ├── MysqlColumnTypeEnum.java │ │ │ │ │ ├── MysqlDefaultValueEnum.java │ │ │ │ │ └── MysqlIndexTypeEnum.java │ │ │ │ └── value/ │ │ │ │ ├── GeometryValueHandler.java │ │ │ │ ├── MysqlValueProcessor.java │ │ │ │ ├── factory/ │ │ │ │ │ └── MysqlValueProcessorFactory.java │ │ │ │ ├── sub/ │ │ │ │ │ ├── MysqlBinaryProcessor.java │ │ │ │ │ ├── MysqlBitProcessor.java │ │ │ │ │ ├── MysqlDecimalProcessor.java │ │ │ │ │ ├── MysqlGeometryProcessor.java │ │ │ │ │ ├── MysqlTextProcessor.java │ │ │ │ │ ├── MysqlTimestampProcessor.java │ │ │ │ │ ├── MysqlVarBinaryProcessor.java │ │ │ │ │ └── MysqlYearProcessor.java │ │ │ │ └── template/ │ │ │ │ └── MysqlDmlValueTemplate.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-oceanbase/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── oceanbase/ │ │ │ │ ├── OceanBaseDBManage.java │ │ │ │ ├── OceanBaseMetaData.java │ │ │ │ ├── OceanBasePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── OceanBaseSqlBuilder.java │ │ │ │ ├── oceanbase.json │ │ │ │ └── type/ │ │ │ │ ├── OceanBaseColumnTypeEnum.java │ │ │ │ └── OceanBaseIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-oracle/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── oracle/ │ │ │ │ ├── OracleDBManage.java │ │ │ │ ├── OracleMetaData.java │ │ │ │ ├── OraclePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── OracleSqlBuilder.java │ │ │ │ ├── oracle.json │ │ │ │ ├── type/ │ │ │ │ │ ├── OracleColumnTypeEnum.java │ │ │ │ │ ├── OracleDefaultValueEnum.java │ │ │ │ │ └── OracleIndexTypeEnum.java │ │ │ │ └── value/ │ │ │ │ ├── OracleValueProcessor.java │ │ │ │ ├── factory/ │ │ │ │ │ └── OracleValueProcessorFactory.java │ │ │ │ ├── sub/ │ │ │ │ │ ├── OracleAnyDataProcessor.java │ │ │ │ │ ├── OracleBlobProcessor.java │ │ │ │ │ ├── OracleClobProcessor.java │ │ │ │ │ ├── OracleDateProcessor.java │ │ │ │ │ ├── OracleIntervalDSProcessor.java │ │ │ │ │ ├── OracleIntervalYMProcessor.java │ │ │ │ │ ├── OracleLongRawProcessor.java │ │ │ │ │ ├── OracleNumberProcessor.java │ │ │ │ │ ├── OracleRawValueProcessor.java │ │ │ │ │ ├── OracleTimeStampLTZProcessor.java │ │ │ │ │ ├── OracleTimeStampProcessor.java │ │ │ │ │ ├── OracleTimeStampTZProcessor.java │ │ │ │ │ └── OracleXmlValueProcessor.java │ │ │ │ └── template/ │ │ │ │ └── OracleDmlValueTemplate.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-postgresql/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── postgresql/ │ │ │ │ ├── PostgreSQLDBManage.java │ │ │ │ ├── PostgreSQLMetaData.java │ │ │ │ ├── PostgreSQLPlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── PostgreSQLSqlBuilder.java │ │ │ │ ├── consts/ │ │ │ │ │ ├── SQLConst.java │ │ │ │ │ └── SequenceCommonConst.java │ │ │ │ ├── pg.json │ │ │ │ └── type/ │ │ │ │ ├── PostgreSQLCharsetEnum.java │ │ │ │ ├── PostgreSQLCollationEnum.java │ │ │ │ ├── PostgreSQLColumnTypeEnum.java │ │ │ │ ├── PostgreSQLDefaultValueEnum.java │ │ │ │ └── PostgreSQLIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-presto/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── presto/ │ │ │ │ ├── PrestoDBManage.java │ │ │ │ ├── PrestoMetaData.java │ │ │ │ ├── PrestoPlugin.java │ │ │ │ └── presto.json │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-sqlite/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── sqlite/ │ │ │ │ ├── SqliteDBManage.java │ │ │ │ ├── SqliteMetaData.java │ │ │ │ ├── SqlitePlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── SqliteBuilder.java │ │ │ │ ├── sqlite.json │ │ │ │ └── type/ │ │ │ │ ├── SqliteCharsetEnum.java │ │ │ │ ├── SqliteCollationEnum.java │ │ │ │ ├── SqliteColumnTypeEnum.java │ │ │ │ ├── SqliteDefaultValueEnum.java │ │ │ │ └── SqliteIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-sqlserver/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── sqlserver/ │ │ │ │ ├── SqlServerCommandExecutor.java │ │ │ │ ├── SqlServerDBManage.java │ │ │ │ ├── SqlServerMetaData.java │ │ │ │ ├── SqlServerPlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── SqlServerSqlBuilder.java │ │ │ │ ├── sqlserver.json │ │ │ │ └── type/ │ │ │ │ ├── SqlServerColumnTypeEnum.java │ │ │ │ ├── SqlServerDefaultValueEnum.java │ │ │ │ └── SqlServerIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ ├── chat2db-timeplus/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── plugin/ │ │ │ │ └── timeplus/ │ │ │ │ ├── TimeplusDBManage.java │ │ │ │ ├── TimeplusMetaData.java │ │ │ │ ├── TimeplusPlugin.java │ │ │ │ ├── builder/ │ │ │ │ │ └── TimeplusSqlBuilder.java │ │ │ │ ├── timeplus.json │ │ │ │ └── type/ │ │ │ │ ├── TimeplusColumnTypeEnum.java │ │ │ │ ├── TimeplusEngineTypeEnum.java │ │ │ │ └── TimeplusIndexTypeEnum.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── ai.chat2db.spi.Plugin │ │ └── pom.xml │ ├── chat2db-server-domain/ │ │ ├── README.md │ │ ├── chat2db-server-domain-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── domain/ │ │ │ └── api/ │ │ │ ├── chart/ │ │ │ │ ├── ChartCreateParam.java │ │ │ │ ├── ChartListQueryParam.java │ │ │ │ ├── ChartPageQueryParam.java │ │ │ │ ├── ChartQueryParam.java │ │ │ │ └── ChartUpdateParam.java │ │ │ ├── enums/ │ │ │ │ ├── AccessObjectTypeEnum.java │ │ │ │ ├── AiSqlSourceEnum.java │ │ │ │ ├── DataSourceKindEnum.java │ │ │ │ ├── DeletedTypeEnum.java │ │ │ │ ├── EnvironmentEnum.java │ │ │ │ ├── ExportFileSuffix.java │ │ │ │ ├── ExportSizeEnum.java │ │ │ │ ├── ExportTypeEnum.java │ │ │ │ ├── OperationStatusEnum.java │ │ │ │ ├── RoleCodeEnum.java │ │ │ │ ├── TableVectorEnum.java │ │ │ │ ├── TaskStatusEnum.java │ │ │ │ ├── TaskTypeEnum.java │ │ │ │ └── ValidStatusEnum.java │ │ │ ├── model/ │ │ │ │ ├── AIConfig.java │ │ │ │ ├── Chart.java │ │ │ │ ├── ChatGptConfig.java │ │ │ │ ├── Config.java │ │ │ │ ├── Dashboard.java │ │ │ │ ├── DataSource.java │ │ │ │ ├── DataSourceAccess.java │ │ │ │ ├── DataSourceAccessObject.java │ │ │ │ ├── Environment.java │ │ │ │ ├── IndexInfo.java │ │ │ │ ├── Operation.java │ │ │ │ ├── OperationLog.java │ │ │ │ ├── TableParameter.java │ │ │ │ ├── Task.java │ │ │ │ ├── Team.java │ │ │ │ ├── TeamUser.java │ │ │ │ └── User.java │ │ │ ├── param/ │ │ │ │ ├── ConsoleCloseParam.java │ │ │ │ ├── ConsoleConnectParam.java │ │ │ │ ├── ConsoleCreateParam.java │ │ │ │ ├── DlCountParam.java │ │ │ │ ├── DlExecuteParam.java │ │ │ │ ├── DmlSqlCopyParam.java │ │ │ │ ├── DropParam.java │ │ │ │ ├── EnvironmentPageQueryParam.java │ │ │ │ ├── GroupByParam.java │ │ │ │ ├── MetaDataQueryParam.java │ │ │ │ ├── OrderByParam.java │ │ │ │ ├── PinTableParam.java │ │ │ │ ├── SchemaOperationParam.java │ │ │ │ ├── SchemaQueryParam.java │ │ │ │ ├── SelectResultOperation.java │ │ │ │ ├── SequencePageQueryParam.java │ │ │ │ ├── SequenceQueryParam.java │ │ │ │ ├── ShowCreateSequenceParam.java │ │ │ │ ├── ShowCreateTableParam.java │ │ │ │ ├── SqlAnalyseParam.java │ │ │ │ ├── SystemConfigParam.java │ │ │ │ ├── TablePageQueryParam.java │ │ │ │ ├── TableQueryParam.java │ │ │ │ ├── TableSelector.java │ │ │ │ ├── TableVectorParam.java │ │ │ │ ├── TaskCreateParam.java │ │ │ │ ├── TaskPageParam.java │ │ │ │ ├── TaskUpdateParam.java │ │ │ │ ├── TypeQueryParam.java │ │ │ │ ├── UpdateSelectResultParam.java │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── DashboardCreateParam.java │ │ │ │ │ ├── DashboardPageQueryParam.java │ │ │ │ │ ├── DashboardQueryParam.java │ │ │ │ │ ├── DashboardSelector.java │ │ │ │ │ └── DashboardUpdateParam.java │ │ │ │ ├── datasource/ │ │ │ │ │ ├── DataSourceCloseParam.java │ │ │ │ │ ├── DataSourceCreateParam.java │ │ │ │ │ ├── DataSourcePageQueryParam.java │ │ │ │ │ ├── DataSourcePreConnectParam.java │ │ │ │ │ ├── DataSourceSelector.java │ │ │ │ │ ├── DataSourceTestParam.java │ │ │ │ │ ├── DataSourceUpdateParam.java │ │ │ │ │ ├── DatabaseCreateParam.java │ │ │ │ │ ├── DatabaseExportDataParam.java │ │ │ │ │ ├── DatabaseExportParam.java │ │ │ │ │ ├── DatabaseQueryAllParam.java │ │ │ │ │ └── access/ │ │ │ │ │ ├── DataSourceAccessBatchCreatParam.java │ │ │ │ │ ├── DataSourceAccessComprehensivePageQueryParam.java │ │ │ │ │ ├── DataSourceAccessCreatParam.java │ │ │ │ │ ├── DataSourceAccessObjectParam.java │ │ │ │ │ ├── DataSourceAccessPageQueryParam.java │ │ │ │ │ └── DataSourceAccessSelector.java │ │ │ │ ├── message/ │ │ │ │ │ └── MessageCreateParam.java │ │ │ │ ├── operation/ │ │ │ │ │ ├── OperationLogCreateParam.java │ │ │ │ │ ├── OperationLogPageQueryParam.java │ │ │ │ │ ├── OperationPageQueryParam.java │ │ │ │ │ ├── OperationQueryParam.java │ │ │ │ │ ├── OperationSavedParam.java │ │ │ │ │ └── OperationUpdateParam.java │ │ │ │ ├── team/ │ │ │ │ │ ├── TeamCreateParam.java │ │ │ │ │ ├── TeamPageQueryParam.java │ │ │ │ │ ├── TeamSelector.java │ │ │ │ │ ├── TeamUpdateParam.java │ │ │ │ │ └── user/ │ │ │ │ │ ├── TeamUserComprehensivePageQueryParam.java │ │ │ │ │ ├── TeamUserCreatParam.java │ │ │ │ │ ├── TeamUserPageQueryParam.java │ │ │ │ │ └── TeamUserSelector.java │ │ │ │ └── user/ │ │ │ │ ├── UserCreateParam.java │ │ │ │ ├── UserPageQueryParam.java │ │ │ │ ├── UserSelector.java │ │ │ │ └── UserUpdateParam.java │ │ │ └── service/ │ │ │ ├── ChartService.java │ │ │ ├── ConfigService.java │ │ │ ├── ConsoleService.java │ │ │ ├── DashboardService.java │ │ │ ├── DataSourceAccessBusinessService.java │ │ │ ├── DataSourceAccessService.java │ │ │ ├── DataSourceService.java │ │ │ ├── DatabaseService.java │ │ │ ├── DlTemplateService.java │ │ │ ├── EnvironmentService.java │ │ │ ├── FunctionService.java │ │ │ ├── JdbcDriverService.java │ │ │ ├── OperationLogService.java │ │ │ ├── OperationService.java │ │ │ ├── PinService.java │ │ │ ├── ProcedureService.java │ │ │ ├── SequenceService.java │ │ │ ├── TableService.java │ │ │ ├── TaskService.java │ │ │ ├── TeamService.java │ │ │ ├── TeamUserService.java │ │ │ ├── TriggerService.java │ │ │ ├── UserService.java │ │ │ ├── ViewService.java │ │ │ └── WebhookSender.java │ │ ├── chat2db-server-domain-core/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── domain/ │ │ │ └── core/ │ │ │ ├── cache/ │ │ │ │ ├── CacheKey.java │ │ │ │ ├── CacheManage.java │ │ │ │ └── MemoryCacheManage.java │ │ │ ├── converter/ │ │ │ │ ├── ChartConverter.java │ │ │ │ ├── CommandConverter.java │ │ │ │ ├── ConfigConverter.java │ │ │ │ ├── DashboardConverter.java │ │ │ │ ├── DataSourceAccessConverter.java │ │ │ │ ├── DataSourceConverter.java │ │ │ │ ├── DriverConfigConverter.java │ │ │ │ ├── EnvironmentConverter.java │ │ │ │ ├── OperationConverter.java │ │ │ │ ├── OperationLogConverter.java │ │ │ │ ├── PinTableConverter.java │ │ │ │ ├── TableConverter.java │ │ │ │ ├── TaskConverter.java │ │ │ │ ├── TeamConverter.java │ │ │ │ ├── TeamUserConverter.java │ │ │ │ └── UserConverter.java │ │ │ ├── enums/ │ │ │ │ └── ExternalNotificationTypeEnum.java │ │ │ ├── impl/ │ │ │ │ ├── ChartServiceImpl.java │ │ │ │ ├── ConfigServiceImpl.java │ │ │ │ ├── ConsoleServiceImpl.java │ │ │ │ ├── DashboardServiceImpl.java │ │ │ │ ├── DataSourceAccessBusinessServiceImpl.java │ │ │ │ ├── DataSourceAccessServiceImpl.java │ │ │ │ ├── DataSourceServiceImpl.java │ │ │ │ ├── DatabaseServiceImpl.java │ │ │ │ ├── DlTemplateServiceImpl.java │ │ │ │ ├── EnvironmentServiceImpl.java │ │ │ │ ├── FunctionServiceImpl.java │ │ │ │ ├── JdbcDriverServiceImpl.java │ │ │ │ ├── OperationLogServiceImpl.java │ │ │ │ ├── OperationServiceImpl.java │ │ │ │ ├── PinServiceImpl.java │ │ │ │ ├── ProcedureServiceImpl.java │ │ │ │ ├── SequenceServiceImpl.java │ │ │ │ ├── TableServiceImpl.java │ │ │ │ ├── TaskServiceImpl.java │ │ │ │ ├── TeamServiceImpl.java │ │ │ │ ├── TeamUserServiceImpl.java │ │ │ │ ├── TriggerServiceImpl.java │ │ │ │ ├── UserServiceImpl.java │ │ │ │ └── ViewServiceImpl.java │ │ │ ├── notification/ │ │ │ │ ├── BaseWebhookSender.java │ │ │ │ ├── DingTalkWebhookSender.java │ │ │ │ ├── LarkWebhookSender.java │ │ │ │ └── WeComWebhookSender.java │ │ │ └── util/ │ │ │ ├── DesUtil.java │ │ │ ├── H2Functions.java │ │ │ ├── H2Triggers.java │ │ │ ├── MetaNameUtils.java │ │ │ └── PermissionUtils.java │ │ ├── chat2db-server-domain-repository/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── server/ │ │ │ │ └── domain/ │ │ │ │ └── repository/ │ │ │ │ ├── Dbutils.java │ │ │ │ ├── MapperUtils.java │ │ │ │ ├── entity/ │ │ │ │ │ ├── ChartDO.java │ │ │ │ │ ├── DashboardChartRelationDO.java │ │ │ │ │ ├── DashboardDO.java │ │ │ │ │ ├── DataSourceAccessDO.java │ │ │ │ │ ├── DataSourceDO.java │ │ │ │ │ ├── DbhubUserDO.java │ │ │ │ │ ├── EnvironmentDO.java │ │ │ │ │ ├── JdbcDriverDO.java │ │ │ │ │ ├── OperationLogDO.java │ │ │ │ │ ├── OperationSavedDO.java │ │ │ │ │ ├── PinTableDO.java │ │ │ │ │ ├── SystemConfigDO.java │ │ │ │ │ ├── TableCacheDO.java │ │ │ │ │ ├── TableCacheVersionDO.java │ │ │ │ │ ├── TableVectorMappingDO.java │ │ │ │ │ ├── TaskDO.java │ │ │ │ │ ├── TeamDO.java │ │ │ │ │ └── TeamUserDO.java │ │ │ │ └── mapper/ │ │ │ │ ├── ChartMapper.java │ │ │ │ ├── DashboardChartRelationMapper.java │ │ │ │ ├── DashboardMapper.java │ │ │ │ ├── DataSourceAccessCustomMapper.java │ │ │ │ ├── DataSourceAccessMapper.java │ │ │ │ ├── DataSourceCustomMapper.java │ │ │ │ ├── DataSourceMapper.java │ │ │ │ ├── DbhubUserMapper.java │ │ │ │ ├── EnvironmentMapper.java │ │ │ │ ├── JdbcDriverMapper.java │ │ │ │ ├── OperationLogMapper.java │ │ │ │ ├── OperationSavedMapper.java │ │ │ │ ├── PinTableMapper.java │ │ │ │ ├── SystemConfigMapper.java │ │ │ │ ├── TableCacheMapper.java │ │ │ │ ├── TableCacheVersionMapper.java │ │ │ │ ├── TableVectorMappingMapper.java │ │ │ │ ├── TaskMapper.java │ │ │ │ ├── TeamMapper.java │ │ │ │ ├── TeamUserCustomMapper.java │ │ │ │ └── TeamUserMapper.java │ │ │ └── resources/ │ │ │ ├── db/ │ │ │ │ ├── migration/ │ │ │ │ │ ├── V1_0_0__初始化信息.sql │ │ │ │ │ ├── V1_0_2__修改Console.sql │ │ │ │ │ ├── V1_0_3__增加SSH.sql │ │ │ │ │ ├── V1_0_4__增加报表.sql │ │ │ │ │ ├── V1_0_5__增加置顶表.sql │ │ │ │ │ ├── V1_0_6__初始化demo信息.sql │ │ │ │ │ ├── V1_0_7__自定义驱动.sql │ │ │ │ │ ├── V1_0_8__操作保存类型.sql │ │ │ │ │ ├── V2_1_0__支持环境、用户权限.sql │ │ │ │ │ ├── V2_1_10__REMOVEdEMO.sql │ │ │ │ │ ├── V2_1_1__TableCache.sql │ │ │ │ │ ├── V2_1_2__OPERATION.sql │ │ │ │ │ ├── V2_1_4__OPERATION.sql │ │ │ │ │ ├── V2_1_5__TableVector.sql │ │ │ │ │ ├── V2_1_6__TableVectorUpdate.sql │ │ │ │ │ ├── V2_1_7__DATASOURCE.sql │ │ │ │ │ ├── V2_1_8__Chart.sql │ │ │ │ │ └── V2_1_9__task.sql │ │ │ │ └── temp/ │ │ │ │ └── V2_1_0__补充.sql │ │ │ └── mapper/ │ │ │ ├── ChartMapper.xml │ │ │ ├── DashboardChartRelationMapper.xml │ │ │ ├── DashboardMapper.xml │ │ │ ├── DataSourceAccessCustomMapper.xml │ │ │ ├── DataSourceAccessMapper.xml │ │ │ ├── DataSourceCustomMapper.xml │ │ │ ├── DataSourceMapper.xml │ │ │ ├── DbhubUserMapper.xml │ │ │ ├── EnvironmentMapper.xml │ │ │ ├── JdbcDriverMapper.xml │ │ │ ├── OperationLogMapper.xml │ │ │ ├── OperationSavedMapper.xml │ │ │ ├── PinTableMapper.xml │ │ │ ├── SystemConfigMapper.xml │ │ │ ├── TableCacheMapper.xml │ │ │ ├── TableCacheVersionMapper.xml │ │ │ ├── TableVectorMappingMapper.xml │ │ │ ├── TaskMapper.xml │ │ │ ├── TeamMapper.xml │ │ │ ├── TeamUserCustomMapper.xml │ │ │ └── TeamUserMapper.xml │ │ └── pom.xml │ ├── chat2db-server-start/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── server/ │ │ │ │ └── start/ │ │ │ │ ├── Application.java │ │ │ │ ├── config/ │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── AsyncContextRefreshedListener.java │ │ │ │ │ │ ├── Chat2dbForestConfiguration.java │ │ │ │ │ │ ├── Chat2dbWebMvcConfigurer.java │ │ │ │ │ │ ├── JarDownloadTask.java │ │ │ │ │ │ └── WebLogConfiguration.java │ │ │ │ │ ├── i18n/ │ │ │ │ │ │ └── I18nConfig.java │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ └── CorsFilter.java │ │ │ │ │ ├── listener/ │ │ │ │ │ │ ├── DbhubTomcatConnectorCustomizer.java │ │ │ │ │ │ ├── FailedEventApplicationListener.java │ │ │ │ │ │ ├── ManageApplicationListener.java │ │ │ │ │ │ └── manage/ │ │ │ │ │ │ ├── ManageMessage.java │ │ │ │ │ │ └── MessageTypeEnum.java │ │ │ │ │ └── mybatis/ │ │ │ │ │ └── MyBatisPlusConfig.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── oauth/ │ │ │ │ │ │ ├── OauthController.java │ │ │ │ │ │ └── request/ │ │ │ │ │ │ └── LoginRequest.java │ │ │ │ │ └── thymeleaf/ │ │ │ │ │ └── ThymeleafController.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── EasyControllerExceptionHandler.java │ │ │ │ │ └── convertor/ │ │ │ │ │ ├── BindExceptionConvertor.java │ │ │ │ │ ├── BusinessExceptionConvertor.java │ │ │ │ │ ├── DefaultExceptionConvertor.java │ │ │ │ │ ├── ExceptionConvertor.java │ │ │ │ │ ├── ExceptionConvertorUtils.java │ │ │ │ │ ├── MaxUploadSizeExceededExceptionConvertor.java │ │ │ │ │ ├── MethodArgumentNotValidExceptionConvertor.java │ │ │ │ │ ├── MethodArgumentTypeMismatchExceptionConvertor.java │ │ │ │ │ └── ParamExceptionConvertor.java │ │ │ │ └── log/ │ │ │ │ ├── EasyLogSink.java │ │ │ │ ├── LogOncePerRequestFilter.java │ │ │ │ └── WebLog.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring.factories │ │ │ ├── application-dev.yml │ │ │ ├── application-release.yml │ │ │ ├── application-test.yml │ │ │ ├── application.yml │ │ │ ├── i18n/ │ │ │ │ ├── messages.properties │ │ │ │ ├── messages_en_US.properties │ │ │ │ └── messages_zh_CN.properties │ │ │ ├── logback-spring.xml │ │ │ └── thymeleaf/ │ │ │ └── template.html │ │ └── test/ │ │ ├── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── start/ │ │ │ └── test/ │ │ │ ├── TestApplication.java │ │ │ ├── common/ │ │ │ │ └── BaseTest.java │ │ │ ├── core/ │ │ │ │ ├── ChartServiceTest.java │ │ │ │ ├── ConfigServiceTest.java │ │ │ │ ├── ConsoleServiceTest.java │ │ │ │ ├── DashboardServiceTest.java │ │ │ │ ├── DataSourceAccessBusinessServiceTest.java │ │ │ │ ├── DataSourceAccessServiceTest.java │ │ │ │ ├── DataSourceServiceTest.java │ │ │ │ ├── DatabaseServiceTest.java │ │ │ │ ├── DlTemplateServiceTest.java │ │ │ │ ├── EnvironmentServiceTest.java │ │ │ │ ├── FunctionServiceTest.java │ │ │ │ ├── JdbcDriverServiceTest.java │ │ │ │ ├── OperationLogServiceTest.java │ │ │ │ ├── OperationServiceTest.java │ │ │ │ ├── PinServiceTest.java │ │ │ │ ├── ProcedureServiceTest.java │ │ │ │ ├── TableServiceTest.java │ │ │ │ ├── TaskServiceTest.java │ │ │ │ ├── TeamServiceTest.java │ │ │ │ ├── TeamUserServiceTest.java │ │ │ │ ├── TriggerServiceTest.java │ │ │ │ ├── UserServiceTest.java │ │ │ │ ├── ViewServiceTest.java │ │ │ │ └── WebhookServiceTest.java │ │ │ ├── dialect/ │ │ │ │ ├── DialectProperties.java │ │ │ │ ├── MariadbDialectProperties.java │ │ │ │ ├── MongodbDialectProperties.java │ │ │ │ ├── MysqlDialectProperties.java │ │ │ │ ├── OracleDialectProperties.java │ │ │ │ ├── PostgresqlDialectProperties.java │ │ │ │ └── TestUtils.java │ │ │ ├── druid/ │ │ │ │ ├── SerializationUtilsTest.java │ │ │ │ ├── SqlUtilsTest.java │ │ │ │ └── SqlUtilsTest2.java │ │ │ ├── dto/ │ │ │ │ └── TestDTO.java │ │ │ ├── mybatis/ │ │ │ │ └── MybatisGeneratorTest.java │ │ │ └── sql/ │ │ │ └── DbhubJdbcTemplateTest.java │ │ └── resources/ │ │ └── logback-test-spring.xml │ ├── chat2db-server-test/ │ │ ├── pom.xml │ │ └── src/ │ │ └── test/ │ │ ├── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── test/ │ │ │ ├── common/ │ │ │ │ └── BaseTest.java │ │ │ ├── domain/ │ │ │ │ └── data/ │ │ │ │ ├── service/ │ │ │ │ │ ├── ConfigServiceTest.java │ │ │ │ │ ├── ConsoleOperationsTest.java │ │ │ │ │ ├── DatabaseOperationsTest.java │ │ │ │ │ ├── ExampleOperationsTest.java │ │ │ │ │ ├── JdbcOperationsTest.java │ │ │ │ │ ├── SQLExecutorOperationsTest.java │ │ │ │ │ ├── TableOperationsTest.java │ │ │ │ │ └── dialect/ │ │ │ │ │ ├── ClickHouseDialectProperties.java │ │ │ │ │ ├── DialectProperties.java │ │ │ │ │ ├── H2DialectProperties.java │ │ │ │ │ ├── MysqlDialectProperties.java │ │ │ │ │ ├── OracleDialectProperties.java │ │ │ │ │ ├── PostgresqlDialectProperties.java │ │ │ │ │ ├── SQLITEDialectProperties.java │ │ │ │ │ └── SQLServerDialectProperties.java │ │ │ │ └── utils/ │ │ │ │ └── TestUtils.java │ │ │ └── temp/ │ │ │ ├── HttpTest.java │ │ │ ├── SQLParseTest.java │ │ │ ├── SqlTest.java │ │ │ ├── TempTest.java │ │ │ └── UserTest.java │ │ └── resources/ │ │ ├── h2/ │ │ │ ├── init.sql │ │ │ ├── init_close.sql │ │ │ └── init_transaction.sql │ │ └── logback-test-spring.xml │ ├── chat2db-server-tools/ │ │ ├── chat2db-server-tools-base/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── tools/ │ │ │ └── base/ │ │ │ ├── constant/ │ │ │ │ ├── EasyToolsConstant.java │ │ │ │ └── SymbolConstant.java │ │ │ ├── enums/ │ │ │ │ ├── BaseEnum.java │ │ │ │ ├── DataSourceTypeEnum.java │ │ │ │ ├── DeletedIdEnum.java │ │ │ │ ├── OperationEnum.java │ │ │ │ ├── OrderByDirectionEnum.java │ │ │ │ ├── StatusEnum.java │ │ │ │ ├── SystemEnvironmentEnum.java │ │ │ │ ├── WhiteListTypeEnum.java │ │ │ │ └── YesOrNoEnum.java │ │ │ ├── excption/ │ │ │ │ ├── BusinessException.java │ │ │ │ └── SystemException.java │ │ │ ├── handler/ │ │ │ │ └── EasyCallBackHandler.java │ │ │ └── wrapper/ │ │ │ ├── Result.java │ │ │ ├── Traceable.java │ │ │ ├── param/ │ │ │ │ ├── OrderBy.java │ │ │ │ ├── OrderCondition.java │ │ │ │ ├── PageQueryParam.java │ │ │ │ └── QueryParam.java │ │ │ ├── request/ │ │ │ │ └── PageQueryRequest.java │ │ │ └── result/ │ │ │ ├── ActionResult.java │ │ │ ├── DataResult.java │ │ │ ├── ListResult.java │ │ │ ├── PageResult.java │ │ │ └── web/ │ │ │ └── WebPageResult.java │ │ ├── chat2db-server-tools-common/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── tools/ │ │ │ └── common/ │ │ │ ├── config/ │ │ │ │ ├── Chat2dbProperties.java │ │ │ │ └── GlobalDict.java │ │ │ ├── enums/ │ │ │ │ └── ModeEnum.java │ │ │ ├── exception/ │ │ │ │ ├── ConnectionException.java │ │ │ │ ├── DataAlreadyExistsBusinessException.java │ │ │ │ ├── DataNotFoundException.java │ │ │ │ ├── NeedLoggedInBusinessException.java │ │ │ │ ├── ParamBusinessException.java │ │ │ │ ├── PermissionDeniedBusinessException.java │ │ │ │ └── RedirectBusinessException.java │ │ │ ├── model/ │ │ │ │ ├── ConfigJson.java │ │ │ │ ├── Context.java │ │ │ │ ├── EasyLambdaQueryWrapper.java │ │ │ │ ├── IntegerWrapper.java │ │ │ │ └── LoginUser.java │ │ │ └── util/ │ │ │ ├── ConfigUtils.java │ │ │ ├── ContextUtils.java │ │ │ ├── EasyBooleanUtils.java │ │ │ ├── EasyCollectionUtils.java │ │ │ ├── EasyEnumUtils.java │ │ │ ├── EasyIntegerUtils.java │ │ │ ├── EasyOptionalUtils.java │ │ │ ├── EasySqlUtils.java │ │ │ ├── EasyStringUtils.java │ │ │ ├── I18nUtils.java │ │ │ └── LogUtils.java │ │ └── pom.xml │ ├── chat2db-server-web/ │ │ ├── chat2db-server-admin-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── admin/ │ │ │ └── api/ │ │ │ └── controller/ │ │ │ ├── common/ │ │ │ │ ├── CommonAdminController.java │ │ │ │ ├── converter/ │ │ │ │ │ └── CommonAdminConverter.java │ │ │ │ ├── request/ │ │ │ │ │ └── TeamUserPageQueryRequest.java │ │ │ │ └── vo/ │ │ │ │ └── TeamUserListVO.java │ │ │ ├── datasource/ │ │ │ │ ├── DataSourceAccessAdminController.java │ │ │ │ ├── DataSourceAdminController.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── DataSourceAccessAdminConverter.java │ │ │ │ │ └── DataSourceAdminConverter.java │ │ │ │ ├── request/ │ │ │ │ │ ├── DataSourceAccessBatchCreateRequest.java │ │ │ │ │ ├── DataSourceAccessObjectRequest.java │ │ │ │ │ ├── DataSourceAccessPageQueryRequest.java │ │ │ │ │ ├── DataSourceCloneRequest.java │ │ │ │ │ ├── DataSourceCreateRequest.java │ │ │ │ │ └── DataSourceUpdateRequest.java │ │ │ │ └── vo/ │ │ │ │ ├── DataSourceAccessObjectVO.java │ │ │ │ ├── DataSourceAccessPageQueryVO.java │ │ │ │ ├── DataSourcePageQueryVO.java │ │ │ │ └── SimpleDataSourceVO.java │ │ │ ├── team/ │ │ │ │ ├── TeamAdminController.java │ │ │ │ ├── TeamDataSourceAdminController.java │ │ │ │ ├── TeamUserAdminController.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── TeamAdminConverter.java │ │ │ │ │ ├── TeamDataSourcesAdminConverter.java │ │ │ │ │ └── TeamUserAdminConverter.java │ │ │ │ ├── request/ │ │ │ │ │ ├── TeamCreateRequest.java │ │ │ │ │ ├── TeamDataSourceBatchCreateRequest.java │ │ │ │ │ ├── TeamPageCommonQueryRequest.java │ │ │ │ │ ├── TeamUpdateRequest.java │ │ │ │ │ └── TeamUserBatchCreateRequest.java │ │ │ │ └── vo/ │ │ │ │ ├── SimpleTeamVO.java │ │ │ │ ├── TeamDataSourcePageQueryVO.java │ │ │ │ ├── TeamPageQueryVO.java │ │ │ │ └── TeamUserPageQueryVO.java │ │ │ └── user/ │ │ │ ├── UserAdminController.java │ │ │ ├── UserDataSourceAdminController.java │ │ │ ├── UserTeamAdminController.java │ │ │ ├── converter/ │ │ │ │ ├── UserAdminConverter.java │ │ │ │ ├── UserDataSourcesAdminConverter.java │ │ │ │ └── UserTeamAdminConverter.java │ │ │ ├── request/ │ │ │ │ ├── UserCreateRequest.java │ │ │ │ ├── UserDataSourceBatchCreateRequest.java │ │ │ │ ├── UserPageCommonQueryRequest.java │ │ │ │ ├── UserTeamBatchCreateRequest.java │ │ │ │ └── UserUpdateRequest.java │ │ │ └── vo/ │ │ │ ├── SimpleUserVO.java │ │ │ ├── UserDataSourcePageQueryVO.java │ │ │ ├── UserPageQueryVO.java │ │ │ └── UserTeamPageQueryVO.java │ │ ├── chat2db-server-common-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── common/ │ │ │ └── api/ │ │ │ └── controller/ │ │ │ ├── CommonCommonController.java │ │ │ ├── converter/ │ │ │ │ └── EnvironmentCommonConverter.java │ │ │ ├── request/ │ │ │ │ ├── CommonPageQueryRequest.java │ │ │ │ └── CommonQueryRequest.java │ │ │ └── vo/ │ │ │ ├── SimpleEnvironmentVO.java │ │ │ └── SimpleUserVO.java │ │ ├── chat2db-server-web-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── ai/ │ │ │ │ └── chat2db/ │ │ │ │ └── server/ │ │ │ │ └── web/ │ │ │ │ └── api/ │ │ │ │ ├── aspect/ │ │ │ │ │ ├── ConnectionInfoAspect.java │ │ │ │ │ └── ConnectionInfoHandler.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── PageController.java │ │ │ │ │ ├── ai/ │ │ │ │ │ │ ├── AiConfigController.java │ │ │ │ │ │ ├── ChatController.java │ │ │ │ │ │ ├── DocParser/ │ │ │ │ │ │ │ ├── AbstractParser.java │ │ │ │ │ │ │ └── PdfParse.java │ │ │ │ │ │ ├── EmbeddingController.java │ │ │ │ │ │ ├── KnowledgeController.java │ │ │ │ │ │ ├── TextGenerationController.java │ │ │ │ │ │ ├── azure/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── AzureOpenAIClient.java │ │ │ │ │ │ │ │ └── AzureOpenAiStreamClient.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── AzureHeaderAuthorizationInterceptor.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── AzureOpenAIEventSourceListener.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── AzureChatChoice.java │ │ │ │ │ │ │ │ ├── AzureChatCompletions.java │ │ │ │ │ │ │ │ ├── AzureChatCompletionsOptions.java │ │ │ │ │ │ │ │ ├── AzureChatMessage.java │ │ │ │ │ │ │ │ ├── AzureChatRole.java │ │ │ │ │ │ │ │ ├── AzureChoice.java │ │ │ │ │ │ │ │ ├── AzureCompletions.java │ │ │ │ │ │ │ │ ├── AzureCompletionsFinishReason.java │ │ │ │ │ │ │ │ ├── AzureCompletionsLogProbabilityModel.java │ │ │ │ │ │ │ │ ├── AzureCompletionsUsage.java │ │ │ │ │ │ │ │ └── AzureExpandableStringEnum.java │ │ │ │ │ │ │ └── util/ │ │ │ │ │ │ │ └── AzureReflectionUtils.java │ │ │ │ │ │ ├── baichuan/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── BaichuanAIClient.java │ │ │ │ │ │ │ │ └── BaichuanAIStreamClient.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── BaichuanHeaderAuthorizationInterceptor.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── BaichuanChatAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ ├── BaichuanChatCompletions.java │ │ │ │ │ │ │ ├── BaichuanChatCompletionsOptions.java │ │ │ │ │ │ │ ├── BaichuanChatCompletionsUsage.java │ │ │ │ │ │ │ ├── BaichuanChatData.java │ │ │ │ │ │ │ └── BaichuanChatMessage.java │ │ │ │ │ │ ├── chat2db/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── Chat2DBAIStreamClient.java │ │ │ │ │ │ │ │ └── Chat2dbAIClient.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── Chat2dbHeaderAuthorizationInterceptor.java │ │ │ │ │ │ │ └── listener/ │ │ │ │ │ │ │ └── Chat2dbAIEventSourceListener.java │ │ │ │ │ │ ├── claude/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── ClaudeAIClient.java │ │ │ │ │ │ │ │ └── ClaudeAiStreamClient.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── ClaudeHeaderAuthorizationInterceptor.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── ClaudeAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ ├── ClaudeChatCompletionsOptions.java │ │ │ │ │ │ │ ├── ClaudeChatMessage.java │ │ │ │ │ │ │ ├── ClaudeCompletionResponse.java │ │ │ │ │ │ │ └── ClaudeMessageLimit.java │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── LocalCache.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ └── ChatConverter.java │ │ │ │ │ │ ├── enums/ │ │ │ │ │ │ │ ├── GptVersionType.java │ │ │ │ │ │ │ └── PromptType.java │ │ │ │ │ │ ├── fastchat/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── FastChatAIClient.java │ │ │ │ │ │ │ │ ├── FastChatAIStreamClient.java │ │ │ │ │ │ │ │ └── FastChatOpenAiApi.java │ │ │ │ │ │ │ ├── embeddings/ │ │ │ │ │ │ │ │ ├── FastChatEmbedding.java │ │ │ │ │ │ │ │ ├── FastChatEmbeddingResponse.java │ │ │ │ │ │ │ │ └── FastChatItem.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── FastChatHeaderAuthorizationInterceptor.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── FastChatAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ ├── FastChatChoice.java │ │ │ │ │ │ │ ├── FastChatCompletions.java │ │ │ │ │ │ │ ├── FastChatCompletionsFinishReason.java │ │ │ │ │ │ │ ├── FastChatCompletionsOptions.java │ │ │ │ │ │ │ ├── FastChatCompletionsUsage.java │ │ │ │ │ │ │ ├── FastChatExpandableStringEnum.java │ │ │ │ │ │ │ ├── FastChatMessage.java │ │ │ │ │ │ │ └── FastChatRole.java │ │ │ │ │ │ ├── openai/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ └── OpenAIClient.java │ │ │ │ │ │ │ └── listener/ │ │ │ │ │ │ │ └── OpenAIEventSourceListener.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── ChatQueryRequest.java │ │ │ │ │ │ │ └── ChatRequest.java │ │ │ │ │ │ ├── response/ │ │ │ │ │ │ │ ├── ChatChoice.java │ │ │ │ │ │ │ └── ChatCompletionResponse.java │ │ │ │ │ │ ├── rest/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── RestAIClient.java │ │ │ │ │ │ │ │ └── RestAIStreamClient.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── RestAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ ├── RestAIChatCompletions.java │ │ │ │ │ │ │ └── RestAiCompletion.java │ │ │ │ │ │ ├── tongyi/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── TongyiChatAIClient.java │ │ │ │ │ │ │ │ └── TongyiChatAIStreamClient.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── TongyiChatAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ ├── TongyiChatCompletions.java │ │ │ │ │ │ │ ├── TongyiChatCompletionsOptions.java │ │ │ │ │ │ │ ├── TongyiChatCompletionsUsage.java │ │ │ │ │ │ │ ├── TongyiChatMessage.java │ │ │ │ │ │ │ └── TongyiChatOutput.java │ │ │ │ │ │ ├── wenxin/ │ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ │ ├── WenxinAIClient.java │ │ │ │ │ │ │ │ └── WenxinAIStreamClient.java │ │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ │ └── AccessTokenInterceptor.java │ │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ │ └── WenxinAIEventSourceListener.java │ │ │ │ │ │ │ └── model/ │ │ │ │ │ │ │ └── WenxinChatCompletions.java │ │ │ │ │ │ └── zhipu/ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── ZhipuChatAIClient.java │ │ │ │ │ │ │ └── ZhipuChatAIStreamClient.java │ │ │ │ │ │ ├── interceptor/ │ │ │ │ │ │ │ └── ZhipuChatHeaderAuthorizationInterceptor.java │ │ │ │ │ │ ├── listener/ │ │ │ │ │ │ │ └── ZhipuChatAIEventSourceListener.java │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── ZhipuChatBody.java │ │ │ │ │ │ │ ├── ZhipuChatCompletions.java │ │ │ │ │ │ │ └── ZhipuChatCompletionsOptions.java │ │ │ │ │ │ └── util/ │ │ │ │ │ │ └── ZhipuUtils.java │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── ConfigController.java │ │ │ │ │ │ └── request/ │ │ │ │ │ │ ├── AIConfigCreateRequest.java │ │ │ │ │ │ ├── AISystemConfigRequest.java │ │ │ │ │ │ └── SystemConfigRequest.java │ │ │ │ │ ├── dashboard/ │ │ │ │ │ │ ├── ChartController.java │ │ │ │ │ │ ├── DashboardController.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ ├── ChartWebConverter.java │ │ │ │ │ │ │ └── DashboardWebConverter.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── ChartCreateRequest.java │ │ │ │ │ │ │ ├── ChartQueryRequest.java │ │ │ │ │ │ │ ├── ChartUpdateRequest.java │ │ │ │ │ │ │ ├── DashboardCreateRequest.java │ │ │ │ │ │ │ └── DashboardUpdateRequest.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ ├── ChartVO.java │ │ │ │ │ │ └── DashboardVO.java │ │ │ │ │ ├── data/ │ │ │ │ │ │ └── source/ │ │ │ │ │ │ ├── DataSourceController.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ ├── DataSourceWebConverter.java │ │ │ │ │ │ │ └── SSHWebConverter.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── ConsoleCloseRequest.java │ │ │ │ │ │ │ ├── ConsoleConnectRequest.java │ │ │ │ │ │ │ ├── DataSourceAttachRequest.java │ │ │ │ │ │ │ ├── DataSourceBaseRequest.java │ │ │ │ │ │ │ ├── DataSourceBaseRequestInfo.java │ │ │ │ │ │ │ ├── DataSourceCloneRequest.java │ │ │ │ │ │ │ ├── DataSourceCloseRequest.java │ │ │ │ │ │ │ ├── DataSourceConsoleRequestInfo.java │ │ │ │ │ │ │ ├── DataSourceCreateRequest.java │ │ │ │ │ │ │ ├── DataSourceQueryRequest.java │ │ │ │ │ │ │ ├── DataSourceTestRequest.java │ │ │ │ │ │ │ ├── DataSourceUpdateRequest.java │ │ │ │ │ │ │ └── SSHTestRequest.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ ├── DataSourceVO.java │ │ │ │ │ │ ├── DatabaseVO.java │ │ │ │ │ │ └── EnvVO.java │ │ │ │ │ ├── driver/ │ │ │ │ │ │ ├── JdbcDriverController.java │ │ │ │ │ │ └── request/ │ │ │ │ │ │ └── JdbcDriverRequest.java │ │ │ │ │ ├── ncx/ │ │ │ │ │ │ ├── ConverterController.java │ │ │ │ │ │ ├── cipher/ │ │ │ │ │ │ │ ├── CommonCipher.java │ │ │ │ │ │ │ ├── Navicat11Cipher.java │ │ │ │ │ │ │ └── Navicat12Cipher.java │ │ │ │ │ │ ├── dbeaver/ │ │ │ │ │ │ │ ├── DBSValueEncryptor.java │ │ │ │ │ │ │ └── DefaultValueEncryptor.java │ │ │ │ │ │ ├── enums/ │ │ │ │ │ │ │ ├── DataBaseType.java │ │ │ │ │ │ │ ├── ExportConstants.java │ │ │ │ │ │ │ └── VersionEnum.java │ │ │ │ │ │ ├── factory/ │ │ │ │ │ │ │ └── CipherFactory.java │ │ │ │ │ │ ├── service/ │ │ │ │ │ │ │ ├── ConverterService.java │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ └── ConverterServiceImpl.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ └── UploadVO.java │ │ │ │ │ ├── operation/ │ │ │ │ │ │ ├── log/ │ │ │ │ │ │ │ ├── OperationLogController.java │ │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ │ └── OperationLogWebConverter.java │ │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ │ ├── OperationLogCreateRequest.java │ │ │ │ │ │ │ │ └── OperationLogQueryRequest.java │ │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ │ └── OperationLogVO.java │ │ │ │ │ │ └── saved/ │ │ │ │ │ │ ├── OperationSavedController.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ └── OperationWebConverter.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── BatchTabCloseRequest.java │ │ │ │ │ │ │ ├── OperationCreateRequest.java │ │ │ │ │ │ │ ├── OperationQueryRequest.java │ │ │ │ │ │ │ └── OperationUpdateRequest.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ └── OperationVO.java │ │ │ │ │ ├── pin/ │ │ │ │ │ │ ├── PinController.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ └── PinWebConverter.java │ │ │ │ │ │ └── request/ │ │ │ │ │ │ └── PinTableRequest.java │ │ │ │ │ ├── rdb/ │ │ │ │ │ │ ├── DatabaseController.java │ │ │ │ │ │ ├── FunctionController.java │ │ │ │ │ │ ├── ProcedureController.java │ │ │ │ │ │ ├── RdbDdlController.java │ │ │ │ │ │ ├── RdbDmlController.java │ │ │ │ │ │ ├── RdbDmlExportController.java │ │ │ │ │ │ ├── RdbDocController.java │ │ │ │ │ │ ├── SchemaController.java │ │ │ │ │ │ ├── SequenceController.java │ │ │ │ │ │ ├── TableController.java │ │ │ │ │ │ ├── TriggerController.java │ │ │ │ │ │ ├── ViewController.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ ├── DatabaseConverter.java │ │ │ │ │ │ │ ├── FunctionConverter.java │ │ │ │ │ │ │ ├── ProcedureConverter.java │ │ │ │ │ │ │ └── RdbWebConverter.java │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ ├── BaseDataExporter.java │ │ │ │ │ │ │ ├── BaseDataImporter.java │ │ │ │ │ │ │ ├── BaseExcelExporter.java │ │ │ │ │ │ │ ├── BaseExcelImporter.java │ │ │ │ │ │ │ ├── DataExportStrategy.java │ │ │ │ │ │ │ ├── DataImportStrategy.java │ │ │ │ │ │ │ ├── csv/ │ │ │ │ │ │ │ │ ├── CsvDataExporter.java │ │ │ │ │ │ │ │ └── CsvDataImporter.java │ │ │ │ │ │ │ ├── factory/ │ │ │ │ │ │ │ │ ├── DataExportFactory.java │ │ │ │ │ │ │ │ └── DataImportFactory.java │ │ │ │ │ │ │ ├── json/ │ │ │ │ │ │ │ │ ├── JsonDataExporter.java │ │ │ │ │ │ │ │ └── JsonDataImporter.java │ │ │ │ │ │ │ ├── service/ │ │ │ │ │ │ │ │ ├── DatabaseDataService.java │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ └── DatabaseDataImpl.java │ │ │ │ │ │ │ ├── sql/ │ │ │ │ │ │ │ │ └── SqlDataExporter.java │ │ │ │ │ │ │ ├── task/ │ │ │ │ │ │ │ │ ├── TaskManager.java │ │ │ │ │ │ │ │ └── TaskState.java │ │ │ │ │ │ │ ├── xls/ │ │ │ │ │ │ │ │ ├── XlsDataExporter.java │ │ │ │ │ │ │ │ └── XlsDataImporter.java │ │ │ │ │ │ │ └── xlsx/ │ │ │ │ │ │ │ ├── XlsxDataExporter.java │ │ │ │ │ │ │ └── XlsxDataImporter.java │ │ │ │ │ │ ├── doc/ │ │ │ │ │ │ │ ├── DatabaseExportService.java │ │ │ │ │ │ │ ├── adaptive/ │ │ │ │ │ │ │ │ ├── CustomCellWriteHeightConfig.java │ │ │ │ │ │ │ │ └── CustomCellWriteWidthConfig.java │ │ │ │ │ │ │ ├── conf/ │ │ │ │ │ │ │ │ └── ExportOptions.java │ │ │ │ │ │ │ ├── constant/ │ │ │ │ │ │ │ │ ├── CommonConstant.java │ │ │ │ │ │ │ │ └── PatternConstant.java │ │ │ │ │ │ │ ├── event/ │ │ │ │ │ │ │ │ └── TemplateEvent.java │ │ │ │ │ │ │ ├── export/ │ │ │ │ │ │ │ │ ├── ExportExcelService.java │ │ │ │ │ │ │ │ ├── ExportHtmlService.java │ │ │ │ │ │ │ │ ├── ExportMarkdownService.java │ │ │ │ │ │ │ │ ├── ExportPdfService.java │ │ │ │ │ │ │ │ └── ExportWordSuperService.java │ │ │ │ │ │ │ ├── merge/ │ │ │ │ │ │ │ │ └── MyMergeExcel.java │ │ │ │ │ │ │ └── style/ │ │ │ │ │ │ │ └── CustomExcelStyle.java │ │ │ │ │ │ ├── factory/ │ │ │ │ │ │ │ └── ExportServiceFactory.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── ColumnRequest.java │ │ │ │ │ │ │ ├── DataExportRequest.java │ │ │ │ │ │ │ ├── DatabaseCreateRequest.java │ │ │ │ │ │ │ ├── DatabaseExportDataRequest.java │ │ │ │ │ │ │ ├── DatabaseExportRequest.java │ │ │ │ │ │ │ ├── DdlCountRequest.java │ │ │ │ │ │ │ ├── DdlExportRequest.java │ │ │ │ │ │ │ ├── DdlRequest.java │ │ │ │ │ │ │ ├── DmlRequest.java │ │ │ │ │ │ │ ├── DmlSqlCopyRequest.java │ │ │ │ │ │ │ ├── DmlTableRequest.java │ │ │ │ │ │ │ ├── FunctionDetailRequest.java │ │ │ │ │ │ │ ├── FunctionPageRequest.java │ │ │ │ │ │ │ ├── FunctionUpdateRequest.java │ │ │ │ │ │ │ ├── GroupByRequest.java │ │ │ │ │ │ │ ├── IndexRequest.java │ │ │ │ │ │ │ ├── NewTableSqlRequest.java │ │ │ │ │ │ │ ├── OrderByRequest.java │ │ │ │ │ │ │ ├── ProcedureDetailRequest.java │ │ │ │ │ │ │ ├── ProcedurePageRequest.java │ │ │ │ │ │ │ ├── ProcedureUpdateRequest.java │ │ │ │ │ │ │ ├── SchemaCreateRequest.java │ │ │ │ │ │ │ ├── SchemaQueryRequest.java │ │ │ │ │ │ │ ├── SelectResultUpdateRequest.java │ │ │ │ │ │ │ ├── SequenceBriefQueryRequest.java │ │ │ │ │ │ │ ├── SequenceDeleteRequest.java │ │ │ │ │ │ │ ├── SequenceDetailQueryRequest.java │ │ │ │ │ │ │ ├── SequenceModifySqlRequest.java │ │ │ │ │ │ │ ├── SequenceRequest.java │ │ │ │ │ │ │ ├── TableBriefQueryRequest.java │ │ │ │ │ │ │ ├── TableCreateDdlQueryRequest.java │ │ │ │ │ │ │ ├── TableDeleteRequest.java │ │ │ │ │ │ │ ├── TableDetailQueryRequest.java │ │ │ │ │ │ │ ├── TableMilvusQueryRequest.java │ │ │ │ │ │ │ ├── TableModifySqlRequest.java │ │ │ │ │ │ │ ├── TableQueryRequest.java │ │ │ │ │ │ │ ├── TableRequest.java │ │ │ │ │ │ │ ├── TableUpdateDdlQueryRequest.java │ │ │ │ │ │ │ ├── TriggerDetailRequest.java │ │ │ │ │ │ │ ├── TriggerPageRequest.java │ │ │ │ │ │ │ ├── TypeQueryRequest.java │ │ │ │ │ │ │ ├── UpdateDatabaseRequest.java │ │ │ │ │ │ │ └── UpdateSchemaRequest.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ ├── ColumnVO.java │ │ │ │ │ │ ├── ExecuteResultVO.java │ │ │ │ │ │ ├── IndexColumnVO.java │ │ │ │ │ │ ├── IndexVO.java │ │ │ │ │ │ ├── KeyVO.java │ │ │ │ │ │ ├── MetaSchemaVO.java │ │ │ │ │ │ ├── SchemaVO.java │ │ │ │ │ │ ├── SequenceVO.java │ │ │ │ │ │ ├── SqlVO.java │ │ │ │ │ │ ├── TableVO.java │ │ │ │ │ │ └── ViewVO.java │ │ │ │ │ ├── redis/ │ │ │ │ │ │ ├── RedisKeyManageController.java │ │ │ │ │ │ ├── RedisKeyValueManageController.java │ │ │ │ │ │ ├── request/ │ │ │ │ │ │ │ ├── KeyCreateRequest.java │ │ │ │ │ │ │ ├── KeyDeleteRequest.java │ │ │ │ │ │ │ ├── KeyQueryRequest.java │ │ │ │ │ │ │ ├── KeyUpdateRequest.java │ │ │ │ │ │ │ ├── KeyValueManageRequest.java │ │ │ │ │ │ │ └── ValueUpdateRequest.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ └── KeyVO.java │ │ │ │ │ ├── sql/ │ │ │ │ │ │ ├── SqlController.java │ │ │ │ │ │ └── request/ │ │ │ │ │ │ └── SqlFormatRequest.java │ │ │ │ │ ├── system/ │ │ │ │ │ │ ├── SystemController.java │ │ │ │ │ │ ├── util/ │ │ │ │ │ │ │ └── SystemUtils.java │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ ├── AppVersionVO.java │ │ │ │ │ │ └── SystemVO.java │ │ │ │ │ ├── task/ │ │ │ │ │ │ ├── ExportController.java │ │ │ │ │ │ ├── TaskController.java │ │ │ │ │ │ └── biz/ │ │ │ │ │ │ └── TaskBizService.java │ │ │ │ │ └── user/ │ │ │ │ │ ├── UserController.java │ │ │ │ │ ├── converter/ │ │ │ │ │ │ └── UserWebConverter.java │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── UserCreateRequest.java │ │ │ │ │ │ ├── UserQueryRequest.java │ │ │ │ │ │ └── UserUpdateRequest.java │ │ │ │ │ └── vo/ │ │ │ │ │ └── UserVO.java │ │ │ │ ├── http/ │ │ │ │ │ ├── GatewayClientService.java │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── EsTableSchema.java │ │ │ │ │ │ ├── Knowledge.java │ │ │ │ │ │ └── TableSchema.java │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── EsTableSchemaRequest.java │ │ │ │ │ │ ├── KnowledgeRequest.java │ │ │ │ │ │ ├── SqlExecuteHistoryCreateRequest.java │ │ │ │ │ │ ├── TableSchemaRequest.java │ │ │ │ │ │ └── WhiteListRequest.java │ │ │ │ │ └── response/ │ │ │ │ │ ├── ApiKeyResponse.java │ │ │ │ │ ├── EsTableSchemaResponse.java │ │ │ │ │ ├── InviteQrCodeResponse.java │ │ │ │ │ ├── KnowledgeResponse.java │ │ │ │ │ ├── QrCodeResponse.java │ │ │ │ │ └── TableSchemaResponse.java │ │ │ │ ├── util/ │ │ │ │ │ ├── AddToTopic.java │ │ │ │ │ ├── ApplicationContextUtil.java │ │ │ │ │ ├── FileUtils.java │ │ │ │ │ ├── StringUtils.java │ │ │ │ │ └── XMLUtils.java │ │ │ │ └── ws/ │ │ │ │ ├── WsConfig.java │ │ │ │ ├── WsMessage.java │ │ │ │ ├── WsResult.java │ │ │ │ ├── WsServer.java │ │ │ │ └── WsService.java │ │ │ └── resources/ │ │ │ └── template/ │ │ │ ├── sub_template_diy.docx │ │ │ ├── template.html │ │ │ └── template_diy.docx │ │ └── pom.xml │ ├── chat2db-server-web-start/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── ai/ │ │ │ └── chat2db/ │ │ │ └── server/ │ │ │ └── web/ │ │ │ └── start/ │ │ │ ├── Application.java │ │ │ ├── config/ │ │ │ │ ├── StdinReader.java │ │ │ │ ├── config/ │ │ │ │ │ ├── AsyncContextRefreshedListener.java │ │ │ │ │ ├── Chat2dbForestConfiguration.java │ │ │ │ │ ├── Chat2dbWebMvcConfigurer.java │ │ │ │ │ ├── JarDownloadTask.java │ │ │ │ │ └── WebLogConfiguration.java │ │ │ │ ├── i18n/ │ │ │ │ │ └── I18nConfig.java │ │ │ │ ├── interceptor/ │ │ │ │ │ └── CorsFilter.java │ │ │ │ ├── listener/ │ │ │ │ │ ├── DbhubTomcatConnectorCustomizer.java │ │ │ │ │ ├── FailedEventApplicationListener.java │ │ │ │ │ ├── ManageApplicationListener.java │ │ │ │ │ └── manage/ │ │ │ │ │ ├── ManageMessage.java │ │ │ │ │ └── MessageTypeEnum.java │ │ │ │ ├── mybatis/ │ │ │ │ │ └── MyBatisPlusConfig.java │ │ │ │ └── oauth/ │ │ │ │ ├── SaLogForSlf4j.java │ │ │ │ └── SaTokenConfigure.java │ │ │ ├── controller/ │ │ │ │ ├── oauth/ │ │ │ │ │ ├── OauthController.java │ │ │ │ │ └── request/ │ │ │ │ │ └── LoginRequest.java │ │ │ │ └── thymeleaf/ │ │ │ │ └── ThymeleafController.java │ │ │ ├── exception/ │ │ │ │ ├── EasyControllerExceptionHandler.java │ │ │ │ └── convertor/ │ │ │ │ ├── BindExceptionConvertor.java │ │ │ │ ├── BusinessExceptionConvertor.java │ │ │ │ ├── DefaultExceptionConvertor.java │ │ │ │ ├── ExceptionConvertor.java │ │ │ │ ├── ExceptionConvertorUtils.java │ │ │ │ ├── MaxUploadSizeExceededExceptionConvertor.java │ │ │ │ ├── MethodArgumentNotValidExceptionConvertor.java │ │ │ │ ├── MethodArgumentTypeMismatchExceptionConvertor.java │ │ │ │ └── ParamExceptionConvertor.java │ │ │ └── log/ │ │ │ ├── EasyLogSink.java │ │ │ ├── LogOncePerRequestFilter.java │ │ │ └── WebLog.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring.factories │ │ ├── application-dev.yml │ │ ├── application-release.yml │ │ ├── application-test.yml │ │ ├── application.yml │ │ ├── i18n/ │ │ │ ├── messages.properties │ │ │ ├── messages_en_US.properties │ │ │ └── messages_zh_CN.properties │ │ ├── logback-spring.xml │ │ └── thymeleaf/ │ │ └── template.html │ ├── chat2db-spi/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── ai/ │ │ └── chat2db/ │ │ └── spi/ │ │ ├── ColumnBuilder.java │ │ ├── CommandExecutor.java │ │ ├── DBManage.java │ │ ├── MetaData.java │ │ ├── Plugin.java │ │ ├── SqlBuilder.java │ │ ├── ValueHandler.java │ │ ├── ValueProcessor.java │ │ ├── config/ │ │ │ ├── DBConfig.java │ │ │ └── DriverConfig.java │ │ ├── enums/ │ │ │ ├── CellTypeEnum.java │ │ │ ├── CollationEnum.java │ │ │ ├── DataTypeEnum.java │ │ │ ├── DmlType.java │ │ │ ├── EditStatus.java │ │ │ ├── IndexTypeEnum.java │ │ │ └── SqlTypeEnum.java │ │ ├── jdbc/ │ │ │ ├── BaseValueProcessor.java │ │ │ ├── DefaultDBManage.java │ │ │ ├── DefaultMetaService.java │ │ │ ├── DefaultSqlBuilder.java │ │ │ ├── DefaultValueHandler.java │ │ │ └── DefaultValueProcessor.java │ │ ├── model/ │ │ │ ├── AsyncCall.java │ │ │ ├── AsyncContext.java │ │ │ ├── Cell.java │ │ │ ├── Charset.java │ │ │ ├── Collation.java │ │ │ ├── ColumnType.java │ │ │ ├── Command.java │ │ │ ├── CreateTableSql.java │ │ │ ├── DataSourceConnect.java │ │ │ ├── DataType.java │ │ │ ├── Database.java │ │ │ ├── DefaultValue.java │ │ │ ├── DriverEntry.java │ │ │ ├── EngineType.java │ │ │ ├── ExecuteResult.java │ │ │ ├── Function.java │ │ │ ├── Header.java │ │ │ ├── IndexType.java │ │ │ ├── JDBCDataValue.java │ │ │ ├── KeyValue.java │ │ │ ├── MetaSchema.java │ │ │ ├── OrderBy.java │ │ │ ├── Procedure.java │ │ │ ├── QueryResult.java │ │ │ ├── ResultOperation.java │ │ │ ├── SQLDataValue.java │ │ │ ├── SSHInfo.java │ │ │ ├── SSLInfo.java │ │ │ ├── Schema.java │ │ │ ├── Sequence.java │ │ │ ├── ShowDatabaseResult.java │ │ │ ├── SimpleColumn.java │ │ │ ├── SimpleSequence.java │ │ │ ├── SimpleTable.java │ │ │ ├── Sql.java │ │ │ ├── Table.java │ │ │ ├── TableColumn.java │ │ │ ├── TableIndex.java │ │ │ ├── TableIndexColumn.java │ │ │ ├── TableMeta.java │ │ │ ├── Trigger.java │ │ │ └── Type.java │ │ ├── sql/ │ │ │ ├── Chat2DBContext.java │ │ │ ├── ConnectInfo.java │ │ │ ├── ConnectionPool.java │ │ │ ├── DocumentUtils.java │ │ │ ├── IDriverManager.java │ │ │ ├── MongExtendedJsonObjectIdConverter.java │ │ │ ├── ResultSetConsumer.java │ │ │ ├── ResultSetFunction.java │ │ │ ├── SQLExecutor.java │ │ │ └── SqlParseUtils.java │ │ ├── ssh/ │ │ │ ├── MyUserInfo.java │ │ │ └── SSHManager.java │ │ └── util/ │ │ ├── ExceptionUtils.java │ │ ├── FileUtils.java │ │ ├── Holder.java │ │ ├── JdbcJarUtils.java │ │ ├── JdbcUtils.java │ │ ├── LexerFactories.java │ │ ├── LexerFactory.java │ │ ├── LexerTokenDefinition.java │ │ ├── LexerTokenDefinitions.java │ │ ├── OBOraclePLLexerDefinition.java │ │ ├── OBOraclePLLexerFactory.java │ │ ├── OracleLexerDefinition.java │ │ ├── OracleLexerFactory.java │ │ ├── ResultSetUtils.java │ │ ├── SortUtils.java │ │ ├── SplitSqlString.java │ │ ├── SqlSplitProcessor.java │ │ ├── SqlSplitter.java │ │ ├── SqlStatementIterator.java │ │ ├── SqlUtils.java │ │ └── TableUtils.java │ ├── lombok.config │ └── pom.xml ├── docker/ │ ├── Dockerfile │ ├── docker-build.sh │ ├── docker-compose-start.sh │ ├── docker-compose.yml │ ├── docker-start.sh │ └── test/ │ ├── docker-compose.yml │ ├── redis/ │ │ ├── data/ │ │ │ └── dump.rdb │ │ └── redis.conf │ ├── start.sh │ └── stop.sh ├── document/ │ ├── git/ │ │ └── git.sh │ ├── sql/ │ │ └── mysql.sql │ └── style/ │ └── Alibaba_CodeStyle.xml └── script/ └── local-client-build.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.css linguist-language=java ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: 💣 Bug title: 'Bug: ' description: bug template labels: bug body: - type: input id: version attributes: label: Chat2DB Version validations: required: true - type: textarea id: description attributes: label: Describe the bug description: | If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem and error log. 清晰的描述遇到的问题,并建议附上错误截图及错误日志。 validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/optimized.md ================================================ --- name: "💫 Optimized" about: optimized template title: '优化: ' labels: 'optimized' --- ================================================ FILE: .github/ISSUE_TEMPLATE/suggest.md ================================================ --- name: "💌 Suggest" about: suggest template title: 'suggest: ' labels: 'suggest' --- ================================================ FILE: .github/workflows/pushdocker.yml ================================================ # When tagging a release, do two things # 1. name: Push To Docker # Workflow's trigger on: release: types: [ published ] # Workflow's jobs jobs: docker: strategy: matrix: include: - arch: amd64 - arch: arm64 variant: v8 runs-on: ubuntu-latest steps: - name: Check out git repository uses: actions/checkout@main # Unable to obtain version number. Since the workflow doesn't support it, we'll use a plugin - name: Create version id: chat2db_version uses: bhowell2/github-substring-action@1.0.1 with: value: ${{ github.ref }} index_of_str: "refs/tags/v" # Outputting basic information - name: Print basic information run: | echo "current version: ${{ steps.chat2db_version.outputs.substring }}" # Install Node.js - name: Install Node.js uses: actions/setup-node@main with: node-version: 16 cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # Build static file information - name: Yarn install & build & copy run: | cd chat2db-client yarn install yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-web-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-web-start/src/main/resources/thymeleaf/ # Install java and maven - name: Install Java and Maven uses: actions/setup-java@main with: java-version: "17" distribution: "adopt" cache: "maven" # Compile server-side Java version - name: Build Java run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: amd64,arm64 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 # Log in docker hub - name: Log in to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Packaging and sending to Docker - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true platforms: linux/amd64,linux/arm64/v8 tags: chat2db/chat2db:${{ steps.chat2db_version.outputs.substring }},chat2db/chat2db:latest file: docker/Dockerfile ================================================ FILE: .github/workflows/release.yml ================================================ # Workflow's name name: Build Client # Workflow's trigger # Pack when creating tags on: push: tags: - v* # Workflow's jobs # A total of 3 computers are required to run # windows # macos-latest x86_64 # macos-latest arm64 jobs: release: strategy: fail-fast: false matrix: include: - os: windows-latest - os: macos-latest arch: x86_64 - os: macos-latest arch: arm64 - os: ubuntu-latest runs-on: ${{ matrix.os }} steps: - name: Check out git repository uses: actions/checkout@main # Obtaining the version number is not supported by workflow, so a plug-in is used. - name: Create version id: chat2db_version uses: bhowell2/github-substring-action@1.0.1 with: value: ${{ github.ref }} index_of_str: "refs/tags/v" # Output basic information - name: Print basic information run: | echo "current environment: ${{ env.CHAT2DB_ENVIRONMENT }}" echo "current version: ${{ steps.chat2db_version.outputs.substring }}" # Install jre Windows - name: Install Jre for Windows if: ${{ runner.os == 'Windows' }} uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" # Install jre MacOS X64 - name: Install Jre MacOS X64 if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" architecture: "x64" # Install jre MacOS arm64 - name: Install Jre MacOS arm64 if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" architecture: "aarch64" # Install jre Linux - name: Install Jre for Linux if: ${{ runner.os == 'Linux' }} uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" # java.security open tls1 Windows - name: Enable tls1 if: ${{ runner.os == 'Windows' }} run: | # sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}\conf\security\java.security" $filePath = "${{ env.JAVA_HOME }}\conf\security\java.security" $content = Get-Content $filePath -Raw $updatedContent = $content -replace '^(jdk.tls.disabledAlgorithms=)(.*)( TLSv1, TLSv1.1,)(.*)', '$1$2$4' $updatedContent | Set-Content $filePath shell: pwsh # java.security open tls1 macOS - name: Enable tls1 if: ${{ runner.os == 'macOS' }} run: | sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security # Copy jre Windows - name: Copy Jre for Windows if: ${{ runner.os == 'Windows' }} run: | mkdir chat2db-client/static cp -r "${{ env.JAVA_HOME }}" chat2db-client/static/jre # Copy jre macOS - name: Copy Jre for macOS if: ${{ runner.os == 'macOS' }} run: | mkdir chat2db-client/static cp -r $JAVA_HOME chat2db-client/static/jre chmod -R 777 chat2db-client/static/jre/ # Copy jre Linux - name: Copy Jre for Linux if: ${{ runner.os == 'Linux' }} run: | mkdir chat2db-client/static cp -r $JAVA_HOME chat2db-client/static/jre chmod -R 777 chat2db-client/static/jre/ # Install node - name: Install Node.js uses: actions/setup-node@main with: node-version: 16 cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # Install java - name: Install Java and Maven uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" cache: "maven" # Build static file information - name: Yarn install & build & copy run: | cd chat2db-client yarn yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ cd src/main yarn yarn run build # Compile server-side java version - name: Build Java run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml # touch versions - name: touch versions run: | cd chat2db-client mkdir versions mkdir versions/${{ steps.chat2db_version.outputs.substring }} mkdir versions/${{ steps.chat2db_version.outputs.substring }}/static touch version echo -n ${{ steps.chat2db_version.outputs.substring }} > version cp -r version ./versions/ # Copy server-side java to the specified location - name: Copy App run: | cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ # cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/lib - name: Prepare Build Electron run: | cd chat2db-client yarn run build:web:desktop --app_version=${{ steps.chat2db_version.outputs.substring }} cp -r dist ./versions/${{ steps.chat2db_version.outputs.substring }}/ rm -r dist # windows - name: Build/release Electron app for Windows if: ${{ runner.os == 'Windows' }} uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} skip_build: true args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --win --x64" release: true # macos x86_64 - name: Build/release Electron app for MacOS X64 if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} skip_build: true args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --mac --x64" release: true # x86_64 notarization - name: Notarization x86_64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} run: | xcrun notarytool store-credentials "Chat2DB" --apple-id "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --team-id "${{secrets.MAC_TEAM_ID}}" xcrun notarytool submit chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg --keychain-profile "Chat2DB" # macos arm64 - name: Build/release Electron app for MacOS arm64 if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} skip_build: true args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --mac --arm64" release: true # arm notarization - name: Notarization arm64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} run: | xcrun notarytool store-credentials "Chat2DB" --apple-id "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --team-id "${{secrets.MAC_TEAM_ID}}" xcrun notarytool submit chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg --keychain-profile "Chat2DB" # Linux - name: Delete File if: ${{ runner.os == 'Linux' }} run: | cd chat2db-client/static/jre/ ls -la rm -rf legal ls -la - name: Build/release Electron app for Linux if: ${{ runner.os == 'Linux' }} uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} skip_build: true args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --linux" release: true # Prepare the required data Windows - name: Prepare upload for Windows if: runner.os == 'Windows' run: | mkdir oss_temp_file cp -r chat2db-client/release/*Setup*.exe ./oss_temp_file # Prepare the required data MacOS x86_64 - name: Prepare upload for MacOS x86_64 if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} run: | mkdir oss_temp_file cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/ && zip -r ${{ steps.chat2db_version.outputs.substring }}.zip ./ cp -r ${{ steps.chat2db_version.outputs.substring }}.zip ../../../oss_temp_file cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file # Prepare the required data MacOS arm64 - name: Prepare upload for MacOS arm64 if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} run: | mkdir oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file # Prepare the required data Linux - name: Prepare upload for Linux if: runner.os == 'Linux' run: | mkdir oss_temp_file cp -r chat2db-client/release/*.AppImage ./oss_temp_file # Upload files to OSS for easy downloading - name: Set up oss utils uses: yizhoumo/setup-ossutil@v1 with: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} ossutil-version: "1.7.16" - name: Upload to oss run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/release/${{ steps.chat2db_version.outputs.substring }}/ # Build completion notification - name: Send dingtalk message for Windows if: ${{ runner.os == 'Windows' }} uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "Windows-release-打包完成通知", "text": "# Windows-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " } # Build completion notification - name: Send dingtalk message for MacOS x86_64 if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "MacOS-x86_64-release-构建完成通知", "text": "# MacOS-x86_64-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) " } # Build completion notification - name: Send dingtalk message for MacOS arm64 if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "MacOS-arm64-release-构建完成通知", "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " } # Build completion notification - name: Send dingtalk message for Linux if: ${{ runner.os == 'Linux' }} uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "Linux-test-打包完成通知", "text": "# Linux-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)" } ================================================ FILE: .github/workflows/release_test.yml ================================================ name: Build Test Client on: push: branches: - "release_test" jobs: release: strategy: fail-fast: false matrix: include: - os: windows-latest file_extension: ".exe" file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe" build_arg: "--win --x64" - os: macos-latest arch: x86_64 file_name: "https://download.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg" file_extension: ".dmg" build_arg: "--mac --x64" - os: macos-latest arch: arm64 file_name: "https://download.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg" file_extension: ".dmg" build_arg: "--mac --arm64" - os: ubuntu-latest file_name: "https://download.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage" file_extension: ".AppImage" build_arg: "--linux" runs-on: ${{ matrix.os }} steps: - name: Check out git repository uses: actions/checkout@main # Install JRE - name: Install JRE uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" # architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} # OpenTLS - name: Enable TLS 1.0 and 1.1 in java.security run: | if [ "$RUNNER_OS" = "Windows" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" fi shell: bash env: RUNNER_OS: ${{ runner.os }} JAVA_HOME: ${{ env.JAVA_HOME }} # Copy JRE to the front-end static directory - name: Copy JRE to static directory run: | mkdir -p chat2db-client/static cp -r "$JAVA_HOME"/ chat2db-client/static/jre if [ "${{ runner.os }}" != "Windows" ]; then chmod -R 777 chat2db-client/static/jre fi shell: bash env: JAVA_HOME: ${{ env.JAVA_HOME }} # Delete related files in jre in Linux - if: ${{ runner.os == 'Linux' }} name: Delete File on Linux run: | cd chat2db-client/static/jre/ ls -la rm -rf legal ls -la # Install Node.js - name: Install Node.js uses: actions/setup-node@main with: node-version: "16" cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # Install Java - name: Install Java and Maven uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" cache: "maven" # Packaging web front-end resources - name: Build FE Static run: | cd chat2db-client yarn yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ cd src/main yarn yarn run build # Package backend project & send to frontend - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml mkdir -p chat2db-client/versions/99.0.${{ github.run_id }}/static echo -n 99.0.${{ github.run_id }} > chat2db-client/version cp -r chat2db-client/version chat2db-client/versions/ cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ # Packaging desktop front-end resources - name: Prepare Build Electron run: | cd chat2db-client yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist # Packaging Electron - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs_temp }} mac_certs_password: ${{ secrets.mac_certs_password_temp }} skip_build: true args: > -c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test ${{ matrix.build_arg}} # Notarization & Signature Mac App - name: Notarize MacOS x86_64 App if: matrix.os == 'macos-latest' && matrix.arch == 'x86_64' run: | xcrun notarytool store-credentials "Chat2DB" --apple-id "${{ secrets.MAC_APPLE_ID }}" --password "${{ secrets.MAC_APPLE_PASSWORD }}" --team-id "${{ secrets.MAC_TEAM_ID }}" xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg --keychain-profile "Chat2DB" - name: Notarize MacOS ARM64 App if: matrix.os == 'macos-latest' && matrix.arch == 'arm64' run: | xcrun notarytool store-credentials "Chat2DB" --apple-id "${{ secrets.MAC_APPLE_ID }}" --password "${{ secrets.MAC_APPLE_PASSWORD }}" --team-id "${{ secrets.MAC_TEAM_ID }}" xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg --keychain-profile "Chat2DB" # Build Jar - name: Prepare upload for Jar if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} run: | mkdir -p oss_temp_file cp chat2db-client/versions/99.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file cp -r chat2db-client/versions/99.0.${{ github.run_id }}/dist ./oss_temp_file/dist cd chat2db-client/versions/99.0.${{ github.run_id }}/ && zip -r 99.0.${{ github.run_id }}.zip ./ cp -r 99.0.${{ github.run_id }}.zip ../../../oss_temp_file cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file # Prepare files to be sent to OSS - name: Prepare upload for OSS run: | mkdir -p oss_temp_file cp -r chat2db-client/release/*${{ matrix.file_extension }} ./oss_temp_file # Set up OSS - name: Set up oss utils uses: yizhoumo/setup-ossutil@v1 with: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} ossutil-version: "1.7.16" # Upload to OSS - name: Upload to OSS run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ # Configure SSH to be uploaded to the server - name: Install ssh key run: | mkdir -p ~/.ssh echo "${{ secrets.SERVER_DOWNLOAD_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -t rsa ${{ secrets.SERVER_DOWNLOAD_HOST }} >> ~/.ssh/known_hosts eval `ssh-agent -s` ssh-add ~/.ssh/id_rsa # upload to server - name: Upload package run: | ssh -t ${{ secrets.SERVER_DOWNLOAD_USERNAME }}@${{ secrets.SERVER_DOWNLOAD_HOST }} "mkdir -p ${{ secrets.SERVER_DOWNLOAD_PATH }}/test//99.0.${{ github.run_id }}" scp ./oss_temp_file/* ${{ secrets.SERVER_DOWNLOAD_USERNAME }}@${{ secrets.SERVER_DOWNLOAD_HOST }}:${{ secrets.SERVER_DOWNLOAD_PATH }}/test//99.0.${{ github.run_id }}/ # Send to DingTalk - name: Send dingtalk message uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "${{ matrix.os }}-test-打包完成通知", "text": "# ${{ matrix.os }}-test-打包完成通知\n !\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[${{matrix.file_name}}](${{matrix.file_name}})" } # Send Jar package address to DingTalk - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} name: Send dingtalk message uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "Jar-test-构建完成通知", "text": "### jar包下载地址:[https://download.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](https://download.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip) " } ================================================ FILE: .github/workflows/release_test_2.yml ================================================ name: Build Test Client 2 on: push: branches: - "release_test_2" jobs: release: strategy: fail-fast: false matrix: include: - os: windows-latest file_extension: ".exe" file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe" build_arg: "--win --x64" - os: macos-latest arch: x86_64 file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg" file_extension: ".dmg" build_arg: "--mac --x64" - os: macos-latest arch: arm64 file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg" file_extension: ".dmg" build_arg: "--mac --arm64" - os: ubuntu-latest file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage" file_extension: ".AppImage" build_arg: "--linux" runs-on: ${{ matrix.os }} steps: - name: Check out git repository uses: actions/checkout@main # Install JRE - name: Install JRE uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" java-package: "jre" # architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} # Open TLS - name: Enable TLS 1.0 and 1.1 in java.security run: | if [ "$RUNNER_OS" = "Windows" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" fi shell: bash env: RUNNER_OS: ${{ runner.os }} JAVA_HOME: ${{ env.JAVA_HOME }} # Copy JRE to the front-end static directory - name: Copy JRE to static directory run: | mkdir -p chat2db-client/static cp -r "$JAVA_HOME"/ chat2db-client/static/jre if [ "${{ runner.os }}" != "Windows" ]; then chmod -R 777 chat2db-client/static/jre fi shell: bash env: JAVA_HOME: ${{ env.JAVA_HOME }} # Delete related files in jre in Linux - if: ${{ runner.os == 'Linux' }} name: Delete File on Linux run: | cd chat2db-client/static/jre/ ls -la rm -rf legal ls -la # Install Node.js - name: Install Node.js uses: actions/setup-node@main with: node-version: "16" cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # Install Java - name: Install Java and Maven uses: actions/setup-java@main with: java-version: "17" distribution: "temurin" cache: "maven" # Packaging web front-end resources - name: Build FE Static run: | cd chat2db-client yarn yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ cd src/main yarn yarn run build # Package backend project & send to frontend - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml mkdir -p chat2db-client/versions/99.0.${{ github.run_id }}/static echo -n 99.0.${{ github.run_id }} > chat2db-client/version cp -r chat2db-client/version chat2db-client/versions/ cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ # Packaging desktop front-end resources - name: Prepare Build Electron run: | cd chat2db-client yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist # Packing Electron - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} skip_build: true args: > -c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test ${{ matrix.build_arg}} # Notarization & Signature Mac App - name: Notarize MacOS x86_64 App if: matrix.os == 'macos-latest' && matrix.arch == 'x86_64' run: | xcrun notarytool store-credentials "Chat2DB" --apple-id "${{ secrets.MAC_APPLE_ID }}" --password "${{ secrets.MAC_APPLE_PASSWORD }}" --team-id "${{ secrets.MAC_TEAM_ID }}" xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg --keychain-profile "Chat2DB" # Build Jar - name: Prepare upload for Jar if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} run: | mkdir -p oss_temp_file cp chat2db-client/versions/99.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file cp -r chat2db-client/versions/99.0.${{ github.run_id }}/dist ./oss_temp_file/dist cd chat2db-client/versions/99.0.${{ github.run_id }}/ && zip -r 99.0.${{ github.run_id }}.zip ./ cp -r 99.0.${{ github.run_id }}.zip ../../../oss_temp_file cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file # Prepare files to be sent to OSS - name: Prepare upload for OSS run: | mkdir -p oss_temp_file cp -r chat2db-client/release/*${{ matrix.file_extension }} ./oss_temp_file # Set up OSS - name: Set up oss utils uses: yizhoumo/setup-ossutil@v1 with: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} ossutil-version: "1.7.16" # Upload to OSS - name: Upload to OSS run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ # Send to DingTalk - name: Send dingtalk message uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "${{ matrix.os }}-test-打包完成通知", "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[${{matrix.file_name}}](${{matrix.file_name}})" } # Send Jar address to DingTalk - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} name: Send dingtalk message uses: ghostoy/dingtalk-action@master with: webhook: ${{ secrets.DINGTALK_WEBHOOK }} msgtype: markdown content: | { "title": "Jar-test-构建完成通知", "text": "### jar包下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip) " } ================================================ FILE: .gitignore ================================================ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr .out ### Visual Studio Code ### package-lock.json ### Mac .DS_Store /chat2db-server/ali-dbhub-server-start/src/main/resources/static/front/* /chat2db-server/ali-dbhub-server-start/src/main/resources/thymeleaf ### docker 数据不用上传 /docker/redis/data/* /docker/test/reds/* /docker/test/mongodb/* /chat2db-server/ali-dbhub-server-domain/ali-dbhub-server-domain-support/src/main/resources/lib/* /chat2db-server/ali-dbhub-server-domain/ali-dbhub-server-domain-support/lib/* /lib /out/* ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "aarch", "ahooks", "alicdn", "aliyuncs", "altool", "andale", "antd", "asar", "AZUREAI", "bgcolor", "blockmap", "cascader", "charsets", "chmod", "CLOB", "Consolas", "Datas", "datasource", "Datetime", "DBAI", "DBHUB", "Dchat", "dingtalk", "Dloader", "Dmaven", "Dproject", "Dserver", "Dspring", "echart", "echarts", "favicons", "fulltext", "ghostoy", "hljs", "iconfont", "indexs", "jdbc", "kingbase", "lucida", "macos", "Mddhhmmss", "Menlo", "mkdir", "monaco", "msgtype", "Navciat", "Navicat", "nsis", "OPENAI", "ossutil", "partialize", "pgsql", "plsql", "pnpm", "Popconfirm", "Prec", "quickinput", "redownload", "remaininguses", "RESTAI", "samuelmeuli", "scroller", "Sercurity", "singlestoredb", "sortablejs", "SQLSERVER", "temurin", "thymeleaf", "Tigger", "togglefullscreen", "transactsql", "trino", "umijs", "umirc", "USERANDPASSWORD", "uuidv", "VARCHAR", "VIEWCOLUMN", "VIEWCOLUMNS", "wechat", "wireframe", "Wppk", "xcrun", "yapi", "yizhoumo", "zustand" ], "java.compile.nullAnalysis.mode": "automatic" } ================================================ FILE: CHANGELOG.md ================================================ ## 3.1.19 `2024-1-3` **Changelog** - ⭐【New Features】Open table supports filtering sort - ⚡️【Optimize】Optimize startup speed - 🐞【Fixed】SQL error generated by modifying the table structure - 🐞【Fixed】The problem of direct query table data error due to characters - 🐞【Fixed】null point error ## 3.1.18 `2023-12-28` **Changelog** - 🐞【Fixed】 docker startup error ## 3.1.17 `2023-12-27` **Changelog** - ⭐【New Features】Connection replicable - ⚡️【Optimize】Select the table to view the DDL of the table in Information on the right expansion bar - 🐞【Fixed】 Modifying table sorting does not generate sorting SQL problems - 🐞【Fixed】Home connection cannot switch pages - 🐞【Fixed】Columns cannot be viewed or edited in some tables of the Oracle database ## 3.1.16 `2023-12-25` **Changelog** - ⭐【New Features】Execute the Record sql Add replication button - ⭐【New Features】Execution records can be opened directly in the console - ⭐【New Features】The SQL column on the right of execution record and save can be expanded and dragged - 🐞【Fixed】Some databases could not display the Database/Schema structure - 🐞【Fixed】During web deployment, a page error occurs after the page switching page is refreshed - ⚡️【Optimize】The query result cancels in-cell scrolling and instead uses hover to view cell contents ## 3.1.15 `2023-12-20` **Changelog** - 🐞【Fixed】Every time I open the application, I occasionally cannot select the database problem - 🐞【Fixed】Compatible with old data types can not show deletion problems - 🐞【Fixed】Some databases cannot display the database /Schema structure ## 3.1.14 `2023-12-17` **Changelog** - 🐞【Fixed】Tree structure search for bugs - 🐞【Fixed】Switching tab causes edit data reset problem - 🐞【Fixed】Rename is reset after switching tab ## 3.1.12 `2023-12-15` **Changelog** - ⚡️【Optimize】Optimized tree structure search - ⚡️【Optimize】Tree structure search box resident ## 3.1.11 `2023-12-13` **Changelog** - 🐞【Fixed】A chart with a Schema cannot be saved and executed - 🐞【Fixed】Failure to start after the upgrade ## 3.1.1 `2023-12-13` **Changelog** - 🐞【Fixed】Table blank problem when switching Tabs - 🐞【Fixed】DM or Oracle cannot display Schema - 🐞【Fixed】The import connection is lost. Procedure ## 3.1.0 `2023-12-12` **Changelog** - 🔥🔥【Optimize】The first startup time has been increased by 65% - 🔥🔥【Optimize】Changed the structure of the left tree - 🔥🔥【Optimize】Optimized the tab switchover problem - ⚡️ 【Optimize】All nodes are supported. The name of each node can be copied - ⚡️【Optimize】The sql console input box supports switching databases, and will not change when the left database is switched - ⭐ 【New Features】Save records moved to the right toolbar, you can directly modify the name in the list - ⭐【New Features】Support mongoDB - ⭐【New Features】Support for viewing all tables ## 3.0.14 `2023-11-20` **Changelog** - 🐞【Fixed】Team paging problem - 🐞【Fixed】Oracle service name bug - 🐞【Fixed】Oracle datatype error - 🐞【Fixed】Fixed an issue where MySQL changed table structure without displaying comments. - ⚡️【Optimize】Support database or schema - 【Developer】Friends don't worry, the company has some things recently, and is preparing 3.1.0, be patient ## 3.0.13 `2023-11-15` **Changelog** - 🐞【Fixed】oracle datatype error - 🐞【Fixed】DM index error ## 3.0.12 `2023-11-13` **Changelog** - 🐞【Fixed】Copy as insert first row lost problem - 🐞【Fixed】DM database index bug - 🐞【Fixed】Point Garbled code problem - 🐞【Fixed】MariaDB connec database bug - 🐞【Fixed】Issues 792 NullPointerException - 🐞【Fixed】Kingbase8r6 error ## 3.0.11 `2023-11-08` **Changelog** - ⭐【New Features】Oracle connections support the Service name mode - ⭐【New Features】【New function】 Edit table data to support batch copy, clone, delete (click 1X1 cell to select/cancel, hold down shift/ctrl/cmd to select multiple) - ⚡️【Optimize】After the update is completed, click restart to close the problem that cannot be automatically opened (hot update cannot fix this problem, you need to download a new version to cover the client) - 🐞【Fixed】database and schema searches support case ambiguity matching - 🐞【Fixed】Where database was not displayed after being added - 🐞【Fixed】sql formatting to ·now()· format error ## 3.0.10 `2023-11-06` **Changelog** - ⭐【New Features】Add multiple CN AI configurations Add multiple domestic AI configurations - Supports single-row replication of Insert, Update, table header fields, and row data - Clone the selected row - Replication of cell data is supported - You can set the cell to Null or Default - Row deletion is supported - Supports zooming in to view or modify data - ⭐【New Features】Supports the ctrl/cmd+c shortcut to copy row data or cell data - ⭐【New Features】Supports the shortcut key ctrl/cmd+v to paste and copy row data/cell data to row/cell - ⭐【New Features】Edit table structure supports setting primary keys in columns - ⭐【New Features】History is added to the foldable panel on the right - ⭐【New Features】Edit data to support cell-level undo changes - ⭐【New Features】The Table tree node operation menu on the left supports copying table, field, key, index, and function names - ⭐【New Features】The node in the left Table tree supports ctrl/cmd+c to copy the node text - ⭐【New Features】You can right-click to close tabs, close other tabs, or close all tabs - ⭐【New Features】Top database and schema support search - ⚡️【Optimize】Smart prompts for SQL editing - ⚡️【Optimize】Edit the table structure to add loading - ⚡️【Optimize】The tree node operation menu supports right-clicking - 🐞【Fixed】Fixed table structure editing floating-point decimal Settings display exception - 🐞【Fixed】Fixed switching the saved sql on the console will eliminate the problem - 🐞【Fixed】After multiple tables are paged, the context cannot select a table other than the current page - 🐞【Fixed】Console and resulting Tabs mouse wheel not scrolling ## 3.0.9 `2023-11-01` **Changelog** - ⭐【New Features】Query results can be refreshed - ⚡️【Optimize】Console Tabs adaptive width - 🐞【Fixed】console save bug - 🐞【Fixed】sqlite can only retrieve one piece of data ## 3.0.5 `2023-10-23` **Changelog** - ⭐【New Features】Supports visual database creation - ⭐【New Features】Support hot update - ⭐【New Features】Double-click the table to open it directly - ⚡️【Optimize】The search table supports size fuzzy matching - ⚡️【Optimize】Sort Database and Schema at the top - ⚡️【Optimize】The queried data supports editing and modification in the large popup window of the view - ⚡️【Optimize】Example Query the page loading effect of data - ⚡️【Optimize】Keep the top focused tab always in the viewable area - ⚡️【Optimize】Query data cell does not have scroll bar problem ## 3.0.4 `2023-10-20` **Changelog** - 🐞【Fixed】Bugs are displayed when more than 100 data items are queried ## 3.0.1 `2023-10-19` **Changelog** - ⚡️【Optimize】Search result scroll bar - ⚡️【Fixed】Oracle update result data bug ## 3.0.0 `2023-10-17` **Changelog** - 🔥【New Features】Support for team collaboration mode - 🔥【New Features】Support for visual table structure creation, editing, and deletion - 🔥【New Features】Support for editing, adding, and deleting query data results - ⭐【New Features】Support the feature of importing Navicat/DBever data source links - ⭐【New Features】Support for AI automatic sync table structure。 - ⭐【New Features】Support export table structure - ⭐【New Features】Support importing SQL files - ⭐【New Features】Support the connection supports adding an environment,better distinguishing between online and daily - ⚡️【Optimize】Optimize Editor Intellisense - ⚡️【Optimize】Optimize AI Input - ⚡️【Optimize】Sql query support is stopped - ⚡️【Optimize】Sql execution supports viewing the number of affected rows - ⚡️【Optimize】Reclaiming non-administrator permissions to edit shared connections - ⚡️【Optimize】`Cmd/Ctrl + R` Run SQL, `Cmd/Ctrl + Shift + R` Refresh Page - 🐞【Fixed】Table operation columns are overridden by table comments - 🐞【Fixed】The last Tab in the query result cannot be closed ## 2.1.0 ## ⭐ New Features - 🔥The team function is newly launched, supporting team collaboration. R&D does not require knowing the online database password, solving the security issue of enterprise database accounts. It is recommended to directly deploy the team function using 'docker' - Added support for environment selection, better distinguishing between online and daily ## 2.0.14 ## 🐞 Bug Fixes - Fix the issue of 'Oracle' query 'Blob' reporting errors - Modify the paging logic and fix some SQL queries that cannot be queried ## 2.0.13 ## ⭐ New Features ## 🐞 Bug Fixes - Fixed a bug where sql formatting was not selected - Fixed open view lag issue - Solve the white screen problem of connected non-relational databases (non-relational databases are not supported) ## 2.0.12 ## ⭐ New Features - 🔥Supports viewing views, functions, triggers, and procedures - Support selected sql formatting - Added new dark themes ## 🐞 Bug Fixes - Fixed sql formatting failure issue - Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing page crashes - Logs desensitize sensitive data - Fix the issue of 'CLOB' not displaying specific content 【Issue #440】(https://github.com/chat2db/Chat2DB/issues/440) - Fix the problem that non-Select does not display query results - Fix the problem that Oracle cannot query without schema - Fix the problem of special type of SQL execution error reporting - Fix the problem that the test link is successful, but the error is reported when saving the link ## 2.0.11 ## 🐞 Bug Fixes - Fix the issue where SSH does not support older versions of encryption algorithms - Fix the issue of SQL Server 2008 not being able to connect - Fix the issue of not being able to view table name notes and field notes ## 2.0.10 ## 🐞 Bug Fixes - Activate the console for the latest operation when you create or start a console、Records the last console used - The replication function of the browser, such as edge, is unavailable - table Indicates an error when ddl is exported after the search - Adds table comments and column field types and comments ## 2.0.9 ## 🐞 Bug Fixes -Fix the issue of Windows flash back ## 2.0.8 ## 🐞 Bug Fixes - Repair the Scientific notation in some databases 【Issue #378】(https://github.com/chat2db/Chat2DB/issues/378) - Fix some cases where data is not displayed ## 🐞 问题修复 - 修复部分数据库出现科学计数法的情况 【Issue #378】(https://github.com/chat2db/Chat2DB/issues/378) - 修复部分情况数据不展示 ## 2.0.7 ## ⭐ New Features - Export query result as file is supported ## 🐞 Bug Fixes - Fixed ai config issues 【Issue #346】(https://github.com/chat2db/Chat2DB/issues/346) ## 2.0.6 ## 🐞 Bug Fixes - Fixed: When there are too many tables under the selected library, the "New Console" button at the bottom disappears 【Issue #314】(https://github.com/chat2db/Chat2DB/issues/314) ## 2.0.5 ## ⭐ New Features - Supports 25 free uses of AIGC every day. - Support for querying data pagination. - Support switching between multiple databases in PostgreSQL. - Support for hot updating of client-side code allows for rapid bug fixes. ## 🐞 Bug Fixes - Default return alias for returned results 【Issue #270】(https://github.com/chat2db/Chat2DB/issues/270) - Fixed around 100 bugs, of course, many were repetitive bugs. ## 2.0.4 ## ⭐ New Features - Support DB2 database - Support renaming after console saving - Support prompts during SQL execution ## 🐞 Bug Fixes - Fix the bug that the database in sqlserver is all numbers - Fix ssh connection bug ## 2.0.2 ## ⭐ New Features - Brand new AI binding process - Support for custom drivers ## 🐞 Bug Fixes - Optimized dataSource link editing - Enhanced error messages - Improved table selection interaction - Enhanced table experience ## 2.0.1 ## 🐞 Bug Fixes - Fix bug where executing multiple SQL statements at once will prompt for exceptions - Fix getJDBCDriver error: null 【Issue #123】(https://github.com/chat2db/Chat2DB/issues/123) - Fixing the Hive connection and then viewing columns results in an error. 【Issue #136】(https://github.com/chat2db/Chat2DB/issues/136) ## 2.0.0 ## What's Changed - 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. - 🔥New focused mode experience for advanced datasource management. - AI integration of more LLM. - Bilingual in Chinese and English support for client. ## 1.0.11 - fixed: SQL 有特殊字符时 AI 功能无法正常使用 - 增减版本信息检测 ## 1.0.10 - fixed: The formatted SQL is abnormal - Optimized AI network connection exception message - Custom AI Adds a local example - Support OceanBase Presto DB2 Redis MongoDB Hive KingBase ## 1.0.9 - Fixed an issue where Open Ai could not connect - Support domestic Dameng database - Supports custom OPEN AI API_HOST - 🔥 Supports custom AI interfaces - Support theme color following system ## 1.0.6 - Fixed Oracle database character set issues - Fix mac installation prompts for security issues ## 1.0.5 - 🔥 Optimizes the boot speed of Apple chips - Rectify database connection problems on Windows - The database modification does not take effect - NullPointerException ## 1.0.4 - Fix ClickHouse jdbc issues - Restore the NPE managed by the connection pool - Fixed front-end edit data source error - Added default database properties ## 1.0.3 - 🔥 Supports SSH connection to the database - 🎉 Allows a client to view logs - 🎉 Supports chat sessions on the Console - Supports setting OPENAI agents on clients - An application that has been started will not be started again ## 1.0.1 - Fixed oracle connection configuration editing and connection query issues - Fix possible risks of Apikey output to logs - Fixed the login bug of web version ## 1.0.0 - Fixed oracle connection configuration editing and connection query issues - Fix possible risks of Apikey output to logs - repair bugChat2DB login web version 1.0.0 release come 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 - 🌈 AI intelligent assistant, supports natural language to SQL, SQL to natural language, and SQL optimization suggestions - 👭 Support team collaboration, R & D does not need to know the online database password, to solve the security problem of enterprise database account - ⚙️ Provides powerful data management capabilities, including data tables, views, stored procedures, functions, triggers, indexes, sequences, users, roles, and authorization - 🔌 Powerful expansion ability, currently supports Mysql, PostgreSQL, Oracle, SQLServer, ClickHouse, Oceanbase, H2, SQLite and so on, the future will support more databases - 🛡 The front-end is developed using Electron, providing an integrated solution of Windows, Mac, Linux clients, and web versions - 🎁 Supports environment isolation and separation of online and daily data rights ## 0.0.0 `2023--` **Changelog** - ⭐【New Features】 - ⚡️【Optimize】 - 🐞【Fixed】 ================================================ FILE: CHANGELOG_CN.md ================================================ ## 3.1.19 `2024-1-3` **更新日志** - ⭐【新功能】打开表支持筛选排序 - ⚡️【优化】优化启动速度 - 🐞【修复】修改表结构生成SQL错误 - 🐞【修复】直接查询表因字符导致数据错误的问题 - 🐞【修复】null point error ## 3.1.18 `2023-12-28` **更新日志** - 🐞【修复】docker启动报错问题 ## 3.1.17 `2023-12-27` **更新日志** - ⭐【新功能】连接可复制 - ⚡️【优化】选中表可直接在右侧拓展栏的“信息”中查看表DDL - 🐞【修复】修改表排序无法生成排序SQL问题 - 🐞【修复】Oracle数据库部分表无法查看列、无法编辑问题 - 🐞【修复】团队管理归属连接无法切换分页 ## 3.1.16 `2023-12-25` **更新日志** - ⭐【新功能】执行记录添加复制sql按钮 - ⭐【新功能】执行记录可直接在控制台打开 - ⭐【新功能】右侧执行记录、保存SQL栏展开大小可拖动 - 🐞【修复】部分数据库无法显示数据库/Schema结构的问题 - 🐞【修复】网页部署时,切换页面刷新后页面报错问题 - ⚡️【优化】查询结果取消单元格内滚动,改为hover查看单元格内容 ## 3.1.15 `2023-12-20` **更新日志** - 🐞【修复】每次打开应用时,偶现无法选择数据库问题 - 🐞【修复】兼容老数据类型无法显示删除问题 ## 3.1.14 `2023-12-17` **更新日志** - 🐞【修复】树结构搜索bug - 🐞【修复】切换tab导致编辑数据重置问题 - 🐞【修复】切换tab后重命名被重置 ## 3.1.12 `2023-12-15` **更新日志** - ⚡️【优化】优化树结构搜索 - ⚡️【优化】树结构搜索框常驻 ## 3.1.11 `2023-12-13` **更新日志** - 🐞【修复】带Schema的图表无法保存和执行 - 🐞【修复】升级后无法启动问题 ## 3.1.10 `2023-12-13` **更新日志** - 🐞【修复】切换Tabs时,表空白问题 - 🐞【修复】DM、Oracle无法显示Schema问题 - 🐞【修复】导入连接丢失问题 ## 3.1.0 `2023-12-12` **更新日志** - 🔥🔥【优化】首次启动时间时间提升了65% - 🔥🔥【优化】更改了左侧树的结构 - 🔥🔥【优化】优化切换tab卡顿问题 - ⚡️【优化】所有节点都支持选中,可以复制每个节点的名称 - ⚡️【优化】sql的console输入框支持切换数据库,不会在跟着左侧数据库的切换而改变了 - ⭐【新功能】保存记录移动到了右侧工具栏,可以直接在列表里修改名称 - ⭐【新功能】支持mongoDB - ⭐【新功能】支持查看所有表 ## 3.0.14 `2023-11-20` **更新日志** - 🐞【修复】团队分页问题 - 🐞【修复】Oracle服务名称错误 - 🐞【修复】Oracle数据类型错误 - 🐞【修复】修复MySQL修改表结构,不回显注释的问题。 - ⚡️【优化】支持数据库或模式 - 【开发者】友友们不要着急呀,最近公司有些事情,并且在准备3.1.0,耐心等待哦 ## 3.0.13 `2023-11-15` **更新日志** - 🐞【修复】oracle datatype 错误 - 🐞【修复】DM index 错误 ## 3.0.12 `2023-11-13` **更新日志** - 🐞【修复】复制为insert第一行丢失问题 - 🐞【修复】达梦数据库index问题 - 🐞【修复】Point 乱码问题 - 🐞【修复】MariaDB连接数据库错误 - 🐞【修复】#792 NullPointerException - 🐞【修复】Kingbase8r6 错误 ## 3.0.11 `2023-11-10` **更新日志** - ⭐【新功能】Oracle 连接支持 Service name 方式 - ⭐【新功能】编辑表数据支持批量复制、克隆、删除(点击1X1单元格全选/取消,按住shift/ctrl/cmd多选) - ⚡️【优化】更新完成后点击重启关闭后无法自动打开问题(热更新无法修复该问题,需要下载新版版本覆盖客户端) - 🐞【修复】database和schema搜索支持大小写模糊匹配 - 🐞【修复】添加database后不显示问题 - 🐞【修复】sql格式化对·now()·格式错误问题 ## 3.0.10 `2023-11-06` **更新日志** - ⭐【新功能】增加多个国内 AI 配置 - ⭐【新功能】编辑数据支持右键操作 - 支持单行复制 Insert、Update、表头字段、行数据 - 支持克隆选中行 - 支持复制单元格数据 - 支持设置单元格为Null和Default - 支持删除行 - 支持放大查看或修改数据 - ⭐【新功能】支持快捷键ctrl/cmd+c 复制行数据/单元格数据 - ⭐【新功能】支持快捷键ctrl/cmd+v 粘贴复制行数据/单元格数据到行/单元格 - ⭐【新功能】编辑表结构支持在列中设置主键 - ⭐【新功能】编辑数据支持单元格级别撤销修改 - ⭐【新功能】左侧Table树节点操作菜单支持复制表、字段、key、index、函数等名称 - ⭐【新功能】左侧Table树节点支持ctrl/cmd+c 复制节点文本 - ⭐【新功能】右侧可折叠面板中增加历史记录 - ⭐【新功能】支持右键关闭tab/关闭其他tab/关闭所有tab - ⭐【新功能】顶部database和schema支持搜索 - ⚡️【优化】SQL 编辑时的智能提示 - ⚡️【优化】编辑表结构添加loading - ⚡️【优化】树节点操作菜单支持右键唤出 - 🐞【修复】修复表结构编辑浮点数小数位设置显示异常 - 🐞【修复】修复切换控制台保存的sql会消失问题 - 🐞【修复】表多的分页后,上下文选不到当前分页以外的表 - 🐞【修复】console和结果的Tabs鼠标滚轮无法滚动的问题 ## 3.0.9 `2023-11-01` **更新日志** - ⭐【新功能】查询结果支持刷新 - ⚡️【优化】控制台Tabs自适应宽度 - 🐞【修复】console保存bug - 🐞【修复】sqlite只能查到一条数据问题 ## 3.0.5 `2023-10-23` **更新日志** - ⭐【新功能】支持可视化创建数据库 - ⭐【新功能】支持热更新 - ⭐【新功能】双击表直接打开表 - ⚡️【优化】搜索表支持大小模糊匹配 - ⚡️【优化】Database 和 Schema 排序 - ⚡️【优化】查询的数据支持在查看的大的弹窗中编辑修改 - ⚡️【优化】查询数据翻页loading效果 - ⚡️【优化】保持顶部聚焦的tab永远在可视区域内 - ⚡️【优化】查询数据单元格没有滚动条问题 ## 3.0.4 `2023-10-20` **更新日志** - 🐞【修复】查询数据超过100条时显示bug ## 3.0.1 `2023-10-19` **更新日志** - ⚡️【优化】查询结果滚动条 - 🐞【修复】Oracle更新结果数据错误 ## 3.0.0 `2023-10-17` **更新日志** - 🔥【新功能】支持团队协作模式 - 🔥【新功能】支持可视化表结构新增、编辑、删除 - 🔥【新功能】支持查询数据结果编辑、新增、删除 - ⭐【新功能】支持导入Navicat/DBeaver数据源链接的功能 - ⭐【新功能】支持AI自动同步表结构 - ⭐【新功能】支持导出表结构 - ⭐【新功能】支持导入sql文件 - ⭐【新功能】连接支持添加环境标识,更好地区分在线和日常 - ⚡️【优化】优化编辑器提示功能 - ⚡️【优化】优化AI输入 - ⚡️【优化】sql查询支持停止 - ⚡️【优化】sql执行支持查看影响行数 - ⚡️【优化】回收非管理员编辑共享连接权限 - ⚡️【优化】`Cmd/Ctrl + R` 运行SQL, `Cmd/Ctrl + Shift + R` 刷新页面 - 🐞【修复】表操作列被表注释覆盖问题 - 🐞【修复】查询结果最后一个Tab无法关闭问题 ## 2.1.0 ## ⭐ 新特性 -🔥 新推出团队功能,支持团队协作。研发不需要知道在线数据库 密码,解决企业数据库帐号的安全问题。建议直接部署团队 使用'docker'的函数 -增加了环境选择的支持,更好地区分在线和日常 ## 2.0.14 ## ⭐ 新特性 - 🔥 团队功能全新上线,支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题,团队功能建议直接使用 `docker` 部署 - 新增支持环境选择,更好的区分线上、日常环境 ## 🐞 问题修复 - 修复 `Oracle` 查询 `Blob` 报错的问题 - 修改分页逻辑,修复部分 SQL 无法查询 ## 2.0.13 ## 🐞 问题修复 - 修复不选中 sql 格式化的 bug - 修复打开视图卡顿问题 - 解决已连接的非关系型数据库打开白屏问题(暂不支持非关系性数据库) ## 2.0.12 ## ⭐ 新特性 - 🔥 支持查看视图、函数、触发器、存储过程 - 支持选中 sql 格式化 - 增加新的暗色主题 ## 🐞 问题修复 - 修复 sql 格式化会失败问题 - 修复本地存储的主题色、背景色与新版本不兼容时会导致页面崩溃问题 - 日志对敏感数据进行脱敏 - 修复 `CLOB` 不展示具体内容的问题 [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) - 修复非 Select 不展示查询结果的问题 - 修复 Oracle 不带 schema 无法查询的问题 - 修复特殊类型的 SQL 执行报错的问题 - 修复测试链接成功,但保存链接报错的问题 ## 2.0.11 ## 🐞 问题修复 - 修复 SSH 不支持老版本加密算法的问题 - 修复 SQLServer2008 无法连接的问题 - 修复无法查看表名备注、字段备注的问题 ## 2.0.10 ## 🐞 问题修复 - 新建、开打 console 时激活最新操作的 console、记录最后一次使用的 console - edge 等浏览器复制功能无法正常使用 - table 搜索后导出 ddl 报错 - 增加表注释以及列字段类型和注释 - 当数据源添加了 database 默认选择第一个 database ## 2.0.9 ## 🐞 问题修复 - 修复 windows 闪退的问题 ## 2.0.8 ## 🐞 问题修复 - 修复部分数据库出现科学计数法的情况 [Issue #378](https://github.com/chat2db/Chat2DB/issues/378) - 修复部分情况数据不展示 ## 2.0.7 ## ⭐ 新特性 - 支持导出查询结果 ## 🐞 问题修复 - 修复 ai 配置 [Issue #346](https://github.com/chat2db/Chat2DB/issues/346) ## 2.0.6 ## 🐞 问题修复 - Fixed: 当选择的库下面表过多时最下面的“新建控制台”按钮消失 [Issue #314](https://github.com/chat2db/Chat2DB/issues/314) ## 2.0.5 ## ⭐ 新特性 - 支持每天 25 次免费使用 AIGC - 支持查询数据分页 - 支持 PostgreSQL 数据库多个 database 的切换 - 支持客户端代码热更新可以快速修复 bug - 支持客户端字体放大缩小 ## 🐞 问题修复 - 返回结果默认返回别名 [Issue #270](https://github.com/chat2db/Chat2DB/issues/270) - 修复了 100 个左右的 bug,当然很多是重复 bug ## 2.0.4 ## ⭐ 新特性 - 支持 DB2 数据库 - 支持控制台保存后重命名 - 支持 SQL 执行中提示 ## 🐞 问题修复 - 修复 sqlserver 中 database 全是数字的 bug - 修复 ssh 连接 bug ## 2.0.2 ## ⭐ 新特性 - 全新的 AI 绑定流程 - 支持自定义驱动 ## 🐞 问题修复 - 优化 dataSource 链接编辑 - 优化错误提示 - 优化选表交互 - 优化表格体验 ## 2.0.1 ## 🐞 问题修复 - 修复一次性执行多条 SQL 会提示异常的 BUG - 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) - 修复 hive 方式连接,然后查看 columns 报错 [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) ## 2.0.0 ## 更新内容 - 🔥SQL 查询、AI 查询和数据报表完美集成的一体化解决方案设计与实现 - 🔥 数据源连接和管理进阶为专注模式的全新体验设计与实现 - 🔥AI 对话 SQL 升级为极简模式的全新交互设计与实现 - 客户端 AI 体验重大升级,响应更多用户的诉求 - 集成更多 AI 模型 - 客户端双语支持 - SQL 查询基础功能优化 - Issue 问题修复 ## 1.0.11 - fixed: SQL 有特殊字符时 AI 功能无法正常使用 - 增减版本信息检测 ## 1.0.10 - fixed: 格式化 SQL 异常 - 优化 AI 网络连接异常提示 - 自定义 AI 添加本地样例 - Support OceanBase Presto DB2 Redis MongoDB Hive KingBase ## 1.0.9 - 修复 Open Ai 无法连接的问题 - 支持国产达梦数据库 - 支持自定义 OPEN AI API_HOST - 🔥 支持自定义 AI 接口 - 支持主题色跟随系统 ## 1.0.6 - 修复 Oracle 数据库字符集问题 - 修复 mac 安装提示的安全问题 ## 1.0.5 - 🔥 优化 Apple 芯片的启动速度 - 修复 Windows 端数据库连接问题 - 修改 database 不生效 - NullPointerException ## 1.0.4 - 修复 ClickHouse jdbc 问题 - 修复连接池管理的 NPE - 修复前端编辑数据源报错问题 - 增加数据库默认属性配置 ## 1.0.3 - 🔥 支持 SSH 连接数据库 - 🎉 支持客户端查看日志 - 🎉 支持在 Console 中聊天对话 - 支持在客户端内设置 OPENAI 代理 - 已经启动过应用不会再重复启动 ## 1.0.1 - 修复 oracle 连接配置编辑、以及连接查询问题 - 修复 Apikey 输出到日志可能存在的风险 - 修复 web 版本登录的 bug ## 1.0.0 Chat2DB 的 1.0.0 正式版来啦 🎉🎉🎉🎉🎉🎉🎉🎉🎉 - 🌈 AI 智能助手,支持自然语言转 SQL、SQL 转自然语言、SQL 优化建议 - 👭 支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题 - ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 - 🔌 强大的扩展能力,目前已经支持 Mysql、PostgreSQL、Oracle、SQLServer、ClickHouse、Oceanbase、H2、SQLite 等等,未来会支持更多的数据库 - 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 - 🎁 支持环境隔离、线上、日常数据权限分离 ## 0.0.0 `2023--` **更新日志** - ⭐【新功能】 - ⚡️【优化】 - 🐞【修复】 ================================================ FILE: CHAT2DB_AI_SQL.md ================================================ # Chat2DB AI SQL功能使用说明 Chat2DB包含一系列基于ChatGPT的AI SQL使用功能,主要包括自然语言转SQL、SQL解释、SQL优化和SQL转换。 使用这些AI功能,可以将自然语言转换成本地查询SQL,而不仅仅是SQL查询伪代码;可以将SQL解释成自然语言,帮助用户理解复杂的SQL;可以针对慢SQL提供全方位的优化建议,提升查询效率;可以转换不同数据库类型的SQL语言,降低数据库迁移难度等等。 ## 使用配置 ### 点击设置【UI旧】 ### 配置AI #### 配置OPENAI【UI旧】 使用OPENAI的ChatSql功能需要满足两个条件 - 配置OPENAI_API_KEY,如没有OPENAI_API_KEY可加入答疑群根据群公告指引获取chat2db自定义key - 客户端网络可以连接到OPENAI官网,如果本地VPN未能全局生效,可以通过在客户端中设置网络代理HOST和PORT来保证网络连通性 #### 配置自定义AI【UI旧】 - 自定义AI可以是用户自己部署的任意AI模型,例如ChatGLM、ChatGPT、文心一言、通义千问等等,但是自定义的接口输入和输出需要符合自定义的协议规范才可快速使用,否则可能需要二次开发。代码中提供了两个DEMO,只需要配置自定义AI接口地址,以及接口是否流式输出即可查看。具体使用中可以参考DEMO接口来编写自定义接口,或者直接在DEMO接口中进行二次开发,封装自己的自定义接口 - 自定义的流式输出接口配置DEMO - 自定义的非流式输出接口配置DEMO ## 自然语言转SQL【UI旧】 ### 功能描述 输入自然语言,选中执行并传入查询相关的表结构信息,则会返回相应的SQL - 输入样例:查询学生小明的各科目成绩,并传入关联数据表course,score,student,student_course - 输出样例: ```sql SELECT score.score FROM score INNER JOIN student_course ON score.course_id = student_course.course_id INNER JOIN student ON student_course.student_id = student.id WHERE student.name = '小明' ``` ## SQL解释 ### 功能描述 输入SQL,选中执行并传入附加信息,附加信息可以添加希望解释《输入SQL》哪方面的信息,如SQL执行目标,则会返回此条SQL附加信息相关的解释 - 输入样例:SELECT department_name FROM departments WHERE department_id IN (SELECT department_id FROM employees GROUP BY department_id HAVING COUNT(*) > 10) - 输出样例:这个SQL查询语句的意思是:从departments表中选择department_name列,其中department_id列的值在employees表中department_id列的值分组后,如果该组中的记录数大于10,则返回该department_id。换句话说,这个查询语句返回的是在employees表中有超过10个员工的部门名称。 ## SQL优化 ### 功能描述 输入SQL,选中执行并传入优化附加信息,附加信息可以添加希望具体优化方面和要求,则会返回此条SQL附加信息相关的优化建议 - 输入样例:SELECT * FROM department ORDER BY gmt_create DESC - 输出样例: 1. 尽量避免使用SELECT *,应该明确指定需要查询的列,以减少不必要的数据传输和提高查询效率。 2. 对于ORDER BY语句,如果查询的结果集比较大,可以考虑添加LIMIT限制返回的行数,以减少排序的开销。 3. 对于gmt_create字段,可以考虑添加索引以提高排序的效率。 4. 如果查询的结果集经常被访问,可以考虑使用缓存技术,如Redis等,以减少数据库的访问次数。 ## SQL转换 ### 功能描述 输入SQL,选中执行并传入目标SQL类型和附加信息,附加信息中可以添加转换的自定义要求,则会返回目标SQL类型且符合附加要求的SQL语言 - 输入样例:SELECT IFNULL(NULL, "W3Schools.com"),目标SQL类型为Oracle SQL - 输出样例:SELECT NVL(NULL, 'W3Schools.com') FROM dual; ## CHATBOT ### 功能描述 输入任意prompt,点击ChatRobot按钮即可根据prompt返回相应的结果,此处聊天对话默认支持上下文,默认上下文长度为4,可在application.yml文件中修改上下文长度 - 输入样例:针对第二条优化建议给出具体实现方法 - 输出样例:针对第二条优化建议,可以考虑在student表中添加一个索引,以加快查询速度。具体实现方法如下: 1. 查看student表中是否已经存在索引。可以使用以下命令查看: ```sql SHOW INDEX FROM student; ``` 如果已经存在索引,则可以跳过下一步。 2. 在student表中添加索引。可以使用以下命令添加: ```sql ALTER TABLE student ADD INDEX name_index (name); ``` 这里的name_index是索引的名称,name是需要建立索引的列名。 注意:如果student表中的数据量很大,添加索引可能需要一些时间。 3. 重新执行原始SQL语句,查看查询速度是否有所提升。 ```sql SELECT score.score FROM score INNER JOIN student ON score.student_id = student.id WHERE student.name = '小明'; ``` 如果查询速度有所提升,则说明索引建立成功。 ================================================ FILE: Chat2DB_LICENSE ================================================ The Chat2DB License 一、定义 “许可方”是指分发其软件的 Chat2DB 软件团队。 “软件”是指根据本许可提供的 Chat2DB 软件。 2. 许可授予 根据本许可的条款和条件,许可方特此授予您非排他性、全球性、不可转让、不可再许可、可撤销、免版税的版权许可,仅用于您的非商业研究目的。 上述版权声明和本许可声明应包含在本软件的所有副本或重要部分中。 3.限制 您不得出于任何商业、军事或非法目的使用、复制、修改、合并、发布、分发、复制或创建本软件的全部或部分衍生作品。 4.免责声明 本软件“按原样”提供,不提供任何明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和非侵权性的保证。 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权行为还是其他方面,由软件或软件的使用或其他交易引起、由软件引起或与之相关 软件。 5. 责任限制 除适用法律禁止的范围外,在任何情况下且根据任何法律理论,无论是基于侵权行为、疏忽、合同、责任或其他原因,任何许可方均不对您承担任何直接、间接、特殊、偶然、示范性、 或间接损害,或任何其他商业损失,即使许可人已被告知此类损害的可能性。 6.争议解决 本许可受中华人民共和国法律管辖并按其解释。 因本许可引起的或与本许可有关的任何争议应提交北京市海淀区人民法院。 请注意,许可证可能会更新到更全面的版本。 有关许可和版权的任何问题,请通过1558143046@qq.com 与我们联系。 1. Definitions "Licensor" refers to the Chat2DB software team that distributes its software. "Software" refers to the Chat2DB software provided under this license. 2. License Grant Subject to the terms and conditions of this License, the Licensor hereby grants to you a non-exclusive, worldwide, non-transferable, non-sublicensable, revocable, royalty-free copyright license to use the Software solely for your non-commercial research purposes. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 3. Restriction You will not use, copy, modify, merge, publish, distribute, reproduce, or create derivative works of the Software, in whole or in part, for any commercial, military, or illegal purposes. 4. Disclaimer THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5. Limitation of Liability EXCEPT TO THE EXTENT PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER BASED IN TORT, NEGLIGENCE, CONTRACT, LIABILITY, OR OTHERWISE WILL ANY LICENSOR BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES, OR ANY OTHER COMMERCIAL LOSSES, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Dispute Resolution This license shall be governed and construed in accordance with the laws of People’s Republic of China. Any dispute arising from or in connection with this License shall be submitted to Haidian District People's Court in Beijing. Note that the license is subject to update to a more comprehensive version. For any questions related to the license and copyright, please contact us at 1558143046@qq.com. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright Pengfei Ji Licensed 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. ================================================ FILE: README.md ================================================

🚀 Zoer is Launching

Powered by Chat2DB Team - AI-powered app builder that creates professional applications in minutes, no coding required

Zoer - AI App Builder

---
CodePhiliaX%2FChat2DB | Trendshift
[![ReadmeX][readmex-image]][readmex-url] [![Discord][discord-image]][discord-url] [![Twitter][twitter-image]][twitter-url] [![Telegram][telegram-image]][telegram-url] [![Whatsapp][whatsapp-image]][whatsapp-url] [![Reddit][reddit-image]][reddit-url] [![Gmail][gmail-image]][gmail-url] [readmex-image]: https://raw.githubusercontent.com/CodePhiliaX/resource-trusteeship/main/readmex.svg [readmex-url]: https://readmex.com/CodePhiliaX/Chat2DB [discord-image]: https://img.shields.io/badge/-Join%20us%20on%20Discord-%237289DA.svg?style=flat&logo=discord&logoColor=white [discord-url]: https://discord.com/invite/uNjb3n5JVN [twitter-image]: https://img.shields.io/twitter/follow/_Chat2DB?label=Chat2DB [twitter-url]: https://twitter.com/intent/tweet?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.&url=https://github.com/chat2db/Chat2DB&hashtags=ChatGPT,AGI,SQL%20Client,Reporting%20tool [telegram-image]: https://img.shields.io/twitter/url?label=Telegram&logo=Telegram&style=social&url=https://github.com/chat2db/Chat2DB [telegram-url]: https://t.me/share/url?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.&url=https://github.com/chat2db/Chat2DB [whatsapp-image]: https://img.shields.io/twitter/url?label=whatsapp&logo=whatsapp&style=social&url=https://github.com/chat2db/Chat2DB [whatsapp-url]: https://api.whatsapp.com/send?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.%20https://github.com/chat2db/Chat2DB [reddit-image]: https://img.shields.io/twitter/url?label=Reddit&logo=Reddit&style=social&url=https://github.com/chat2db/Chat2DB [reddit-url]: https://www.reddit.com/submit?url=https://github.com/chat2db/Chat2DB&title=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities. [gmail-image]: https://img.shields.io/twitter/url?label=Gmail&logo=Gmail&style=social&url=https://github.com/chat2db/Chat2DB [gmail-url]: mailto:?subject=Check%20this%20GitHub%20repository%20out.&body=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.%3A%0Ahttps://github.com/chat2db/Chat2DB
README in English 简体中文版自述文件 日本語のREADME
**1. Intelligent SQL Generation**: Chat2DB Pro supports AI-driven intelligent SQL development to help you write SQL queries faster. **2. Database Management**: Supports more than 10 databases, including MySQL, PostgreSQL, H2, Oracle, SQLServer, SQLite, MariaDB, ClickHouse, DM, Presto, DB2, OceanBase, Hive, KingBase, MongoDB, Redis, Snowflake, and more. **3. Intelligent Report Generation**: Chat2DB Pro supports AI-driven intelligent data reporting to help you generate dashboards faster. **4. Data Structure Synchronization**: Chat2DB Pro supports database table structure synchronization to help you sync database table structures faster. ## Feature Comparison
Feature Community Open Source Local Pro
Database Types 16+ Target 100+ Target 100+
Supported AI Requires AI Configuration AI ready on installation AI ready on installation
AI Capabilities Basic Varied Varied
Visual Table Editor
SQL Console
SQL Formatting
Save Query Records
Theme Color Settings
Data Structure Sync
Database Grouping
Database Structure Import/Export
Data Import/Export
Data Migration
Copy/Clear Table
Open and Run SQL Files
UML Diagram In Development In Development
Generate Code
Copy Results as Insert/Update
Modify Query Results
Intelligent SQL Editor
AI Table Creation
AI Data Sets
Chat2Excel
Intelligent Dashboard
Editor Settings
Custom Shortcuts
Cross-device Usage
## Download and Installation Chat2DB is a cross-platform application that supports Windows, MacOS, and Linux. You can download Chat2DB from the following links: - [Download Pro Version](https://chat2db.ai/download) - [Download Local Version](https://chat2db.ai/download) - [Download Open Source Version](https://github.com/CodePhiliaX/Chat2DB/releases/tag/v0.3.6) ## Community Edition Docker Installation ### System Requirements Before installing Chat2DB, ensure your system meets the following requirements: - Docker 19.03.0 or later - Docker Compose 1.25.0 or later - CPU >= 2 Cores - RAM >= 4 GiB ```bash docker rm chat2db docker run --name=chat2db -ti -p 10824:10824 -v ~/.chat2db-docker:/root/.chat2db chat2db/chat2db:latest docker start chat2db ``` ## Code Debugging ## Runtime Environment Note: If local debugging is needed: - Java runtime: Open JDK 17 - Node.js runtime: Node 16 Node.js. **Clone the repository locally** ```bash $ git clone git@github.com:chat2db/Chat2DB.git ``` **Frontend Debugging** ```bash Node version must be 16 or higher Use yarn only, npm is not supported $ cd Chat2DB/chat2db-client $ yarn $ yarn run start:web ``` **Backend Debugging** ```bash $ cd ../chat2db-server $ mvn clean install # Maven version 3.8 or higher is required $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 需要安装java 17以上版本,启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` **Standalone Deployment** ```bash # chat2db-client $ npm run build:web:prod $ cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front $ cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf ``` ## Contact Us - Email: Chat2DB@ch2db.com - Discord: [Join our Discord server](https://discord.gg/JDkwB6JS8A) - Twitter: [@Chat2DB](https://x.com/Chat2DB_AI) - YouTube: [Chat2DB Channel](https://www.youtube.com/@chat2db.tutorial) - GitHub: [Chat2DB GitHub](https://github.com/codePhiliaX/chat2db) ## Acknowledgments Thanks to everyone who has contributed to Chat2DB~~ ## Star History Star History Chart ## License The primary license used by this software is the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0), supplemented by the [Chat2DB License](./Chat2DB_LICENSE). ================================================ FILE: README_CN.md ================================================
CodePhiliaX%2FChat2DB | Trendshift

[![ReadmeX][readmex-image]][readmex-url] [![Discord][discord-image]][discord-url] [![Twitter][twitter-image]][twitter-url] [![Telegram][telegram-image]][telegram-url] [![Whatsapp][whatsapp-image]][whatsapp-url] [![Reddit][reddit-image]][reddit-url] [![Gmail][gmail-image]][gmail-url] [readmex-image]: https://raw.githubusercontent.com/CodePhiliaX/resource-trusteeship/main/readmex.svg [readmex-url]: https://readmex.com/CodePhiliaX/Chat2DB [discord-image]: https://img.shields.io/badge/-Join%20us%20on%20Discord-%237289DA.svg?style=flat&logo=discord&logoColor=white [discord-url]: https://discord.com/invite/uNjb3n5JVN [twitter-image]: https://img.shields.io/twitter/follow/_Chat2DB?label=Chat2DB [twitter-url]: https://twitter.com/intent/tweet?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.&url=https://github.com/chat2db/Chat2DB&hashtags=ChatGPT,AGI,SQL%20Client,Reporting%20tool [telegram-image]: https://img.shields.io/twitter/url?label=Telegram&logo=Telegram&style=social&url=https://github.com/chat2db/Chat2DB [telegram-url]: https://t.me/share/url?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.&url=https://github.com/chat2db/Chat2DB [whatsapp-image]: https://img.shields.io/twitter/url?label=whatsapp&logo=whatsapp&style=social&url=https://github.com/chat2db/Chat2DB [whatsapp-url]: https://api.whatsapp.com/send?text=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.%20https://github.com/chat2db/Chat2DB [reddit-image]: https://img.shields.io/twitter/url?label=Reddit&logo=Reddit&style=social&url=https://github.com/chat2db/Chat2DB [reddit-url]: https://www.reddit.com/submit?url=https://github.com/chat2db/Chat2DB&title=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities. [gmail-image]: https://img.shields.io/twitter/url?label=Gmail&logo=Gmail&style=social&url=https://github.com/chat2db/Chat2DB [gmail-url]: mailto:?subject=Check%20this%20GitHub%20repository%20out.&body=Chat2DB-An%20intelligent%20and%20versatile%20general-purpose%20SQL%20client%20and%20reporting%20tool%20for%20databases%20which%20integrates%20ChatGPT%20capabilities.%3A%0Ahttps://github.com/chat2db/Chat2DB
README in English 简体中文版自述文件 日本語のREADME
https://github.com/user-attachments/assets/3c857883-8153-4bda-92b8-d25c6adb5b13 # Chat2DB 是一个智能的通用SQL客户端和数据报表工具,它集成了AI的能力。Chat2DB可以帮助您更快地编写SQL查询、管理数据库、生成报告、探索数据、并且可以与多种数据库进行交互。Chat2DB是一个开源项目,我们欢迎您的贡献。 **1. 智能生成SQL**: Chat2DB Pro支持AI驱动的智能SQL开发,可以帮助您更快地编写SQL查询。 **2. 数据库管理**: 支持多种10+数据库,包括MySQL、PostgreSQL、H2、Oracle、SQLServer、SQLite、MariaDB、ClickHouse、DM、Presto、DB2、OceanBase、Hive、KingBase、MongoDB、Redis、Snowflake等。 **3. 智能生成报表**: Chat2DB Pro支持AI驱动的智能数据报表,可以帮助您更快地生成看板。 **4. 数据结构同步**: Chat2DB Pro支持数据库表结构同步,可以帮助您更快地同步数据库表结构。 ## 功能比较
功能 社区开源版 Local版(收费) Pro版(收费)
数据库类型 16+ 目标100+ 目标100+
支持的 AI 需要配置AI 安装即可使用AI 安装即可使用AI
AI 能力 简单 多样 多样
可视化编辑表
SQL控制台
SQL格式化
保存查询记录
主题颜色设置
数据结构同步
数据库分组
数据库结构导入导出
数据导入导出
数据迁移
复制/清空表
打开运行SQL文件
UML图 开发中 开发中
生成代码
复制结果为 Insert/update
修改查询结果
智能SQL编辑器
AI建表
AI数据集
Chat2Excel
智能看板
编辑器设置
自定义快捷键
跨多设备使用
## 下载安装 Chat2DB 是一个跨平台的应用程序,支持Windows、MacOS和Linux。您可以从以下链接下载Chat2DB。 - [下载 Pro 版](https://chat2db.ai/download) - [下载 Local 版](https://chat2db.ai/download) - [下载开源版](https://github.com/CodePhiliaX/Chat2DB/releases/tag/v0.3.6) ## 社区版 Docker 安装 ### 系统要求 在安装 Chat2DB 之前,请确保您的系统满足以下要求: - Docker 19.03.0 或更高版本 - Docker Compose 1.25.0 或更高版本 - CPU >= 2 Core - RAM >= 4 GiB ```bash // 拉取最新客户端,然后运行docker,名字是 `chat2db` , 并且将 `/root/.chat2db` 挂载到 `~/.chat2db-docker` docker run --name=chat2db -ti -p 10824:10824 -v ~/.chat2db-docker:/root/.chat2db chat2db/chat2db:latest // 这里正常会提示`Tomcat started on port(s): 10824 (http) with context path` 就可以结束了 // 如果这里提示 `The container name "/chat2db" is already in use by container`, 代表已经存在容器了 运行 docker start chat2db // 如果想更新chat2db 则需要先rm docker rm chat2db ``` ## 代码调试 ## 运行环境 注意: 如果需要本地调试 - java 运行 Open JDK 17 - Node 运行环境 Node16 Node.js. **git clone 到本地** ```bash $ git clone git@github.com:chat2db/Chat2DB.git ``` **前端调试** ```bash node版本必须为16及以上 一定要用yarn $ cd Chat2DB/chat2db-client $ yarn $ yarn run start:web ``` **后端调试** ```bash $ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 需要安装java 17以上版本,启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` **独立部署** ```bash # chat2db-client $ npm run build:web:prod $ cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front $ cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf # 再打包后端服务 ``` ## 联系我们 - Email: Chat2DB@ch2db.com - Discord: [Join our Discord server](https://discord.gg/JDkwB6JS8A) - Twitter: [@Chat2DB](https://x.com/Chat2DB_AI) - YouTube: [Chat2DB Channel](https://www.youtube.com/@chat2db.tutorial) - GitHub: [Chat2DB GitHub](https://github.com/codePhiliaX/chat2db) ## 致谢 感谢所有为 Chat2DB 贡献力量的同学们~~ ## Star History Star History Chart ## License The primary license used by this software is the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0), supplemented by the [Chat2DB License](./Chat2DB_LICENSE). ================================================ FILE: README_JA.md ================================================
CodePhiliaX%2FChat2DB | Trendshift

chat on Discord Share on Telegram Share on Reddit

README in English 简体中文版自述文件 日本語のREADME
https://github.com/user-attachments/assets/bd5d5f64-540f-4793-a801-17fa96c4766e Chat2DBはAI機能を統合したインテリジェントで汎用的なSQLクライアントおよびデータ報告ツールです。Chat2DBは、SQLクエリの作成を迅速化し、データベースの管理、レポートの生成、データの探索、および複数のデータベースとのインタラクションをサポートします。Chat2DBはオープンソースプロジェクトであり、皆様の貢献を歓迎します。 **1. インテリジェントSQL生成**: Chat2DB Proは、AI駆動によるインテリジェントなSQL開発をサポートし、SQLクエリをより速く作成する手助けをします。 **2. データベース管理**: MySQL、PostgreSQL、H2、Oracle、SQLServer、SQLite、MariaDB、ClickHouse、DM、Presto、DB2、OceanBase、Hive、KingBase、MongoDB、Redis、Snowflakeなど、10種類以上のデータベースをサポートしています。 **3. インテリジェントレポート生成**: Chat2DB Proは、AI駆動によるインテリジェントなデータ報告をサポートし、ダッシュボードの作成を迅速に行う手助けをします。 **4. データ構造の同期**: Chat2DB Proは、データベーステーブル構造の同期をサポートし、データベーステーブルの構造を迅速に同期する手助けをします。 ## 機能比較
機能 コミュニティ オープンソース ローカル Pro
データベースの種類 16種類以上 100種類以上を対象 100種類以上を対象
サポートされているAI AI設定が必要 インストール時にAIが利用可能 インストール時にAIが利用可能
AI機能 基本的 多様 多様
視覚的テーブルエディタ
SQLコンソール
SQLフォーマット
クエリ記録の保存
テーマカラー設定
データ構造の同期
データベースのグループ化
データベース構造のインポート/エクスポート
データのインポート/エクスポート
データ移行
テーブルのコピー/削除
SQLファイルのオープンと実行
UMLダイアグラム 開発中 開発中
コード生成
インサート/アップデートとして結果をコピー
クエリ結果の修正
インテリジェントSQLエディタ
AIによるテーブル作成
AIデータセット
Chat2Excel
インテリジェントダッシュボード
エディタ設定
カスタムショートカット
クロスデバイス使用
## ダウンロードとインストール Chat2DBは、Windows、MacOS、Linuxをサポートするクロスプラットフォームアプリケーションです。以下のリンクからChat2DBをダウンロードできます: - [Proバージョンのダウンロード](https://chat2db.ai/download) - [ローカルバージョンのダウンロード](https://chat2db.ai/download) - [オープンソースバージョンのダウンロード](https://github.com/CodePhiliaX/Chat2DB/releases/tag/v0.3.6) ## コミュニティエディションのDockerインストール ### システム要件 Chat2DBをインストールする前に、システムが以下の要件を満たしていることを確認してください: - Docker 19.03.0以上 - Docker Compose 1.25.0以上 - CPU >= 2コア - RAM >= 4 GiB ```bash docker rm chat2db docker run --name=chat2db -ti -p 10824:10824 -v ~/.chat2db-docker:/root/.chat2db chat2db/chat2db:latest docker start chat2db ``` ## コードデバッグ ## 実行環境 注意: ローカルデバッグが必要な場合: - Java runtime: Open JDK 17 - Node.js runtime: Node 16 Node.js. **リポジトリをローカルにクローン** ```bash $ git clone git@github.com:chat2db/Chat2DB.git ``` **フロントエンドデバッグ** ```bash Node version must be 16 or higher Use yarn only, npm is not supported $ cd Chat2DB/chat2db-client $ yarn $ yarn run start:web ``` **バックエンドデバッグ** ```bash $ cd ../chat2db-server $ mvn clean install # Maven version 3.8 or higher is required $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 需要安装java 17以上版本,启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` **スタンドアロンデプロイ** ```bash # chat2db-client $ npm run build:web:prod $ cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front $ cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf ``` ## お問い合わせ - メール: Chat2DB@ch2db.com - Discord: [Discordサーバーに参加](https://discord.gg/JDkwB6JS8A) - Twitter: [@Chat2DB](https://x.com/Chat2DB_AI) - YouTube: [Chat2DB チャンネル](https://www.youtube.com/@chat2db.tutorial) - GitHub: [Chat2DB GitHub](https://github.com/codePhiliaX/chat2db) ## 謝辞 Chat2DBに貢献してくださったすべての方々に感謝します~~ ## Star History Star History Chart ## License このソフトウェアで使用されている主なライセンスは[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)であり、[Chat2DB License](./Chat2DB_LICENSE)が補完されています。 ================================================ FILE: chat2db-client/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', env: { browser: true, es2021: true, }, plugins: ['@typescript-eslint', 'babel', 'react-hooks', 'react'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react/recommended', // 'airbnb-base', // airbnb-base中已经包含了eslint-plugin-import // 'prettier', // 使得eslint中的样式规范失效,遵循prettier中的样式规范 // 'prettier/@typescript-eslint', // 使得@typescript-eslint中的样式规范失效,遵循prettier中的样式规范 ], overrides: [ { env: { node: true, }, files: ['.eslintrc.{js,cjs}'], // parserOptions: { sourceType: 'script', }, }, ], parserOptions: { ecmaVersion: 'latest', sourceType: 'module', }, ignorePatterns: ['src/main'], rules: { 'func-names': 0, // 函数表达式必须有名字 'one-var': [1, 'never'], // 连续声明 'prefer-const': 1, // 首选const 'no-unused-expressions': 0, // 禁止无用的表达式 'new-cap': 2, // 构造函数首字母大写 'prefer-arrow-callback': 2, // 首选箭头函数 'arrow-body-style': 0, // 箭头函数体使用大括号 'max-len': [ // 一行最大长度 1, { code: 120, ignoreStrings: true, ignoreUrls: true, ignoreRegExpLiterals: true, }, ], 'consistent-return': 'off', // return 后面是否允许省略 'default-case': 2, // switch 语句必须有 default 'prefer-rest-params': 2, // 必须使用解构 ...args 来代替 arguments 'no-script-url': 0, // 禁止使用 javascript:void(0) // 'no-console': [ // 禁止使用 console // 2, // { // allow: ['info', 'error', 'warn'], // }, // ], 'no-duplicate-imports': [2], // 禁止重复 import 'newline-per-chained-call': 2, // 链式调用必须换行 // 'no-underscore-dangle': 2, // 禁止标识符中有悬空下划线 'eol-last': 2, // 文件以单一的换行符结束 'no-useless-rename': 2, // 禁止无用的重命名 'no-undef': 0, // 禁止使用未定义的变量 'class-methods-use-this': 0, // class 的非静态方法必须包含 this 'prefer-destructuring': 0, // 优先使用数组和对象解构 'no-unused-vars': 0, // 禁止未使用过的变量 '@typescript-eslint/no-unused-vars': 1, // 禁止未使用过的变量 'react/self-closing-comp': 2, // 非单行 JSX 必须使用括号包裹 'react/jsx-indent-props': [2, 2], // jsx props 缩进 'no-plusplus': 0, // 禁止使用 ++,-- 'react/jsx-uses-vars': 1, // jsx 文件中禁止使用变量 // 'react/no-multi-comp': [ // 禁止一个文件中定义多个组件 // 2, // { // ignoreStateless: true, // }, // ], 'react/jsx-uses-react': 2, // jsx 文件中禁止使用 React 'react/react-in-jsx-scope': 2, // jsx 文件中禁止使用 React 'react/sort-comp': 1, // 组件内方法顺序 'react/jsx-tag-spacing': 2, // jsx 中的属性禁止使用空格 'react/jsx-no-bind': 0, // jsx 中禁止使用 bind 'react/jsx-closing-bracket-location': 2, // jsx 中的右括号必须换行 'react/prefer-stateless-function': 0, // 优先使用无状态组件 'react/display-name': 0, // 组件必须写 displayName 'react/prop-types': 0, // 组件必须写 propTypes 'import/prefer-default-export': 0, // 优先使用 export default '@typescript-eslint/no-var-requires': 2, // 禁止 require() 使用表达式 'no-use-before-define': 0, // 禁止定义前使用 '@typescript-eslint/no-use-before-define': [ // 禁止定义前使用 0, // { // functions: false, // }, ], '@typescript-eslint/explicit-function-return-type': 0, // 函数必须有返回值 '@typescript-eslint/interface-name-prefix': 0, // 接口名称必须以 I 开头 '@typescript-eslint/explicit-module-boundary-types': 0, // 导出函数和类的公共方法必须声明返回类型 'no-shadow': 0, // 禁止变量名与上层作用域内的定义过的变量重复 '@typescript-eslint/no-shadow': 1, // 禁止变量名与上层作用域内的定义过的变量重复TODO: 为2是不是好点? 'no-invalid-this': 0, // 禁止 this 关键字出现在类和类对象之外 'no-await-in-loop': 'off', // 禁止在循环中出现 await 'array-callback-return': 'off', // 数组方法的回调函数中必须有 return 语句 'no-restricted-syntax': 'off', // 禁止使用特定的语法 '@typescript-eslint/no-explicit-any': 0, // 禁止使用 any 'import/no-extraneous-dependencies': 0, // 禁止使用无关的 package 'import/no-unresolved': 0, // 禁止使用无关的 package '@typescript-eslint/explicit-member-accessibility': 0, // 类的成员之间是否需要空行 '@typescript-eslint/no-object-literal-type-assertion': 0, // 禁止使用 as Type 'react/no-find-dom-node': 0, // 禁止使用 findDOMNode 'no-param-reassign': [ // 禁止对函数参数再赋值 2, { props: false, }, ], 'arrow-parens': 0, // 箭头函数参数括号 indent: 0, // 缩进 'operator-linebreak': [0], // 换行符位置 'max-classes-per-file': [2, 10], // 一个文件最多定义几个类 '@typescript-eslint/no-empty-function': [0], // 禁止空函数 'import/extensions': 0, // 禁止导入文件时带上文件后缀 }, }; ================================================ FILE: chat2db-client/.gitignore ================================================ /node_modules /.env.local /.umirc.local.ts /config/config.local.ts /src/.umi /src/.umi-production /src/.umi-test /src/main/node_modules /src/main/dist /dist .swc ./yarn-error.log /release /static /versions ================================================ FILE: chat2db-client/.npmrc ================================================ registry=https://registry.npmmirror.com/ ================================================ FILE: chat2db-client/.prettierignore ================================================ node_modules .umi .umi-production ================================================ FILE: chat2db-client/.prettierrc ================================================ { "printWidth": 120, "singleQuote": true, "trailingComma": "all", "proseWrap": "never", "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }], "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"] } ================================================ FILE: chat2db-client/.umirc.prod.desktop.ts ================================================ import { extractYarnConfig } from './src/utils/webpack'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const yarn_config = extractYarnConfig(process.argv); const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); }; export default defineConfig({ history: { type: 'hash', }, publicPath: './', chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, }, headScripts: [ `window.dataLayer = window.dataLayer || []; function gtag() { window.dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-V8M4E5SF61', { platform: 'DESKTOP', version: '${yarn_config['app_version']}' });`, ], }); ================================================ FILE: chat2db-client/.umirc.prod.ts ================================================ import { defineConfig } from 'umi'; import { extractYarnConfig } from './src/utils/webpack'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const yarn_config = extractYarnConfig(process.argv); const publicPath = yarn_config.public_path || './static/front/'; const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); }; export default defineConfig({ publicPath: publicPath, chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, }, headScripts: [ `window.dataLayer = window.dataLayer || []; function gtag() { window.dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-V8M4E5SF61', { platform: 'WEB', version: '${yarn_config['app_version']}' });`, ], }); ================================================ FILE: chat2db-client/.umirc.ts ================================================ import { defineConfig } from 'umi'; import { extractYarnConfig, transitionTimezoneTimestamp } from './src/utils/webpack'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); // yarn run build --app_port=xx 获取打包时命令行传入的参数 const yarn_config = extractYarnConfig(process.argv); const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); }; export default defineConfig({ title: 'Chat2DB', base: '/', publicPath: '/', hash: true, routes: [ { path: '/', component: '@/layouts/GlobalLayout', routes: [ { path: '/login', component: '@/pages/login', }, { path: '/demo', component: '@/pages/demo', }, { path: '/connections', component: 'main', }, { path: '/dashboard', component: 'main', }, { path: '/team', component: 'main', }, { path: '/workspace', component: 'main', }, { path: '/', component: 'main', }, ], }, ], npmClient: 'yarn', dva: {}, plugins: ['@umijs/plugins/dist/dva'], chainWebpack, proxy: { '/api': { target: 'http://127.0.0.1:10821', changeOrigin: true, }, '/client/remaininguses/': { target: 'http://127.0.0.1:1889', changeOrigin: true, }, }, targets: { chrome: 80, }, // links: [{ // rel: 'manifest', // href: 'manifest.json', // }], links: [{ rel: 'icon', type: 'image/ico', sizes: '32x32', href: '/static/front/logo.ico' }], headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v4') { localStorage.clear(); localStorage.setItem('app-local-storage-versions', 'v4'); }`, // `if (window.electronApi) { window.electronApi.startServerForSpawn() }`, // `if ("serviceWorker" in navigator) { // window.addEventListener("load", function () { // navigator.serviceWorker // .register("sw.js") // .then(res => console.log("service worker registered")) // .catch(err => console.log("service worker not registered", err)); // }) // }`, // `var deferredPrompt = null; // window.addEventListener("beforeinstallprompt", e => { // e.preventDefault(); // deferredPrompt = e; // }); // window.addEventListener("appinstalled", () => { // deferredPrompt = null; // })`, { src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', async: true, }, // `window.dataLayer = window.dataLayer || []; // function gtag() { // window.dataLayer.push(arguments); // } // gtag('js', new Date()); // gtag('config', 'G-V8M4E5SF61', { // platform: 'WEB', // version: '1.0.0' // });`, ], favicons: ['logo.ico'], define: { __ENV__: process.env.UMI_ENV, __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), __APP_VERSION__: yarn_config.app_version || '0.0.0', __APP_PORT__: yarn_config.app_port, }, esbuildMinifyIIFE: true, }); ================================================ FILE: chat2db-client/.vscode/settings.json ================================================ { "workbench.colorTheme": "One Dark Pro", "workbench.iconTheme": "vscode-icons-mac", "editor.fontSize": 14, "editor.tabSize": 2, // 自动格式化 "editor.formatOnType": true, "editor.formatOnSave": true, "prettier.bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }" "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[less]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "explorer.confirmDelete": true, "emmet.includeLanguages": { "javascript": "javascriptreact" }, "git.mergeEditor": false, "cSpell.words": [ "ahooks", "antd", "Appstore", "asar", "AZUREAI", "bgcolor", "Cascader", "charsets", "chatgpt", "CLICKHOUSE", "Consolas", "datas", "datasource", "DATETIME", "DBAI", "dbhub", "Dmaven", "echart", "echarts", "favicons", "findstr", "fulltext", "gtag", "hexi", "hljs", "icns", "Iconfont", "IIFE", "indexs", "JDBC", "KEYPAIR", "KINGBASE", "linebreak", "lsof", "MARIADB", "Mddhhmmss", "Menlo", "netstat", "NOCASE", "nsis", "OCEANBASE", "OPENAI", "packagejson", "Parens", "partialize", "pgsql", "plusplus", "pnpm", "POSTGRESQL", "Prec", "remaininguses", "RESTAI", "RTRIM", "scrollbar", "Sercurity", "sortablejs", "SQLSERVER", "tailwindcss", "Tigger", "ueabe", "ueabf", "ueac", "umijs", "USERANDPASSWORD", "uuidv", "VARCHAR", "VIEWCOLUMN", "VIEWCOLUMNS", "webp", "wireframe", "Wppk", "yapi", "zustand" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" } ================================================ FILE: chat2db-client/mock/sqlResult.json ================================================ { "success": true, "errorCode": null, "errorMessage": null, "data": [ { "sql": "SELECT *\nFROM students\nLIMIT 500", "description": "执行成功", "message": null, "success": true, "headerList": [ { "dataType": "NUMERIC", "name": "id" }, { "dataType": "STRING", "name": "name" }, { "dataType": "STRING", "name": "gender" }, { "dataType": "DATETIME", "name": "birthday" }, { "dataType": "STRING", "name": "address" }, { "dataType": "STRING", "name": "phone" }, { "dataType": "STRING", "name": "email" }, { "dataType": "DATETIME", "name": "create_time" }, { "dataType": "DATETIME", "name": "update_time" } ], "dataList": [ [ "1", "张三", "男", null, "北京市海淀区", "12345678901", "zhangsan@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "2", "李四", "男", null, "上海市浦东新区", "12345678902", "lisi@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "3", "王五", "女", null, "广州市天河区", "12345678903", "wangwu@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "4", "赵六", "男", null, "深圳市南山区", "12345678904", "zhaoliu@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "5", "陈七", "女", null, "武汉市江汉区", "12345678905", "chenqi@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "6", "刘八", "男", null, "成都市高新区", "12345678906", "liuba@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "7", "魏九", "女", null, "重庆市渝北区", "12345678907", "weijiu@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "8", "孙十", "男", null, "南京市鼓楼区", "12345678908", "sunshi@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "9", "郑十一", "男", null, "西安市雁塔区", "12345678909", "zhengshiyi@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ], [ "10", "许十二", "女", null, "苏州市姑苏区", "12345678910", "xushier@example.com", "2023-05-31 10:41:56.000", "2023-05-31 10:41:56.000" ] ], "sqlType": "SELECT", "hasNextPage": false, "pageNo": 1, "pageSize": 500, "duration": 6 } ], "traceId": null } ================================================ FILE: chat2db-client/package.json ================================================ { "name": "chat2db", "version": "1.0.0", "private": true, "repository": { "type": "git", "url": "https://github.com/chat2db/Chat2DB" }, "author": "fjy, hexi", "main": "src/main/main.js", "scripts": { "build": "npm run build:web && npm run build:main", "build:desktop": "npm run build:web:desktop && npm run build:main:prod", "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} cross-env UMI_PublicPath=${npm_config_public_path} umi build", "postinstall": "umi setup", "lint": "umi lint", "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", "start:web": "cross-env UMI_ENV=local HMR=none cross-env APP_VERSION=${npm_config_app_version} umi dev", "start:web:hot": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" }, "dependencies": { "@dnd-kit/modifiers": "^6.0.1", "ahooks": "^3.7.8", "ali-react-table": "^2.6.1", "antd": "^5.12.1", "copy-to-clipboard": "^3.3.3", "echarts": "^5.4.2", "echarts-for-react": "^3.0.2", "event-source-polyfill": "^1.0.31", "highlight.js": "^11.9.0", "lodash": "^4.17.21", "lucide-react": "^0.365.0", "markdown-it-link-attributes": "^4.0.1", "monaco-editor": "^0.44.0", "monaco-editor-esm-webpack-plugin": "^2.1.0", "monaco-editor-webpack-plugin": "^7.0.1", "react-monaco-editor": "^0.54.0", "react-sortablejs": "^6.1.4", "sql-formatter": "^13.0.4", "styled-components": "^6.0.1", "umi": "^4.0.87", "umi-request": "^1.4.0", "uuid": "^9.0.0", "zustand": "^4.4.4" }, "devDependencies": { "@types/event-source-polyfill": "^1.0.1", "@types/lodash": "^4.14.195", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", "@types/uuid": "^9.0.1", "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", "electron": "^22.3.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "eslint": "^8.49.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-webpack": "^0.13.7", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-import": "^2.28.1", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "is-electron": "^2.2.2", "prettier": "^2", "prettier-plugin-organize-imports": "^2", "prettier-plugin-packagejson": "^2", "tailwindcss": "^3", "typescript": "^5.0.3" }, "peerDependencies": { "react": "^16.8.0", "react-dom": "^16.8.0" }, "engines": { "node": ">=16" }, "build": { "appId": "com.chat2db", "directories": { "output": "release/" }, "productName": "Chat2DB", "asar": false, "files": [ "dist/**/*", "src/main", "static/", "versions/**/*", "package.json", "!node_modules/**/*" ], "nsis": { "oneClick": false, "perMachine": false, "allowElevation": true, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "deleteAppDataOnUninstall": false, "shortcutName": "Chat2DB" }, "mac": { "icon": "src/assets/logo/logo.icns", "target": [ "zip", "dmg" ] }, "win": { "target": [ { "target": "nsis" } ], "publisherName": "Chat2DB", "icon": "src/assets/logo/logo.ico" }, "linux": { "maintainer": "Chat2DB, huanyueyaoqin@qq.com", "category": "Network;", "target": [ "AppImage" ] } } } ================================================ FILE: chat2db-client/readme.md ================================================ ## 技术选型 1. 脚手架:umi v4 2. 组件库:antd v5 3. 状态管理库 dva 4. 图表库 5. 国际化 目录结构 tree ./ -L 2 -I node_modules ## 启动项目 强制使用 yarn,因为环境变量、lock 文件只维护了 yarn,npm/pnpm 可能会产生意想不到的 bug node 版本要求 16 以上 `npm i -g yarn` `yarn` `yarn run build:web:prod` `cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front` (复制打包结果到指定目录。windows 可能命令不一样,可以手动复制下) 之后就可以启动后端了 `mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml` 启动前端项目调试 `yarn run start:web` 注意:因为 electron 包比较难下载,如果 yarn 时 electron 下载失败或超时,可以删除掉 chat2db-client/package.json 下的 electron,再次 yarn ## TS书写规范 1. 所有的interface 与 type 必须已I开头 `interface IState { name: string }` // good `interface State { name: string }` // bad ## 如何在 js 与 css 中使用颜色 具体转换在 /theme/index.ts 中的 injectThemeVar - js 在 window.\_AppThemePack 中去取 eg:`window._AppThemePack.controlItemBgActive` // good - css eg: `background: var(--control-item-bg-active)` // good - css `color: #fff` // bad ## 如何使用国际化 所有 key 参考格式为 `模块名称.文案类型.文案描述`。若文案包含可变部分,可使用 `{1}`、`{2}`、`{3}` 代替。 `src/i18n/index.ts` 中默认导出 `i18n` 转换方法,可以将 key 转换为对应的实际文案。文案中的 `{1}` 将被替换为第二个入参,以此类推。例如: ```tsx // 'home.tip.welcome': '欢迎您,{1}!' i18n('home.tip.welcome', user.name); // => '欢迎您,张三!' ``` 也可以使用 `src/i18n/index.ts` 中导出的 `i18nElement` 方法,可以将文案中的占位符替换为 JSX 元素。例如: ```tsx i18nElement('home.tip.welcome', {user.name}); // => <>欢迎您,张三!' ``` ```code ├── dist │ ├── index.html │ ├── layouts__index.async.js │ ├── layouts__index.chunk.css │ ├── p__docs.async.js │ ├── p__index.async.js │ └── umi.js ├── package.json ├── readme.md ├── release │ ├── Chat2DB-1.0.0-arm64-mac.zip │ ├── Chat2DB-1.0.0-arm64-mac.zip.blockmap │ ├── Chat2DB-1.0.0-arm64.dmg │ ├── Chat2DB-1.0.0-arm64.dmg.blockmap │ ├── builder-debug.yml │ ├── builder-effective-config.yaml │ └── mac-arm64 ├── src │ ├── assets │ ├── blocks │ ├── components │ ├── config │ ├── constant │ ├── layouts │ ├── locales │ ├── main │ ├── models │ ├── pages │ ├── typings │ └── utils ├── tsconfig.json ├── typings.d.ts └── yarn.lock ``` ================================================ FILE: chat2db-client/src/assets/font/demo.css ================================================ /* Logo 字体 */ @font-face { font-family: "iconfont logo"; src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); } .logo { font-family: "iconfont logo"; font-size: 160px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* tabs */ .nav-tabs { position: relative; } .nav-tabs .nav-more { position: absolute; right: 0; bottom: 0; height: 42px; line-height: 42px; color: #666; } #tabs { border-bottom: 1px solid #eee; } #tabs li { cursor: pointer; width: 100px; height: 40px; line-height: 40px; text-align: center; font-size: 16px; border-bottom: 2px solid transparent; position: relative; z-index: 1; margin-bottom: -1px; color: #666; } #tabs .active { border-bottom-color: #f00; color: #222; } .tab-container .content { display: none; } /* 页面布局 */ .main { padding: 30px 100px; width: 960px; margin: 0 auto; } .main .logo { color: #333; text-align: left; margin-bottom: 30px; line-height: 1; height: 110px; margin-top: -50px; overflow: hidden; *zoom: 1; } .main .logo a { font-size: 160px; color: #333; } .helps { margin-top: 40px; } .helps pre { padding: 20px; margin: 10px 0; border: solid 1px #e7e1cd; background-color: #fffdef; overflow: auto; } .icon_lists { width: 100% !important; overflow: hidden; *zoom: 1; } .icon_lists li { width: 100px; margin-bottom: 10px; margin-right: 20px; text-align: center; list-style: none !important; cursor: default; } .icon_lists li .code-name { line-height: 1.2; } .icon_lists .icon { display: block; height: 100px; line-height: 100px; font-size: 42px; margin: 10px auto; color: #333; -webkit-transition: font-size 0.25s linear, width 0.25s linear; -moz-transition: font-size 0.25s linear, width 0.25s linear; transition: font-size 0.25s linear, width 0.25s linear; } .icon_lists .icon:hover { font-size: 100px; } .icon_lists .svg-icon { /* 通过设置 font-size 来改变图标大小 */ width: 1em; /* 图标和文字相邻时,垂直对齐 */ vertical-align: -0.15em; /* 通过设置 color 来改变 SVG 的颜色/fill */ fill: currentColor; /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 normalize.css 中也包含这行 */ overflow: hidden; } .icon_lists li .name, .icon_lists li .code-name { color: #666; } /* markdown 样式 */ .markdown { color: #666; font-size: 14px; line-height: 1.8; } .highlight { line-height: 1.5; } .markdown img { vertical-align: middle; max-width: 100%; } .markdown h1 { color: #404040; font-weight: 500; line-height: 40px; margin-bottom: 24px; } .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { color: #404040; margin: 1.6em 0 0.6em 0; font-weight: 500; clear: both; } .markdown h1 { font-size: 28px; } .markdown h2 { font-size: 22px; } .markdown h3 { font-size: 16px; } .markdown h4 { font-size: 14px; } .markdown h5 { font-size: 12px; } .markdown h6 { font-size: 12px; } .markdown hr { height: 1px; border: 0; background: #e9e9e9; margin: 16px 0; clear: both; } .markdown p { margin: 1em 0; } .markdown>p, .markdown>blockquote, .markdown>.highlight, .markdown>ol, .markdown>ul { width: 80%; } .markdown ul>li { list-style: circle; } .markdown>ul li, .markdown blockquote ul>li { margin-left: 20px; padding-left: 4px; } .markdown>ul li p, .markdown>ol li p { margin: 0.6em 0; } .markdown ol>li { list-style: decimal; } .markdown>ol li, .markdown blockquote ol>li { margin-left: 20px; padding-left: 4px; } .markdown code { margin: 0 3px; padding: 0 5px; background: #eee; border-radius: 3px; } .markdown strong, .markdown b { font-weight: 600; } .markdown>table { border-collapse: collapse; border-spacing: 0px; empty-cells: show; border: 1px solid #e9e9e9; width: 95%; margin-bottom: 24px; } .markdown>table th { white-space: nowrap; color: #333; font-weight: 600; } .markdown>table th, .markdown>table td { border: 1px solid #e9e9e9; padding: 8px 16px; text-align: left; } .markdown>table th { background: #F7F7F7; } .markdown blockquote { font-size: 90%; color: #999; border-left: 4px solid #e9e9e9; padding-left: 0.8em; margin: 1em 0; } .markdown blockquote p { margin: 0; } .markdown .anchor { opacity: 0; transition: opacity 0.3s ease; margin-left: 8px; } .markdown .waiting { color: #ccc; } .markdown h1:hover .anchor, .markdown h2:hover .anchor, .markdown h3:hover .anchor, .markdown h4:hover .anchor, .markdown h5:hover .anchor, .markdown h6:hover .anchor { opacity: 1; display: inline-block; } .markdown>br, .markdown>p>br { clear: both; } .hljs { display: block; background: white; padding: 0.5em; color: #333333; overflow-x: auto; } .hljs-comment, .hljs-meta { color: #969896; } .hljs-string, .hljs-variable, .hljs-template-variable, .hljs-strong, .hljs-emphasis, .hljs-quote { color: #df5000; } .hljs-keyword, .hljs-selector-tag, .hljs-type { color: #a71d5d; } .hljs-literal, .hljs-symbol, .hljs-bullet, .hljs-attribute { color: #0086b3; } .hljs-section, .hljs-name { color: #63a35c; } .hljs-tag { color: #333333; } .hljs-title, .hljs-attr, .hljs-selector-id, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo { color: #795da3; } .hljs-addition { color: #55a532; background-color: #eaffea; } .hljs-deletion { color: #bd2c00; background-color: #ffecec; } .hljs-link { text-decoration: underline; } /* 代码高亮 */ /* PrismJS 1.15.0 https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ /** * prism.js default theme for JavaScript, CSS and HTML * Based on dabblet (http://dabblet.com) * @author Lea Verou */ code[class*="language-"], pre[class*="language-"] { color: black; background: none; text-shadow: 0 1px white; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { text-shadow: none; background: #b3d4fc; } pre[class*="language-"]::selection, pre[class*="language-"] ::selection, code[class*="language-"]::selection, code[class*="language-"] ::selection { text-shadow: none; background: #b3d4fc; } @media print { code[class*="language-"], pre[class*="language-"] { text-shadow: none; } } /* Code blocks */ pre[class*="language-"] { padding: 1em; margin: .5em 0; overflow: auto; } :not(pre)>code[class*="language-"], pre[class*="language-"] { background: #f5f2f0; } /* Inline code */ :not(pre)>code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: slategray; } .token.punctuation { color: #999; } .namespace { opacity: .7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #905; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #690; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #9a6e3a; background: hsla(0, 0%, 100%, .5); } .token.atrule, .token.attr-value, .token.keyword { color: #07a; } .token.function, .token.class-name { color: #DD4A68; } .token.regex, .token.important, .token.variable { color: #e90; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.entity { cursor: help; } ================================================ FILE: chat2db-client/src/assets/font/demo_index.html ================================================ iconfont Demo

  • right_on_5
    &#xe672;
  • right_off_5-01
    &#xe673;
  • left_on_2
    &#xe674;
  • left_off
    &#xe670;
  • minimize21
    &#xe671;
  • restore
    &#xe66b;
  • resize
    &#xe66e;
  • close
    &#xe66f;
  • 筛选
    &#xe66a;
  • 排序
    &#xe69a;
  • 305信息-线性圆框
    &#xe8e8;
  • 加号
    &#xe726;
  • 列表
    &#xec6b;
  • 减去
    &#xe65d;
  • database
    &#xe669;
  • 筛选
    &#xe888;
  • 刷新
    &#xe668;
  • 加号_o
    &#xeb78;
  • 数据库_jurassic
    &#xe6a9;
  • 权限
    &#xe667;
  • sharpicons_add-database
    &#xe816;
  • 组织管理
    &#xe663;
  • 空间
    &#xe691;
  • 𐂾
    下箭头-copy
    &#x100be;
  • 查看
    &#xe788;
  • clone
    &#xe8db;
  • 提交
    &#xe687;
  • 查看
    &#xe665;
  • 复制
    &#xec7a;
  • icon_answer
    &#xe686;
  • icon_question
    &#xe6a8;
  • 𐂽
    发送
    &#x100bd;
  • 重启
    &#xe662;
  • 提醒
    &#xe6cc;
  • 提醒
    &#xe661;
  • 提醒
    &#xe716;
  • 升级
    &#xe69c;
  • 全局_升级
    &#xe659;
  • 关于我们
    &#xe65c;
  • ico版本更新
    &#xe67d;
  • 对话气泡
    &#xe657;
  • 角色权限
    &#xe658;
  • preview
    &#xe654;
  • 导入
    &#xe653;
  • 终止
    &#xe652;
  • 退出
    &#xe6b2;
  • 控桩终端
    &#xe6bb;
  • 撤销
    &#xe6e2;
  • 向上
    &#xe650;
  • 查看
    &#xe651;
  • 编辑数据_编辑录入操作_jurassic
    &#xe6f2;
  • 编辑表格_编辑录入操作_jurassic
    &#xe6f3;
  • 报表数据录入
    &#xe7b5;
  • 播放5
    &#xe656;
  • 清空@3x
    &#xe64f;
  • 删除
    &#xe64e;
  • new-document-worksheet
    &#xe792;
  • file-excel
    &#xe7b7;
  • file-markdown
    &#xe7b8;
  • file-word
    &#xe7ba;
  • HTML5
    &#xe87d;
  • HTML
    &#xe64d;
  • pdf
    &#xe67a;
  • 个人用户
    &#xe64c;
  • 后台管理
    &#xe64b;
  • 字体代码
    &#xec83;
  • 版本
    &#xe70c;
  • 车位管理
    &#xe73c;
  • dictate
    &#xe64a;
  • circle-f
    &#xe76a;
  • 图表-函数
    &#xe6fd;
  • 视图管理器
    &#xe647;
  • 回车
    &#xe643;
  • 缺省
    &#xe642;
  • 进入箭头
    &#xe88e;
  • 右箭头
    &#xe641;
  • 向右箭头
    &#xe660;
  • 数据源
    &#xe640;
  • question
    &#xe67c;
  • 星星-copy
    &#xe63a;
  • 控制台
    &#xe69f;
  • 星系
    &#xe639;
  • 暂无数据 (1)
    &#xe638;
  • 开始
    &#xe637;
  • 关闭
    &#xe634;
  • 下箭头
    &#xeb6d;
  • more
    &#xe633;
  • 设置
    &#xe630;
  • 对话-未选
    &#xe628;
  • 图表-未选
    &#xe629;
  • 编组 13备份 3
    &#xe62b;
  • 编组备份
    &#xe616;
  • 表格
    &#xe618;
  • 收藏 (1)
    &#xe61d;
  • guthub-未选
    &#xe621;
  • 数据-未选
    &#xe622;
  • 编组 4
    &#xe624;
  • 编组 14备份
    &#xe627;
  • guthub-未选
    &#xe615;
  • 24gl-folderMinus
    &#xeabe;
  • 
    24gl-folderOpen
    &#xeabf;
  • 24gf-folderOpen
    &#xeac7;
  • 云数据库
    &#xe744;
  • 报表
    &#xe612;
  • 工作台
    &#xe614;
  • mongodb
    &#xec21;
  • Redis
    &#xe6a2;
  • HIVE_2
    &#xe60e;
  • Kingbase
    &#xe6a0;
  • 仪表盘
    &#xe60d;
  • presto
    &#xe60b;
  • DB2
    &#xe60a;
  • oceanbase
    &#xe982;
  • 达梦
    &#xe655;
  • proxy
    &#xe63f;
  • openai
    &#xe646;
  • 关于
    &#xe60c;
  • 衣服
    &#xe666;
  • 数据库
    &#xe609;
  • 数据源配置
    &#xe649;
  • 服务器_数据库_jurassic
    &#xe6a6;
  • 数据库
    &#xe607;
  • 数据库
    &#xe625;
  • 数据库数据
    &#xe63c;
  • 数据库
    &#xe636;
  • 配置数据源
    &#xe62f;
  • SQL历史查询
    &#xe80a;
  • 重命名
    &#xe623;
  • ico_数据查询与统计_预约情况查询
    &#xe8ff;
  • clickhouse-云数据库ClickHouse
    &#xe8f4;
  • rds_mariadb
    &#xe6f5;
  • 减少减去减号
    &#xe62a;
  • sqlserver
    &#xe664;
  • sqlite
    &#xe65a;
  • 缺省页_暂无数据
    &#xe760;
  • 未完成
    &#xe755;
  • 完成-01
    &#xe62e;
  • 成功
    &#xe620;
  • 机器人
    &#xe70e;
  • 换一换
    &#xe635;
  • icon_infomation
    &#xe65b;
  • key
    &#xe775;
  • mysql
    &#xec6d;
  • oracle
    &#xec48;
  • postgresql
    &#xec5d;
  • h2
    &#xe61c;
  • cc-schema
    &#xe696;
  • 新建表格
    &#xe6b6;
  • export
    &#xe613;
  • 角色管理
    &#xe66d;
  • console
    &#xe619;
  • 24gf-folderMinus
    &#xeac5;
  • 查看
    &#xe606;
  • 复制_o
    &#xeb4e;
  • 执行
    &#xe626;
  • m-格式化文字
    &#xe7f8;
  • github-fill
    &#xe885;
  • 保存
    &#xe645;
  • 箭头_向左两次_o
    &#xeb93;
  • 新建窗口
    &#xe603;
  • loading
    &#xe6cd;
  • 链接克隆
    &#xe6ca;
  • SQL升级文件
    &#xe63b;
  • sql
    &#xe610;
  • 连接流
    &#xec57;
  • 跳转/退出
    &#xe685;
  • key
    &#xe648;
  • 播放记录
    &#xe8ad;
  • 成功
    &#xe605;
  • 失败
    &#xe87c;
  • 收回 上下
    &#xe790;
  • 展开 上下
    &#xe7b1;
  • 数据库
    &#xe62c;
  • 保存
    &#xe936;
  • 查询
    &#xec4c;
  • 对勾
    &#xe61f;
  • check
    &#xe617;
  • 概览
    &#xe632;
  • 概览
    &#xe63d;
  • 编辑
    &#xe602;
  • 刷新
    &#xec08;
  • 菜单/列表
    &#xe611;
  • 表格
    &#xe63e;
  • 展开
    &#xe65f;
  • 收起
    &#xe61e;
  • 主题_o
    &#xeb6f;
  • 断开连接
    &#xe65e;
  • 修改
    &#xe60f;
  • 删除
    &#xe604;
  • 更多
    &#xe601;
  • 减少
    &#xe644;
  • &#xe61b;
  • 加号
    &#xe631;
  • arrow drop down
    &#xe608;
  • search
    &#xe600;
  • download
    &#xe66c;
  • 向右箭头
    &#xe79c;
  • 删除线型
    &#xe6a7;
  • cross
    &#xec8e;
  • 刷新
    &#xe62d;
  • 提醒
    &#xe913;
  • 138设置、系统设置、功能设置、属性
    &#xe795;
  • 执行sql脚本
    &#xe759;
  • 虚拟数据库管理
    &#xe61a;

Unicode 引用


Unicode 是字体在网页端最原始的应用方式,特点是:

  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • 默认情况下不支持多色,直接添加多色图标会自动去色。

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

Unicode 使用步骤如下:

第一步:拷贝项目下面生成的 @font-face

@font-face {
  font-family: 'iconfont';
  src: url('iconfont.woff2?t=1704794525154') format('woff2'),
       url('iconfont.woff?t=1704794525154') format('woff'),
       url('iconfont.ttf?t=1704794525154') format('truetype');
}

第二步:定义使用 iconfont 的样式

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

第三步:挑选相应图标并获取字体编码,应用于页面

<span class="iconfont">&#x33;</span>

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

  • right_on_5
    .icon-right_on_5
  • right_off_5-01
    .icon-right_off_5-01
  • left_on_2
    .icon-a-left_on_huaban11
  • left_off
    .icon-a-left_off_huaban1
  • minimize21
    .icon-minimize21
  • restore
    .icon-restore_button2
  • resize
    .icon-resize_button2
  • close
    .icon-close_button2
  • 筛选
    .icon-shaixuan
  • 排序
    .icon-a-44tubiao-122
  • 305信息-线性圆框
    .icon-xinxi-xianxingyuankuang
  • 加号
    .icon-jiahao
  • 列表
    .icon-liebiao
  • 减去
    .icon-jianqu
  • database
    .icon-database
  • 筛选
    .icon-shaixuan1
  • 刷新
    .icon-shuaxin2
  • 加号_o
    .icon-jiahao_o
  • 数据库_jurassic
    .icon-jurassic_data
  • 权限
    .icon-quanxian
  • sharpicons_add-database
    .icon-sharpicons_add-database
  • 组织管理
    .icon-zuzhiguanli-
  • 空间
    .icon-moxing-miaobian
  • 下箭头-copy
    .icon-xiajiantou1-copy
  • 查看
    .icon-chakan2
  • clone
    .icon-clone
  • 提交
    .icon-tijiao
  • 查看
    .icon-chakan1
  • 复制
    .icon-fuzhi
  • icon_answer
    .icon-icon_answer
  • icon_question
    .icon-icon_question
  • 发送
    .icon-fasong
  • 重启
    .icon-zhongqi
  • 提醒
    .icon-tixing2
  • 提醒
    .icon-tixing3
  • 提醒
    .icon-tixing1
  • 升级
    .icon-shengji
  • 全局_升级
    .icon-quanju_shengji
  • 关于我们
    .icon-guanyuwomen1
  • ico版本更新
    .icon-icobanbengengxin
  • 对话气泡
    .icon-duihuaqipao
  • 角色权限
    .icon-jiaosequanxian
  • preview
    .icon-preview1
  • 导入
    .icon-daoru
  • 终止
    .icon-zhongzhi
  • 退出
    .icon-tuichu
  • 控桩终端
    .icon-kongzhuangzhongduan
  • 撤销
    .icon-chexiao1
  • 向上
    .icon-xiangshang
  • 查看
    .icon-chakan-copy
  • 编辑数据_编辑录入操作_jurassic
    .icon-jurassic_edit-data
  • 编辑表格_编辑录入操作_jurassic
    .icon-jurassic_edit-table
  • 报表数据录入
    .icon-baobiaoshujuluru
  • 播放5
    .icon-bofang5
  • 清空@3x
    .icon-a-qingkong3x
  • 删除
    .icon-shanchu
  • new-document-worksheet
    .icon-newdocumentworksheet
  • file-excel
    .icon-file-excel
  • file-markdown
    .icon-file-markdown
  • file-word
    .icon-file-word
  • HTML5
    .icon-HTML
  • HTML
    .icon-HTML1
  • pdf
    .icon-pdf
  • 个人用户
    .icon-gerenyonghu
  • 后台管理
    .icon-houtaiguanli
  • 字体代码
    .icon-zitidaima
  • 版本
    .icon-banben
  • 车位管理
    .icon-cheweiguanli
  • dictate
    .icon-dianzhelidaochu
  • circle-f
    .icon-circle-f
  • 图表-函数
    .icon-tubiao-hanshu
  • 视图管理器
    .icon-shituguanliqi
  • 回车
    .icon-huiche
  • 缺省
    .icon-quesheng
  • 进入箭头
    .icon-jinrujiantou
  • 右箭头
    .icon-youjiantou_huaban
  • 向右箭头
    .icon-xiangyoujiantou1
  • 数据源
    .icon-shujuyuan
  • question
    .icon-question
  • 星星-copy
    .icon-xingxing
  • 控制台
    .icon-kongzhitai
  • 星系
    .icon-xingxi
  • 暂无数据 (1)
    .icon-a-zanwushuju1
  • 开始
    .icon-kaishi
  • 关闭
    .icon-guanbi
  • 下箭头
    .icon-xiajiantou
  • more
    .icon-gengduo
  • 设置
    .icon-shezhi
  • 对话-未选
    .icon-duihua-weixuan
  • 图表-未选
    .icon-tubiao-weixuan
  • 编组 13备份 3
    .icon-a-bianzu13beifen3
  • 编组备份
    .icon-bianzubeifen
  • 表格
    .icon-biaoge1
  • 收藏 (1)
    .icon-a-shoucang1
  • guthub-未选
    .icon-guthub-weixuan1
  • 数据-未选
    .icon-shuju-weixuan
  • 编组 4
    .icon-a-bianzu4
  • 编组 14备份
    .icon-a-bianzu14beifen
  • guthub-未选
    .icon-guthub-weixuan
  • 24gl-folderMinus
    .icon-24gl-folderMinus
  • 24gl-folderOpen
    .icon-24gl-folderOpen
  • 24gf-folderOpen
    .icon-24gf-folderOpen
  • 云数据库
    .icon-yunshujuku
  • 报表
    .icon-baobiao
  • 工作台
    .icon-gongzuotai
  • mongodb
    .icon-mongodb
  • Redis
    .icon-Redis
  • HIVE_2
    .icon-HIVE
  • Kingbase
    .icon-Kingbase
  • 仪表盘
    .icon-yibiaopan
  • presto
    .icon-presto_sql
  • DB2
    .icon-shujukuleixingtubiao-kuozhan-
  • oceanbase
    .icon-oceanbase
  • 达梦
    .icon-dameng1
  • proxy
    .icon-proxy
  • openai
    .icon-openai
  • 关于
    .icon-guanyu
  • 衣服
    .icon-yifu
  • 数据库
    .icon-shujuku4
  • 数据源配置
    .icon-shujuyuanpeizhi
  • 服务器_数据库_jurassic
    .icon-jurassic_server
  • 数据库
    .icon-shujuku2
  • 数据库
    .icon-shujuku3
  • 数据库数据
    .icon-shujukushuju
  • 数据库
    .icon-shujuku1
  • 配置数据源
    .icon-peizhishujuyuan
  • SQL历史查询
    .icon-SQLlishichaxun
  • 重命名
    .icon-zhongmingming
  • ico_数据查询与统计_预约情况查询
    .icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun
  • clickhouse-云数据库ClickHouse
    .icon-clickhouse-yunshujukuClickHouse
  • rds_mariadb
    .icon-rds_mariadb
  • 减少减去减号
    .icon-jianshaojianqujianhao
  • sqlserver
    .icon-sqlserver
  • sqlite
    .icon-sqlite
  • 缺省页_暂无数据
    .icon-queshengye_zanwushuju
  • 未完成
    .icon-weiwancheng
  • 完成-01
    .icon-wancheng-
  • 成功
    .icon-chenggong1
  • 机器人
    .icon-jiqiren
  • 换一换
    .icon-huanyihuan
  • icon_infomation
    .icon-icon_infomation
  • key
    .icon-key1
  • mysql
    .icon-mysql
  • oracle
    .icon-oracle
  • postgresql
    .icon-postgresql
  • h2
    .icon-h2
  • cc-schema
    .icon-cc-schema
  • 新建表格
    .icon-xinjianbiaoge
  • export
    .icon-export
  • 角色管理
    .icon-jiaoseguanli
  • console
    .icon-console
  • 24gf-folderMinus
    .icon-24gf-folderMinus
  • 查看
    .icon-chakan
  • 复制_o
    .icon-fuzhi_o
  • 执行
    .icon-zhihang
  • m-格式化文字
    .icon-m-geshihuawenzi
  • github-fill
    .icon-github-fill
  • 保存
    .icon-baocun2
  • 箭头_向左两次_o
    .icon-jiantou_xiangzuoliangci_o
  • 新建窗口
    .icon-xinjianchuangkou
  • loading
    .icon-loading2
  • 链接克隆
    .icon-lianjiekelong
  • SQL升级文件
    .icon-SQLshengjiwenjian
  • sql
    .icon-sql
  • 连接流
    .icon-lianjieliu
  • 跳转/退出
    .icon-tiaozhuan
  • key
    .icon-key
  • 播放记录
    .icon-bofangjilu
  • 成功
    .icon-chenggong
  • 失败
    .icon-shibai
  • 收回 上下
    .icon-shouhuishangxia
  • 展开 上下
    .icon-zhankaishangxia
  • 数据库
    .icon-shujuku
  • 保存
    .icon-baocun
  • 查询
    .icon-chaxun
  • 对勾
    .icon-duigou11
  • check
    .icon-check1
  • 概览
    .icon-gailan
  • 概览
    .icon-huaban2
  • 编辑
    .icon-bianji
  • 刷新
    .icon-shuaxin1
  • 菜单/列表
    .icon-caidan
  • 表格
    .icon-biaoge
  • 展开
    .icon-zhankai
  • 收起
    .icon-shouqi
  • 主题_o
    .icon-zhuti_o
  • 断开连接
    .icon-duankailianjie
  • 修改
    .icon-xiugai
  • 删除
    .icon-delete
  • 更多
    .icon-gengduo1
  • 减少
    .icon-jianshao
  • .icon-jia
  • 加号
    .icon-hao
  • arrow drop down
    .icon-right
  • search
    .icon-search1
  • download
    .icon-download1
  • 向右箭头
    .icon-xiangyoujiantou
  • 删除线型
    .icon-shanchuxianxing
  • cross
    .icon-cross-copy
  • 刷新
    .icon-shuaxin
  • 提醒
    .icon-tixing
  • 138设置、系统设置、功能设置、属性
    .icon-shezhixitongshezhigongnengshezhishuxing
  • 执行sql脚本
    .icon-zhihangsqljiaoben
  • 虚拟数据库管理
    .icon-xunishujukuguanli

font-class 引用


font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

与 Unicode 使用方式相比,具有如下特点:

  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。

使用步骤如下:

第一步:引入项目下面生成的 fontclass 代码:

<link rel="stylesheet" href="./iconfont.css">

第二步:挑选相应图标并获取类名,应用于页面:

<span class="iconfont icon-xxx"></span>

" iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

  • right_on_5
    #icon-right_on_5
  • right_off_5-01
    #icon-right_off_5-01
  • left_on_2
    #icon-a-left_on_huaban11
  • left_off
    #icon-a-left_off_huaban1
  • minimize21
    #icon-minimize21
  • restore
    #icon-restore_button2
  • resize
    #icon-resize_button2
  • close
    #icon-close_button2
  • 筛选
    #icon-shaixuan
  • 排序
    #icon-a-44tubiao-122
  • 305信息-线性圆框
    #icon-xinxi-xianxingyuankuang
  • 加号
    #icon-jiahao
  • 列表
    #icon-liebiao
  • 减去
    #icon-jianqu
  • database
    #icon-database
  • 筛选
    #icon-shaixuan1
  • 刷新
    #icon-shuaxin2
  • 加号_o
    #icon-jiahao_o
  • 数据库_jurassic
    #icon-jurassic_data
  • 权限
    #icon-quanxian
  • sharpicons_add-database
    #icon-sharpicons_add-database
  • 组织管理
    #icon-zuzhiguanli-
  • 空间
    #icon-moxing-miaobian
  • 下箭头-copy
    #icon-xiajiantou1-copy
  • 查看
    #icon-chakan2
  • clone
    #icon-clone
  • 提交
    #icon-tijiao
  • 查看
    #icon-chakan1
  • 复制
    #icon-fuzhi
  • icon_answer
    #icon-icon_answer
  • icon_question
    #icon-icon_question
  • 发送
    #icon-fasong
  • 重启
    #icon-zhongqi
  • 提醒
    #icon-tixing2
  • 提醒
    #icon-tixing3
  • 提醒
    #icon-tixing1
  • 升级
    #icon-shengji
  • 全局_升级
    #icon-quanju_shengji
  • 关于我们
    #icon-guanyuwomen1
  • ico版本更新
    #icon-icobanbengengxin
  • 对话气泡
    #icon-duihuaqipao
  • 角色权限
    #icon-jiaosequanxian
  • preview
    #icon-preview1
  • 导入
    #icon-daoru
  • 终止
    #icon-zhongzhi
  • 退出
    #icon-tuichu
  • 控桩终端
    #icon-kongzhuangzhongduan
  • 撤销
    #icon-chexiao1
  • 向上
    #icon-xiangshang
  • 查看
    #icon-chakan-copy
  • 编辑数据_编辑录入操作_jurassic
    #icon-jurassic_edit-data
  • 编辑表格_编辑录入操作_jurassic
    #icon-jurassic_edit-table
  • 报表数据录入
    #icon-baobiaoshujuluru
  • 播放5
    #icon-bofang5
  • 清空@3x
    #icon-a-qingkong3x
  • 删除
    #icon-shanchu
  • new-document-worksheet
    #icon-newdocumentworksheet
  • file-excel
    #icon-file-excel
  • file-markdown
    #icon-file-markdown
  • file-word
    #icon-file-word
  • HTML5
    #icon-HTML
  • HTML
    #icon-HTML1
  • pdf
    #icon-pdf
  • 个人用户
    #icon-gerenyonghu
  • 后台管理
    #icon-houtaiguanli
  • 字体代码
    #icon-zitidaima
  • 版本
    #icon-banben
  • 车位管理
    #icon-cheweiguanli
  • dictate
    #icon-dianzhelidaochu
  • circle-f
    #icon-circle-f
  • 图表-函数
    #icon-tubiao-hanshu
  • 视图管理器
    #icon-shituguanliqi
  • 回车
    #icon-huiche
  • 缺省
    #icon-quesheng
  • 进入箭头
    #icon-jinrujiantou
  • 右箭头
    #icon-youjiantou_huaban
  • 向右箭头
    #icon-xiangyoujiantou1
  • 数据源
    #icon-shujuyuan
  • question
    #icon-question
  • 星星-copy
    #icon-xingxing
  • 控制台
    #icon-kongzhitai
  • 星系
    #icon-xingxi
  • 暂无数据 (1)
    #icon-a-zanwushuju1
  • 开始
    #icon-kaishi
  • 关闭
    #icon-guanbi
  • 下箭头
    #icon-xiajiantou
  • more
    #icon-gengduo
  • 设置
    #icon-shezhi
  • 对话-未选
    #icon-duihua-weixuan
  • 图表-未选
    #icon-tubiao-weixuan
  • 编组 13备份 3
    #icon-a-bianzu13beifen3
  • 编组备份
    #icon-bianzubeifen
  • 表格
    #icon-biaoge1
  • 收藏 (1)
    #icon-a-shoucang1
  • guthub-未选
    #icon-guthub-weixuan1
  • 数据-未选
    #icon-shuju-weixuan
  • 编组 4
    #icon-a-bianzu4
  • 编组 14备份
    #icon-a-bianzu14beifen
  • guthub-未选
    #icon-guthub-weixuan
  • 24gl-folderMinus
    #icon-24gl-folderMinus
  • 24gl-folderOpen
    #icon-24gl-folderOpen
  • 24gf-folderOpen
    #icon-24gf-folderOpen
  • 云数据库
    #icon-yunshujuku
  • 报表
    #icon-baobiao
  • 工作台
    #icon-gongzuotai
  • mongodb
    #icon-mongodb
  • Redis
    #icon-Redis
  • HIVE_2
    #icon-HIVE
  • Kingbase
    #icon-Kingbase
  • 仪表盘
    #icon-yibiaopan
  • presto
    #icon-presto_sql
  • DB2
    #icon-shujukuleixingtubiao-kuozhan-
  • oceanbase
    #icon-oceanbase
  • 达梦
    #icon-dameng1
  • proxy
    #icon-proxy
  • openai
    #icon-openai
  • 关于
    #icon-guanyu
  • 衣服
    #icon-yifu
  • 数据库
    #icon-shujuku4
  • 数据源配置
    #icon-shujuyuanpeizhi
  • 服务器_数据库_jurassic
    #icon-jurassic_server
  • 数据库
    #icon-shujuku2
  • 数据库
    #icon-shujuku3
  • 数据库数据
    #icon-shujukushuju
  • 数据库
    #icon-shujuku1
  • 配置数据源
    #icon-peizhishujuyuan
  • SQL历史查询
    #icon-SQLlishichaxun
  • 重命名
    #icon-zhongmingming
  • ico_数据查询与统计_预约情况查询
    #icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun
  • clickhouse-云数据库ClickHouse
    #icon-clickhouse-yunshujukuClickHouse
  • rds_mariadb
    #icon-rds_mariadb
  • 减少减去减号
    #icon-jianshaojianqujianhao
  • sqlserver
    #icon-sqlserver
  • sqlite
    #icon-sqlite
  • 缺省页_暂无数据
    #icon-queshengye_zanwushuju
  • 未完成
    #icon-weiwancheng
  • 完成-01
    #icon-wancheng-
  • 成功
    #icon-chenggong1
  • 机器人
    #icon-jiqiren
  • 换一换
    #icon-huanyihuan
  • icon_infomation
    #icon-icon_infomation
  • key
    #icon-key1
  • mysql
    #icon-mysql
  • oracle
    #icon-oracle
  • postgresql
    #icon-postgresql
  • h2
    #icon-h2
  • cc-schema
    #icon-cc-schema
  • 新建表格
    #icon-xinjianbiaoge
  • export
    #icon-export
  • 角色管理
    #icon-jiaoseguanli
  • console
    #icon-console
  • 24gf-folderMinus
    #icon-24gf-folderMinus
  • 查看
    #icon-chakan
  • 复制_o
    #icon-fuzhi_o
  • 执行
    #icon-zhihang
  • m-格式化文字
    #icon-m-geshihuawenzi
  • github-fill
    #icon-github-fill
  • 保存
    #icon-baocun2
  • 箭头_向左两次_o
    #icon-jiantou_xiangzuoliangci_o
  • 新建窗口
    #icon-xinjianchuangkou
  • loading
    #icon-loading2
  • 链接克隆
    #icon-lianjiekelong
  • SQL升级文件
    #icon-SQLshengjiwenjian
  • sql
    #icon-sql
  • 连接流
    #icon-lianjieliu
  • 跳转/退出
    #icon-tiaozhuan
  • key
    #icon-key
  • 播放记录
    #icon-bofangjilu
  • 成功
    #icon-chenggong
  • 失败
    #icon-shibai
  • 收回 上下
    #icon-shouhuishangxia
  • 展开 上下
    #icon-zhankaishangxia
  • 数据库
    #icon-shujuku
  • 保存
    #icon-baocun
  • 查询
    #icon-chaxun
  • 对勾
    #icon-duigou11
  • check
    #icon-check1
  • 概览
    #icon-gailan
  • 概览
    #icon-huaban2
  • 编辑
    #icon-bianji
  • 刷新
    #icon-shuaxin1
  • 菜单/列表
    #icon-caidan
  • 表格
    #icon-biaoge
  • 展开
    #icon-zhankai
  • 收起
    #icon-shouqi
  • 主题_o
    #icon-zhuti_o
  • 断开连接
    #icon-duankailianjie
  • 修改
    #icon-xiugai
  • 删除
    #icon-delete
  • 更多
    #icon-gengduo1
  • 减少
    #icon-jianshao
  • #icon-jia
  • 加号
    #icon-hao
  • arrow drop down
    #icon-right
  • search
    #icon-search1
  • download
    #icon-download1
  • 向右箭头
    #icon-xiangyoujiantou
  • 删除线型
    #icon-shanchuxianxing
  • cross
    #icon-cross-copy
  • 刷新
    #icon-shuaxin
  • 提醒
    #icon-tixing
  • 138设置、系统设置、功能设置、属性
    #icon-shezhixitongshezhigongnengshezhishuxing
  • 执行sql脚本
    #icon-zhihangsqljiaoben
  • 虚拟数据库管理
    #icon-xunishujukuguanli

Symbol 引用


这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

  • 支持多色图标了,不再受单色限制。
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • 浏览器渲染 SVG 的性能一般,还不如 png。

使用步骤如下:

第一步:引入项目下面生成的 symbol 代码:

<script src="./iconfont.js"></script>

第二步:加入通用 CSS 代码(引入一次就行):

<style>
.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

第三步:挑选相应图标并获取类名,应用于页面:

<svg class="icon" aria-hidden="true">
  <use xlink:href="#icon-xxx"></use>
</svg>
================================================ FILE: chat2db-client/src/assets/font/iconfont.css ================================================ @font-face { font-family: "iconfont"; /* Project id 3633546 */ src: url('iconfont.woff2?t=1704794525154') format('woff2'), url('iconfont.woff?t=1704794525154') format('woff'), url('iconfont.ttf?t=1704794525154') format('truetype'); } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-right_on_5:before { content: "\e672"; } .icon-right_off_5-01:before { content: "\e673"; } .icon-a-left_on_huaban11:before { content: "\e674"; } .icon-a-left_off_huaban1:before { content: "\e670"; } .icon-minimize21:before { content: "\e671"; } .icon-restore_button2:before { content: "\e66b"; } .icon-resize_button2:before { content: "\e66e"; } .icon-close_button2:before { content: "\e66f"; } .icon-shaixuan:before { content: "\e66a"; } .icon-a-44tubiao-122:before { content: "\e69a"; } .icon-xinxi-xianxingyuankuang:before { content: "\e8e8"; } .icon-jiahao:before { content: "\e726"; } .icon-liebiao:before { content: "\ec6b"; } .icon-jianqu:before { content: "\e65d"; } .icon-database:before { content: "\e669"; } .icon-shaixuan1:before { content: "\e888"; } .icon-shuaxin2:before { content: "\e668"; } .icon-jiahao_o:before { content: "\eb78"; } .icon-jurassic_data:before { content: "\e6a9"; } .icon-quanxian:before { content: "\e667"; } .icon-sharpicons_add-database:before { content: "\e816"; } .icon-zuzhiguanli-:before { content: "\e663"; } .icon-moxing-miaobian:before { content: "\e691"; } .icon-xiajiantou1-copy:before { content: "\100be"; } .icon-chakan2:before { content: "\e788"; } .icon-clone:before { content: "\e8db"; } .icon-tijiao:before { content: "\e687"; } .icon-chakan1:before { content: "\e665"; } .icon-fuzhi:before { content: "\ec7a"; } .icon-icon_answer:before { content: "\e686"; } .icon-icon_question:before { content: "\e6a8"; } .icon-fasong:before { content: "\100bd"; } .icon-zhongqi:before { content: "\e662"; } .icon-tixing2:before { content: "\e6cc"; } .icon-tixing3:before { content: "\e661"; } .icon-tixing1:before { content: "\e716"; } .icon-shengji:before { content: "\e69c"; } .icon-quanju_shengji:before { content: "\e659"; } .icon-guanyuwomen1:before { content: "\e65c"; } .icon-icobanbengengxin:before { content: "\e67d"; } .icon-duihuaqipao:before { content: "\e657"; } .icon-jiaosequanxian:before { content: "\e658"; } .icon-preview1:before { content: "\e654"; } .icon-daoru:before { content: "\e653"; } .icon-zhongzhi:before { content: "\e652"; } .icon-tuichu:before { content: "\e6b2"; } .icon-kongzhuangzhongduan:before { content: "\e6bb"; } .icon-chexiao1:before { content: "\e6e2"; } .icon-xiangshang:before { content: "\e650"; } .icon-chakan-copy:before { content: "\e651"; } .icon-jurassic_edit-data:before { content: "\e6f2"; } .icon-jurassic_edit-table:before { content: "\e6f3"; } .icon-baobiaoshujuluru:before { content: "\e7b5"; } .icon-bofang5:before { content: "\e656"; } .icon-a-qingkong3x:before { content: "\e64f"; } .icon-shanchu:before { content: "\e64e"; } .icon-newdocumentworksheet:before { content: "\e792"; } .icon-file-excel:before { content: "\e7b7"; } .icon-file-markdown:before { content: "\e7b8"; } .icon-file-word:before { content: "\e7ba"; } .icon-HTML:before { content: "\e87d"; } .icon-HTML1:before { content: "\e64d"; } .icon-pdf:before { content: "\e67a"; } .icon-gerenyonghu:before { content: "\e64c"; } .icon-houtaiguanli:before { content: "\e64b"; } .icon-zitidaima:before { content: "\ec83"; } .icon-banben:before { content: "\e70c"; } .icon-cheweiguanli:before { content: "\e73c"; } .icon-dianzhelidaochu:before { content: "\e64a"; } .icon-circle-f:before { content: "\e76a"; } .icon-tubiao-hanshu:before { content: "\e6fd"; } .icon-shituguanliqi:before { content: "\e647"; } .icon-huiche:before { content: "\e643"; } .icon-quesheng:before { content: "\e642"; } .icon-jinrujiantou:before { content: "\e88e"; } .icon-youjiantou_huaban:before { content: "\e641"; } .icon-xiangyoujiantou1:before { content: "\e660"; } .icon-shujuyuan:before { content: "\e640"; } .icon-question:before { content: "\e67c"; } .icon-xingxing:before { content: "\e63a"; } .icon-kongzhitai:before { content: "\e69f"; } .icon-xingxi:before { content: "\e639"; } .icon-a-zanwushuju1:before { content: "\e638"; } .icon-kaishi:before { content: "\e637"; } .icon-guanbi:before { content: "\e634"; } .icon-xiajiantou:before { content: "\eb6d"; } .icon-gengduo:before { content: "\e633"; } .icon-shezhi:before { content: "\e630"; } .icon-duihua-weixuan:before { content: "\e628"; } .icon-tubiao-weixuan:before { content: "\e629"; } .icon-a-bianzu13beifen3:before { content: "\e62b"; } .icon-bianzubeifen:before { content: "\e616"; } .icon-biaoge1:before { content: "\e618"; } .icon-a-shoucang1:before { content: "\e61d"; } .icon-guthub-weixuan1:before { content: "\e621"; } .icon-shuju-weixuan:before { content: "\e622"; } .icon-a-bianzu4:before { content: "\e624"; } .icon-a-bianzu14beifen:before { content: "\e627"; } .icon-guthub-weixuan:before { content: "\e615"; } .icon-24gl-folderMinus:before { content: "\eabe"; } .icon-24gl-folderOpen:before { content: "\eabf"; } .icon-24gf-folderOpen:before { content: "\eac7"; } .icon-yunshujuku:before { content: "\e744"; } .icon-baobiao:before { content: "\e612"; } .icon-gongzuotai:before { content: "\e614"; } .icon-mongodb:before { content: "\ec21"; } .icon-Redis:before { content: "\e6a2"; } .icon-HIVE:before { content: "\e60e"; } .icon-Kingbase:before { content: "\e6a0"; } .icon-yibiaopan:before { content: "\e60d"; } .icon-presto_sql:before { content: "\e60b"; } .icon-shujukuleixingtubiao-kuozhan-:before { content: "\e60a"; } .icon-oceanbase:before { content: "\e982"; } .icon-dameng1:before { content: "\e655"; } .icon-proxy:before { content: "\e63f"; } .icon-openai:before { content: "\e646"; } .icon-guanyu:before { content: "\e60c"; } .icon-yifu:before { content: "\e666"; } .icon-shujuku4:before { content: "\e609"; } .icon-shujuyuanpeizhi:before { content: "\e649"; } .icon-jurassic_server:before { content: "\e6a6"; } .icon-shujuku2:before { content: "\e607"; } .icon-shujuku3:before { content: "\e625"; } .icon-shujukushuju:before { content: "\e63c"; } .icon-shujuku1:before { content: "\e636"; } .icon-peizhishujuyuan:before { content: "\e62f"; } .icon-SQLlishichaxun:before { content: "\e80a"; } .icon-zhongmingming:before { content: "\e623"; } .icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun:before { content: "\e8ff"; } .icon-clickhouse-yunshujukuClickHouse:before { content: "\e8f4"; } .icon-rds_mariadb:before { content: "\e6f5"; } .icon-jianshaojianqujianhao:before { content: "\e62a"; } .icon-sqlserver:before { content: "\e664"; } .icon-sqlite:before { content: "\e65a"; } .icon-queshengye_zanwushuju:before { content: "\e760"; } .icon-weiwancheng:before { content: "\e755"; } .icon-wancheng-:before { content: "\e62e"; } .icon-chenggong1:before { content: "\e620"; } .icon-jiqiren:before { content: "\e70e"; } .icon-huanyihuan:before { content: "\e635"; } .icon-icon_infomation:before { content: "\e65b"; } .icon-key1:before { content: "\e775"; } .icon-mysql:before { content: "\ec6d"; } .icon-oracle:before { content: "\ec48"; } .icon-postgresql:before { content: "\ec5d"; } .icon-h2:before { content: "\e61c"; } .icon-cc-schema:before { content: "\e696"; } .icon-xinjianbiaoge:before { content: "\e6b6"; } .icon-export:before { content: "\e613"; } .icon-jiaoseguanli:before { content: "\e66d"; } .icon-console:before { content: "\e619"; } .icon-24gf-folderMinus:before { content: "\eac5"; } .icon-chakan:before { content: "\e606"; } .icon-fuzhi_o:before { content: "\eb4e"; } .icon-zhihang:before { content: "\e626"; } .icon-m-geshihuawenzi:before { content: "\e7f8"; } .icon-github-fill:before { content: "\e885"; } .icon-baocun2:before { content: "\e645"; } .icon-jiantou_xiangzuoliangci_o:before { content: "\eb93"; } .icon-xinjianchuangkou:before { content: "\e603"; } .icon-loading2:before { content: "\e6cd"; } .icon-lianjiekelong:before { content: "\e6ca"; } .icon-SQLshengjiwenjian:before { content: "\e63b"; } .icon-sql:before { content: "\e610"; } .icon-lianjieliu:before { content: "\ec57"; } .icon-tiaozhuan:before { content: "\e685"; } .icon-key:before { content: "\e648"; } .icon-bofangjilu:before { content: "\e8ad"; } .icon-chenggong:before { content: "\e605"; } .icon-shibai:before { content: "\e87c"; } .icon-shouhuishangxia:before { content: "\e790"; } .icon-zhankaishangxia:before { content: "\e7b1"; } .icon-shujuku:before { content: "\e62c"; } .icon-baocun:before { content: "\e936"; } .icon-chaxun:before { content: "\ec4c"; } .icon-duigou11:before { content: "\e61f"; } .icon-check1:before { content: "\e617"; } .icon-gailan:before { content: "\e632"; } .icon-huaban2:before { content: "\e63d"; } .icon-bianji:before { content: "\e602"; } .icon-shuaxin1:before { content: "\ec08"; } .icon-caidan:before { content: "\e611"; } .icon-biaoge:before { content: "\e63e"; } .icon-zhankai:before { content: "\e65f"; } .icon-shouqi:before { content: "\e61e"; } .icon-zhuti_o:before { content: "\eb6f"; } .icon-duankailianjie:before { content: "\e65e"; } .icon-xiugai:before { content: "\e60f"; } .icon-delete:before { content: "\e604"; } .icon-gengduo1:before { content: "\e601"; } .icon-jianshao:before { content: "\e644"; } .icon-jia:before { content: "\e61b"; } .icon-hao:before { content: "\e631"; } .icon-right:before { content: "\e608"; } .icon-search1:before { content: "\e600"; } .icon-download1:before { content: "\e66c"; } .icon-xiangyoujiantou:before { content: "\e79c"; } .icon-shanchuxianxing:before { content: "\e6a7"; } .icon-cross-copy:before { content: "\ec8e"; } .icon-shuaxin:before { content: "\e62d"; } .icon-tixing:before { content: "\e913"; } .icon-shezhixitongshezhigongnengshezhishuxing:before { content: "\e795"; } .icon-zhihangsqljiaoben:before { content: "\e759"; } .icon-xunishujukuguanli:before { content: "\e61a"; } ================================================ FILE: chat2db-client/src/assets/font/iconfont.js ================================================ window._iconfont_svg_string_3633546='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],c=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,t,i,o,v,z=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,c=document.createElement("div");c.innerHTML=h._iconfont_svg_string_3633546,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(a=document.body).firstChild?z(c,a.firstChild):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=l,o=h.document,v=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){v||(v=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}p()}}(window); ================================================ FILE: chat2db-client/src/assets/font/iconfont.json ================================================ { "id": "3633546", "name": "chat2db", "font_family": "iconfont", "css_prefix_text": "icon-", "description": "", "glyphs": [ { "icon_id": "38884431", "name": "right_on_5", "font_class": "right_on_5", "unicode": "e672", "unicode_decimal": 58994 }, { "icon_id": "38884432", "name": "right_off_5-01", "font_class": "right_off_5-01", "unicode": "e673", "unicode_decimal": 58995 }, { "icon_id": "38883914", "name": "left_on_2", "font_class": "a-left_on_huaban11", "unicode": "e674", "unicode_decimal": 58996 }, { "icon_id": "38882655", "name": "left_off", "font_class": "a-left_off_huaban1", "unicode": "e670", "unicode_decimal": 58992 }, { "icon_id": "38873585", "name": "minimize21", "font_class": "minimize21", "unicode": "e671", "unicode_decimal": 58993 }, { "icon_id": "38872025", "name": "restore", "font_class": "restore_button2", "unicode": "e66b", "unicode_decimal": 58987 }, { "icon_id": "38872027", "name": "resize", "font_class": "resize_button2", "unicode": "e66e", "unicode_decimal": 58990 }, { "icon_id": "38872028", "name": "close", "font_class": "close_button2", "unicode": "e66f", "unicode_decimal": 58991 }, { "icon_id": "8721198", "name": "筛选", "font_class": "shaixuan", "unicode": "e66a", "unicode_decimal": 58986 }, { "icon_id": "38644725", "name": "排序", "font_class": "a-44tubiao-122", "unicode": "e69a", "unicode_decimal": 59034 }, { "icon_id": "1727525", "name": "305信息-线性圆框", "font_class": "xinxi-xianxingyuankuang", "unicode": "e8e8", "unicode_decimal": 59624 }, { "icon_id": "577312", "name": "加号", "font_class": "jiahao", "unicode": "e726", "unicode_decimal": 59174 }, { "icon_id": "5961366", "name": "列表", "font_class": "liebiao", "unicode": "ec6b", "unicode_decimal": 60523 }, { "icon_id": "6318169", "name": "减去", "font_class": "jianqu", "unicode": "e65d", "unicode_decimal": 58973 }, { "icon_id": "5729483", "name": "database", "font_class": "database", "unicode": "e669", "unicode_decimal": 58985 }, { "icon_id": "2076240", "name": "筛选", "font_class": "shaixuan1", "unicode": "e888", "unicode_decimal": 59528 }, { "icon_id": "2787037", "name": "刷新", "font_class": "shuaxin2", "unicode": "e668", "unicode_decimal": 58984 }, { "icon_id": "5387866", "name": "加号_o", "font_class": "jiahao_o", "unicode": "eb78", "unicode_decimal": 60280 }, { "icon_id": "10594610", "name": "数据库_jurassic", "font_class": "jurassic_data", "unicode": "e6a9", "unicode_decimal": 59049 }, { "icon_id": "12515461", "name": "权限", "font_class": "quanxian", "unicode": "e667", "unicode_decimal": 58983 }, { "icon_id": "10573069", "name": "sharpicons_add-database", "font_class": "sharpicons_add-database", "unicode": "e816", "unicode_decimal": 59414 }, { "icon_id": "6789097", "name": "组织管理", "font_class": "zuzhiguanli-", "unicode": "e663", "unicode_decimal": 58979 }, { "icon_id": "15568801", "name": "空间", "font_class": "moxing-miaobian", "unicode": "e691", "unicode_decimal": 59025 }, { "icon_id": "37968169", "name": "下箭头-copy", "font_class": "xiajiantou1-copy", "unicode": "100be", "unicode_decimal": 65726 }, { "icon_id": "22941156", "name": "查看", "font_class": "chakan2", "unicode": "e788", "unicode_decimal": 59272 }, { "icon_id": "7735962", "name": "clone", "font_class": "clone", "unicode": "e8db", "unicode_decimal": 59611 }, { "icon_id": "15488863", "name": "提交", "font_class": "tijiao", "unicode": "e687", "unicode_decimal": 59015 }, { "icon_id": "12331656", "name": "查看", "font_class": "chakan1", "unicode": "e665", "unicode_decimal": 58981 }, { "icon_id": "5993150", "name": "复制", "font_class": "fuzhi", "unicode": "ec7a", "unicode_decimal": 60538 }, { "icon_id": "1624922", "name": "icon_answer", "font_class": "icon_answer", "unicode": "e686", "unicode_decimal": 59014 }, { "icon_id": "1625000", "name": "icon_question", "font_class": "icon_question", "unicode": "e6a8", "unicode_decimal": 59048 }, { "icon_id": "29290564", "name": "发送", "font_class": "fasong", "unicode": "100bd", "unicode_decimal": 65725 }, { "icon_id": "8765142", "name": "重启", "font_class": "zhongqi", "unicode": "e662", "unicode_decimal": 58978 }, { "icon_id": "673801", "name": "提醒", "font_class": "tixing2", "unicode": "e6cc", "unicode_decimal": 59084 }, { "icon_id": "13646873", "name": "提醒", "font_class": "tixing3", "unicode": "e661", "unicode_decimal": 58977 }, { "icon_id": "3457210", "name": "提醒", "font_class": "tixing1", "unicode": "e716", "unicode_decimal": 59158 }, { "icon_id": "3814162", "name": "升级", "font_class": "shengji", "unicode": "e69c", "unicode_decimal": 59036 }, { "icon_id": "3141572", "name": "全局_升级", "font_class": "quanju_shengji", "unicode": "e659", "unicode_decimal": 58969 }, { "icon_id": "4171088", "name": "关于我们", "font_class": "guanyuwomen1", "unicode": "e65c", "unicode_decimal": 58972 }, { "icon_id": "1285185", "name": "ico版本更新", "font_class": "icobanbengengxin", "unicode": "e67d", "unicode_decimal": 59005 }, { "icon_id": "5203239", "name": "对话气泡", "font_class": "duihuaqipao", "unicode": "e657", "unicode_decimal": 58967 }, { "icon_id": "12694416", "name": "角色权限", "font_class": "jiaosequanxian", "unicode": "e658", "unicode_decimal": 58968 }, { "icon_id": "1462912", "name": "preview", "font_class": "preview1", "unicode": "e654", "unicode_decimal": 58964 }, { "icon_id": "11399566", "name": "导入", "font_class": "daoru", "unicode": "e653", "unicode_decimal": 58963 }, { "icon_id": "10933419", "name": "终止", "font_class": "zhongzhi", "unicode": "e652", "unicode_decimal": 58962 }, { "icon_id": "29570629", "name": "退出", "font_class": "tuichu", "unicode": "e6b2", "unicode_decimal": 59058 }, { "icon_id": "25071396", "name": "控桩终端", "font_class": "kongzhuangzhongduan", "unicode": "e6bb", "unicode_decimal": 59067 }, { "icon_id": "4890374", "name": "撤销", "font_class": "chexiao1", "unicode": "e6e2", "unicode_decimal": 59106 }, { "icon_id": "1204", "name": "向上", "font_class": "xiangshang", "unicode": "e650", "unicode_decimal": 58960 }, { "icon_id": "5793838", "name": "查看", "font_class": "chakan-copy", "unicode": "e651", "unicode_decimal": 58961 }, { "icon_id": "10792673", "name": "编辑数据_编辑录入操作_jurassic", "font_class": "jurassic_edit-data", "unicode": "e6f2", "unicode_decimal": 59122 }, { "icon_id": "10792678", "name": "编辑表格_编辑录入操作_jurassic", "font_class": "jurassic_edit-table", "unicode": "e6f3", "unicode_decimal": 59123 }, { "icon_id": "10465899", "name": "报表数据录入", "font_class": "baobiaoshujuluru", "unicode": "e7b5", "unicode_decimal": 59317 }, { "icon_id": "1004675", "name": "播放5", "font_class": "bofang5", "unicode": "e656", "unicode_decimal": 58966 }, { "icon_id": "34080274", "name": "清空@3x", "font_class": "a-qingkong3x", "unicode": "e64f", "unicode_decimal": 58959 }, { "icon_id": "4880413", "name": "删除", "font_class": "shanchu", "unicode": "e64e", "unicode_decimal": 58958 }, { "icon_id": "586995", "name": "new-document-worksheet", "font_class": "newdocumentworksheet", "unicode": "e792", "unicode_decimal": 59282 }, { "icon_id": "4766469", "name": "file-excel", "font_class": "file-excel", "unicode": "e7b7", "unicode_decimal": 59319 }, { "icon_id": "4766474", "name": "file-markdown", "font_class": "file-markdown", "unicode": "e7b8", "unicode_decimal": 59320 }, { "icon_id": "4766477", "name": "file-word", "font_class": "file-word", "unicode": "e7ba", "unicode_decimal": 59322 }, { "icon_id": "4936958", "name": "HTML5", "font_class": "HTML", "unicode": "e87d", "unicode_decimal": 59517 }, { "icon_id": "12904096", "name": "HTML", "font_class": "HTML1", "unicode": "e64d", "unicode_decimal": 58957 }, { "icon_id": "15838516", "name": "pdf", "font_class": "pdf", "unicode": "e67a", "unicode_decimal": 59002 }, { "icon_id": "37118827", "name": "个人用户", "font_class": "gerenyonghu", "unicode": "e64c", "unicode_decimal": 58956 }, { "icon_id": "37118908", "name": "后台管理", "font_class": "houtaiguanli", "unicode": "e64b", "unicode_decimal": 58955 }, { "icon_id": "6337464", "name": "字体代码", "font_class": "zitidaima", "unicode": "ec83", "unicode_decimal": 60547 }, { "icon_id": "27193693", "name": "版本", "font_class": "banben", "unicode": "e70c", "unicode_decimal": 59148 }, { "icon_id": "10021618", "name": "车位管理", "font_class": "cheweiguanli", "unicode": "e73c", "unicode_decimal": 59196 }, { "icon_id": "31775147", "name": "dictate", "font_class": "dianzhelidaochu", "unicode": "e64a", "unicode_decimal": 58954 }, { "icon_id": "33841930", "name": "circle-f", "font_class": "circle-f", "unicode": "e76a", "unicode_decimal": 59242 }, { "icon_id": "2966901", "name": "图表-函数", "font_class": "tubiao-hanshu", "unicode": "e6fd", "unicode_decimal": 59133 }, { "icon_id": "11121428", "name": "视图管理器", "font_class": "shituguanliqi", "unicode": "e647", "unicode_decimal": 58951 }, { "icon_id": "12685114", "name": "回车", "font_class": "huiche", "unicode": "e643", "unicode_decimal": 58947 }, { "icon_id": "36566502", "name": "缺省", "font_class": "quesheng", "unicode": "e642", "unicode_decimal": 58946 }, { "icon_id": "2076256", "name": "进入箭头", "font_class": "jinrujiantou", "unicode": "e88e", "unicode_decimal": 59534 }, { "icon_id": "12754142", "name": "右箭头", "font_class": "youjiantou_huaban", "unicode": "e641", "unicode_decimal": 58945 }, { "icon_id": "630094", "name": "向右箭头", "font_class": "xiangyoujiantou1", "unicode": "e660", "unicode_decimal": 58976 }, { "icon_id": "11520190", "name": "数据源", "font_class": "shujuyuan", "unicode": "e640", "unicode_decimal": 58944 }, { "icon_id": "11891865", "name": "question", "font_class": "question", "unicode": "e67c", "unicode_decimal": 59004 }, { "icon_id": "32077818", "name": "星星-copy", "font_class": "xingxing", "unicode": "e63a", "unicode_decimal": 58938 }, { "icon_id": "6560687", "name": "控制台", "font_class": "kongzhitai", "unicode": "e69f", "unicode_decimal": 59039 }, { "icon_id": "22785129", "name": "星系", "font_class": "xingxi", "unicode": "e639", "unicode_decimal": 58937 }, { "icon_id": "36197034", "name": "暂无数据 (1)", "font_class": "a-zanwushuju1", "unicode": "e638", "unicode_decimal": 58936 }, { "icon_id": "33940358", "name": "开始", "font_class": "kaishi", "unicode": "e637", "unicode_decimal": 58935 }, { "icon_id": "5643512", "name": "关闭", "font_class": "guanbi", "unicode": "e634", "unicode_decimal": 58932 }, { "icon_id": "4175511", "name": "下箭头", "font_class": "xiajiantou", "unicode": "eb6d", "unicode_decimal": 60269 }, { "icon_id": "5978833", "name": "more", "font_class": "gengduo", "unicode": "e633", "unicode_decimal": 58931 }, { "icon_id": "36150033", "name": "设置", "font_class": "shezhi", "unicode": "e630", "unicode_decimal": 58928 }, { "icon_id": "36137656", "name": "对话-未选", "font_class": "duihua-weixuan", "unicode": "e628", "unicode_decimal": 58920 }, { "icon_id": "36137657", "name": "图表-未选", "font_class": "tubiao-weixuan", "unicode": "e629", "unicode_decimal": 58921 }, { "icon_id": "36137693", "name": "编组 13备份 3", "font_class": "a-bianzu13beifen3", "unicode": "e62b", "unicode_decimal": 58923 }, { "icon_id": "36134221", "name": "编组备份", "font_class": "bianzubeifen", "unicode": "e616", "unicode_decimal": 58902 }, { "icon_id": "36134222", "name": "表格", "font_class": "biaoge1", "unicode": "e618", "unicode_decimal": 58904 }, { "icon_id": "36134223", "name": "收藏 (1)", "font_class": "a-shoucang1", "unicode": "e61d", "unicode_decimal": 58909 }, { "icon_id": "36134224", "name": "guthub-未选", "font_class": "guthub-weixuan1", "unicode": "e621", "unicode_decimal": 58913 }, { "icon_id": "36134225", "name": "数据-未选", "font_class": "shuju-weixuan", "unicode": "e622", "unicode_decimal": 58914 }, { "icon_id": "36134226", "name": "编组 4", "font_class": "a-bianzu4", "unicode": "e624", "unicode_decimal": 58916 }, { "icon_id": "36134227", "name": "编组 14备份", "font_class": "a-bianzu14beifen", "unicode": "e627", "unicode_decimal": 58919 }, { "icon_id": "36134208", "name": "guthub-未选", "font_class": "guthub-weixuan", "unicode": "e615", "unicode_decimal": 58901 }, { "icon_id": "7594805", "name": "24gl-folderMinus", "font_class": "24gl-folderMinus", "unicode": "eabe", "unicode_decimal": 60094 }, { "icon_id": "7594806", "name": "24gl-folderOpen", "font_class": "24gl-folderOpen", "unicode": "eabf", "unicode_decimal": 60095 }, { "icon_id": "7594875", "name": "24gf-folderOpen", "font_class": "24gf-folderOpen", "unicode": "eac7", "unicode_decimal": 60103 }, { "icon_id": "2611804", "name": "云数据库", "font_class": "yunshujuku", "unicode": "e744", "unicode_decimal": 59204 }, { "icon_id": "6607912", "name": "报表", "font_class": "baobiao", "unicode": "e612", "unicode_decimal": 58898 }, { "icon_id": "6977892", "name": "工作台", "font_class": "gongzuotai", "unicode": "e614", "unicode_decimal": 58900 }, { "icon_id": "15378610", "name": "mongodb", "font_class": "mongodb", "unicode": "ec21", "unicode_decimal": 60449 }, { "icon_id": "3172491", "name": "Redis", "font_class": "Redis", "unicode": "e6a2", "unicode_decimal": 59042 }, { "icon_id": "16300754", "name": "HIVE_2", "font_class": "HIVE", "unicode": "e60e", "unicode_decimal": 58894 }, { "icon_id": "35572498", "name": "Kingbase", "font_class": "Kingbase", "unicode": "e6a0", "unicode_decimal": 59040 }, { "icon_id": "35892480", "name": "仪表盘", "font_class": "yibiaopan", "unicode": "e60d", "unicode_decimal": 58893 }, { "icon_id": "13487185", "name": "presto", "font_class": "presto_sql", "unicode": "e60b", "unicode_decimal": 58891 }, { "icon_id": "5978710", "name": "DB2", "font_class": "shujukuleixingtubiao-kuozhan-", "unicode": "e60a", "unicode_decimal": 58890 }, { "icon_id": "35222282", "name": "oceanbase", "font_class": "oceanbase", "unicode": "e982", "unicode_decimal": 59778 }, { "icon_id": "15192821", "name": "达梦", "font_class": "dameng1", "unicode": "e655", "unicode_decimal": 58965 }, { "icon_id": "2995206", "name": "proxy", "font_class": "proxy", "unicode": "e63f", "unicode_decimal": 58943 }, { "icon_id": "33483666", "name": "openai", "font_class": "openai", "unicode": "e646", "unicode_decimal": 58950 }, { "icon_id": "11542846", "name": "关于", "font_class": "guanyu", "unicode": "e60c", "unicode_decimal": 58892 }, { "icon_id": "1301391", "name": "衣服", "font_class": "yifu", "unicode": "e666", "unicode_decimal": 58982 }, { "icon_id": "6850413", "name": "数据库", "font_class": "shujuku4", "unicode": "e609", "unicode_decimal": 58889 }, { "icon_id": "12993182", "name": "数据源配置", "font_class": "shujuyuanpeizhi", "unicode": "e649", "unicode_decimal": 58953 }, { "icon_id": "10594601", "name": "服务器_数据库_jurassic", "font_class": "jurassic_server", "unicode": "e6a6", "unicode_decimal": 59046 }, { "icon_id": "1817682", "name": "数据库", "font_class": "shujuku2", "unicode": "e607", "unicode_decimal": 58887 }, { "icon_id": "8765123", "name": "数据库", "font_class": "shujuku3", "unicode": "e625", "unicode_decimal": 58917 }, { "icon_id": "9710796", "name": "数据库数据", "font_class": "shujukushuju", "unicode": "e63c", "unicode_decimal": 58940 }, { "icon_id": "1305114", "name": "数据库", "font_class": "shujuku1", "unicode": "e636", "unicode_decimal": 58934 }, { "icon_id": "1472570", "name": "配置数据源", "font_class": "peizhishujuyuan", "unicode": "e62f", "unicode_decimal": 58927 }, { "icon_id": "31104431", "name": "SQL历史查询", "font_class": "SQLlishichaxun", "unicode": "e80a", "unicode_decimal": 59402 }, { "icon_id": "6550626", "name": "重命名", "font_class": "zhongmingming", "unicode": "e623", "unicode_decimal": 58915 }, { "icon_id": "6607690", "name": "ico_数据查询与统计_预约情况查询", "font_class": "ico_shujuchaxunyutongji_yuyueqingkuangchaxun", "unicode": "e8ff", "unicode_decimal": 59647 }, { "icon_id": "27571255", "name": "clickhouse-云数据库ClickHouse", "font_class": "clickhouse-yunshujukuClickHouse", "unicode": "e8f4", "unicode_decimal": 59636 }, { "icon_id": "13592725", "name": "rds_mariadb", "font_class": "rds_mariadb", "unicode": "e6f5", "unicode_decimal": 59125 }, { "icon_id": "8817896", "name": "减少减去减号", "font_class": "jianshaojianqujianhao", "unicode": "e62a", "unicode_decimal": 58922 }, { "icon_id": "7119672", "name": "sqlserver", "font_class": "sqlserver", "unicode": "e664", "unicode_decimal": 58980 }, { "icon_id": "12600909", "name": "sqlite", "font_class": "sqlite", "unicode": "e65a", "unicode_decimal": 58970 }, { "icon_id": "26519722", "name": "缺省页_暂无数据", "font_class": "queshengye_zanwushuju", "unicode": "e760", "unicode_decimal": 59232 }, { "icon_id": "1330772", "name": "未完成", "font_class": "weiwancheng", "unicode": "e755", "unicode_decimal": 59221 }, { "icon_id": "5979977", "name": "完成-01", "font_class": "wancheng-", "unicode": "e62e", "unicode_decimal": 58926 }, { "icon_id": "24056325", "name": "成功", "font_class": "chenggong1", "unicode": "e620", "unicode_decimal": 58912 }, { "icon_id": "16323142", "name": "机器人", "font_class": "jiqiren", "unicode": "e70e", "unicode_decimal": 59150 }, { "icon_id": "6234556", "name": "换一换", "font_class": "huanyihuan", "unicode": "e635", "unicode_decimal": 58933 }, { "icon_id": "26130627", "name": "icon_infomation", "font_class": "icon_infomation", "unicode": "e65b", "unicode_decimal": 58971 }, { "icon_id": "6150969", "name": "key", "font_class": "key1", "unicode": "e775", "unicode_decimal": 59253 }, { "icon_id": "5961392", "name": "mysql", "font_class": "mysql", "unicode": "ec6d", "unicode_decimal": 60525 }, { "icon_id": "15378663", "name": "oracle", "font_class": "oracle", "unicode": "ec48", "unicode_decimal": 60488 }, { "icon_id": "15378704", "name": "postgresql", "font_class": "postgresql", "unicode": "ec5d", "unicode_decimal": 60509 }, { "icon_id": "19657927", "name": "h2", "font_class": "h2", "unicode": "e61c", "unicode_decimal": 58908 }, { "icon_id": "372246", "name": "cc-schema", "font_class": "cc-schema", "unicode": "e696", "unicode_decimal": 59030 }, { "icon_id": "1789259", "name": "新建表格", "font_class": "xinjianbiaoge", "unicode": "e6b6", "unicode_decimal": 59062 }, { "icon_id": "15550146", "name": "export", "font_class": "export", "unicode": "e613", "unicode_decimal": 58899 }, { "icon_id": "693661", "name": "角色管理", "font_class": "jiaoseguanli", "unicode": "e66d", "unicode_decimal": 58989 }, { "icon_id": "11348953", "name": "console", "font_class": "console", "unicode": "e619", "unicode_decimal": 58905 }, { "icon_id": "7594874", "name": "24gf-folderMinus", "font_class": "24gf-folderMinus", "unicode": "eac5", "unicode_decimal": 60101 }, { "icon_id": "201556", "name": "查看", "font_class": "chakan", "unicode": "e606", "unicode_decimal": 58886 }, { "icon_id": "5387764", "name": "复制_o", "font_class": "fuzhi_o", "unicode": "eb4e", "unicode_decimal": 60238 }, { "icon_id": "10936703", "name": "执行", "font_class": "zhihang", "unicode": "e626", "unicode_decimal": 58918 }, { "icon_id": "21164516", "name": "m-格式化文字", "font_class": "m-geshihuawenzi", "unicode": "e7f8", "unicode_decimal": 59384 }, { "icon_id": "4937000", "name": "github-fill", "font_class": "github-fill", "unicode": "e885", "unicode_decimal": 59525 }, { "icon_id": "8626141", "name": "保存", "font_class": "baocun2", "unicode": "e645", "unicode_decimal": 58949 }, { "icon_id": "5387931", "name": "箭头_向左两次_o", "font_class": "jiantou_xiangzuoliangci_o", "unicode": "eb93", "unicode_decimal": 60307 }, { "icon_id": "1371", "name": "新建窗口", "font_class": "xinjianchuangkou", "unicode": "e603", "unicode_decimal": 58883 }, { "icon_id": "4503683", "name": "loading", "font_class": "loading2", "unicode": "e6cd", "unicode_decimal": 59085 }, { "icon_id": "2505938", "name": "链接克隆", "font_class": "lianjiekelong", "unicode": "e6ca", "unicode_decimal": 59082 }, { "icon_id": "11661836", "name": "SQL升级文件", "font_class": "SQLshengjiwenjian", "unicode": "e63b", "unicode_decimal": 58939 }, { "icon_id": "15214527", "name": "sql", "font_class": "sql", "unicode": "e610", "unicode_decimal": 58896 }, { "icon_id": "5961312", "name": "连接流", "font_class": "lianjieliu", "unicode": "ec57", "unicode_decimal": 60503 }, { "icon_id": "16853978", "name": "跳转/退出", "font_class": "tiaozhuan", "unicode": "e685", "unicode_decimal": 59013 }, { "icon_id": "1433771", "name": "key", "font_class": "key", "unicode": "e648", "unicode_decimal": 58952 }, { "icon_id": "11372646", "name": "播放记录", "font_class": "bofangjilu", "unicode": "e8ad", "unicode_decimal": 59565 }, { "icon_id": "3640170", "name": "成功", "font_class": "chenggong", "unicode": "e605", "unicode_decimal": 58885 }, { "icon_id": "9626932", "name": "失败", "font_class": "shibai", "unicode": "e87c", "unicode_decimal": 59516 }, { "icon_id": "688104", "name": "收回 上下", "font_class": "shouhuishangxia", "unicode": "e790", "unicode_decimal": 59280 }, { "icon_id": "688143", "name": "展开 上下", "font_class": "zhankaishangxia", "unicode": "e7b1", "unicode_decimal": 59313 }, { "icon_id": "970749", "name": "数据库", "font_class": "shujuku", "unicode": "e62c", "unicode_decimal": 58924 }, { "icon_id": "5399558", "name": "保存", "font_class": "baocun", "unicode": "e936", "unicode_decimal": 59702 }, { "icon_id": "5961297", "name": "查询", "font_class": "chaxun", "unicode": "ec4c", "unicode_decimal": 60492 }, { "icon_id": "150096", "name": "对勾", "font_class": "duigou11", "unicode": "e61f", "unicode_decimal": 58911 }, { "icon_id": "1288113", "name": "check", "font_class": "check1", "unicode": "e617", "unicode_decimal": 58903 }, { "icon_id": "14151855", "name": "概览", "font_class": "gailan", "unicode": "e632", "unicode_decimal": 58930 }, { "icon_id": "21918566", "name": "概览", "font_class": "huaban2", "unicode": "e63d", "unicode_decimal": 58941 }, { "icon_id": "201638", "name": "编辑", "font_class": "bianji", "unicode": "e602", "unicode_decimal": 58882 }, { "icon_id": "4686545", "name": "刷新", "font_class": "shuaxin1", "unicode": "ec08", "unicode_decimal": 60424 }, { "icon_id": "8084544", "name": "菜单/列表", "font_class": "caidan", "unicode": "e611", "unicode_decimal": 58897 }, { "icon_id": "1305461", "name": "表格", "font_class": "biaoge", "unicode": "e63e", "unicode_decimal": 58942 }, { "icon_id": "4867114", "name": "展开", "font_class": "zhankai", "unicode": "e65f", "unicode_decimal": 58975 }, { "icon_id": "4942664", "name": "收起", "font_class": "shouqi", "unicode": "e61e", "unicode_decimal": 58910 }, { "icon_id": "5387845", "name": "主题_o", "font_class": "zhuti_o", "unicode": "eb6f", "unicode_decimal": 60271 }, { "icon_id": "11195023", "name": "断开连接", "font_class": "duankailianjie", "unicode": "e65e", "unicode_decimal": 58974 }, { "icon_id": "6010907", "name": "修改", "font_class": "xiugai", "unicode": "e60f", "unicode_decimal": 58895 }, { "icon_id": "10142371", "name": "删除", "font_class": "delete", "unicode": "e604", "unicode_decimal": 58884 }, { "icon_id": "77822", "name": "更多", "font_class": "gengduo1", "unicode": "e601", "unicode_decimal": 58881 }, { "icon_id": "4511969", "name": "减少", "font_class": "jianshao", "unicode": "e644", "unicode_decimal": 58948 }, { "icon_id": "5334173", "name": "加", "font_class": "jia", "unicode": "e61b", "unicode_decimal": 58907 }, { "icon_id": "7424757", "name": "加号", "font_class": "hao", "unicode": "e631", "unicode_decimal": 58929 }, { "icon_id": "11364467", "name": "arrow drop down", "font_class": "right", "unicode": "e608", "unicode_decimal": 58888 }, { "icon_id": "11931077", "name": "search", "font_class": "search1", "unicode": "e600", "unicode_decimal": 58880 }, { "icon_id": "15838464", "name": "download", "font_class": "download1", "unicode": "e66c", "unicode_decimal": 58988 }, { "icon_id": "22303251", "name": "向右箭头", "font_class": "xiangyoujiantou", "unicode": "e79c", "unicode_decimal": 59292 }, { "icon_id": "27901498", "name": "删除线型", "font_class": "shanchuxianxing", "unicode": "e6a7", "unicode_decimal": 59047 }, { "icon_id": "28093125", "name": "cross", "font_class": "cross-copy", "unicode": "ec8e", "unicode_decimal": 60558 }, { "icon_id": "5667975", "name": "刷新", "font_class": "shuaxin", "unicode": "e62d", "unicode_decimal": 58925 }, { "icon_id": "22303207", "name": "提醒", "font_class": "tixing", "unicode": "e913", "unicode_decimal": 59667 }, { "icon_id": "6129178", "name": "138设置、系统设置、功能设置、属性", "font_class": "shezhixitongshezhigongnengshezhishuxing", "unicode": "e795", "unicode_decimal": 59285 }, { "icon_id": "19113301", "name": "执行sql脚本", "font_class": "zhihangsqljiaoben", "unicode": "e759", "unicode_decimal": 59225 }, { "icon_id": "20104543", "name": "虚拟数据库管理", "font_class": "xunishujukuguanli", "unicode": "e61a", "unicode_decimal": 58906 } ] } ================================================ FILE: chat2db-client/src/blocks/AppTitleBar/index.less ================================================ @import '../../styles/var.less'; .appTitleBar { background-color: var(--color-bg-subtle); -webkit-app-region: drag; -webkit-user-select: none; border-bottom: 1px solid var(--color-border); display: flex; justify-content: space-between; align-items: center; height: 30px; .appTitleBarGlobal{ flex: 1; display: flex; align-items: center; justify-content: space-between; padding: 0px 10px; height: 100%; .appName { flex: 1; text-align: start; font-weight: bold; padding-left: 6px; } .rightSlot,.leftSlot{ height: 100%; display: flex; align-items: center; -webkit-app-region: no-drag; } } .spacer { width: 126px; flex-shrink: 0; } .windowsCloseBar { display: flex; height: 100%; flex-shrink: 0; width: 126px; -webkit-app-region: no-drag; .windowsCloseBarItem { width: 42px; height: 100%; display: flex; justify-content: center; align-items: center; cursor: pointer; &:hover { background-color: var(--color-hover-bg); } } } } ================================================ FILE: chat2db-client/src/blocks/AppTitleBar/index.tsx ================================================ import React, { memo, useState, useMemo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { useCommonStore } from '@/store/common'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; const [isMaximized, setIsMaximize] = useState(window.electronApi?.isMaximized()); const { appTitleBarRightComponent } = useCommonStore((state) => { return { appTitleBarRightComponent: state.appTitleBarRightComponent, }; }); const isMac = useMemo(() => { return window.electronApi?.getPlatform().isMac; }, []); // const isMac = false; const handleDoubleClick = () => { window.electronApi?.setMaximize(); setIsMaximize(!isMaximized); }; const handelMinimizeWindow = (e) => { e.stopPropagation(); window.electronApi?.minimizeWindow(); }; const handelMaximize = (e) => { e.stopPropagation(); window.electronApi?.setMaximize(); setIsMaximize(!isMaximized); }; const handelCloseWindow = (e) => { e.stopPropagation(); window.electronApi?.closeWindow(); }; if(isMac === void 0){ return false } return (
Chat2DB
{appTitleBarRightComponent}
{(!isMac && isMac !== void 0) && (
{isMaximized ? : }
)}
); }); ================================================ FILE: chat2db-client/src/blocks/CreateConnection/index.less ================================================ @import '../../styles/var.less'; .box { width: 100%; height: 100%; overflow-y: auto; } .dataBaseListBox{ display: flex; justify-content: center; align-items: center; min-height: 100%; } .dataBaseList { display: flex; justify-content: space-between; flex-wrap: wrap; max-width: 800px; } .databaseItem { flex-grow: 1; border-radius: 4px; height: 50px; width: 210px; margin: 10px 20px; padding: 0px 16px; border-radius: 8px; overflow: hidden; box-sizing: border-box; border: 1px solid var(--color-border); .databaseItemMain { display: flex; justify-content: space-between; align-items: center; height: 50px; border-radius: 8px; } .databaseItemLeft { display: flex; align-items: center; } .databaseItemRight { display: none; i { font-size: 16px; } } &:hover { background-color: var(--color-bg-medium); color: var(--color-primary); border: 1px solid var(--color-primary); cursor: pointer; .databaseItemRight { display: block; i { color: var(--color-primary); } } } .logoBox { display: flex; justify-content: center; align-items: center; height: 28px; width: 28px; border-radius: 8px; margin-right: 16px; i { font-size: 16px; } } } .databaseItemSpacer { flex-grow: 1; width: 210px; margin: 0px 20px; padding: 0px 16px; box-sizing: border-box; } .createConnections { min-height: 100%; display: flex; justify-content: center; align-items: center; background-color: var(--color-bg); transform: scale(0.2); transition: 0.1s ease-in-out; } .showCreateConnections { transform: scale(1); transition: transform 0.3s ease-in-out; } .notPermission { display: flex; flex-direction: column; .notPermissionIconTips { color: var(--color-text-tertiary); text-align: center; width: 340px; } .notPermissionIconBox, .connectButtonBox { display: flex; justify-content: center; } .notPermissionIcon { font-size: 200px; color: var(--color-text-quaternary); margin: 20px 0px; } .connectButton { margin-top: 20px; } } ================================================ FILE: chat2db-client/src/blocks/CreateConnection/index.tsx ================================================ import React, { memo, useEffect, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { IConnectionDetails, IDatabase } from '@/typings'; import ConnectionEdit from '@/components/ConnectionEdit'; import { databaseTypeList } from '@/constants'; import Iconfont from '@/components/Iconfont'; import i18n from '@/i18n'; import FileUploadModal from '@/components/ImportConnection'; import {getConnectionList} from '@/pages/main/store/connection'; // IConnectionDetails 全部信息代表修改 // null 展示因增列表 // { type: string } 只有数据库类型代表新增 type IEditConnectionDetail = IConnectionDetails | null | Pick; interface IProps { className?: string; onSubmit?: (data: IConnectionDetails) => Promise; // 点击保存或修改的回调,我会把数据给你 connectionDetail: IEditConnectionDetail | null | undefined; noPermission?: boolean; } export default memo((props) => { const { className, onSubmit, connectionDetail: externalConnectionDetail } = props; const [connectionDetail, setConnectionDetail] = useState( externalConnectionDetail, ); const [isFileUploadModalOpen, setIsFileUploadModalOpen] = useState(false); useEffect(() => { setConnectionDetail(externalConnectionDetail); }, [externalConnectionDetail]); function handleCreateConnections(database: IDatabase) { setConnectionDetail({ type: database.code, }); } // function handleSubmit(data: IConnectionDetails) { // return onSubmit?.(data); // } return ( <>
{connectionDetail && (
{ setConnectionDetail(null); }} connectionData={connectionDetail as any} submit={onSubmit} />
)} {connectionDetail === null && (
{databaseTypeList.map((t) => { return (
{t.name}
); })}
{setIsFileUploadModalOpen(true)}}>
{i18n('connection.title.importConnection')}
{Array.from({ length: 20 }).map((t, index) => { return
; })}
)}
{ setIsFileUploadModalOpen(false); }} onConfirm={() => { setIsFileUploadModalOpen(false); getConnectionList() }} /> ); }); { /*
{i18n('connection.tips.noConnectionTips')}
*/ } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less ================================================ @import '../../../styles/var.less'; .baseInfo { padding: 20px 10px 0px; display: flex; // justify-content: center; height: 100%; } .formBox { width: 50%; } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx ================================================ import React, { useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Form, Input } from 'antd'; import { Context } from '../index'; import { IBaseInfo } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; export interface IBaseInfoRef { getBaseInfo: () => IBaseInfo; } interface IProps { className?: string; } const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => { const { className } = props; const { tableDetails, databaseType } = useContext(Context); const [form] = Form.useForm(); useEffect(() => { form.setFieldsValue({ name: tableDetails.name, comment: tableDetails.comment, charset: tableDetails.charset, engine: tableDetails.engine, incrementValue: tableDetails.incrementValue, }); }, [tableDetails]); function getBaseInfo(): IBaseInfo { return form.getFieldsValue(); } useImperativeHandle(ref, () => ({ getBaseInfo, })); return (
{databaseType === DatabaseTypeCode.MYSQL && ( <> )}
); }); export default BaseInfo; ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less ================================================ @import '../../../styles/var.less'; .columnList { height: 100%; padding: 10px; box-sizing: border-box; display: flex; flex-direction: column; } .columnListHeader { margin: 0px -5px 10px; flex-shrink: 0; button { margin: 0px 5px; } } .formBox { height: 0px; flex: 1; display: flex; flex-direction: column; } .tableBox { flex: 1; display: flex; flex-direction: column; border-radius: 8px 8px 0px 0px; overflow: hidden; } .addColumnButton { flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1px dashed var(--color-border); line-height: 30px; margin-top: 10px; color: var(--color-text-secondary); cursor: pointer; i { margin-right: 5px; } &:hover { color: var(--color-primary); border-color: var(--color-primary); } } .otherInfo { flex-shrink: 0; margin: 10px -10px 0px; padding: 10px; height: 200px; display: flex; align-items: center; justify-content: center; border-top: 1px solid var(--color-border); } .otherInfoFormBox { min-height: 140px; width: 400px; } .editableCell { height: 28px; line-height: 26px; padding: 0px 7px; box-sizing: border-box; border: 1px solid transparent; .f-single-line(); width: 100%; cursor: pointer; } // .cellContent { // border: 1px solid transparent; // margin: -1px; // &:hover { // border: 1px solid var(--color-primary); // } // } .keyBox { width: 26px; height: 26px; display: flex; justify-content: center; align-items: center; cursor: pointer; position: relative; i { color: var(--color-warning); } span { position: absolute; font-weight: bold; right: 4px; bottom: -2px; transform: scale(0.8); } } .disabledKeyBox { cursor: default; } .operationBar { display: flex; justify-content: end; .deleteIconBox { height: 26px; width: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; &:hover { color: var(--color-primary); } } } .columnList { :global { .ant-table-body { border: 1px solid var(--color-border); border-top: 0px; border-bottom: 0px; } .ant-table-header { border: 1px solid var(--color-border); border-bottom: 0px; } .ant-table { border-radius: 10px; border-bottom: 0px; } .ant-table-wrapper .ant-table-tbody > tr > td { // border: 0px; padding: 4px 2px; } .ant-table-wrapper .ant-table-thead > tr > th { padding: 8px 4px; &::before { display: none; } } .ant-table-wrapper .ant-table-thead > tr > td { &::before { display: none; } } // antd无法设置最小宽度,所以在这里设置最小列宽为100px colgroup col:nth-last-child(2) { min-width: 100px; } } } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx ================================================ import React, { useContext, useEffect, useState, useRef, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { Table, InputNumber, Input, Form, Select, Checkbox } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Context } from '../index'; import { IColumnItemNew, IColumnTypes } from '@/typings'; import i18n from '@/i18n'; import { EditColumnOperationType, DatabaseTypeCode, NullableType } from '@/constants'; import CustomSelect from '@/components/CustomSelect'; import Iconfont from '@/components/Iconfont'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } interface IProps {} // 编辑配置 interface IEditingConfig extends IColumnTypes { editKey: string; } // 本组件暴露给父组件的方法 export interface IColumnListRef { getColumnListInfo: () => IColumnItemNew[]; } const Row = ({ children, ...props }: RowProps) => { const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id: props['data-row-key'], }); const style: React.CSSProperties = { ...props.style, transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), transition, ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), }; return ( {React.Children.map(children, (child) => { if ((child as React.ReactElement).key === 'sort') { return React.cloneElement(child as React.ReactElement, { children: ( ), }); } return child; })} ); }; // 创建一个空的数据结构 const createInitialData = () => { return { key: uuidv4(), oldName: null, name: null, tableName: null, columnType: null, dataType: null, defaultValue: null, autoIncrement: null, comment: null, primaryKey: null, primaryKeyOrder: null, schemaName: null, databaseName: null, typeName: null, columnSize: null, bufferLength: null, decimalDigits: null, numPrecRadix: null, nullableInt: null, sqlDataType: null, sqlDatetimeSub: null, charOctetLength: null, ordinalPosition: null, nullable: null, generatedColumn: null, charSetName: null, collationName: null, value: null, editStatus: EditColumnOperationType.Add, }; }; const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) => { const { databaseSupportField, databaseName, schemaName, tableDetails, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingData, setEditingData] = useState(null); const [editingConfig, setEditingConfig] = useState(null); const tableRef = useRef(null); const isEditing = (record: IColumnItemNew) => record.key === editingData?.key; const edit = (record: IColumnItemNew) => { if (record.key) { form.setFieldsValue({ ...record }); setEditingData(record); // 根据当前字段类型,设置编辑配置 databaseSupportField.columnTypes.forEach((i) => { if (i.typeName === record.columnType) { setEditingConfig({ ...i, editKey: record.key!, }); } }); } }; // 整理服务端返回的数据,构造为前端需要的数据结构 useEffect(() => { if (tableDetails) { const list = tableDetails?.columnList?.map((t) => { return { ...t, oldName: t.name, key: uuidv4(), }; }) || []; setEditingConfig(null); setDataSource(list); } }, [tableDetails]); const columns = [ { key: 'sort', width: '40px', align: 'center', fixed: 'left', }, // { // title: 'O T', // dataIndex: 'editStatus', // width: '60px', // align: 'center', // render: (text: EditColumnOperationType) => { // return text === EditColumnOperationType.Add ? ( // // ) : ( // // ); // }, // }, { title: i18n('editTable.label.columnName'), dataIndex: 'name', width: '160px', fixed: 'left', render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return (
{editable ? ( ) : (
{text}
)}
); }, }, { title: i18n('editTable.label.columnType'), dataIndex: 'columnType', width: '200px', render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return (
{editable ? ( ) : (
{text}
); }, }, { width: '40px', render: (text: string, record: IColumnItemNew) => { // sqlLite不支持删除字段,新增的字段可以删除 if (databaseType === DatabaseTypeCode.SQLITE && record.editStatus !== EditColumnOperationType.Add) { return null; } return (
{ deleteData(record); }} >
); }, }, ]; const handelPrimaryKey = (_data: IColumnItemNew) => { const newData = dataSource.map((item) => { let primaryKeyOrder: null | number = item.primaryKeyOrder; // 取消主键if if (_data.primaryKey) { // 如果取消的时当前的字段,主键顺序为null if (_data.key === item.key) { primaryKeyOrder = null; } else { // 如果当前字段是主键,取消主键的时候,比当前字段顺序大的字段顺序-1 if (_data.primaryKeyOrder && item.primaryKeyOrder && item.primaryKeyOrder >= _data.primaryKeyOrder) { primaryKeyOrder = item.primaryKeyOrder - 1; } } } else { // 增加主键if // 增加主键的时候,主键顺序为当前表的最大主键顺序+1 if (_data.key === item.key) { primaryKeyOrder = Math.max( ...dataSource.map((i) => { return i.primaryKeyOrder || 0; }), ) + 1; } // 对于当前字段之前的字段,主键顺序不变 } if (item.key === _data?.key) { // 判断当前数据是新增的数据还是编辑后的数据 let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } const editingDataItem = { ...item, primaryKey: !item.primaryKey, primaryKeyOrder, nullable: !item.primaryKey ? NullableType.NotNull : item.nullable, editStatus, }; return editingDataItem; } return { ...item, primaryKeyOrder, }; }); setDataSource(newData); }; const handelNullable = (_data: IColumnItemNew) => { const newData = dataSource.map((item) => { if (item.key === _data?.key) { // 判断当前数据是新增的数据还是编辑后的数据 let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } const editingDataItem = { ...item, nullable: !item.nullable ? NullableType.Null : NullableType.NotNull, editStatus, }; return editingDataItem; } return item; }); setDataSource(newData); }; const onDragEnd = ({ active, over }: DragEndEvent) => { if (active.id !== over?.id) { setDataSource((previous) => { const activeIndex = previous.findIndex((i) => i.key === active.id); const overIndex = previous.findIndex((i) => i.key === over?.id); return arrayMove(previous, activeIndex, overIndex); }); } }; const handleFieldsChange = (field: any) => { let { value } = field[0]; const { name: nameList } = field[0]; const name = nameList[0]; if (name === 'nullable') { value = value ? NullableType.Null : NullableType.NotNull; } const newData = dataSource.map((item) => { if (item.key === editingData?.key) { // 判断当前数据是新增的数据还是编辑后的数据 let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } const editingDataItem = { ...item, [name]: value, editStatus, }; if (name === 'columnType') { // 根据当前字段类型,设置编辑配置 databaseSupportField.columnTypes.forEach((i) => { if (i.typeName === value) { setEditingConfig({ ...editingConfig!, ...i, }); } }); // 特殊处理VARCHAR的默认长度 为255 if (value === 'VARCHAR' && editingDataItem.columnSize === null) { editingDataItem.columnSize = 255; form.setFieldsValue({ columnSize: 255, }); } } return editingDataItem; } return item; }); setDataSource(newData); }; const addData = () => { const newData = { ...createInitialData(), }; setDataSource([...dataSource, newData]); edit(newData); setTimeout(() => { tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); }, 0); }; const deleteData = (record) => { let list: any = []; if (record?.editStatus === EditColumnOperationType.Add) { list = dataSource.filter((i) => i.key !== record?.key); } else { list = dataSource.map((i) => { if (i.key === record?.key) { setEditingData(null); setEditingConfig(null); return { ...i, editStatus: EditColumnOperationType.Delete, }; } return i; }); } setDataSource(list); }; function getColumnListInfo(): IColumnItemNew[] { return dataSource.map((i) => { const data = { ...i, tableName: tableDetails?.name, databaseName, schemaName: schemaName || null, }; delete data.key; return data; }); } useImperativeHandle(ref, () => ({ getColumnListInfo, })); const renderOtherInfoForm = () => { const labelCol = { style: { width: 100 }, }; return ( <> {editingConfig?.supportAutoIncrement && ( )} {databaseType === DatabaseTypeCode.SQLSERVER && ( )} {editingConfig?.supportDefaultValue && ( )} {editingConfig?.supportCharset && ( )} {editingConfig?.supportCollation && ( )} {editingConfig?.supportScale && ( )} {editingConfig?.supportUnit && ( )} {editingConfig?.supportValue && ( )} ); }; const onRow = (record: any) => { return { onClick: () => { // sqlLite不支持修改字段,新增的字段可以修改 if (databaseType === DatabaseTypeCode.SQLITE && record.editStatus !== EditColumnOperationType.Add) { return; } if (editingData?.key !== record.key) { edit(record); } }, }; }; return (
{/*
*/}
i.key!)} strategy={verticalListSortingStrategy}> i.editStatus !== EditColumnOperationType.Delete)} />
{i18n('editTable.button.addColumn')}
{renderOtherInfoForm()}
); }); export default ColumnList; ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less ================================================ @import '../../../styles/var.less'; .includeCol { max-height: 40vh; display: flex; flex-direction: column; overflow: hidden; } .ant-input-number { width: 100%; } .formBox { flex: 1; height: 0; overflow: hidden; display: flex; } .indexListHeader { flex-shrink: 0; margin: 0px -10px 10px; button { margin: 0px 10px; } } .editableCell { height: 28px; line-height: 26px; padding: 0px 7px; box-sizing: border-box; border: 1px solid transparent; border-radius: 4px; .f-single-line(); width: 100%; cursor: pointer; &:hover { border: 1px solid var(--color-border); } } .operationBar { display: flex; justify-content: end; .deleteIconBox { height: 26px; width: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; &:hover { color: var(--color-primary); } } } .includeCol { :global { .ant-table-body { border: 1px solid var(--color-border); border-top: 0px; border-bottom: 0px; } .ant-table-header { border: 1px solid var(--color-border); border-bottom: 0px; } .ant-table { border-radius: 10px; border-bottom: 0px; } .ant-table-wrapper .ant-table-tbody > tr > td { // border: 0px; padding: 4px 2px; } .ant-table-wrapper .ant-table-thead > tr > th { padding: 8px 4px; &::before { display: none; } } .ant-table-wrapper .ant-table-thead > tr > td { &::before { display: none; } } } } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx ================================================ /** * 这个组件只负责拿到用户选择的表名 * */ import React, { useMemo, useState, useRef, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle, } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Table, Form, Select, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; import Iconfont from '@/components/Iconfont'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; } const createInitialData = () => { return { key: uuidv4(), ascOrDesc: null, // 升序还是降序 cardinality: null, // 基数 collation: null, // 排序规则 columnName: null, // 列名 comment: null, // 注释 filterCondition: null, // 过滤条件 indexName: null, // 索引名 indexQualifier: null, // 索引限定符 nonUnique: null, // 是否唯一 ordinalPosition: null, // 位置 schemaName: null, // 模式名 type: null, // 类型 pages: null, // 页数 databaseName: null, // 数据库名 tableName: null, // 表名 }; }; export interface IIncludeColRef { getIncludeColInfo: () => IIndexIncludeColumnItem[]; } const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) => { const { includedColumnList } = props; const { columnListRef, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(null); const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey; const tableRef = useRef(null); useEffect(() => { if (includedColumnList.length) { setDataSource( includedColumnList.map((t) => { return { ...t, key: uuidv4(), }; }), ); } }, [includedColumnList]); const columnList: IColumnItemNew[] = useMemo(() => { const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter((i) => i.name !== null); return columnListInfo || []; }, []); const edit = (record: any) => { form.setFieldsValue({ ...record }); setEditingKey(record.key || null); }; const addData = () => { const newData = createInitialData(); setDataSource([...dataSource, newData]); edit(newData); setTimeout(() => { tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); }, 0); }; const deleteData = (record) => { setDataSource(dataSource.filter((i) => i.key !== record.key)); }; const columns = [ { title: i18n('editTable.label.index'), dataIndex: 'index', width: '50px', align: 'center', render: (text: string, record: IIndexIncludeColumnItem) => { return dataSource.findIndex((i) => i.key === record.key) + 1; }, }, { title: i18n('editTable.label.columnName'), dataIndex: 'columnName', // width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? ( ) : (
edit(record)}> {text}
); }, }, { width: '40px', render: (text: string, record: IIndexIncludeColumnItem) => { return (
{ deleteData(record); }} >
); }, }, // { // title: i18n('editTable.label.prefixLength'), // dataIndex: 'prefixLength', // width: '45%', // render: (text: string, record: IIndexIncludeColumnItem) => { // const editable = isEditing(record); // return editable ? ( // // // // ) : ( //
edit(record)}> // {text} //
// ); // }, // }, ]; // sqlLite 添加排序规则 if (databaseType === DatabaseTypeCode.SQLITE) { columns.splice(2, 0, { title: i18n('editTable.label.collation'), dataIndex: 'collation', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? (
); }); export default IncludeCol; ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less ================================================ @import '../../../styles/var.less'; .indexList { height: 100%; padding: 10px; box-sizing: border-box; display: flex; flex-direction: column; } .indexListHeader { margin: 0px -5px 10px; button { margin: 0px 5px; } } .formBox { height: 0px; flex: 1; display: flex; flex-direction: column; } .tableBox { flex: 1; display: flex; flex-direction: column; overflow: hidden; border-radius: 8px 8px 0px 0px; overflow: hidden; } .addColumnButton { flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1px dashed var(--color-border); line-height: 30px; margin-top: 10px; color: var(--color-text-secondary); cursor: pointer; i { margin-right: 5px; } &:hover { color: var(--color-primary); border-color: var(--color-primary); } } .editableCell { height: 28px; line-height: 26px; padding: 0px 7px; box-sizing: border-box; border: 1px solid transparent; border-radius: 4px; .f-single-line(); width: 100%; cursor: pointer; &:hover { border: 1px solid var(--color-border); } } .columnListCell { span { margin-right: 8px; color: var(--color-primary); cursor: pointer; &:hover { color: var(--color-primary-hover); } } } .operationBar { display: flex; justify-content: end; .deleteIconBox { height: 26px; width: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; &:hover { color: var(--color-primary); } } } .indexList { :global { .ant-table-body { border: 1px solid var(--color-border); border-top: 0px; border-bottom: 0px; } .ant-table-header { border: 1px solid var(--color-border); border-bottom: 0px; } .ant-table { border-radius: 10px; border-bottom: 0px; } .ant-table-wrapper .ant-table-tbody > tr > td { // border: 0px; padding: 4px 2px; } .ant-table-wrapper .ant-table-thead > tr > th { padding: 8px 4px; &::before { display: none; } } .ant-table-wrapper .ant-table-thead > tr > td { &::before { display: none; } } // antd无法设置最小宽度,所以在这里设置最小列宽为100px colgroup col:nth-last-child(2) { min-width: 100px; } colgroup col:nth-last-child(3) { min-width: 100px; } } } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx ================================================ import React, { useState, forwardRef, ForwardedRef, useImperativeHandle, useContext, useRef, useMemo, useEffect, } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; import { type DragEndEvent, DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { Table, Input, Form, Select, Modal } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import IncludeCol, { IIncludeColRef } from '../IncludeCol'; import { IIndexItem, IIndexIncludeColumnItem } from '@/typings'; import { EditColumnOperationType, DatabaseTypeCode } from '@/constants'; import Iconfont from '@/components/Iconfont'; import { Context } from '../index'; import i18n from '@/i18n'; import lodash from 'lodash'; interface IProps {} export type IIndexListInfo = IIndexItem[]; export interface IIndexListRef { getIndexListInfo: () => IIndexListInfo; } const createInitialData = (): IIndexItem => { return { key: uuidv4(), columnList: [], name: '', type: null, comment: null, editStatus: EditColumnOperationType.Add, }; }; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } const Row = ({ children, ...props }: RowProps) => { const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id: props['data-row-key'], }); const style: React.CSSProperties = { ...props.style, transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), transition, ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), }; return ( {React.Children.map(children, (child) => { if ((child as React.ReactElement).key === 'sort') { return React.cloneElement(child as React.ReactElement, { children: ( ), }); } return child; })} ); }; const IndexList = forwardRef((props: IProps, ref: ForwardedRef) => { const { databaseSupportField, tableDetails, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [oldDataSource, setOldDataSource] = useState([]); // 用于记录上一次的数据,用于对比是否有变化 const [form] = Form.useForm(); const [editingData, setEditingData] = useState(null); const [includeColModalOpen, setIncludeColModalOpen] = useState(false); const includeColRef = useRef(null); const tableRef = useRef(null); const isEditing = (record: IIndexItem) => record.key === editingData?.key; const edit = (record: IIndexItem) => { form.setFieldsValue({ ...record }); if (record.key !== editingData?.key) { setEditingData(record || null); } }; useEffect(() => { const data = tableDetails.indexList?.map((i) => { const key = uuidv4(); return { ...i, oldName: i.name, key, }; }); setOldDataSource(lodash.cloneDeep(data) || []); setDataSource(lodash.cloneDeep(data) || []); }, [tableDetails]); const addData = () => { const newData = createInitialData(); setDataSource([...dataSource, newData]); edit(newData); setTimeout(() => { tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); }, 0); }; const deleteData = (record) => { const newList: any[] = dataSource ?.map((i) => { if (i.key === record?.key) { setEditingData(null); if (i.editStatus === EditColumnOperationType.Add) { return null; } return { ...i, editStatus: EditColumnOperationType.Delete, }; } return i; }) ?.filter((i) => i); setDataSource(newList || []); }; const handleFieldsChange = (field: any) => { let { value } = field[0]; const { name: nameList } = field[0]; const name = nameList[0]; if (name === 'nullable') { value = value ? 1 : 0; } const newData = dataSource.map((item) => { if (item.key === editingData?.key) { let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } return { ...item, [name]: value, editStatus, }; } return item; }); setDataSource(newData); }; const onDragEnd = ({ active, over }: DragEndEvent) => { if (active.id !== over?.id) { setDataSource((previous) => { const activeIndex = previous.findIndex((i) => i.key === active.id); const overIndex = previous.findIndex((i) => i.key === over?.id); return arrayMove(previous, activeIndex, overIndex); }); } }; function getIndexListInfo(): IIndexListInfo { return dataSource.map((i) => { return lodash.omit(i, 'key'); }); } useImperativeHandle(ref, () => ({ getIndexListInfo, })); const columns = useMemo(() => { const _columns = [ { key: 'sort', width: '40px', align: 'center', fixed: 'left', }, // { // title: i18n('editTable.label.index'), // width: '70px', // align: 'center', // render: (text: string, record: IIndexItem) => { // return dataSource.findIndex((i) => i.key === record.key) + 1; // }, // }, { title: i18n('editTable.label.indexName'), dataIndex: 'name', width: '180px', fixed: 'left', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( ) : (
{text}
); }, }, { title: i18n('editTable.label.indexType'), dataIndex: 'type', width: '100px', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( ) : (
{text}
); }, }, { title: i18n('editTable.label.includeColumn'), dataIndex: 'columnList', render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => { const editable = isEditing(record); const text = columnList ?.map((t) => { return `${t.columnName}`; }) .join(','); return editable ? (
{ setIncludeColModalOpen(true); }} > {i18n('common.button.edit')} {text}
) : (
{text}
); }, }, { title: i18n('editTable.label.comment'), dataIndex: 'comment', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( ) : (
edit(record)}> {text}
); }, }, { width: '40px', render: (text: string, record: IIndexItem) => { return (
{ deleteData(record); }} >
); }, }, ]; if (databaseType === DatabaseTypeCode.MYSQL) { _columns.splice(3, 0, { title: i18n('editTable.label.indexMethod'), dataIndex: 'method', width: '120px', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( ) : (
{text}
); }, }); } if (databaseType === DatabaseTypeCode.ORACLE) { _columns.splice(-2, 1); } return _columns; // TODO: isEditing 每次都会变所以这里是无意义的,后续在优化一下 }, [isEditing]); const getIncludeColInfo = () => { setDataSource( dataSource.map((i) => { const columnList = includeColRef.current?.getIncludeColInfo(); // 对比新老的IncludeColInfo有没有变化 if (i.key === editingData?.key && columnList) { i.columnList = columnList; oldDataSource.map((old) => { if (old.key === editingData?.key) { if (!lodash.isEqual(old.columnList, columnList)) { i.editStatus = EditColumnOperationType.Modify; } else { i.editStatus = null; } } }); } return i; }), ); setIncludeColModalOpen(false); }; const onRow = (record: any) => { return { onClick: () => { if (editingData?.key !== record.key) { edit(record); } }, }; }; const indexIncludedColumnList: IIndexIncludeColumnItem[] = useMemo(() => { let list: IIndexIncludeColumnItem[] = []; dataSource.forEach((i) => { if (i.key === editingData?.key) { list = i.columnList || []; } }); return list; }, [includeColModalOpen]); return (
{/*
*/}
i.key!)} strategy={verticalListSortingStrategy}>
i.editStatus !== EditColumnOperationType.Delete)} />
{i18n('editTable.button.addIndex')}
{ setIncludeColModalOpen(false); }} maskClosable={false} destroyOnClose={true} > ); }); export default IndexList; ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.less ================================================ @import '../../../styles/var.less'; .realTimeSQL { } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; return (
实时 SQL
实时 SQL
); }); ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/index.less ================================================ @import '../../styles/var.less'; .box { height: 100%; display: flex; flex-direction: column; } .header { // position: relative; display: flex; align-items: center; justify-content: space-between; margin: 10px 10px 0px 10px; box-sizing: border-box; overflow: hidden; } .saveButton { // position: absolute; // right: 20px; } .tabList { display: flex; border-bottom: 0; font-size: 14px; border-radius: 6px; background-color: var(--color-bg-subtle); position: relative; &::after { position: absolute; left: calc(82px * var(--i)); content: ''; height: 26px; width: 82px; border-radius: 4px; color: var(--color-primary); background-color: var(--color-primary-bg); z-index: 0; transition: left 0.2s ease-out; } } .tabItem { position: relative; z-index: 1; height: 26px; width: 82px; text-align: center; line-height: 26px; border-radius: 4px; cursor: pointer; font-size: 13px; user-select: none; &:hover { color: var(--color-primary); } } .currentTab { color: var(--color-primary); } .main { flex: 1; position: relative; overflow: hidden; } .tab { z-index: 2; position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; } .hidden { z-index: 1; opacity: 0; } .monacoEditorModal { display: flex; height: 400px; border: 1px solid var(--color-border); border-radius: 4px; .monacoEditorHeader { height: 38px; padding: 0px 10px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--color-border); .formatButton { border-radius: 3px; background-color: var(--color-fill-quaternary); height: 24px; padding: 0px 7px; cursor: pointer; i { margin-right: 6px; } &:hover { color: var(--color-primary); } } .executeButton { height: 24px; line-height: 24px; display: flex; justify-content: center; align-items: center; i { margin-right: 4px; } } } .monacoEditorContent { width: 0px; flex: 1; display: flex; flex-direction: column; padding: 0px -6px; border-right: 1px solid var(--color-border); .monacoEditor { flex: 1; margin: 0px 6px; } } .result { width: 0px; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; .resultHeader { width: 100%; line-height: 38px; text-align: center; border-bottom: 1px solid var(--color-border); } .resultContent { flex: 1; padding: 0px 10px; overflow-y: auto; .errorTitle { display: flex; align-items: center; padding: 4px 10px; // background-color: var(--color-bg-subtle); border-radius: 8px; margin-top: 10px; i { color: var(--color-error); margin-right: 4px; } } .errorMessage { margin: 10px 0px; padding: 4px 10px; background-color: var(--color-bg-subtle); border-radius: 8px; } } } } ================================================ FILE: chat2db-client/src/blocks/DatabaseTableEditor/index.tsx ================================================ import React, { memo, useRef, useState, createContext, useEffect, useMemo } from 'react'; import { Button, Modal, message } from 'antd'; import i18n from '@/i18n'; import lodash from 'lodash'; import styles from './index.less'; import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import ExecuteSQL from '@/components/ExecuteSQL'; import { IEditTableInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import LoadingContent from '@/components/Loading/LoadingContent'; interface IProps { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName?: string; databaseType: DatabaseTypeCode; changeTabDetails: (data: IWorkspaceTab) => void; tabDetails: IWorkspaceTab; submitCallback: () => void; } interface ITabItem { index: number; title: string; key: string; component: any; // TODO: 组件的Ts是什么 } interface IContext extends IProps { tableDetails: IEditTableInfo; baseInfoRef: React.RefObject; columnListRef: React.RefObject; indexListRef: React.RefObject; databaseSupportField: IDatabaseSupportField; } export const Context = createContext({} as any); interface IOption { label: string; value: string | number | null; } // 列字段类型,select组件的options需要的数据结构 interface IColumnTypesOption extends IColumnTypes { label: string; value: string | number | null; } export interface IDatabaseSupportField { columnTypes: IColumnTypesOption[]; charsets: IOption[]; collations: IOption[]; indexTypes: IOption[]; defaultValues: IOption[]; } export default memo((props: IProps) => { const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails, databaseType, submitCallback, } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); const [appendValue, setAppendValue] = useState(''); const tabList = useMemo(() => { return [ { index: 0, title: i18n('editTable.tab.basicInfo'), key: 'basic', component: , }, { index: 1, title: i18n('editTable.tab.columnInfo'), key: 'column', component: , }, { index: 2, title: i18n('editTable.tab.indexInfo'), key: 'index', component: , }, ]; }, []); const [currentTab, setCurrentTab] = useState(tabList[0]); const [databaseSupportField, setDatabaseSupportField] = useState({ columnTypes: [], charsets: [], collations: [], indexTypes: [], defaultValues: [], }); const [isLoading, setIsLoading] = useState(false); function changeTab(item: ITabItem) { setCurrentTab(item); } useEffect(() => { if (tableName) { getTableDetails(); } getDatabaseFieldTypeList(); }, []); // 获取数据库字段类型列表 const getDatabaseFieldTypeList = () => { sqlService .getDatabaseFieldTypeList({ dataSourceId, databaseName, }) .then((res) => { const columnTypes = res?.columnTypes?.map((i) => { return { ...i, value: i.typeName, label: i.typeName, }; }) || []; const charsets = res?.charsets?.map((i) => { return { value: i.charsetName, label: i.charsetName, }; }) || []; const collations = res?.collations?.map((i) => { return { value: i.collationName, label: i.collationName, }; }) || []; const indexTypes = res?.indexTypes?.map((i) => { return { value: i.typeName, label: i.typeName, }; }) || []; const defaultValues = res?.defaultValues?.map((i) => { return { value: i.defaultValue, label: i.defaultValue, }; }) || []; setDatabaseSupportField({ columnTypes, charsets, collations, indexTypes, defaultValues, }); }); }; const getTableDetails = (myParams?: { tableNameProps?: string }) => { const { tableNameProps } = myParams || {}; const myTableName = tableNameProps || tableName; if (myTableName) { const params = { databaseName, dataSourceId, tableName: myTableName, schemaName, refresh: true, }; setIsLoading(true); sqlService .getTableDetails(params) .then((res) => { const newTableDetails = lodash.cloneDeep(res); setTableDetails(newTableDetails || {}); setOldTableDetails(res); }) .finally(() => { setIsLoading(false); }); } }; function submit() { if (baseInfoRef.current && columnListRef.current && indexListRef.current) { const newTable = { ...oldTableDetails, ...baseInfoRef.current.getBaseInfo(), columnList: columnListRef.current.getColumnListInfo()!, indexList: indexListRef.current.getIndexListInfo()!, }; const params: IModifyTableSqlParams = { databaseName, dataSourceId, schemaName, refresh: true, newTable, }; if (tableName) { // params.tableName = tableName; params.oldTable = oldTableDetails; } sqlService.getModifyTableSql(params).then((res) => { setViewSqlModal(true); setAppendValue(res?.[0].sql); }); } } const executeSuccessCallBack = () => { setViewSqlModal(false); message.success(i18n('common.text.successfulExecution')); const newTableName = baseInfoRef.current?.getBaseInfo().name; getTableDetails({ tableNameProps: newTableName }); if (!tableName) { changeTabDetails({ ...tabDetails, title: `${newTableName}`, type: WorkspaceTabType.EditTable, uniqueData: { ...(tabDetails.uniqueData || {}), tableName: newTableName, }, }); } // 保存成功后,刷新左侧树 submitCallback?.(); }; return (
{tabList.map((item) => { return (
{item.title}
); })}
{tabList.map((t) => { return (
{t.component}
); })}
{ setViewSqlModal(false); }} width="60vw" maskClosable={false} footer={false} destroyOnClose={true} >
); }); ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/BaseInfo/index.less ================================================ @import '../../../styles/var.less'; .baseInfo { padding: 20px 10px 0px; display: flex; // justify-content: center; height: 100%; } .formBox { width: 50%; } ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/BaseInfo/index.tsx ================================================ import React, { useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Form, Input, Switch, Select } from 'antd'; import { Context } from '../index'; import { ISequenceInfo } from '@/typings'; import sqlService from '@/service/sql'; import i18n from '@/i18n'; export interface ISequenceInfoRef { getSequenceInfo: () => ISequenceInfo; } interface IProps { className?: string; } const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => { const { className } = props; const { sequenceDetails,databaseType,databaseName,dataSourceId,schemaName } = useContext(Context); const [userOptions, setUserOptions] = useState<{ value: string; label: string }[]>([]); const [form] = Form.useForm(); useEffect(() => { form.setFieldsValue({ comment: sequenceDetails.comment, relname: sequenceDetails.relname, typname: sequenceDetails.typname, seqcache: sequenceDetails.seqcache, rolname: sequenceDetails.rolname, seqstart: sequenceDetails.seqstart, seqincrement: sequenceDetails.seqincrement, seqmax: sequenceDetails.seqmax, seqmin: sequenceDetails.seqmin, seqcycle: sequenceDetails.seqcycle, }); const params = { databaseType, databaseName, dataSourceId, schemaName, refresh: true, }; sqlService. getDatabaseUserNameList(params).then((res) => { setUserOptions(res.map(user => ({value: user,label: user}))); }); }, [sequenceDetails]); function getSequenceInfo(): ISequenceInfo { return form.getFieldsValue(); } function onChange(checked: boolean) { form.setFieldsValue({ seqcycle: checked, }); } function handleChange(value: string) { form.setFieldsValue({ typname: value, }); } function rolnameChange(value: string) { form.setFieldsValue({ rolname: value, }); } useImperativeHandle(ref, () => ({ getSequenceInfo, })); return (
); }); export default BaseInfo; ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/IncludeCol/index.less ================================================ @import '../../../styles/var.less'; .includeCol { max-height: 40vh; display: flex; flex-direction: column; overflow: hidden; } .ant-input-number { width: 100%; } .formBox { flex: 1; height: 0; overflow: hidden; display: flex; } .indexListHeader { flex-shrink: 0; margin: 0px -10px 10px; button { margin: 0px 10px; } } .editableCell { height: 28px; line-height: 26px; padding: 0px 7px; box-sizing: border-box; border: 1px solid transparent; border-radius: 4px; .f-single-line(); width: 100%; cursor: pointer; &:hover { border: 1px solid var(--color-border); } } .operationBar { display: flex; justify-content: end; .deleteIconBox { height: 26px; width: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; &:hover { color: var(--color-primary); } } } .includeCol { :global { .ant-table-body { border: 1px solid var(--color-border); border-top: 0px; border-bottom: 0px; } .ant-table-header { border: 1px solid var(--color-border); border-bottom: 0px; } .ant-table { border-radius: 10px; border-bottom: 0px; } .ant-table-wrapper .ant-table-tbody > tr > td { // border: 0px; padding: 4px 2px; } .ant-table-wrapper .ant-table-thead > tr > th { padding: 8px 4px; &::before { display: none; } } .ant-table-wrapper .ant-table-thead > tr > td { &::before { display: none; } } } } ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/IncludeCol/index.tsx ================================================ /** * 这个组件只负责拿到用户选择的表名 * */ import React, { useMemo, useState, useRef, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle, } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Table, Form, Select, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; import Iconfont from '@/components/Iconfont'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; } const createInitialData = () => { return { key: uuidv4(), ascOrDesc: null, // 升序还是降序 cardinality: null, // 基数 collation: null, // 排序规则 columnName: null, // 列名 comment: null, // 注释 filterCondition: null, // 过滤条件 indexName: null, // 索引名 indexQualifier: null, // 索引限定符 nonUnique: null, // 是否唯一 ordinalPosition: null, // 位置 schemaName: null, // 模式名 type: null, // 类型 pages: null, // 页数 databaseName: null, // 数据库名 tableName: null, // 表名 }; }; export interface IIncludeColRef { getIncludeColInfo: () => IIndexIncludeColumnItem[]; } const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) => { const { includedColumnList } = props; const { columnListRef, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(null); const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey; const tableRef = useRef(null); useEffect(() => { if (includedColumnList.length) { setDataSource( includedColumnList.map((t) => { return { ...t, key: uuidv4(), }; }), ); } }, [includedColumnList]); const columnList: IColumnItemNew[] = useMemo(() => { const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter((i) => i.name !== null); return columnListInfo || []; }, []); const edit = (record: any) => { form.setFieldsValue({ ...record }); setEditingKey(record.key || null); }; const addData = () => { const newData = createInitialData(); setDataSource([...dataSource, newData]); edit(newData); setTimeout(() => { tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); }, 0); }; const deleteData = (record) => { setDataSource(dataSource.filter((i) => i.key !== record.key)); }; const columns = [ { title: i18n('editTable.label.index'), dataIndex: 'index', width: '50px', align: 'center', render: (text: string, record: IIndexIncludeColumnItem) => { return dataSource.findIndex((i) => i.key === record.key) + 1; }, }, { title: i18n('editTable.label.columnName'), dataIndex: 'columnName', // width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? ( ) : (
edit(record)}> {text}
); }, }, { width: '40px', render: (text: string, record: IIndexIncludeColumnItem) => { return (
{ deleteData(record); }} >
); }, }, // { // title: i18n('editTable.label.prefixLength'), // dataIndex: 'prefixLength', // width: '45%', // render: (text: string, record: IIndexIncludeColumnItem) => { // const editable = isEditing(record); // return editable ? ( // // // // ) : ( //
edit(record)}> // {text} //
// ); // }, // }, ]; // sqlLite 添加排序规则 if (databaseType === DatabaseTypeCode.SQLITE) { columns.splice(2, 0, { title: i18n('editTable.label.collation'), dataIndex: 'collation', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? (
); }); export default IncludeCol; ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/RealTimeSQL/index.less ================================================ @import '../../../styles/var.less'; .realTimeSQL { } ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/RealTimeSQL/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; return (
实时 SQL
实时 SQL
); }); ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/index.less ================================================ @import '../../styles/var.less'; .box { height: 100%; display: flex; flex-direction: column; } .header { // position: relative; display: flex; align-items: center; justify-content: space-between; margin: 10px 10px 0px 10px; box-sizing: border-box; overflow: hidden; } .saveButton { // position: absolute; // right: 20px; } .tabList { display: flex; border-bottom: 0; font-size: 14px; border-radius: 6px; background-color: var(--color-bg-subtle); position: relative; &::after { position: absolute; left: calc(82px * var(--i)); content: ''; height: 26px; width: 82px; border-radius: 4px; color: var(--color-primary); background-color: var(--color-primary-bg); z-index: 0; transition: left 0.2s ease-out; } } .tabItem { position: relative; z-index: 1; height: 26px; width: 82px; text-align: center; line-height: 26px; border-radius: 4px; cursor: pointer; font-size: 13px; user-select: none; &:hover { color: var(--color-primary); } } .currentTab { color: var(--color-primary); } .main { flex: 1; position: relative; overflow: hidden; } .tab { z-index: 2; position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; } .hidden { z-index: 1; opacity: 0; } .monacoEditorModal { display: flex; height: 400px; border: 1px solid var(--color-border); border-radius: 4px; .monacoEditorHeader { height: 38px; padding: 0px 10px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--color-border); .formatButton { border-radius: 3px; background-color: var(--color-fill-quaternary); height: 24px; padding: 0px 7px; cursor: pointer; i { margin-right: 6px; } &:hover { color: var(--color-primary); } } .executeButton { height: 24px; line-height: 24px; display: flex; justify-content: center; align-items: center; i { margin-right: 4px; } } } .monacoEditorContent { width: 0px; flex: 1; display: flex; flex-direction: column; padding: 0px -6px; border-right: 1px solid var(--color-border); .monacoEditor { flex: 1; margin: 0px 6px; } } .result { width: 0px; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; .resultHeader { width: 100%; line-height: 38px; text-align: center; border-bottom: 1px solid var(--color-border); } .resultContent { flex: 1; padding: 0px 10px; overflow-y: auto; .errorTitle { display: flex; align-items: center; padding: 4px 10px; // background-color: var(--color-bg-subtle); border-radius: 8px; margin-top: 10px; i { color: var(--color-error); margin-right: 4px; } } .errorMessage { margin: 10px 0px; padding: 4px 10px; background-color: var(--color-bg-subtle); border-radius: 8px; } } } } ================================================ FILE: chat2db-client/src/blocks/SequenceEditor/index.tsx ================================================ import React, { memo, useRef, useState, createContext, useEffect } from 'react'; import { Button, Modal, message } from 'antd'; import i18n from '@/i18n'; import lodash from 'lodash'; import styles from './index.less'; import classnames from 'classnames'; import BaseInfo, { ISequenceInfoRef } from './BaseInfo'; import sqlService, { IModifySequenceSqlParams } from '@/service/sql'; import ExecuteSQL from '@/components/ExecuteSQL'; import { ISequenceInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import LoadingContent from '@/components/Loading/LoadingContent'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; interface IProps { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName?: string; databaseType: DatabaseTypeCode; changeTabDetails: (data: IWorkspaceTab) => void; tabDetails: IWorkspaceTab; submitCallback: () => void; } interface IContext extends IProps { sequenceDetails: ISequenceInfo; sequenceInfoRef: React.RefObject; } export const Context = createContext({} as any); interface IOption { label: string; value: string | number | null; } // 列字段类型,select组件的options需要的数据结构 interface IColumnTypesOption extends IColumnTypes { label: string; value: string | number | null; } export interface IDatabaseSupportField { columnTypes: IColumnTypesOption[]; charsets: IOption[]; collations: IOption[]; indexTypes: IOption[]; defaultValues: IOption[]; } export default memo((props: IProps) => { const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails, databaseType, submitCallback, } = props; const [sequenceDetails, setSequenceDetails] = useState({} as any); const [oldSequenceDetails, setOldSequenceDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); const sequenceInfoRef = useRef(null); const [appendValue, setAppendValue] = useState(''); const [isLoading, setIsLoading] = useState(false); const user = useWorkspaceStore.getState().currentConnectionDetails useEffect(() => { if (tableName) { getSequenceDetails(); }else{ const newSequenceDetails = { ...sequenceDetails, rolname: useWorkspaceStore.getState().currentConnectionDetails?.user ?? '' }; setSequenceDetails(newSequenceDetails) } // getDatabaseFieldTypeList(); }, []); const getSequenceDetails = (myParams?: { sequenceNameProps?: string }) => { const { sequenceNameProps } = myParams || {}; const mySequenceName = sequenceNameProps || tableName; if (mySequenceName) { const params = { databaseName, dataSourceId, sequenceName: mySequenceName, schemaName, refresh: true, }; setIsLoading(true); sqlService .getSequenceDetails(params) .then((res) => { const newSequenceDetails = lodash.cloneDeep(res); setSequenceDetails(newSequenceDetails || {}); setOldSequenceDetails(res); }) .finally(() => { setIsLoading(false); }); } }; function submit() { const sequenceInfo = sequenceInfoRef.current.getSequenceInfo(); if (sequenceInfo.seqmin > sequenceInfo.seqstart) { message.error("最小值不能大于起始值"); // 或其他提示逻辑 return; } if (sequenceInfoRef.current) { const newSequence = { ...oldSequenceDetails, ...sequenceInfoRef.current.getSequenceInfo(), }; newSequence.nspname = props?.schemaName as String; const params: IModifySequenceSqlParams = { databaseName, dataSourceId, schemaName, refresh: true, newSequence, }; if (tableName) { // params.tableName = tableName; params.oldSequence = oldSequenceDetails; } sqlService.getModifySequenceSql(params).then((res) => { setViewSqlModal(true); setAppendValue(res?.[0].sql); }); } } const executeSuccessCallBack = () => { setViewSqlModal(false); message.success(i18n('common.text.successfulExecution')); const newTableName = sequenceInfoRef.current?.getSequenceInfo().nspname; // getTableDetails({ tableNameProps: newTableName }); if (!tableName) { changeTabDetails({ ...tabDetails, title: `${newTableName}`, type: WorkspaceTabType.EditSequence, uniqueData: { ...(tabDetails.uniqueData || {}), tableName: newTableName, }, }); } // 保存成功后,刷新左侧树 submitCallback?.(); }; return (
{ setViewSqlModal(false); }} width="60vw" maskClosable={false} footer={false} destroyOnClose={true} >
); }); ================================================ FILE: chat2db-client/src/blocks/Setting/About/index.less ================================================ .aboutUs { .versionsInfo { display: flex; align-items: center; padding-bottom: 10px; border-bottom: 1px solid var(--color-border); margin-bottom: 10px; } .brandLogo { margin-right: 15px; flex-shrink: 0; } .currentVersion { font-size: 20px; line-height: 24px; .appName { margin-right: 10px; } } .newVersion { color: var(--color-text-tertiary); font-size: 14px; margin-top: 2px; } .updateButton { margin-top: 10px; button:nth-of-type(1) { margin-right: 20px; } } .updateRule { .updateRuleTitle { font-size: 16px; margin-bottom: 10px; } .updateRuleGroup { display: flex; flex-direction: column; .updateRuleRadioContent { display: flex; align-items: center; i { margin-left: 4px; color: var(--color-text-tertiary); font-size: 12px; } } } } .holdingService{ margin-top: 20px; } .brief { display: flex; flex-direction: column; align-items: center; .env, .version { margin: 4px 0px; color: var(--color-text-45); } .log { margin-top: 4px; color: var(--color-primary); cursor: pointer; &:hover { text-decoration: underline; } } } } ================================================ FILE: chat2db-client/src/blocks/Setting/About/index.tsx ================================================ import React, { useEffect, useMemo } from 'react'; import styles from './index.less'; import i18n from '@/i18n'; import classnames from 'classnames'; import BrandLogo from '@/components/BrandLogo'; import { APP_NAME, WEBSITE_DOC } from '@/constants/appConfig'; import { Button, Radio, Space, Checkbox } from 'antd'; import configService from '@/service/config'; import { DownloadOutlined } from '@ant-design/icons'; import { IUpdateDetectionData } from '../index'; import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection'; import Iconfont from '@/components/Iconfont'; import { useSettingStore, setHoldingService } from '@/store/setting'; import s from '@/service/misc' interface IProps { updateDetectionData: IUpdateDetectionData | null; updateDetectionRef: React.MutableRefObject | null; } // 关于我们 export default function AboutUs(props: IProps) { const { updateDetectionData, updateDetectionRef } = props; const [updateRule, setUpdateRule] = React.useState<'manual' | 'auto'>(updateDetectionData?.type || 'manual'); const { holdingService } = useSettingStore((state) => { return { holdingService: state.holdingService, }; }); const onChangeUpdateRul = (e) => { configService.setAppUpdateType(e.target.value).then(() => { setUpdateRule(e.target.value); }); }; useEffect(() => { setUpdateRule(updateDetectionData?.type || 'manual'); }, [updateDetectionData?.type]); const jumpDoc = () => { window.open(WEBSITE_DOC, '_blank'); }; const restartApp = () => { window.electronApi?.quitApp(); }; const updateButton = useMemo(() => { if (!updateDetectionData?.needUpdate) { return false; } switch (updateDetectionData?.updatedStatusEnum) { case UpdatedStatusEnum.NOT_UPDATED: return ( ); case UpdatedStatusEnum.UPDATING: return ( ); case UpdatedStatusEnum.TIMEOUT: return ( ); case UpdatedStatusEnum.UPDATED: return ( ); // case UpdatedStatusEnum.UPDATED: // return ( // // ); default: return false; } }, [updateDetectionData]); const changeHoldingService = (e) => { setHoldingService(e.target.checked); window.electronApi?.setForceQuitCode?.(e.target.checked); }; return (
{APP_NAME} {__APP_VERSION__}
{updateDetectionData?.needUpdate ? ( UpdatedStatusEnum.UPDATED === updateDetectionData?.updatedStatusEnum ? ( {i18n('setting.text.newEditionIsReady')} ) : ( {i18n('setting.text.discoverNewVersion', updateDetectionData?.version)} ) ) : ( {i18n('setting.text.isLatestVersion')} )}
{updateDetectionData?.desktop && (
{updateButton}
)}
{i18n('setting.title.updateRule')}
{i18n('setting.text.autoUpdate')} {i18n('setting.text.manualUpdate')}
{/*
{i18n('setting.title.holdingService')}
{i18n('setting.text.holdingService')}
*/} {/*
{APP_NAME}
{i18n('setting.text.currentEnv')}:{__ENV__}
{i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build {formatDate(getUserTimezoneTimestamp(__BUILD_TIME__), 'yyyyMMddhhmmss')}
{i18n('setting.text.viewingUpdateLogs')}
*/}
); } ================================================ FILE: chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts ================================================ import i18n from '@/i18n'; import { IAiConfig } from '@/typings'; import { AIType } from '@/typings/ai'; export type IAiConfigBooleans = { [K in keyof IAiConfig]?: boolean | string; }; const AITypeName = { [AIType.CHAT2DBAI]: 'Chat2DB', [AIType.ZHIPUAI]: i18n('setting.tab.aiType.zhipu'), [AIType.BAICHUANAI]: i18n('setting.tab.aiType.baichuan'), [AIType.WENXINAI]: i18n('setting.tab.aiType.wenxin'), [AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'), [AIType.OPENAI]: 'Open AI', [AIType.AZUREAI]: 'Azure AI', [AIType.RESTAI]: i18n('setting.tab.custom'), }; const AIFormConfig: Record = { [AIType.CHAT2DBAI]: { apiKey: true, }, [AIType.ZHIPUAI]: { apiKey: true, apiHost: 'https://open.bigmodel.cn/api/paas/v4/chat/completions', model: 'codegeex-4', }, [AIType.BAICHUANAI]: { apiKey: true, secretKey: true, apiHost: 'https://api.baichuan-ai.com/v1/stream/chat/', model: 'Baichuan2-53B', }, [AIType.WENXINAI]: { apiKey: true, apiHost: true, }, [AIType.TONGYIQIANWENAI]: { apiKey: true, apiHost: true, model: true, }, [AIType.OPENAI]: { apiKey: true, apiHost: 'https://api.openai.com/', httpProxyHost: true, httpProxyPort: true, // model: 'gpt-3.5-turbo', }, [AIType.AZUREAI]: { apiKey: true, apiHost: true, model: true, }, [AIType.RESTAI]: { apiKey: true, apiHost: true, model: true, }, }; export { AIFormConfig, AITypeName }; ================================================ FILE: chat2db-client/src/blocks/Setting/AiSetting/index.less ================================================ .aiSqlSource { display: flex; margin-bottom: 20px; } .aiSqlSourceTitle { margin-right: 20px; min-width: 50px; } .title { font-size: 14px; margin-bottom: 12px; i { margin-left: 10px; color: var(--color-primary); } & label{ font-size: 14px !important; } } .content { margin-bottom: 15px; } .bottomButton { display: flex; justify-content: flex-end; margin-top: 20px; } ================================================ FILE: chat2db-client/src/blocks/Setting/AiSetting/index.tsx ================================================ import React, { useEffect, useState } from 'react'; import configService from '@/service/config'; import { AIType } from '@/typings/ai'; import { Alert, Button, Flex, Form, Input, Radio, RadioChangeEvent } from 'antd'; import i18n from '@/i18n'; import { IAiConfig } from '@/typings/setting'; import { IRole } from '@/typings/user'; import { AIFormConfig, AITypeName } from './aiTypeConfig'; import styles from './index.less'; import { useUserStore } from '@/store/user'; import { getLinkBasedOnTimezone } from '@/utils/timezone'; interface IProps { handleApplyAiConfig: (aiConfig: IAiConfig) => void; aiConfig: IAiConfig; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } // openAI 的设置项 export default function SettingAI(props: IProps) { const [aiConfig, setAiConfig] = useState(); const { userInfo } = useUserStore((state) => { return { userInfo: state.curUser, }; }); useEffect(() => { setAiConfig(props.aiConfig); }, [props.aiConfig]); if (!aiConfig) { return ; } if (userInfo?.roleCode && userInfo?.roleCode === IRole.USER) { // 如果是用户,不能配置ai return ; } const handleAiTypeChange = async (e: RadioChangeEvent) => { const aiSqlSource = e.target.value; // 查询对应ai类型的配置 const res = await configService.getAiSystemConfig({ aiSqlSource, }); setAiConfig(res); }; /** 应用Ai配置 */ const handleApplyAiConfig = () => { const newAiConfig = { ...aiConfig }; /*if (newAiConfig.apiHost && !newAiConfig.apiHost?.endsWith('/')) { newAiConfig.apiHost = newAiConfig.apiHost + '/'; }*/ if (aiConfig?.aiSqlSource === AIType.CHAT2DBAI) { newAiConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway'}${'/model/'}`; } if (props.handleApplyAiConfig) { props.handleApplyAiConfig(newAiConfig); } }; const renderAIConfig = () => { if (aiConfig?.aiSqlSource === AIType.CHAT2DBAI) { return ( ); } return ( <>
{Object.keys(AIFormConfig[aiConfig?.aiSqlSource]).map((key: string) => ( { setAiConfig({ ...aiConfig, [key]: e.target.value }); }} /> ))} {aiConfig.aiSqlSource === AIType.RESTAI && (
{`Tips: ${i18n( 'setting.tab.aiType.custom.tips', )}`}
)}
); }; return ( <>
{i18n('setting.title.aiSource')}:
{Object.keys(AIType).map((key) => ( {AITypeName[key]} ))}
{renderAIConfig()} {/* {aiConfig?.aiSqlSource === AIType.CHAT2DBAI && !aiConfig.apiKey && } */} ); } ================================================ FILE: chat2db-client/src/blocks/Setting/BaseSetting/index.less ================================================ .title { font-size: 14px; margin-bottom: 10px; i { margin-left: 10px; color: var(--color-primary); } } .backgroundList { display: flex; margin: 0px 0px 20px -7px; .themeItemBox { display: flex; flex-direction: column; align-items: center; margin: 0px 7px; .themeImg { margin-bottom: 6px; width: 64px; height: 48px; overflow: hidden; border-radius: 2px; background-size: cover; background-repeat: no-repeat; cursor: pointer; } .themeName { text-align: center; font-size: 12px; } .current { box-sizing: border-box; border: 2px solid var(--color-primary); } } filter: brightness(var(--filter-brightness)); } .langBox { margin-bottom: 20px; } .sqlEditorFontSize { margin-bottom: 20px; } .primaryColorList { display: flex; margin-bottom: 20px; .themeColorItem { margin-right: 20px; display: flex; flex-direction: column; align-items: center; } .colorLump { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 2px solid transparent; border-radius: 50%; cursor: pointer; border: 1px solid var(--color-bg-hover); } i { color: #fff; } .colorName { margin-top: 6px; font-size: 12px; } } ================================================ FILE: chat2db-client/src/blocks/Setting/BaseSetting/index.tsx ================================================ import React, { useState } from 'react'; import { LangType, ThemeType } from '@/constants'; import i18n, { currentLang } from '@/i18n'; import classnames from 'classnames'; import themeDarkImg from '@/assets/img/theme-dark.png'; import themeLightImg from '@/assets/img/theme-light.png'; import themeAutoImg from '@/assets/img/theme-auto.png'; import { Select } from 'antd'; import Iconfont from '@/components/Iconfont'; import { setLang as setLangLocalStorage } from '@/utils/localStorage'; import { useTheme } from '@/hooks/useTheme'; import styles from './index.less'; const themeList = [ { code: ThemeType.Light, name: i18n('setting.text.light'), img: themeLightImg, }, { code: ThemeType.Dark, name: i18n('setting.text.dark'), img: themeDarkImg, }, { code: ThemeType.DarkDimmed, name: i18n('setting.text.dark2'), img: themeDarkImg }, { code: ThemeType.FollowOs, name: i18n('setting.text.followOS'), img: themeAutoImg, }, // { // code: 'eyeshield', // name: '护眼', // img: 'https://img.alicdn.com/imgextra/i1/O1CN01KGCqY21uJpuFjEQW2_!!6000000006017-2-tps-181-135.png' // }, ]; const languageOptions = [ { value: LangType.ZH_CN, label: '简体中文' }, { value: LangType.EN_US, label: 'English' }, { value: LangType.TR_TR, label: 'Turkish' }, { value: LangType.JA_JP, label: '日本語' }, ] const colorList = [ { code: 'golden-purple', name: i18n('setting.label.violet'), color: '#9373ee', }, { code: 'polar-blue', name: i18n('setting.label.blue'), color: '#1a90ff', }, { code: 'blue2', name: i18n('setting.label.violet'), color: '#00c3ee', }, { code: 'polar-green', name: i18n('setting.label.green'), color: '#039e74', }, { code: 'gold', name: i18n('setting.label.violet'), color: '#9a7d56', }, { code: 'silver', name: i18n('setting.label.violet'), color: '#8e8374', }, { code: 'red', name: i18n('setting.label.violet'), color: '#fd6874', }, { code: 'orange', name: i18n('setting.label.violet'), color: '#fa8c16', }, ]; // baseBody 基础设置 export default function BaseSetting() { const [appTheme, setAppTheme] = useTheme(); const [currentTheme, setCurrentTheme] = useState(appTheme.backgroundColor); const [currentPrimaryColor, setCurrentPrimaryColor] = useState(localStorage.getItem('primary-color')); const changePrimaryColor = (item: any) => { const html = document.documentElement; html.setAttribute('primary-color', item.code); localStorage.setItem('primary-color', item.code); setCurrentPrimaryColor(item.code); setAppTheme({ ...appTheme, primaryColor: item.code, }); }; function changeLang(e: any) { setLangLocalStorage(e); //切换语言时,需要设置cookie,用来改变后台服务的Locale const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.LOCALE=${e};Expires=${date}`; location.reload(); } function handleChangeTheme(backgroundColor: any) { setAppTheme({ ...appTheme, backgroundColor, }); setCurrentTheme(backgroundColor); } // const changeSqlEditorFontSize = (e: any) => { // } return ( <>
{i18n('setting.title.backgroundColor')}
    {themeList.map((t) => { return (
    {t.name}
    ); })}
{i18n('setting.title.language')}
{/* 简体中文 English Turkish */}
); } ================================================ FILE: chat2db-client/src/blocks/Setting/UpdateDetection/index.less ================================================ @import '../../../styles/var.less'; .versionText{ color: var(--color-primary); } .notificationBtnBox{ display: flex; align-items: center; justify-content: space-between; } .updateReminder{ display: flex; align-items: center; .bell{ // 做个动画,当前div像铃铛一样摇晃,以div的上中心点为中心,动画循环10次以后停止 animation: shake 0.2s linear 10; margin-right: 4px; } i{ color: var(--color-primary); } } .notification{ z-index: 999 !important; width: 300px !important; font-size: 12px !important; padding: 8px 12px !important; :global{ .ant-notification-notice-close{ top: 10px !important; inset-inline-end: 8px !important; } } } // 做个动画,当前div像铃铛一样摇晃,以div的上中心点为中心,左右3px晃动 @keyframes shake { 0% { transform: rotate(0deg); } 25% { transform: rotate(8deg); } 50% { transform: rotate(0deg); } 75% { transform: rotate(-8deg); } 100% { transform: rotate(0deg); } } ================================================ FILE: chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx ================================================ import React, { memo, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import configService from '@/service/config'; import styles from './index.less'; import { notification, Button, Space } from 'antd'; import { compareVersion } from '@/utils'; import { IUpdateDetectionData } from '../index'; import i18n, { i18nElement } from '@/i18n'; import Iconfont from '@/components/Iconfont'; export enum UpdatedStatusEnum { // 未更新 NOT_UPDATED = 'NOT_UPDATED', // 更新中 UPDATING = 'UPDATING', // 更新完成 UPDATED = 'UPDATED', // 更新失败 UPDATE_FAILED = 'UPDATE_FAILED', // 超时 TIMEOUT = 'TIMEOUT', } interface IProps { openSettingModal: (number) => void; updateDetectionData: IUpdateDetectionData | null; setUpdateDetectionData: (data: IUpdateDetectionData) => void; } export interface IUpdateDetectionRef { openDownload: (data: IUpdateDetectionData) => void; } // 轮训间隔时间 const INTERVAL_TIME = 5000; // 最大轮训次数 const MAX_TIMES = 200; const UpdateDetection = memo( forwardRef((props: IProps, ref: ForwardedRef) => { const { openSettingModal, setUpdateDetectionData } = props; const [notificationApi, notificationDom] = notification.useNotification(); const timesRef = React.useRef(0); // useEffect(() => { // checkUpdate(); // }, []); const close = () => {}; // 检测是否有可更新的版本 function checkUpdate() { configService .getLatestVersion({ currentVersion: __APP_VERSION__, }) .then((res) => { // 如果是服务端,那么就不用更新 if(!res) { return } if (res.desktop === false) { return; } const _updateDetectionData = { ...res, needUpdate: compareVersion(res.version, __APP_VERSION__) === 1, updatedStatusEnum: UpdatedStatusEnum.NOT_UPDATED, }; setUpdateDetectionData(_updateDetectionData); // 如果监测到localStorage里面有存的老版本,那么就提示首次更新 const lastLatestVersion = localStorage.getItem('last-latest-version'); if (lastLatestVersion && compareVersion(lastLatestVersion, __APP_VERSION__) === -1) { openNotificationUpdated(); } // 最新版本存入localStorage localStorage.setItem('last-latest-version', __APP_VERSION__); // 如果是最新版本,那么就不用更新 if (compareVersion(res.version, __APP_VERSION__) !== 1) { return; } // 如果用户点过知道那么,就不用提示更新 if (localStorage.getItem('i-see-latest-version') === res.version && res.type === 'manual') { return; } // 如果是自动更新那么就轮询调后端接口,判断是否更新完成 if (res.type === 'auto') { timesRef.current = 0; openDownload(_updateDetectionData); } else { // 如果是手动更新,那么就提示下载 if (res.version) { openNotificationManual(res); } } }); } function isUpdateSuccess(_updateDetectionData) { if (timesRef.current > MAX_TIMES) { setUpdateDetectionData({ ..._updateDetectionData!, updatedStatusEnum: UpdatedStatusEnum.TIMEOUT, }); return; } timesRef.current = timesRef.current + 1; if (!_updateDetectionData?.version) { return; } configService .isUpdateSuccess({ version: _updateDetectionData.version, }) .then((res) => { if (res) { setUpdateDetectionData({ ..._updateDetectionData!, updatedStatusEnum: UpdatedStatusEnum.UPDATED, }); openNotificationAuto(_updateDetectionData); } else { setTimeout(() => { isUpdateSuccess(_updateDetectionData); }, INTERVAL_TIME); } }); } // function go() { // // window.open(responseText.downloadLink); // notificationApi.destroy(); // } const handleISee = (_updateDetectionData) => { // 存入localStorage localStorage.setItem('i-see-latest-version', _updateDetectionData?.version || ''); notificationApi.destroy(); }; const openNotificationAuto = (_updateDetectionData) => { const key = `open${Date.now()}`; const btn = ( {/* */} ); notificationApi.open({ className: styles.notification, duration: null, message: (
{i18n('common.text.updateReminder')}
), description: i18n('setting.text.newEditionIsReady'), style: { width: 260, backgroundColor: 'var(--color-bg-subtle)', }, btn, key, onClose: close, }); }; const openNotificationManual = (_updateDetectionData) => { const key = `open${Date.now()}`; const btn = (
); notificationApi.open({ duration: null, className: styles.notification, message: (
{i18n('common.text.updateReminder')}
), description: i18nElement( 'setting.text.discoverNewVersion', v{_updateDetectionData.version}, ), style: { width: 260, backgroundColor: 'var(--color-bg-subtle)', }, btn, key, onClose: close, }); }; // 首次更新完成提示 const openNotificationUpdated = () => { const key = `open${Date.now()}`; notificationApi.open({ className: styles.notification, duration: 6, message: (
{i18n('common.text.updateReminder')}
), description: i18n('setting.text.UpdatedLatestVersion', `v${__APP_VERSION__}`), style: { width: 310, backgroundColor: 'var(--color-bg-subtle)', }, btn: null, key, onClose: close, }); }; const openDownload = (_updateDetectionData: IUpdateDetectionData) => { if (!_updateDetectionData) { return; } configService.updateDesktopVersion(_updateDetectionData).then(() => { timesRef.current = 0; isUpdateSuccess(_updateDetectionData); setUpdateDetectionData({ ..._updateDetectionData, updatedStatusEnum: UpdatedStatusEnum.UPDATING, }); }); }; useImperativeHandle(ref, () => ({ openDownload, })); return <>{notificationDom}; }), ); export default UpdateDetection; ================================================ FILE: chat2db-client/src/blocks/Setting/index.less ================================================ @import '../../styles/var.less'; .box { .f-icon-button(); } .settingIcon { color: var(--custom-color-icon); &:hover { color: var(--color-primary); } } // .setText { // color: var(--custom-primary); // font-size: 16px; // &:hover { // text-decoration: underline; // } // } .title { font-size: 14px; margin-bottom: 10px; i { margin-left: 10px; color: var(--color-primary); } } .content { margin-bottom: 15px; } .modalBox { display: flex; margin: -16px; height: 70vh; overflow-y: auto; border-radius: var(--border-radius-l-g); } .menus { width: 200px; background-color: var(--color-bg-subtle); padding: 20px; position: sticky; top: 0; .menusTitle { font-size: 16px; padding-bottom: 10px; } .menuItem { margin: 10px 0px; padding: 10px; cursor: pointer; border-radius: 4px; display: flex; justify-content: flex-start; align-items: center; .prefixIcon { font-size: 16px; margin-right: 10px; } .rightSlot { flex: 1; display: flex; justify-content: flex-end; } .rightSlotAbout { i { font-size: 18px; color: var(--color-error); } } } .activeMenu { color: var(--color-primary); background-color: var(--color-hover-bg); } } .menuContent { min-height: 400px; padding: 20px; flex: 1; .menuContentTitle { font-size: 16px; padding-bottom: 20px; } } ================================================ FILE: chat2db-client/src/blocks/Setting/index.tsx ================================================ import React, { ReactNode, useEffect, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import { Modal, Tooltip } from 'antd'; import i18n from '@/i18n'; import BaseSetting from './BaseSetting'; import AISetting from './AiSetting'; import ProxySetting from './ProxySetting'; import About from './About'; import styles from './index.less'; import { ILatestVersion } from '@/service/config'; import UpdateDetection, { IUpdateDetectionRef, UpdatedStatusEnum } from '@/blocks/Setting/UpdateDetection'; // ---- store ----- import { useSettingStore, getAiSystemConfig, setAiSystemConfig } from '@/store/setting'; interface IProps { className?: string; render?: ReactNode; noLogin?: boolean; // 用于在没有登录的页面使用,不显示ai设置等需要登录的功能 defaultArouse?: boolean; // 是否默认弹出 defaultMenu?: number; // 默认选中的菜单 } export interface IUpdateDetectionData extends ILatestVersion { updatedStatusEnum: UpdatedStatusEnum; needUpdate: boolean; } function Setting(props: IProps) { const { className, noLogin = false, defaultArouse, defaultMenu = 0 } = props; const [isModalVisible, setIsModalVisible] = useState(false); const [currentMenu, setCurrentMenu] = useState(defaultMenu); const [updateDetectionData, setUpdateDetectionData] = useState(null); const updateDetectionRef = React.useRef(null); const aiConfig = useSettingStore((state) => state.aiConfig); useEffect(() => { if (defaultArouse) { showModal(); } }, []); useEffect(() => { if (isModalVisible && !noLogin) { getAiSystemConfig(); } }, [isModalVisible]); useEffect(() => { if (!noLogin) { getAiSystemConfig(); } }, []); const showModal = (_currentMenu: number = 0) => { setCurrentMenu(_currentMenu); setIsModalVisible(true); }; const handleOk = () => { setIsModalVisible(false); }; const handleCancel = () => { setIsModalVisible(false); }; function changeMenu(t: any) { setCurrentMenu(t); } const menusList = [ { label: i18n('setting.nav.basic'), icon: '\ue795', body: , code: 'basic', }, { label: i18n('setting.nav.customAi'), icon: '\ue646', body: , code: 'ai', }, { label: i18n('setting.nav.proxy'), icon: '\ue63f', body: , code: 'proxy', }, { label: i18n('setting.nav.aboutUs'), icon: '\ue65c', rightSlot: updateDetectionData?.needUpdate && (
), body: , code: 'about', }, ]; return ( <>
{ showModal(); }} > {props.render ? props.render : }
{i18n('setting.title.setting')}
{menusList.map((t, index) => { // 如果是没有登录的页面,不显示ai设置等需要登录的功能 if (noLogin && index === 1) { return false; } return (
{t.label} {t.rightSlot}
); })}
{menusList[currentMenu].label}
{menusList[currentMenu].body}
); } export default Setting; ================================================ FILE: chat2db-client/src/blocks/Tree/functions/deleteSequence.tsx ================================================ // 置顶表格 import React, { useState } from 'react'; import mysqlService from '@/service/sql'; import { Button, Checkbox } from 'antd'; import { openModal } from '@/store/common/components'; import styles from './deleteTable.less'; import i18n from '@/i18n'; export const deleteSequence = (treeNodeData,loadData) => { openModal({ width: '450px', content: , }); }; export const DeleteModalContent = (params: { treeNodeData: any; openModal: any; loadData: any }) => { const { treeNodeData,loadData } = params; // 禁用确定按钮 const [userChecked, setUserChecked] = useState(false); const onOk = () => { const p: any = { dataSourceId: treeNodeData.extraParams.dataSourceId, databaseName: treeNodeData.extraParams.databaseName, schemaName: treeNodeData.extraParams.schemaName, tableName: treeNodeData.name, sequenceName: treeNodeData.sequenceName }; mysqlService.deleteSequence(p).then(() => { loadData({ refresh: true, treeNodeData: treeNodeData.parentNode }); openModal(false); }); }; return (
{i18n('workspace.tree.delete.sequence.tip', `"${treeNodeData.name}"`)}
{ setUserChecked(e.target.checked); }} > {i18n('workspace.tree.delete.tip')}
); }; ================================================ FILE: chat2db-client/src/blocks/Tree/functions/deleteTable.less ================================================ .deleteTableFooter{ display: flex; justify-content: center; button{ margin: 0px 15px; } } .checkContainer{ margin: 15px 0px 25px; } .deleteModalContent{ display: flex; flex-direction: column; justify-content: center; font-size: 16px; font-weight: 500; text-align: center; } ================================================ FILE: chat2db-client/src/blocks/Tree/functions/deleteTable.tsx ================================================ // 置顶表格 import React, { useState } from 'react'; import mysqlService from '@/service/sql'; import { Button, Checkbox } from 'antd'; import { openModal } from '@/store/common/components'; import styles from './deleteTable.less'; import i18n from '@/i18n'; export const deleteTable = (treeNodeData,loadData) => { openModal({ width: '450px', content: , }); }; export const DeleteModalContent = (params: { treeNodeData: any; openModal: any; loadData: any }) => { const { treeNodeData,loadData } = params; // 禁用确定按钮 const [userChecked, setUserChecked] = useState(false); const onOk = () => { const p: any = { dataSourceId: treeNodeData.extraParams.dataSourceId, databaseName: treeNodeData.extraParams.databaseName, schemaName: treeNodeData.extraParams.schemaName, tableName: treeNodeData.name, }; mysqlService.deleteTable(p).then(() => { loadData({ refresh: true, treeNodeData: treeNodeData.parentNode }); openModal(false); }); }; return (
{i18n('workspace.tree.delete.table.tip', `"${treeNodeData.name}"`)}
{ setUserChecked(e.target.checked); }} > {i18n('workspace.tree.delete.tip')}
); }; ================================================ FILE: chat2db-client/src/blocks/Tree/functions/openAsyncSql.ts ================================================ import { WorkspaceTabType } from '@/constants'; import sqlService from '@/service/sql'; import {createConsole} from '@/pages/main/workspace/store/console' export const openView = (props:{ addWorkspaceTab: any; treeNodeData: any; }) => { const { treeNodeData } = props; createConsole({ name: treeNodeData.name, operationType: WorkspaceTabType.VIEW, dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ return new Promise((resolve) => { sqlService .getViewDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams!.databaseName!, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData.name } as any) .then((res) => { // 更新ddl resolve(res.ddl); }); }); } }) } export const openFunction = (props:{ treeNodeData: any; }) => { const { treeNodeData } = props; createConsole({ name: treeNodeData.name, operationType: WorkspaceTabType.FUNCTION, dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ return new Promise((resolve) => { sqlService .getFunctionDetail({ name:treeNodeData.name, dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams!.databaseName!, schemaName: treeNodeData.extraParams?.schemaName, functionName: treeNodeData.name } as any) .then((res) => { // 更新ddl resolve(res.functionBody); }); }); } }) } export const openProcedure = (props:{ treeNodeData: any; }) => { const { treeNodeData } = props; createConsole({ name: treeNodeData.name, operationType: WorkspaceTabType.PROCEDURE, dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ return new Promise((resolve) => { sqlService .getProcedureDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams!.databaseName!, schemaName: treeNodeData.extraParams?.schemaName, procedureName: treeNodeData.name } as any) .then((res) => { // 更新ddl resolve(res.procedureBody); }); }); } }) } export const openTrigger = (props:{ treeNodeData: any; }) => { const {treeNodeData } = props; createConsole({ name: treeNodeData.name, operationType: WorkspaceTabType.TRIGGER, dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ return new Promise((resolve) => { sqlService .getTriggerDetail({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams!.databaseName!, schemaName: treeNodeData.extraParams?.schemaName, triggerName: treeNodeData.name } as any) .then((res) => { // 更新ddl resolve(res.triggerBody); }); }); } }) } export const openSequence = (props:{ treeNodeData: any; }) => { const { treeNodeData } = props; createConsole({ name: treeNodeData.name, operationType: WorkspaceTabType.SEQUENCE, dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, loadSQL: ()=>{ return new Promise((resolve) => { sqlService .exportCreateSequenceSql({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams!.databaseName!, schemaName: treeNodeData.extraParams?.schemaName, name: treeNodeData.name } as any) .then((res) => { // 更新ddl resolve(res); }); }); } }) } ================================================ FILE: chat2db-client/src/blocks/Tree/functions/pinTable.ts ================================================ // 置顶表格 import mysqlService from '@/service/sql'; export const handelPinTable = ({ treeNodeData,loadData }) => { const api = treeNodeData.pinned ? 'deleteTablePin' : 'addTablePin'; mysqlService[api]({ dataSourceId: treeNodeData.extraParams.dataSourceId, databaseName: treeNodeData.extraParams.databaseName, schemaName: treeNodeData.extraParams.schemaName, tableName: treeNodeData.name, }).then(()=>{ loadData({ refresh: true, }) }) }; ================================================ FILE: chat2db-client/src/blocks/Tree/functions/refresh.ts ================================================ import { ITreeNode } from '@/typings'; export const refreshTreeNode = (props:{ treeNodeData: ITreeNode; }) => { const { treeNodeData } = props; } ================================================ FILE: chat2db-client/src/blocks/Tree/functions/viewDDL.less ================================================ .monacoEditorBox{ border: 1px solid var(--color-border); border-radius: 4px; height: 60vh; overflow: hidden; } ================================================ FILE: chat2db-client/src/blocks/Tree/functions/viewDDL.tsx ================================================ // 置顶表格 import React from 'react'; import mysqlService from '@/service/sql'; import { v4 as uuid } from 'uuid'; import styles from './viewDDL.less'; import { openModal } from '@/store/common/components'; import MonacoEditor from '@/components/MonacoEditor'; export const viewDDL = (treeNodeData) => { const getSql = () => { return new Promise((resolve) => { mysqlService .exportCreateTableSql({ dataSourceId: treeNodeData.extraParams.dataSourceId, databaseName: treeNodeData.extraParams.databaseName, schemaName: treeNodeData.extraParams.schemaName, name: treeNodeData.name, }) .then((res) => { resolve(res); }); }); }; openModal({ title: `DDL-${treeNodeData.name}`, width: '60%', height: '60%', footer: false, content: (
), }); }; export const MonacoEditorAsync = (params: { getSql: any }) => { const { getSql } = params; const monacoEditorRef = React.useRef(); getSql().then((sql) => { monacoEditorRef.current.setValue(sql); }); return ; }; ================================================ FILE: chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts ================================================ import { ITreeNode } from '@/typings'; import { OperationColumn, WorkspaceTabType, TreeNodeType } from '@/constants'; import i18n from '@/i18n'; import { v4 as uuid } from 'uuid'; // ----- components ----- import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; // ----- config ----- import { ITreeConfigItem, treeConfig } from '../treeConfig'; import { useMemo } from 'react'; // ----- store ----- import { createConsole, addWorkspaceTab } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; // ---- functions ----- import { openView, openFunction, openProcedure, openTrigger, openSequence} from '../functions/openAsyncSql'; import { handelPinTable } from '../functions/pinTable'; import { viewDDL } from '../functions/viewDDL'; import { deleteTable } from '../functions/deleteTable'; import { deleteSequence } from '../functions/deleteSequence'; // ----- utils ----- import { compatibleDataBaseName } from '@/utils/database'; interface IProps { treeNodeData: ITreeNode; loadData: any; } interface IOperationColumnConfigItem { text: string; icon: string; doubleClickTrigger?: boolean; handle: (treeNodeData: ITreeNode) => void; discard?: boolean; } interface IRightClickMenu { key: number; onClick: (treeNodeData: ITreeNode) => void; type: OperationColumn; doubleClickTrigger?: boolean; labelProps: { icon: string; label: string; }; } export const useGetRightClickMenu = (props: IProps) => { const { treeNodeData, loadData } = props; const { openCreateDatabaseModal, currentConnectionDetails } = useWorkspaceStore((state) => { return { openCreateDatabaseModal: state.openCreateDatabaseModal, currentConnectionDetails: state.currentConnectionDetails, }; }); const handelOpenCreateDatabaseModal = (type: 'database' | 'schema') => { const relyOnParams = { databaseType: treeNodeData.extraParams!.databaseType, dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseName: treeNodeData.name, } openCreateDatabaseModal?.({ type, relyOnParams, executedCallback: () => { loadData({ refresh: true, }); }, }); }; const rightClickMenu = useMemo(() => { // 拿出当前节点的配置 const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.treeNodeType]; const { operationColumn } = treeNodeConfig; const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === treeNodeData.extraParams?.databaseType; })!; // 有些数据库不支持的操作,需要排除掉 function excludeSomeOperation() { const excludes = dataSourceFormConfig.baseInfo.excludes; const newOperationColumn: OperationColumn[] = []; operationColumn?.map((item: OperationColumn) => { let flag = false; excludes?.map((t) => { if (item === t) { flag = true; } }); if (!flag) { newOperationColumn.push(item); } }); return newOperationColumn; } const operationColumnConfig: { [key in string]: IOperationColumnConfigItem } = { // 刷新 [OperationColumn.Refresh]: { text: i18n('common.button.refresh'), icon: '\uec08', handle: () => { loadData?.({ refresh: true, }); }, }, // 创建console [OperationColumn.CreateConsole]: { text: i18n('workspace.menu.queryConsole'), icon: '\ue619', handle: () => { createConsole({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }); }, }, // 查看所有表 [OperationColumn.ViewAllTable]: { text: i18n('workspace.menu.viewAllTable'), icon: '\ue611', handle: () => { addWorkspaceTab({ id: uuid(), type: WorkspaceTabType.ViewAllTable, title: `${treeNodeData.extraParams!.databaseName!}-tables`, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }, }) }, }, // 创建表 [OperationColumn.CreateTable]: { text: i18n('editTable.button.createTable'), icon: '\ue792', handle: () => { addWorkspaceTab({ id: uuid(), title: i18n('editTable.button.createTable'), type: WorkspaceTabType.CreateTable, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, submitCallback: () => {loadData?.({refresh: true})}, }, }); }, discard: (treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema), }, // 删除表 [OperationColumn.DeleteTable]: { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { deleteTable(treeNodeData,loadData); }, }, // 查看ddl [OperationColumn.ViewDDL]: { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { viewDDL(treeNodeData) }, }, // 置顶 [OperationColumn.Pin]: { text: treeNodeData.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: treeNodeData.pinned ? '\ue61d' : '\ue627', handle: () => { handelPinTable({ treeNodeData, loadData: () => { loadData({treeNodeData:treeNodeData.parentNode}) } }); }, }, // 编辑表 [OperationColumn.EditTable]: { text: i18n('workspace.menu.editTable'), icon: '\ue602', handle: () => { addWorkspaceTab({ id: `${OperationColumn.EditTable}-${treeNodeData.uuid}`, title: treeNodeData?.name, type: WorkspaceTabType.EditTable, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, submitCallback: () => { loadData({ treeNodeData: treeNodeData.parentNode, refresh: true }) }, }, }); }, }, // 复制名称 [OperationColumn.CopyName]: { text: i18n('common.button.copyName'), icon: '\uec7a', handle: () => { navigator.clipboard.writeText(treeNodeData.name); }, }, // 打开表 [OperationColumn.OpenTable]: { text: i18n('workspace.menu.openTable'), icon: '\ue618', doubleClickTrigger: true, handle: () => { const databaseName = compatibleDataBaseName(treeNodeData.name!, treeNodeData.extraParams!.databaseType); addWorkspaceTab({ id: `${OperationColumn.OpenTable}-${treeNodeData.uuid}`, title: treeNodeData.name, type: WorkspaceTabType.EditTableData, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData.name, sql: 'select * from ' + databaseName, }, }); }, }, // 打开视图 [OperationColumn.OpenView]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openView({ addWorkspaceTab, treeNodeData, }); }, }, // 打开函数 [OperationColumn.OpenFunction]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openFunction({ addWorkspaceTab, treeNodeData, }); }, }, // 打开存储过程 [OperationColumn.OpenProcedure]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openProcedure({ addWorkspaceTab, treeNodeData, }); }, }, // 打开触发器 [OperationColumn.OpenTrigger]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openTrigger({ addWorkspaceTab, treeNodeData, }); }, }, // 打开序列 [OperationColumn.OpenSequence]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openSequence({ addWorkspaceTab, treeNodeData, }); }, }, //创建序列 [OperationColumn.CreateSequence]:{ text: i18n('editSequence.button.createSequence'), icon: '\ue792', handle: () => { addWorkspaceTab({ id: uuid(), title: i18n('editSequence.button.createSequence'), type: WorkspaceTabType.CreateSequence, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, submitCallback: () => {loadData?.({refresh: true})}, }, }); }, discard: (treeNodeData.treeNodeType === TreeNodeType.SEQUENCE && currentConnectionDetails?.supportSchema), }, // 编辑序列 [OperationColumn.EditSequence]: { text: i18n('workspace.menu.editSequence'), icon: '\ue602', handle: () => { addWorkspaceTab({ id: `${OperationColumn.EditSequence}-${treeNodeData.uuid}`, title: treeNodeData?.name, type: WorkspaceTabType.EditSequence, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, submitCallback: () => { loadData({ treeNodeData: treeNodeData.parentNode, refresh: true }) }, }, }); }, }, // 删除序列 [OperationColumn.DeleteSequence]: { text: i18n('workspace.menu.deleteSequence'), icon: '\ue6a7', handle: () => { deleteSequence(treeNodeData,loadData); }, }, // 创建数据库 [OperationColumn.CreateDatabase]: { text: i18n('workspace.menu.createDatabase'), icon: '\ue816', handle: () => { handelOpenCreateDatabaseModal('database'); }, }, // 创建schema [OperationColumn.CreateSchema]: { text: i18n('workspace.menu.createSchema'), icon: '\ue696', handle: () => { handelOpenCreateDatabaseModal('schema'); }, discard: !currentConnectionDetails?.supportSchema, }, }; // 根据配置生成右键菜单 const finalList: IRightClickMenu[] = []; excludeSomeOperation().forEach((t, i) => { const concrete = operationColumnConfig[t]; if (!concrete.discard) { finalList.push({ key: i, onClick: concrete?.handle, type: t, doubleClickTrigger: concrete.doubleClickTrigger, labelProps: { icon: concrete?.icon, label: concrete?.text, }, }); } }); return finalList; }, [treeNodeData]); return rightClickMenu; }; export const getRightClickMenu = (props: IProps) => { const { treeNodeData, loadData } = props; const openCreateDatabaseModal = useWorkspaceStore.getState().openCreateDatabaseModal; const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; const handelOpenCreateDatabaseModal = (type: 'database' | 'schema') => { const relyOnParams = { databaseType: treeNodeData.extraParams!.databaseType, dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseName: treeNodeData.name, } openCreateDatabaseModal?.({ type, relyOnParams, executedCallback: () => { loadData({ refresh: true, }); }, }); }; // 拿出当前节点的配置 const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.treeNodeType]; const { operationColumn } = treeNodeConfig; const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === treeNodeData.extraParams?.databaseType; })!; // 有些数据库不支持的操作,需要排除掉 function excludeSomeOperation() { const excludes = dataSourceFormConfig.baseInfo.excludes; const newOperationColumn: OperationColumn[] = []; operationColumn?.map((item: OperationColumn) => { let flag = false; excludes?.map((t) => { if (item === t) { flag = true; } }); if (!flag) { newOperationColumn.push(item); } }); return newOperationColumn; } const operationColumnConfig: { [key in string]: IOperationColumnConfigItem } = { // 刷新 [OperationColumn.Refresh]: { text: i18n('common.button.refresh'), icon: '\uec08', handle: () => { loadData?.({ refresh: true, }); }, }, // 创建console [OperationColumn.CreateConsole]: { text: i18n('workspace.menu.queryConsole'), icon: '\ue619', handle: () => { createConsole({ dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }); }, }, // 查看所有表 [OperationColumn.ViewAllTable]: { text: i18n('workspace.menu.viewAllTable'), icon: '\ue611', handle: () => { addWorkspaceTab({ id: uuid(), type: WorkspaceTabType.ViewAllTable, title: `${treeNodeData.extraParams!.databaseName!}-tables`, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, dataSourceName: treeNodeData.extraParams!.dataSourceName!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }, }) }, }, // 创建表 [OperationColumn.CreateTable]: { text: i18n('editTable.button.createTable'), icon: '\ue792', handle: () => { addWorkspaceTab({ id: uuid(), title: i18n('editTable.button.createTable'), type: WorkspaceTabType.CreateTable, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, submitCallback: () => {treeNodeData.loadData?.({refresh: true})}, }, }); }, discard: (treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema), }, // 删除表 [OperationColumn.DeleteTable]: { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { deleteTable(treeNodeData); }, }, // 查看ddl [OperationColumn.ViewDDL]: { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { viewDDL(treeNodeData) }, }, // 置顶 [OperationColumn.Pin]: { text: treeNodeData.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: treeNodeData.pinned ? '\ue61d' : '\ue627', handle: () => { handelPinTable({treeNodeData, loadData: treeNodeData.parentNode!.loadData!}); }, }, // 编辑表 [OperationColumn.EditTable]: { text: i18n('workspace.menu.editTable'), icon: '\ue602', handle: () => { addWorkspaceTab({ id: `${OperationColumn.EditTable}-${treeNodeData.uuid}`, title: treeNodeData?.name, type: WorkspaceTabType.EditTable, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, submitCallback: () => {treeNodeData.parentNode?.loadData?.({refresh: true})}, }, }); }, }, // 复制名称 [OperationColumn.CopyName]: { text: i18n('common.button.copyName'), icon: '\uec7a', handle: () => { navigator.clipboard.writeText(treeNodeData.name); }, }, // 打开表 [OperationColumn.OpenTable]: { text: i18n('workspace.menu.openTable'), icon: '\ue618', doubleClickTrigger: true, handle: () => { const databaseName = compatibleDataBaseName(treeNodeData.name!, treeNodeData.extraParams!.databaseType); addWorkspaceTab({ id: `${OperationColumn.OpenTable}-${treeNodeData.uuid}`, title: treeNodeData.name, type: WorkspaceTabType.EditTableData, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData.name, sql: 'select * from ' + databaseName, }, }); }, }, // 打开视图 [OperationColumn.OpenView]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openView({ addWorkspaceTab, treeNodeData, }); }, }, // 打开函数 [OperationColumn.OpenFunction]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openFunction({ addWorkspaceTab, treeNodeData, }); }, }, // 打开序列 [OperationColumn.OpenSequence]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openSequence({ addWorkspaceTab, treeNodeData, }); }, }, // 删除序列 [OperationColumn.DeleteSequence]: { text: i18n('workspace.menu.deleteSequence'), icon: '\ue6a7', handle: () => { deleteSequence(treeNodeData); }, }, // 创建序列 [OperationColumn.CreateSequence]: { text: i18n('editSequence.button.createSequence'), icon: '\ue792', handle: () => { addWorkspaceTab({ id: uuid(), title: i18n('editSequence.button.createSequence'), type: WorkspaceTabType.CreateSequence, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, submitCallback: () => {treeNodeData.loadData?.({refresh: true})}, }, }); }, discard: (treeNodeData.treeNodeType === TreeNodeType.SEQUENCES && currentConnectionDetails?.supportSchema), }, // 打开存储过程 [OperationColumn.OpenProcedure]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openProcedure({ addWorkspaceTab, treeNodeData, }); }, }, // 打开触发器 [OperationColumn.OpenTrigger]: { text: i18n('workspace.menu.view'), icon: '\ue651', doubleClickTrigger: true, handle: () => { openTrigger({ addWorkspaceTab, treeNodeData, }); }, }, // 创建数据库 [OperationColumn.CreateDatabase]: { text: i18n('workspace.menu.createDatabase'), icon: '\ue816', handle: () => { handelOpenCreateDatabaseModal('database'); }, }, // 创建schema [OperationColumn.CreateSchema]: { text: i18n('workspace.menu.createSchema'), icon: '\ue696', handle: () => { handelOpenCreateDatabaseModal('schema'); }, discard: !currentConnectionDetails?.supportSchema, }, }; // 根据配置生成右键菜单 const finalList: IRightClickMenu[] = []; excludeSomeOperation().forEach((t,i) => { const concrete = operationColumnConfig[t]; if (!concrete.discard) { finalList.push({ key: i, onClick: concrete?.handle, type: t, doubleClickTrigger: concrete.doubleClickTrigger, labelProps: { icon: concrete?.icon, label: concrete?.text, }, }); } }); return finalList; }; ================================================ FILE: chat2db-client/src/blocks/Tree/hooks/useTreeNodeFocus.ts ================================================ import { useTreeStore } from '../treeStore'; export const useTreeNodeFocus = (treeId) => { const focusId = useTreeStore((state) => state.focusId); return focusId === treeId; } ================================================ FILE: chat2db-client/src/blocks/Tree/index.less ================================================ @import '../../styles/var.less'; .scrollBox { overflow-y: auto; padding: 2px 6px 10px; box-sizing: border-box; height: 100%; } .treeBox { overflow: hidden; height: 100%; } .treeListHolder { height: calc(var(--tree-node-count) * 26px); min-height: 100%; } // 如果treeBox滚动的高度>0那么久加一个上边框 .treeBoxScroll { border-top: 1px solid var(--color-border-secondary); } .leftModuleTitleShadow { border-top: 1px solid red; } .treeNode { display: flex; align-items: center; overflow: hidden; height: 26px; border-radius: 4px; opacity: 1; cursor: pointer; transition: opacity 0.05s ease-in, height 0.1s ease-in; user-select: none; padding-right: 6px; &:hover { } } .treeNodeFocus { // background-color: var(--color-hover-bg); // background-color: var(--color-primary-bg-hover); background-color: var(--color-primary-hover); // background-color: var(--color-primary); .right { // color: var(--color-primary); color: var(--color-bg-base); } .indent { &::before { opacity: 0; } } .type { color: var(--color-bg-base); } } .left { display: flex; align-items: center; height: 100%; flex-shrink: 0; } .right { flex: 1; display: flex; align-items: center; height: 100%; padding-left: 4px; border-radius: 2px; .moreBox { width: 22px; height: 100%; display: flex; justify-content: center; align-items: center; opacity: 0; } .moreButton { flex-shrink: 0; transform: rotate(90deg); } } .arrows { flex-shrink: 0; height: 20px; width: 18px; display: flex; align-items: center; transform: rotate(0deg); transition: transform 0.2s ease-in; } .loadingArrows { display: flex; } .arrowsIcon { display: inline-block; transform: rotate(0deg); font-size: 12px; } .rotateArrowsIcon { transform: rotate(90deg); } .dblclickArea { display: flex; flex: 1; height: 26px; } .typeIcon { height: 26px; display: flex; align-items: center; width: 20px; } .typeImg { height: 20px; width: 20px; background-repeat: no-repeat; background-size: 20px 20px; } .contentText { width: 0; flex: 1; display: flex; align-items: center; } .name { .f-lines(1); line-height: 20px; } .type { font-size: 11px; flex-shrink: 0; line-height: 20px; color: var(--color-text-tertiary); margin-left: 10px; } .describe { flex: 1; font-size: 10px; margin-left: 20px; color: var(--color-text-tertiary); .f-single-line(); } .indent { width: 20px; height: 100%; position: relative; &::before { position: absolute; top: 0; right: 9px; bottom: -4px; border-right: 1px solid var(--color-border); content: ''; } } .hiddenTreeNode { height: 0; opacity: 0; transition: opacity 0.2s ease-in, height 0.1s ease-in; .arrows { transform: rotate(0deg); transition: transform 0.2s ease-in; } } ================================================ FILE: chat2db-client/src/blocks/Tree/index.tsx ================================================ import React, { memo, useEffect, useMemo, useState, createContext, useContext } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import { Tooltip, Dropdown } from 'antd'; import { ITreeNode } from '@/typings'; import { TreeNodeType, databaseMap } from '@/constants'; import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; import { useCommonStore } from '@/store/common'; import { setCurrentWorkspaceGlobalExtend } from '@/pages/main/workspace/store/common'; import LoadingGracile from '@/components/Loading/LoadingGracile'; import { setFocusId, setFocusTreeNode, useTreeStore, clearTreeStore } from './treeStore'; import { useGetRightClickMenu } from './hooks/useGetRightClickMenu'; import MenuLabel from '@/components/MenuLabel'; import LoadingContent from '@/components/Loading/LoadingContent'; import { cloneDeep } from 'lodash'; // import { flushSync } from 'react-dom'; interface IProps { className?: string; treeData: ITreeNode[] | null; searchValue: string; } interface TreeNodeIProps { data: ITreeNode; level: number; } interface IContext { treeData: ITreeNode[]; setTreeData: (value: ITreeNode[] | null) => void; searchTreeData: ITreeNode[] | null; setSearchTreeData: (value: ITreeNode[] | null) => void; } export const Context = createContext({} as any); // 树转平级 const smoothTree = (treeData: ITreeNode[], result: ITreeNode[] = [], parentNode?: ITreeNode) => { treeData.forEach((item) => { if (parentNode) { item.parentNode = parentNode; item.level = (parentNode.level || 0) + 1; } result.push(item); if (item.children) { smoothTree(item.children, result, item); } }); return result; }; // 平级转树 function tranListToTreeData(list:ITreeNode[], rootValue) { const arr:ITreeNode[] = [] list.forEach((item:ITreeNode) => { if (item.parentNode?.uuid === rootValue) { arr.push(item) const children = tranListToTreeData(list, item.uuid) if (children.length) { item.children = children } } }) return arr } // 判断是否匹配 const isMatch = (target: string, searchValue: string) => { const reg = new RegExp(searchValue, 'i'); return reg.test(target || ''); }; // 树结构搜索 function searchTree(treeData: ITreeNode[], searchValue: string): ITreeNode[] { let result: ITreeNode[] = []; // 深度优先遍历 function dfs(node: ITreeNode, path: ITreeNode[] = []) { if (isMatch(node.name, searchValue)) { result = [...result, ...path, node]; return; } if (!node.children) return; node.children.forEach((child) => { dfs(child, [...path, node]); }); } // 遍历树 treeData.forEach((node) => dfs(node)); // 根据uuid去重 const deWeightList: ITreeNode[] = []; result.forEach((item) => { // 如果不匹配,说明该节点为path,不需要保留该节点的子元素,就把children置空 if (!isMatch(item.name, searchValue)) { item.children = null; } deWeightList.findIndex((i) => i.uuid === item.uuid) === -1 && deWeightList.push(item); }); return tranListToTreeData(deWeightList, undefined); } const itemHeight = 26; // 每个 item 的高度 const paddingCount = 2; const Tree = (props: IProps) => { const { className, treeData: outerTreeData, searchValue } = props; const [treeData, setTreeData] = useState(null); const [smoothTreeData, setSmoothTreeData] = useState([]); const [searchTreeData, setSearchTreeData] = useState(null); // 搜索结果 const [searchSmoothTreeData, setSearchSmoothTreeData] = useState(null); // 搜索结果 平级 const [scrollTop, setScrollTop] = useState(0); // 滚动位置 // 继续需要渲染的 item 索引有哪些 const startIdx = useMemo(() => { let _startIdx = Math.floor(scrollTop / itemHeight); _startIdx = Math.max(_startIdx - paddingCount, 0); // 处理越界情况 return _startIdx; }, [scrollTop]); const top = itemHeight * startIdx; // 第一个渲染的 item 到顶部距离 // 清空treeStore useEffect(() => { return () => { clearTreeStore(); } }, [searchValue]); useEffect(() => { setTreeData(outerTreeData); setScrollTop(0); }, [outerTreeData]); useEffect(() => { if (treeData) { const result: ITreeNode[] = []; smoothTree(treeData, result); setSmoothTreeData(result); } else { setSmoothTreeData([]); } }, [treeData]); // 搜索结果转平级 useEffect(() => { if (searchTreeData) { const result: ITreeNode[] = []; smoothTree(searchTreeData, result); setSearchSmoothTreeData(result); } else { setSearchSmoothTreeData(null); } }, [searchTreeData]); const treeNodes = useMemo(() => { const realNodeList = (searchSmoothTreeData || smoothTreeData).slice(startIdx, startIdx + 50); return realNodeList.map((item) => { return ; }); }, [smoothTreeData, searchSmoothTreeData, startIdx]); useEffect(() => { if (searchValue && treeData) { const _searchTreeData = searchTree(cloneDeep(treeData), searchValue); setSearchTreeData(_searchTreeData); setScrollTop(0); } else { setSearchTreeData(null); } }, [searchValue]); return (
{ setScrollTop(e.target.scrollTop); }} >
{treeNodes}
); }; const TreeNode = memo((props: TreeNodeIProps) => { const { data: treeNodeData, level } = props; const [isLoading, setIsLoading] = useState(false); const indentArr = new Array(level).fill('indent'); const { treeData, setTreeData, searchTreeData, setSearchTreeData } = useContext(Context); // 加载数据 function loadData(_props?: { refresh: boolean; pageNo: number; treeNodeData?: ITreeNode }) { const _treeNodeData = _props?.treeNodeData || props.data; const treeNodeConfig: ITreeConfigItem = treeConfig[_treeNodeData.pretendNodeType || _treeNodeData.treeNodeType]; setIsLoading(true); if (_props?.pageNo === 1 || !_props?.pageNo) { insertData(treeData!, _treeNodeData.uuid!, null,[treeData, setTreeData]); if(searchTreeData){ insertData(searchTreeData!, _treeNodeData.uuid!, null,[searchTreeData, setSearchTreeData]); } } treeNodeConfig .getChildren?.({ ..._treeNodeData.extraParams, extraParams: { ..._treeNodeData.extraParams, }, refresh: _props?.refresh || false, pageNo: _props?.pageNo || 1, }) .then((res: any) => { if (res.length || res.data) { if (res.data) { insertData(treeData!, _treeNodeData.uuid!, res.data, [treeData, setTreeData]); if(searchTreeData){ insertData(searchTreeData!, _treeNodeData.uuid!, res.data,[searchTreeData, setSearchTreeData]); } if (res.hasNextPage) { loadData({ refresh: _props?.refresh || false, pageNo: res.pageNo + 1, }); } } else { insertData(treeData!, _treeNodeData.uuid!, res,[treeData, setTreeData]); if(searchTreeData){ insertData(searchTreeData!, _treeNodeData.uuid!, res,[searchTreeData, setSearchTreeData]); } } setIsLoading(false); } else { // 处理树可能出现不连续的情况 if (treeNodeConfig.next) { _treeNodeData.pretendNodeType = treeNodeConfig.next; loadData(); } else { insertData(treeData!, _treeNodeData.uuid!, [],[treeData, setTreeData]); if(searchTreeData){ insertData(searchTreeData!, _treeNodeData.uuid!, [],[searchTreeData, setSearchTreeData]); } setIsLoading(false); } } }) .catch(() => { setIsLoading(false); }); } // 当前节点是否是focus const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.uuid; // 在treeData中找到对应的节点,插入数据 const insertData = (_treeData: ITreeNode[], uuid: string, data: any, originalDataList:any): ITreeNode | null => { const [originalData,setOriginalData] = originalDataList let result: ITreeNode | null = null; for (let i = 0; i < _treeData?.length; i++) { if (_treeData[i].uuid === uuid) { result = _treeData[i]; if (data) { data.map((item: any) => { item.parentNode = result; }); result.children = [...(result.children || []), ...(data || [])]; } else { result.children = null; } setOriginalData?.(cloneDeep([...(originalData || [])])); break; } else { if (_treeData[i].children) { result = insertData(_treeData[i].children!, uuid, data, originalDataList); if (result) { break; } } } } return result; }; //展开-收起 const handleClick = () => { if (treeNodeData?.children) { insertData(treeData!, treeNodeData.uuid!, null,[treeData, setTreeData]); if(searchTreeData){ insertData(searchTreeData!, treeNodeData.uuid!, null,[searchTreeData, setSearchTreeData]); } } else { loadData(); } }; // 找到对应的icon const recognizeIcon = (treeNodeType: TreeNodeType) => { if (treeNodeType === TreeNodeType.DATA_SOURCE) { return databaseMap[treeNodeData.extraParams!.databaseType!]?.icon; } else { return ( switchIcon[treeNodeType]?.[treeNodeData.children ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon ); } }; // 点击节点 const handelClickTreeNode = () => { useCommonStore.setState({ focusedContent: (treeNodeData.name || '') as any, }); if(treeNodeData.treeNodeType === TreeNodeType.TABLE){ setCurrentWorkspaceGlobalExtend({ code: 'viewDDL', uniqueData: { dataSourceId: treeNodeData.extraParams?.dataSourceId, dataSourceName: treeNodeData.extraParams?.dataSourceName, databaseName: treeNodeData.extraParams?.databaseName, databaseType: treeNodeData.extraParams?.databaseType, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData.name, } }); } setFocusId(treeNodeData.uuid || ''); setFocusTreeNode({ dataSourceId: treeNodeData.extraParams!.dataSourceId, dataSourceName: treeNodeData.extraParams!.dataSourceName, databaseType: treeNodeData.extraParams!.databaseType, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, }); }; // 双击节点 const handelDoubleClickTreeNode = () => { if ( treeNodeData.treeNodeType === TreeNodeType.TABLE || treeNodeData.treeNodeType === TreeNodeType.VIEW || treeNodeData.treeNodeType === TreeNodeType.PROCEDURE || treeNodeData.treeNodeType === TreeNodeType.FUNCTION || treeNodeData.treeNodeType === TreeNodeType.SEQUENCE || treeNodeData.treeNodeType === TreeNodeType.TRIGGER ) { rightClickMenu.find((item) => item.doubleClickTrigger)?.onClick(treeNodeData); } else { handleClick(); } }; const rightClickMenu = useGetRightClickMenu({ treeNodeData, loadData, }); const treeNodeDom = useMemo(() => { const dropdownsItems: any = rightClickMenu.map((item) => { return { key: item.key, onClick: () => { item.onClick(treeNodeData); }, label: , }; }); return (
{indentArr.map((item, i) => { return
; })}
{!treeNodeData.isLeaf && (
{isLoading ? ( ) : ( )}
)}
{treeNodeData.treeNodeType === TreeNodeType.COLUMN && (
{/* 转小写 */} {treeNodeData.columnType?.toLowerCase()}
)}
); }, [isFocus, isLoading, rightClickMenu, treeNodeData.children]); return treeNodeDom; }); export default memo(Tree); ================================================ FILE: chat2db-client/src/blocks/Tree/treeConfig.tsx ================================================ import { ITreeNode, IConnectionDetails } from '@/typings'; import { TreeNodeType, OperationColumn } from '@/constants'; import connectionService from '@/service/connection'; import { v4 as uuid } from 'uuid'; import mysqlServer from '@/service/sql'; export type ITreeConfig = Partial<{ [key in TreeNodeType]: ITreeConfigItem }>; export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfoldIcon?: string } }> = { [TreeNodeType.DATABASE]: { icon: '\ue669', }, [TreeNodeType.SCHEMAS]: { icon: '\ue696', }, [TreeNodeType.TABLE]: { icon: '\ue63e', }, [TreeNodeType.TABLES]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.COLUMNS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.COLUMN]: { icon: '\ue611', }, [TreeNodeType.KEYS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.KEY]: { icon: '\ue775', }, [TreeNodeType.INDEXES]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.INDEX]: { icon: '\ue65b', }, [TreeNodeType.VIEWS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.VIEW]: { icon: '\ue70c', }, [TreeNodeType.FUNCTION]: { icon: '\ue76a', }, [TreeNodeType.PROCEDURE]: { icon: '\ue73c', }, [TreeNodeType.TRIGGER]: { icon: '\ue64a', }, [TreeNodeType.VIEWCOLUMNS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.VIEWCOLUMN]: { icon: '\ue647', }, [TreeNodeType.FUNCTIONS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.PROCEDURES]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.TRIGGERS]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.SEQUENCES]: { icon: '\ueabe', unfoldIcon: '\ueabf', }, [TreeNodeType.SEQUENCE]: { icon: '\ue611', }, }; export interface ITreeConfigItem { icon?: string; getChildren?: (params: any, options?: any) => Promise; next?: TreeNodeType; operationColumn?: OperationColumn[]; } export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.DATA_SOURCES]: { getChildren: () => { return new Promise((r: (value: ITreeNode[]) => void, j) => { const p = { pageNo: 1, pageSize: 1000, }; connectionService .getList(p) .then((res) => { const data: ITreeNode[] = res.data.map((t: IConnectionDetails) => { return { uuid: uuid(), key: t.id, name: t.alias, treeNodeType: TreeNodeType.DATA_SOURCE, extraParams: { databaseType: t.type, dataSourceId: t.id, dataSourceName: t.alias, }, }; }); r(data); }) .catch(() => { j(); }); }); }, }, [TreeNodeType.DATA_SOURCE]: { getChildren: (params: { dataSourceId: number; dataSourceName: string; extraParams: any }) => { return new Promise((r, j) => { const _extraParams = params.extraParams; delete params.extraParams; connectionService .getDatabaseList(params) .then((res) => { const data: ITreeNode[] = res.map((t: any) => { return { uuid: uuid(), key: t.name, name: t.name, treeNodeType: TreeNodeType.DATABASE, extraParams: { ..._extraParams, databaseName: t.name, }, }; }); r(data); }) .catch(() => { j(); }); }); }, operationColumn: [OperationColumn.EditSource, OperationColumn.Refresh, OperationColumn.ShiftOut], next: TreeNodeType.DATABASE, }, [TreeNodeType.DATABASE]: { icon: '\ue62c', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[], b?: any) => void, j) => { connectionService .getSchemaList(params) .then((res) => { const data: ITreeNode[] = res.map((t: any) => { return { uuid: uuid(), key: t.name, name: t.name, treeNodeType: TreeNodeType.SCHEMAS, schemaName: t.name, extraParams: { ..._extraParams, schemaName: t.name, }, }; }); r(data); }) .catch(() => { j(); }); }); }, operationColumn: [ OperationColumn.CreateConsole, OperationColumn.CreateSchema, // OperationColumn.CreateTable, OperationColumn.CopyName, OperationColumn.Refresh, ], next: TreeNodeType.SCHEMAS, }, [TreeNodeType.SCHEMAS]: { icon: '\ue696', getChildren: (parentData: ITreeNode) => { const { dataSourceId, databaseName, schemaName } = parentData.extraParams!; const preCode = [dataSourceId, databaseName, schemaName].join('-'); return new Promise((r: (value: ITreeNode[]) => void) => { const data = [ { uuid: uuid(), key: `${preCode}-tables`, name: 'tables', treeNodeType: TreeNodeType.TABLES, extraParams: parentData.extraParams, }, { uuid: uuid(), key: `${preCode}-views`, name: 'view', treeNodeType: TreeNodeType.VIEWS, extraParams: parentData.extraParams, }, { uuid: uuid(), key: `${preCode}-functions`, name: 'functions', treeNodeType: TreeNodeType.FUNCTIONS, extraParams: parentData.extraParams, }, { uuid: uuid(), key: `${preCode}-procedures`, name: 'procedures', treeNodeType: TreeNodeType.PROCEDURES, extraParams: parentData.extraParams, }, { uuid: uuid(), key: `${preCode}-triggers`, name: 'triggers', treeNodeType: TreeNodeType.TRIGGERS, extraParams: parentData.extraParams, }, ]; if((parentData.extraParams?.databaseType === 'POSTGRESQL'|| parentData.extraParams?.databaseType === 'ORACLE')&& schemaName==='public'){ data.push({ uuid: uuid(), key: `${preCode}-sequences`, name: 'sequences', treeNodeType: TreeNodeType.SEQUENCES, extraParams: parentData.extraParams, }); } r(data); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.TABLES]: { icon: '\ueac5', getChildren: (params, options) => { const _extraParams = params.extraParams; delete params.extraParams; params.pageSize = 1000; return new Promise((r, j) => { mysqlServer .getTableList(params, options) .then((res) => { const tableList: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.name, treeNodeType: TreeNodeType.TABLE, key: t.name, pinned: t.pinned, comment: t.comment, extraParams: { ..._extraParams, tableName: t.name, }, }; }); r({ data: tableList, pageNo: res.pageNo, pageSize: res.pageSize, total: res.total, hasNextPage: res.hasNextPage, } as any); }) .catch((error) => { j(error); }); }); }, operationColumn: [ OperationColumn.CreateConsole, OperationColumn.ViewAllTable, OperationColumn.CreateTable, OperationColumn.Refresh, ], }, [TreeNodeType.TABLE]: { icon: '\ue63e', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void) => { const { dataSourceId, databaseName, schemaName, tableName } = params.extraParams!; const preCode = [dataSourceId, databaseName, schemaName, tableName].join('-'); const list = [ { uuid: uuid(), key: `${preCode}-columns`, name: 'columns', treeNodeType: TreeNodeType.COLUMNS, extraParams: params.extraParams, }, { uuid: uuid(), key: `${preCode}-keys`, name: 'keys', treeNodeType: TreeNodeType.KEYS, extraParams: params.extraParams, }, { uuid: uuid(), key: `${preCode}-indexs`, name: 'indexs', treeNodeType: TreeNodeType.INDEXES, extraParams: params.extraParams, }, ]; r(list); }); }, operationColumn: [ OperationColumn.OpenTable, OperationColumn.CreateConsole, OperationColumn.Pin, OperationColumn.ViewDDL, OperationColumn.EditTable, OperationColumn.CopyName, OperationColumn.Refresh, OperationColumn.DeleteTable, ], }, [TreeNodeType.VIEWS]: { icon: '\ue70c', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getViewList(params) .then((res) => { const viewList: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.name, treeNodeType: TreeNodeType.VIEW, key: t.name, pinned: t.pinned, comment: t.comment, extraParams: { ..._extraParams, tableName: t.name, }, }; }); r(viewList); }) .catch((error) => { j(error); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.FUNCTIONS]: { icon: '\ue76a', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getFunctionList(params) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.functionName, treeNodeType: TreeNodeType.FUNCTION, key: t.name, pinned: t.pinned, comment: t.comment, isLeaf: true, extraParams: { ..._extraParams, functionName: t.functionName, }, }; }); r(list); }) .catch((error) => { j(error); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.FUNCTION]: { icon: '\ue76a', operationColumn: [OperationColumn.CreateConsole, OperationColumn.OpenFunction, OperationColumn.CopyName], }, [TreeNodeType.PROCEDURES]: { icon: '\ue73c', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getProcedureList(params) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.procedureName, treeNodeType: TreeNodeType.PROCEDURE, key: t.name, pinned: t.pinned, comment: t.comment, isLeaf: true, extraParams: { ..._extraParams, procedureName: t.procedureName, }, }; }); r(list); }) .catch((error) => { j(error); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.PROCEDURE]: { icon: '\ue73c', operationColumn: [OperationColumn.CreateConsole, OperationColumn.OpenProcedure, OperationColumn.CopyName], }, [TreeNodeType.TRIGGERS]: { icon: '\ue64a', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getTriggerList(params) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.triggerName, treeNodeType: TreeNodeType.TRIGGER, key: t.name, pinned: t.pinned, comment: t.comment, isLeaf: true, extraParams: { ..._extraParams, triggerName: t.triggerName, }, }; }); r(list); }) .catch((error) => { j(error); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.TRIGGER]: { icon: '\ue64a', operationColumn: [OperationColumn.CreateConsole, OperationColumn.OpenTrigger, OperationColumn.CopyName], }, [TreeNodeType.VIEW]: { icon: '\ue70c', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void) => { const list = [ { uuid: uuid(), name: 'columns', treeNodeType: TreeNodeType.COLUMNS, key: 'columns', extraParams: params.extraParams, }, ]; r(list); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.OpenView, OperationColumn.CopyName], }, [TreeNodeType.VIEWCOLUMNS]: { icon: '\ue647', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getViewColumnList(params) .then((res) => { const list: ITreeNode[] = res.data?.map((t: any) => { return { uuid: uuid(), name: t.name, treeNodeType: TreeNodeType.VIEWCOLUMN, key: t.name, pinned: t.pinned, comment: t.comment, isLeaf: true, extraParams: _extraParams, }; }); r(list); }) .catch((error) => { j(error); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName, OperationColumn.Refresh], }, [TreeNodeType.VIEWCOLUMN]: { icon: '\ue647', operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], }, [TreeNodeType.COLUMNS]: { icon: '\ueac5', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getColumnList(params) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { return { uuid: uuid(), name: item.name, treeNodeType: TreeNodeType.COLUMN, key: item.name, isLeaf: true, columnType: item.columnType, comment: item.comment, extraParams: _extraParams, }; }); r(tableList); }) .catch(() => { j(); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.Refresh], }, [TreeNodeType.COLUMN]: { icon: '\ue611', operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], }, [TreeNodeType.KEYS]: { icon: '\ueac5', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getKeyList(params) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { return { uuid: uuid(), name: item.name, treeNodeType: TreeNodeType.KEY, key: item.name, isLeaf: true, extraParams: _extraParams, }; }); r(tableList); }) .catch(() => { j(); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName, OperationColumn.Refresh], }, [TreeNodeType.KEY]: { icon: '\ue775', operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], }, [TreeNodeType.INDEXES]: { icon: '\ueac5', getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getIndexList(params) .then((res) => { const tableList: ITreeNode[] = res?.map((item) => { return { uuid: uuid(), name: item.name, treeNodeType: TreeNodeType.INDEX, key: item.name, isLeaf: true, extraParams: _extraParams, }; }); r(tableList); }) .catch(() => { j(); }); }); }, operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName, OperationColumn.Refresh], }, [TreeNodeType.INDEX]: { icon: '\ue65b', operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName], }, [TreeNodeType.SEQUENCES]: { icon: '\ueabe', // 使用现有图标(如文件夹折叠) getChildren: (params) => { const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getSequenceList(params) .then((res) => { const data: ITreeNode[] = res?.map((item:any) => { return { uuid: uuid(), key: item.name, name: item.name, treeNodeType: TreeNodeType.SEQUENCE, sequenceName: item.name, isLeaf: true, extraParams: { ..._extraParams, sequenceName: item.name, }, }; }); r(data); }) .catch((error) => { j(error); }); }) }, operationColumn: [ OperationColumn.CreateSequence, OperationColumn.CopyName, OperationColumn.Refresh, ], }, [TreeNodeType.SEQUENCE]: { icon: '\ue611', operationColumn: [OperationColumn.OpenSequence, OperationColumn.EditSequence, OperationColumn.CopyName,OperationColumn.DeleteSequence], }, }; ================================================ FILE: chat2db-client/src/blocks/Tree/treeStore.ts ================================================ /** * 树的store */ import { create, UseBoundStore, StoreApi } from 'zustand'; import { devtools } from 'zustand/middleware'; export interface ITreeStore { focusId: number | string | null; focusTreeNode: { dataSourceId: number; dataSourceName: string; databaseType: string, databaseName?: string; schemaName?: string, tableName?: string, } | null; } const treeStore = { focusId: null, focusTreeNode: null, } export const useTreeStore: UseBoundStore> = create( devtools(() => (treeStore)), ); export const setFocusId = (focusId: ITreeStore['focusId']) => { useTreeStore.setState({ focusId }); } export const setFocusTreeNode = (focusTreeNode: ITreeStore['focusTreeNode']) => { useTreeStore.setState({ focusTreeNode }); } // 清除treeStore export const clearTreeStore = () => { useTreeStore.setState(treeStore); } ================================================ FILE: chat2db-client/src/components/BrandLogo/index.less ================================================ @import '../../styles/var.less'; .box { display: flex; justify-content: center; border-radius: 10%; overflow: hidden; img { display: block; height: 100%; } } ================================================ FILE: chat2db-client/src/components/BrandLogo/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import logo from '@/assets/logo/logo.webp'; interface IProps extends React.DetailedHTMLProps, HTMLDivElement> { className?: any; size?: number; } export default memo(({ className, size = 48, ...res }) => { return (
); }); ================================================ FILE: chat2db-client/src/components/CascaderDB/index.less ================================================ .cascaderDB { display: inline-flex; align-items: center; border-radius: 16px; padding: 4px 8px 4px 0; background-color: var(--color-bg-subtle); &:hover { background-color: var(--color-hover-bg); } } .optionItem { display: flex; align-items: center; height: 26px; cursor: pointer; padding: 0px 6px; .optionItemIcon { margin-right: 10px; font-weight: 400; color: var(--color-primary); } .optionItemText { font-weight: bold; } } .select { :global { .ant-select-selector { caret-color: var(--color-text-placeholder); // caret-width: 4px; // width: 4px; } } } ================================================ FILE: chat2db-client/src/components/CascaderDB/index.tsx ================================================ import React, { useEffect, useState } from 'react'; import { Divider, Select, Typography } from 'antd'; import connection from '@/service/connection'; import cs from 'classnames'; import styles from './index.less'; import Iconfont from '../Iconfont'; import { databaseMap } from '@/constants/database'; interface IProps { className?: string; curConnectionId?: number; onChange?: (value: { dataSourceId: number; databaseName: string; schemaName: string }) => void; } interface IOption { label: string | React.ReactNode; value: number | string; } function CascaderDB(props: IProps) { const [dataSourceOptions, setDataSourceOptions] = useState([]); const [curDataSourceId, setCurDataSourceId] = useState(props.curConnectionId); const [databaseOptions, setDatabaseOptions] = useState([]); const [curDatabaseName, setCurDatabaseName] = useState(''); const [schemaOptions, setSchemaOptions] = useState([]); const [curSchemeName, setCurSchemeName] = useState(''); useEffect(() => { loadDataSource(); }, []); useEffect(() => { loadDatabase(); }, [curDataSourceId]); useEffect(() => { loadSchema(); }, [curDatabaseName]); const handleChangeDataSource = (value) => { setCurDataSourceId(value); setDatabaseOptions([]); setSchemaOptions([]); setCurDatabaseName(''); setCurSchemeName(''); props.onChange && props.onChange({ dataSourceId: value, databaseName: '', schemaName: '', }); }; const handleChangeDatabase = (value) => { setCurDatabaseName(value); setSchemaOptions([]); setCurSchemeName(''); props.onChange && props.onChange({ dataSourceId: curDataSourceId!, databaseName: value, schemaName: '', }); }; const handleChangeSchema = (value) => { setCurSchemeName(value); props.onChange && props.onChange({ dataSourceId: curDataSourceId!, databaseName: curDatabaseName, schemaName: value }); }; /** 加载DataSource数据 */ const loadDataSource = async () => { // 请求 dataSource 数据 const dataSourceList = await connection.getList({ pageNo: 1, pageSize: 999, refresh: true, }); const formattedData = (dataSourceList?.data || []).map((item) => ({ ...item, key: `dataSource-${item.id}`, value: item.id, label: (
{item.alias}
), })); setDataSourceOptions(formattedData); if (curDataSourceId === undefined) { setCurDataSourceId(formattedData[0]?.value); } }; /** 加载Database数据 */ const loadDatabase = async () => { if (curDataSourceId === undefined) { return; } const databaseList = await connection.getDatabaseList({ dataSourceId: curDataSourceId, }); const formattedData = (databaseList || []).map((item) => ({ ...item, key: `database-${item.name}`, value: item.name, label: (
{item.name}
), })); setDatabaseOptions(formattedData); if (!curDatabaseName) { setCurDatabaseName(formattedData[0]?.value); } }; const loadSchema = async () => { if (curDataSourceId === undefined || !curDatabaseName) { return; } const schemaList = await connection.getSchemaList({ dataSourceId: curDataSourceId, databaseName: curDatabaseName, refresh: false, }); const formattedData = (schemaList || []).map((item) => ({ ...item, key: `schema-${item.name}`, value: item.name, label: (
{item.name}
), })); setSchemaOptions(formattedData); if (!curSchemeName) { setCurSchemeName(formattedData[0]?.value); } }; return (
( <>
数据库
{menu} )} /> {!!schemaOptions.length && ( {driverObj?.driverConfigList?.map((t) => ( ))}
{(driverObj?.driverConfigList && !driverObj?.driverConfigList?.length) || downloadStatus === DownloadStatus.Success ? (
{downloadStatus === DownloadStatus.Default && (
{i18n('connection.text.downloadDriver')}
)} {downloadStatus === DownloadStatus.Loading && (
{i18n('connection.text.downloading')}
)} {downloadStatus === DownloadStatus.Error && (
{i18n('connection.text.tryAgainDownload')}
)} {downloadStatus === DownloadStatus.Success && (
{i18n('connection.text.downloadSuccess')}
)}
) : (
)} {backfillData.isAdmin !== false && (
{ setUploadDriverModal(true); }} > {i18n('connection.tips.customUpload')}
)}
{ saveDriver(); }} onCancel={() => { setUploadDriverModal(false); }} >
); }); ================================================ FILE: chat2db-client/src/components/ConnectionEdit/config/dataSource.ts ================================================ import { DatabaseTypeCode, OperationColumn } from '@/constants'; import { IConnectionConfig } from './types'; import { InputType, AuthenticationType } from './enum'; import i18n from '@/i18n'; export const sshConfig: IConnectionConfig['ssh'] = { items: [ { defaultValue: false, inputType: InputType.SELECT, labelNameCN: '使用SSH', labelNameEN: 'USE SSH', name: 'use', required: false, selects: [ { label: 'false', value: false, }, { label: 'true', value: true, }, ], }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: 'SSH 主机', labelNameEN: 'SSH Hostname', name: 'hostName', required: false, styles: { width: '70%', }, }, { defaultValue: '22', inputType: InputType.INPUT, labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '70px', labelAlign: 'right', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { width: '70%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '本地端口', labelNameEN: 'LocalPort', name: 'localPort', placeholder: '不必填', placeholderEN: 'Need not fill in', required: false, styles: { width: '30%', labelWidthEN: '70px', labelWidthCN: '70px', labelAlign: 'right', }, }, { defaultValue: 'password', inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'password', value: 'password', }, { items: [ { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '密钥文件', labelNameEN: 'Private key file', name: 'keyFile', required: true, placeholder: '/user/userName/.ssh/xxxx', placeholderEN: '/user/userName/.ssh/xxxx', }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码短语', labelNameEN: 'Passphrase', name: 'passphrase', required: true, }, ], label: 'Private key', value: 'keyFile', }, ], styles: { width: '50%', }, }, ], }; const envItem = { defaultValue: '', inputType: InputType.SELECT, labelNameCN: '环境', labelNameEN: 'Env', name: 'environmentId', required: true, selects: [], styles: { width: '50%', }, }; export const dataSourceFormConfigs: IConnectionConfig[] = [ // MYSQL { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '3306', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:mysql://localhost:3306', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:mysql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:mysql://{host}:{port}/{database}', }, ssh: sshConfig, extendInfo: [ { key: 'zeroDateTimeBehavior', value: 'convertToNull', }, { key: 'useInformationSchema', value: 'true', }, { key: 'tinyInt1isBit', value: 'false', }, ], type: DatabaseTypeCode.MYSQL, }, // POSTGRESQL { type: DatabaseTypeCode.POSTGRESQL, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '5432', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: 'postgres', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:postgresql://localhost:5432', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:postgresql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:postgresql://{host}:{port}/{database}', }, ssh: sshConfig, }, // ORACLE { type: DatabaseTypeCode.ORACLE, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '1521', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: 'sid', inputType: InputType.SELECT, labelNameCN: '链接类型', labelNameEN: 'Service type', name: 'serviceType', required: true, selects: [ { label: 'SID', value: 'sid', items: [ { defaultValue: 'XE', inputType: InputType.INPUT, labelNameCN: 'SID', labelNameEN: 'SID', name: 'sid', required: true, styles: { width: '70%', }, }, ], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:oracle:(.*):@(.*):(\d+):(.*)/; data.baseInfo.template = 'jdbc:oracle:{driver}:@{host}:{port}:{sid}'; return data; }, }, { label: 'Service', value: 'service', items: [ { defaultValue: 'XE', inputType: InputType.INPUT, labelNameCN: '服务名', labelNameEN: 'Service name', name: 'serviceName', required: true, styles: { width: '70%', }, }, ], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:oracle:(.*):@\/\/(.*):(\d+)\/(.*)/; data.baseInfo.template = 'jdbc:oracle:{driver}:@//{host}:{port}/{serviceName}'; return data; }, }, ], styles: { width: '50%', }, }, { defaultValue: 'thin', inputType: InputType.SELECT, labelNameCN: '驱动', labelNameEN: 'Driver', name: 'driver', required: true, labelTextAlign: 'right', selects: [ { value: 'thin', label: 'thin', }, { value: 'oci', label: 'oci', }, { value: 'oci8', label: 'oci8', }, ], styles: { width: '30%', labelWidthEN: '70px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: 'jdbc:oracle:thin:@localhost:1521:XE', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:oracle:(.*):@(.*):(\d+):(.*)/, template: 'jdbc:oracle:{driver}:@{host}:{port}:{sid}', }, ssh: sshConfig, }, // H2 { type: DatabaseTypeCode.H2, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '9092', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: 'TCP', inputType: InputType.SELECT, labelNameCN: '链接类型', labelNameEN: 'Service type', name: 'serviceType', required: true, selects: [ { label: i18n('common.label.tcp'), value: 'TCP', items: [], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/; data.baseInfo.template = 'jdbc:h2:tcp://{host}:{port}/{database}'; return data; }, }, { label: i18n('common.label.LocalFile'), value: 'LocalFile', items: [ { defaultValue: '', inputType: InputType.INPUT, labelNameCN: 'File', labelNameEN: 'File', name: 'file', required: true, }, ], onChange: (data: IConnectionConfig) => { data.baseInfo.pattern = /jdbc:h2:(.*)?/; data.baseInfo.template = 'jdbc:h2:{file}'; return data; }, }, ], styles: { width: '70%', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:h2:tcp://localhost:9092', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:h2:tcp://{host}:{port}/{database}', }, ssh: sshConfig, }, // SQLSERVER encrypt=true;trustServerCertificate=true;integratedSecurity=false;Trusted_Connection=yes { type: DatabaseTypeCode.SQLSERVER, extendInfo: [ { key: 'encrypt', value: 'false', }, { key: 'trustServerCertificate', value: 'true', }, { key: 'integratedSecurity', value: 'false', }, { key: 'Trusted_Connection', value: 'yes', }, ], baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '1433', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: 'Instance', labelNameEN: 'Instance', name: 'instance', required: false, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:sqlserver://localhost:1433', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:sqlserver:\/\/(.*):(\d+)(;database=(\w+))?/, template: 'jdbc:sqlserver://{host}:{port};database={database}', }, ssh: sshConfig, }, // SQLITE { type: DatabaseTypeCode.SQLITE, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'identifier.sqlite', inputType: InputType.INPUT, labelNameCN: 'File', labelNameEN: 'File', name: 'file', required: true, }, { defaultValue: 'jdbc:sqlite:identifier.sqlite', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:sqlite:(.*)?/, template: 'jdbc:sqlite:{file}', }, ssh: sshConfig, }, // MARIADB { type: DatabaseTypeCode.MARIADB, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '3306', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:mariadb://localhost:3306', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:mariadb:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:mariadb://{host}:{port}/{database}', }, ssh: sshConfig, }, // CLICKHOUSE { type: DatabaseTypeCode.CLICKHOUSE, baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '8123', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:clickhouse://localhost:8123', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:clickhouse:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:clickhouse://{host}:{port}/{database}', excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable], //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn }, ssh: sshConfig, }, // DM { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '5236', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:dm://localhost:5236', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:dm:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:dm://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [ { key: 'zeroDateTimeBehavior', value: 'convertToNull', }, ], type: DatabaseTypeCode.DM, }, //DB2 { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '50000', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:db2://localhost:50000', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:db2://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.DB2, }, //presto { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '8080', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:presto://localhost:8080', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:presto:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:presto://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.PRESTO, }, //oceanbase { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '2883', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:oceanbase://localhost:2883', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:oceanbase:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:oceanbase://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.OCEANBASE, }, //redis { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '6379', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:redis://localhost:6379', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:redis://{host}:{port}/{database}', }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.REDIS, }, //hive { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '10000', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:hive2://localhost:10000', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:hive2://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.HIVE, }, //KINGBASE { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '54321', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'jdbc:kingbase8://127.0.0.1:54321', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /jdbc:kingbase8:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:kingbase8://{host}:{port}/{database}', // excludes: [OperationColumn.EditTable] }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.KINGBASE, }, //MONGODB { baseInfo: { items: [ { defaultValue: '@localhost', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, styles: { width: '100%', }, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '27017', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'root', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, styles: { width: '100%', }, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, styles: { width: '100%', }, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, styles: { width: '100%', }, }, { defaultValue: 'mongodb://localhost:27017', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, styles: { width: '100%', }, }, ], pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, template: 'mongodb://{host}:{port}/{database}', excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable], }, ssh: sshConfig, extendInfo: [], type: DatabaseTypeCode.MONGODB, }, // TIMEPLUS { type: DatabaseTypeCode.TIMEPLUS, baseInfo: { items: [ { defaultValue: 'timeplus', inputType: InputType.INPUT, labelNameCN: '名称', labelNameEN: 'Name', name: 'alias', required: true, }, envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, labelNameCN: '主机', labelNameEN: 'Host', name: 'host', required: true, styles: { width: '70%', }, }, { defaultValue: '7587', inputType: InputType.INPUT, labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', required: true, styles: { width: '30%', labelWidthEN: '40px', labelWidthCN: '40px', labelAlign: 'right', }, }, { defaultValue: AuthenticationType.USERANDPASSWORD, inputType: InputType.SELECT, labelNameCN: '身份验证', labelNameEN: 'Authentication', name: 'authenticationType', required: true, selects: [ { items: [ { defaultValue: 'default', inputType: InputType.INPUT, labelNameCN: '用户名', labelNameEN: 'User', name: 'user', required: true, }, { defaultValue: '', inputType: InputType.PASSWORD, labelNameCN: '密码', labelNameEN: 'Password', name: 'password', required: true, }, ], label: 'User&Password', value: AuthenticationType.USERANDPASSWORD, }, { label: 'NONE', value: AuthenticationType.NONE, items: [], }, ], styles: { width: '50%', }, }, { defaultValue: '', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', name: 'database', required: false, }, { defaultValue: 'jdbc:timeplus://localhost:7587', inputType: InputType.INPUT, labelNameCN: 'URL', labelNameEN: 'URL', name: 'url', required: true, }, ], pattern: /jdbc:timeplus:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:timeplus://{host}:{port}/{database}', excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable, OperationColumn.EditTable], }, ssh: sshConfig, }, ]; ================================================ FILE: chat2db-client/src/components/ConnectionEdit/config/enum.ts ================================================ export enum InputType { INPUT = 'input', PASSWORD = 'password', SELECT = 'select', } export enum AuthenticationType { USERANDPASSWORD = '1', NONE = '2', } export enum SSHAuthenticationType { PASSWORD = 1, KEYPAIR = 2, OPENSSH = 3 } ================================================ FILE: chat2db-client/src/components/ConnectionEdit/config/types.ts ================================================ import { InputType, AuthenticationType, SSHAuthenticationType } from './enum'; import { DatabaseTypeCode, OperationColumn } from '@/constants'; export type ISelect = { value?: AuthenticationType | SSHAuthenticationType | string | boolean; label?: string; onChange?: (value: IConnectionConfig) => IConnectionConfig; rest?: { [key in string]: any } items?: IFormItem[]; }; export interface IFormItem { defaultValue: any; inputType: InputType; labelNameCN: string; labelNameEN: string; name: string; required: boolean; selected?: any; selects?: ISelect[]; labelTextAlign?: 'right'; placeholder?: string; placeholderEN?: string; styles?: { width?: string; // 表单占用的长度 推荐百分比 默认值为 100% labelWidthEN?: string; // 英文环境下表单label的长度 推荐px 默认值为 70px labelWidthCN?: string; // 中文环境下表单label的长度 推荐px 默认值为 100px labelAlign?: 'left' | 'right'; // label的对齐方式 默认值为左对齐 }, } // 配置链接数据源表单 Json export type IConnectionConfig = { type: DatabaseTypeCode; baseInfo: { items: IFormItem[]; pattern: RegExp; template: string; excludes?: OperationColumn[]; }, driver?: { items: IFormItem[]; } ssh: { items: IFormItem[]; }, extendInfo?: { key: string; value: any; }[], // TODO: 先取form里的配置,在取form.item的配置, 最后取默认值,目前没有取全局的 styles?: { width?: string; // 表单占用的长度 推荐百分比 默认值为 100% labelWidthEN?: string; // 英文环境下表单label的长度 推荐px 默认值为 70px labelWidthCN?: string; // 中文环境下表单label的长度 推荐px 默认值为 100px labelAlign?: 'left' | 'right'; // label的对齐方式 默认值为左对齐 } }; ================================================ FILE: chat2db-client/src/components/ConnectionEdit/index.less ================================================ @import '../../styles/var.less'; .connectionBox { width: 100%; box-sizing: border-box; flex-shrink: 0; padding: 20px 20%; height: auto; } .title { // position: sticky; // top: 0; // z-index: 1; display: flex; align-items: center; justify-content: center; background-color: var(--color-bg); font-size: 26px; text-align: center; margin: 0 0px 30px 0px; i { font-size: 20px; margin-right: 10px; color: var(--color-primary); } } .form { :global { display: flex; justify-content: space-between; flex-wrap: wrap; .ant-form-item-label { width: var(--form-label-width); } } } .extendInfoBox .form { :global { .custom-form-item { > div > div:nth-of-type(1) { width: 200px; } } } } .labelTextAlign { :global { .custom-form-item { > div > div:nth-of-type(1) { text-align: right; } } } } .formFooter { width: 100%; display: flex; justify-content: space-between; margin-top: 20px; margin-bottom: 50px; .cancel { margin-right: 20px; .f-button(); } .rightButton { display: flex; } .test, .save, .cancel { .f-button(); } } .tabsBox { margin-bottom: 20px; } .testSSHConnect { margin: 10px 0px; display: flex; align-items: center; i { margin-right: 10px; color: var(--success-color); } .testSSHConnectText { margin-left: 4px; font-size: 12px; color: var(--color-primary); cursor: pointer; &:hover { text-decoration: underline; } } } .extendTable { max-height: 300px; overflow-y: auto; input { border: 0px; } :global { .custom-input[disabled], .custom-input-disabled { background-color: transparent; } .custom-table-tbody > tr.custom-table-row:hover > td { background-color: transparent; } .custom-input { padding: 0px; } .custom-input:focus { border: 0px; box-shadow: none; } thead .custom-table-cell { box-sizing: content-box; height: 28px; } } } .optionItem{ display: flex; align-items: center; } .envTag { flex-shrink: 0; width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-primary); margin-right: 8px; } ================================================ FILE: chat2db-client/src/components/ConnectionEdit/index.tsx ================================================ import React, { useEffect, useMemo, useState, Fragment, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import { i18n, isEn } from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; import connectionService from '@/service/connection'; import { ConnectionEnvType, databaseMap } from '@/constants'; import { dataSourceFormConfigs } from './config/dataSource'; import { IConnectionConfig, IFormItem, ISelect } from './config/types'; import { InputType } from './config/enum'; import { IConnectionDetails } from '@/typings'; import { deepClone } from '@/utils'; import { Select, Form, Input, message, Table, Button, Collapse } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingGracile from '@/components/Loading/LoadingGracile'; import Driver from './components/Driver'; // ----- store ----- import { useConnectionStore, getConnectionList } from '@/pages/main/store/connection'; const { Option } = Select; type ITabsType = 'ssh' | 'baseInfo' | 'driver'; export enum submitType { UPDATE = 'update', SAVE = 'save', TEST = 'test', } interface IProps { closeCreateConnection: () => void; connectionData: IConnectionDetails; submit?: (data: IConnectionDetails) => Promise; } export interface ICreateConnectionFunction { getData: () => IConnectionDetails; } const ConnectionEdit = forwardRef((props: IProps, ref: ForwardedRef) => { const { closeCreateConnection, connectionData, submit } = props; const [baseInfoForm] = Form.useForm(); const [sshForm] = Form.useForm(); const [driveData, setDriveData] = useState({}); const [backfillData, setBackfillData] = useState(connectionData); const [loadings, setLoading] = useState({ confirmButton: false, testButton: false, sshTestLoading: false, }); const { connectionEnvList } = useConnectionStore((state) => { return { connectionEnvList: state.connectionEnvList, }; }); const [envList, setEnvList] = useState<{ value: number; label: string }[]>([]); useEffect(() => { const _envList = connectionEnvList?.map((t) => { return { value: t.id, label: t.name, color: t.color, }; }) if(_envList){ setEnvList(_envList); } }, [connectionEnvList]); const dataSourceFormConfigPropsMemo = useMemo(() => { const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs); const data = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === backfillData.type; }); data.baseInfo.items.forEach((t: IFormItem) => { if (t.name === 'environmentId' && envList?.length) { t.selects = envList; t.defaultValue = envList[0].value; } }); return data; }, [backfillData, envList]); useEffect(() => { setBackfillData(props.connectionData); }, [props.connectionData]); function driverFormChange(data: any) { setDriveData(data); } const getItems = () => [ { forceRender: true, key: 'driver', label: i18n('connection.title.driver'), children: , }, { key: 'ssh', forceRender: true, label: i18n('connection.label.sshConfiguration'), children: (
{loadings.sshTestLoading && }
{i18n('connection.message.testSshConnection')}
), }, { forceRender: true, key: 'extendInfo', label: i18n('connection.label.advancedConfiguration'), children: (
), }, ]; useImperativeHandle(ref, () => ({ getData, })); function getData() { const ssh = sshForm.getFieldsValue(); const baseInfo = baseInfoForm.getFieldsValue(); const extendInfo: any = []; extendTableData.map((t: any) => { if (t.label || t.value) { extendInfo.push({ key: t.label, value: t.value, }); } }); const data = { ssh, driverConfig: driveData, ...baseInfo, extendInfo, connectionEnvType: ConnectionEnvType.DAILY, type: backfillData.type, }; if (backfillData.id) { data.id = backfillData.id; } return data; } // 测试、保存、修改连接 function saveConnection(type: submitType) { const p = getData(); if (type !== submitType.SAVE) { p.id = backfillData.id; } // TODO: 如果用户没选环境,默认选第一个。这里应该直接默认给用户选上的,但是动态表单现在有点问题,后续解决 if (!p.environmentId) { p.environmentId = envList[0].value; } const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; setLoading({ ...loadings, [loadingsButton]: true, }); if ((type === submitType.SAVE) && submit) { submit?.(p).finally(() => { setLoading({ ...loadings, [loadingsButton]: false, }); }); return; } const api: any = connectionService[type](p); api .then((res: any) => { if (type === submitType.TEST) { message.success( res === false ? i18n('connection.message.testConnectResult', i18n('common.text.failure')) : i18n('connection.message.testConnectResult', i18n('common.text.successful')), ); } else { message.success( type === submitType.UPDATE ? i18n('common.message.modifySuccessfully') : i18n('common.message.addedSuccessfully'), ); getConnectionList(); if (type === submitType.SAVE) { setBackfillData({ ...backfillData, id: res, }); } } }) .finally(() => { setLoading({ ...loadings, [loadingsButton]: false, }); }); } function onCancel() { closeCreateConnection(); } function testSSH() { const p = sshForm.getFieldsValue(); setLoading({ ...loadings, sshTestLoading: true, }); connectionService .testSSH(p) .then(() => { message.success(i18n('connection.message.testConnectResult', i18n('common.text.successful'))); }) .finally(() => { setLoading({ ...loadings, sshTestLoading: false, }); }); } return (
{databaseMap[backfillData.type]?.name}
{ }
); }); export default ConnectionEdit; interface IRenderFormProps { tab: ITabsType; form: any; backfillData: IConnectionDetails; dataSourceFormConfigProps: IConnectionConfig; disabled: boolean } function RenderForm(props: IRenderFormProps) { const { tab, form, backfillData, dataSourceFormConfigProps } = props; let aliasChanged = false; const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigProps); const formDataRef = React.useRef(null); useEffect(() => { form.resetFields(); changeDataSourceFormConfig(backfillData); formDataRef.current = backfillData; }, [backfillData.id, backfillData.type]); useEffect(() => { setDataSourceFormConfig(dataSourceFormConfigProps); }, [dataSourceFormConfigProps]); const initialValuesMemo = useMemo(() => { return initialFormData(dataSourceFormConfigProps[tab]?.items); }, []); const [initialValues] = useState(initialValuesMemo); useEffect(() => { if (!backfillData) { return; } if (tab === 'baseInfo') { regEXFormatting({ url: backfillData.url }, backfillData); } if (tab === 'ssh') { regEXFormatting({}, backfillData.ssh || {}); } if (tab === 'driver') { regEXFormatting({}, backfillData.driverConfig || {}); } }, [backfillData]); function changeDataSourceFormConfig(_backfillData: any) { // 这里应该循环一遍所有的 dataSourceFormConfig.ssh.items.forEach((t: IFormItem) => { if (t.selects) { t.defaultValue = _backfillData?.ssh?.[t.name] || t.defaultValue; } }); dataSourceFormConfig.baseInfo.items.forEach((t: IFormItem) => { if (t.selects) { t.defaultValue = _backfillData[t.name] || t.defaultValue; t.selects.forEach((selectItem: ISelect) => { // 调用select内的回掉函数 if (selectItem.value === t.defaultValue) { if(selectItem.onChange){ setDataSourceFormConfig(selectItem.onChange({...dataSourceFormConfig})) } } }); } }); } function initialFormData(_dataSourceFormConfig: IFormItem[] | undefined) { let initValue: any = {}; _dataSourceFormConfig?.map((t) => { initValue[t.name] = t.defaultValue; if (t.selects?.length) { t.selects?.map((item) => { if (item.value === t.defaultValue) { initValue = { ...initValue, ...initialFormData(item.items), }; } }); } }); return initValue; } function onFieldsChange(data: any, datas: any) { // 将antd的格式转换为正常的对象格式 if (!data.length) { return; } const keyName = data[0].name[0]; const keyValue = data[0].value; const variableData = { [keyName]: keyValue, }; const dataObj: any = {}; datas.map((t: any) => { dataObj[t.name[0]] = t.value; }); const finalData = { ...(formDataRef.current || {}), ...dataObj, } formDataRef.current = finalData; // 正则拆分url/组建url if (tab === 'baseInfo') { regEXFormatting(variableData, finalData); } } function extractObj(url: any) { const { template, pattern } = dataSourceFormConfig.baseInfo; // 提取关键词对应的内容 value const matches = url.match(pattern)!; // 提取花括号内的关键词 key const reg = /{(.*?)}/g; let match; const arr = []; while ((match = reg.exec(template)) !== null) { arr.push(match[1]); } // key与value一一对应 const newExtract: any = {}; arr.map((t, i) => { newExtract[t] = t === 'database' ? matches[i + 2] || '' : matches[i + 1]; }); return newExtract; } function regEXFormatting( variableData: { [key: string]: any }, dataObj: { [key: string]: any }, _dataSourceFormConfig?: IConnectionConfig, ) { const { template, pattern } = (_dataSourceFormConfig || dataSourceFormConfig).baseInfo; const keyName = Object.keys(variableData)[0]; const keyValue = variableData[Object.keys(variableData)[0]]; let newData: any = {}; if (keyName === 'url') { //先判断url是否符合规定的正则 if (pattern.test(keyValue)) { newData = extractObj(keyValue); } } else if (keyName === 'alias') { aliasChanged = true; } else { // 改变上边url动 let url = template; Object.keys(dataObj).map((t) => { url = url.replace(`{${t}}`, dataObj[t] || ''); }); newData = { url, }; } if (keyName === 'host' && !aliasChanged) { newData.alias = '@' + keyValue; } form.setFieldsValue({ ...dataObj, ...newData, }); } function renderFormItem(t: IFormItem): React.ReactNode { const label = isEn ? t.labelNameEN : t.labelNameCN; const name = t.name; const width = t?.styles?.width || '100%'; const labelWidth = isEn ? t?.styles?.labelWidthEN || '100px' : t?.styles?.labelWidthCN || '70px'; const placeholder = isEn ? t.placeholderEN : t.placeholder; const labelAlign = t?.styles?.labelAlign || 'left'; const FormItemTypes: { [key in InputType]: () => React.ReactNode } = { [InputType.INPUT]: () => ( ), [InputType.SELECT]: () => ( ), [InputType.PASSWORD]: () => ( ), }; return (
{FormItemTypes[t.inputType]()}
{t.selects?.map((item) => { if (t.defaultValue === item.value) { return item.items?.map((t) => { return renderFormItem(t); }); } })}
); } return (
{dataSourceFormConfig[tab]!.items.map((t) => renderFormItem(t))} ); } interface IRenderExtendTableProps { backfillData: IConnectionDetails; } let extendTableData: any = []; interface IExtendTable { key: number; label: string; value: string; } function RenderExtendTable(props: IRenderExtendTableProps) { const { backfillData } = props; const databaseType = backfillData.type; const [data, setData] = useState([{ key: 0, label: '', value: '' }]); const dataSourceFormConfigMemo = useMemo(() => { return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { return t.type === databaseType; }); }, [backfillData.type]); // 禁止修改 const disabled = backfillData.isAdmin === false; useEffect(() => { const extendInfoList = backfillData?.extendInfo?.length ? backfillData?.extendInfo : dataSourceFormConfigMemo.extendInfo; const extendInfo = extendInfoList?.map((t, i) => { return { key: i, label: t.key, value: t.value, }; }) || []; setData([...extendInfo, { key: extendInfo.length, label: '', value: '' }]); }, [dataSourceFormConfigMemo, backfillData]); useEffect(() => { extendTableData = data; }, [data]); const columns: any = [ { title: i18n('connection.tableHeader.name'), dataIndex: 'label', width: '60%', render: (value: any, row: any, index: number) => { let isCustomLabel = true; dataSourceFormConfigMemo.extendInfo?.map((item) => { if (item.key === row.label) { isCustomLabel = false; } }); function change(e: any) { const newData = [...data]; newData[index] = { key: index, label: e.target.value, value: '', }; setData(newData); } function blur() { const newData = []; data.map((t) => { if (t.label) { newData.push(t); } }); if (index === data.length - 1 && row.label) { newData[index] = { key: index, label: row.label, value: '', }; } setData([...newData, { key: newData.length, label: '', value: '' }]); } if (index === data.length - 1 || isCustomLabel) { return ( ); } else { return {value}; } }, }, { title: i18n('connection.tableHeader.value'), dataIndex: 'value', width: '40%', render: (value: any, row: any, index: number) => { function change(e: any) { const newData = [...data]; newData[index] = { key: index, label: row.label, value: e.target.value, }; setData(newData); } if (index === data.length - 1) { return ; } else { return ; } }, }, ]; return (
); } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less ================================================ .chatWrapper { display: flex; align-items: center; padding: 2px 4px 2px 20px; height: 42px; box-sizing: border-box; border-bottom: 1px solid var(--color-border-secondary); } .chatShortcut { padding: 4px 8px; color: #8a9099; font-size: 12px; line-height: 1; white-space: nowrap; background-color: #fffc; border-radius: 4px; border: 1px solid #d0d5d8; margin-right: 10px; } .chatAi { width: 16px; height: 16px; margin-right: 14px; } .suffixBlock { display: flex; align-items: center; } .enter { width: 32px; height: 24px; padding: 0; margin-right: 16px; .enterIcon { font-size: 12px; } } .stop { font-size: 16px; color: red; cursor: pointer; padding: 2px 8px; margin-right: 4px; border-radius: 4px; &:hover { background-color: var(--color-hover-bg); } } .tableSelectBlock { // margin-right: 8px; cursor: pointer; padding: 4px 8px; border-radius: 8px; &:hover { background-color: var(--color-bg-subtle); } } .remainBlock { border-radius: 16px; background-color: var(--color-bg-subtle); padding: 4px 12px; &:hover { //cursor: pointer; } } .aiSelectedTable { display: flex; flex-direction: column; max-width: 280px; } .aiSelectedTableTips { padding-bottom: 4px; border-bottom: 1px solid var(--color-border-secondary); margin-bottom: 4px; } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx ================================================ import React, { useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; import { Button, Input, Popover, Select, Radio, Space } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { AIType } from '@/typings/ai'; export const enum SyncModelType { AUTO = 0, MANUAL = 1, } interface IProps { value?: string; result?: string; tables?: string[]; syncTableModel: number; selectedTables?: string[]; aiType: AIType; disabled?: boolean; isStream?: boolean; onPressEnter: (value: string) => void; onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; // onClickRemainBtn: Function; onCancelStream: () => void; } const ChatInput = (props: IProps) => { const [value, setValue] = useState(props.value); const onPressEnter = (e: any) => { if (!e.target.value) { return; } if (e.nativeEvent.isComposing && e.key === 'Enter') { e.preventDefault(); return; } props.onPressEnter && props.onPressEnter(e.target.value); }; const renderSelectTable = () => { const { tables, onSelectTableSyncModel, selectedTables, onSelectTables } = props; const options = (tables || []).map((t) => ({ value: t, label: t })); return (
onSelectTableSyncModel(v.target.value)} // value={syncTableModel} value={SyncModelType.MANUAL} style={{ marginBottom: '8px' }} > {/* 自动 */} 手动 {/* {syncTableModel === 0 ? ( i18n('chat.input.syncTable.tips') ) : ( )} */} <> {i18n('chat.input.remain.tooltip')} setValue(e.target.value)} bordered={false} placeholder={i18n('workspace.ai.input.placeholder')} onPressEnter={onPressEnter} suffix={renderSuffix()} />
); }; export default ChatInput; ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less ================================================ .consoleOptionsWrapper { position: absolute; bottom: 0px; left: 0; right: 0; height: 40px; display: flex; align-items: center; display: flex; margin: 0 12px; justify-content: space-between; align-items: center; } .consoleOptionsLeft { display: flex; align-items: center; margin: 0px -6px; button { margin: 0px 6px; } } .runButton { display: flex; align-items: center; margin-right: 20px; height: 28px; i { margin-right: 4px; } } .saveButton { width: 70px; height: 28px; } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx ================================================ import React from 'react'; import i18n from '@/i18n'; import { Button, Popover } from 'antd'; import { IBoundInfo } from '@/typings/workspace'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import SelectBoundInfo from '../SelectBoundInfo'; import { formatSql } from '@/utils/sql'; import { osNow } from '@/utils'; interface IProps { boundInfo: IBoundInfo; saveConsole: (sql: string) => void; executeSQL: () => void; setBoundInfo: (boundInfo: IBoundInfo) => void; editorRef: any; hasSaveBtn: boolean; } const keyboardKey = (function () { if (osNow().isMac) { return { command: 'Cmd', Shift: 'Shift', }; } return { command: 'Ctrl', Shift: 'Shift', }; })(); const OperationLine = (props: IProps) => { const { boundInfo, saveConsole, editorRef, hasSaveBtn, executeSQL, setBoundInfo } = props; /** * 格式化sql */ const handleSQLFormat = () => { let setValueType = 'select'; let sql = editorRef?.current?.getCurrentSelectContent(); if (!sql) { sql = editorRef?.current?.getAllContent() || ''; setValueType = 'cover'; } formatSql(sql, boundInfo.databaseType!).then((res) => { editorRef?.current?.setValue(res, setValueType); }); }; return (
{hasSaveBtn && ( )}
); }; export default OperationLine; ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.less ================================================ @import '../../../../styles/var.less'; .consoleOptionsRight { flex: 1; display: flex; justify-content: flex-end; } .boundInfoBoxSpacer { flex: 1; } .boundInfoBox { max-width: 160px; display: flex; align-items: center; padding: 5px 6px 5px 8px; border-radius: 4px; cursor: pointer; .boundInfoName { .f-single-line(); margin: 0px 6px 0px 4px; } &:hover { background-color: var(--color-hover-bg); } } :global { .ant-dropdown-menu-title-content { height: 20px; } } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx ================================================ import React, { useEffect, useMemo, useState, memo, useContext } from 'react'; import { IntelligentEditorContext } from '../../index'; import { Dropdown } from 'antd'; import { useConnectionStore } from '@/pages/main/store/connection'; import connectionService from '@/service/connection'; import historyService from '@/service/history'; import Iconfont from '@/components/Iconfont'; import { databaseMap } from '@/constants/database'; import styles from './index.less'; import sqlService from '@/service/sql'; import { IBoundInfo } from '@/typings'; import { registerIntelliSenseField, registerIntelliSenseKeyword, registerIntelliSenseTable, registerIntelliSenseDatabase, resetSenseKeyword, resetSenseTable, resetSenseDatabase, resetSenseField, } from '@/utils/IntelliSense'; interface IProps { boundInfo: IBoundInfo; setBoundInfo: (params: IBoundInfo) => void; } interface IOption { key: string; label: string; value: T; } const emptyOption = { key: '', label: '', value: '', }; const SelectBoundInfo = memo((props: IProps) => { const { boundInfo, setBoundInfo } = props; const { setSelectedTables, setTableNameList, isActive } = useContext(IntelligentEditorContext); const connectionList = useConnectionStore((state) => state.connectionList); const [databaseNameList, setDatabaseNameList] = useState[]>([emptyOption]); const [schemaList, setSchemaList] = useState[]>([emptyOption]); const [allTableList, setAllTableList] = useState([]); useEffect(() => { if(!isActive){ resetSenseKeyword(); resetSenseTable(); resetSenseDatabase(); resetSenseField(); } }, [isActive]); const dataSourceList = useMemo(() => { return ( connectionList?.map((item) => ({ key: item.id.toString(), label: item.alias, value: item.id, type: item.type, })) || [] ); }, [connectionList]); const supportDatabase = useMemo(() => { return connectionList?.find((item) => item.id === boundInfo.dataSourceId)?.supportDatabase; }, [boundInfo.dataSourceId, connectionList]); const supportSchema = useMemo(() => { return connectionList?.find((item) => item.id === boundInfo.dataSourceId)?.supportSchema; }, [boundInfo.dataSourceId, connectionList]); // 编辑器绑定的数据库类型变化时,重新注册智能提示 useEffect(() => { if(!isActive){ return } registerIntelliSenseKeyword(boundInfo.databaseType); }, [boundInfo.dataSourceId, isActive]); // 当数据源变化时,重新获取数据库列表 useEffect(() => { if (!isActive || boundInfo.connectable === false) { return; } if (supportDatabase) { setSchemaList([]); setDatabaseNameList([]); getDatabaseList(); } }, [boundInfo.dataSourceId, isActive]); // 当数据库名变化时,重新获取schema列表 useEffect(() => { if (!isActive || boundInfo.connectable === false) { return; } if (supportSchema) { setSchemaList([]); getSchemaList(); } if (!supportSchema && boundInfo.databaseName) { getAllTableNameList(boundInfo.dataSourceId, boundInfo.databaseName); } }, [boundInfo.databaseName, isActive, supportSchema]); useEffect(() => { if (!isActive || boundInfo.connectable === false) { return; } if (supportSchema && boundInfo.schemaName) { getAllTableNameList(boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName); } }, [boundInfo.schemaName, isActive, supportSchema]); // 获取数据库列表 const getDatabaseList = () => { if (boundInfo.dataSourceId === undefined || boundInfo.dataSourceId === null) { return; } connectionService .getDatabaseList({ dataSourceId: boundInfo.dataSourceId, }) .then((res) => { const editorDatabaseTips: any = []; const _databaseNameList = res.map((item) => { editorDatabaseTips.push({ name: item.name, dataSourceName: boundInfo.dataSourceName, }); return { key: item.name, label: item.name, value: item.name, }; }); if (!_databaseNameList.length) { getSchemaList(); } setDatabaseNameList([emptyOption, ..._databaseNameList]); }); }; // 获取schema列表 const getSchemaList = () => { if (boundInfo.dataSourceId === undefined || boundInfo.dataSourceId === null) { return; } connectionService .getSchemaList({ dataSourceId: boundInfo.dataSourceId!, databaseName: boundInfo?.databaseName, }) .then((res: any) => { const _schemaList = res.map((item) => ({ key: item.name, label: item.name, value: item.name, })); setSchemaList([emptyOption, ..._schemaList]); }); }; // 注册表名 useEffect(() => { if (isActive) { const tableNameListTemp = allTableList.map((t) => t.name); setTableNameList(tableNameListTemp); registerIntelliSenseTable( allTableList, boundInfo.databaseType, boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName, ); registerIntelliSenseField( tableNameListTemp, boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName, ); setSelectedTables(tableNameListTemp.slice(0, 1)); } }, [allTableList, isActive]); // 注册数据库名 useEffect(() => { const editorDatabaseTips = databaseNameList.map((item) => ({ name: item.value, dataSourceName: boundInfo.dataSourceName, })); registerIntelliSenseDatabase(editorDatabaseTips); }, [databaseNameList]); // 选择数据源 const changeDataSource = (item) => { const currentData = dataSourceList.find((i) => i.key === item.key)!; setBoundInfo({ ...boundInfo, dataSourceId: currentData.value, dataSourceName: currentData.label, databaseType: currentData.type, databaseName: void 0, schemaName: void 0, }); if (boundInfo.consoleId) { historyService.updateSavedConsole({ id: boundInfo.consoleId, dataSourceId: currentData.value, dataSourceName: currentData.label, type: currentData.type, }); } }; // 选择数据库 const changeDataBase = (item) => { const _databaseName = databaseNameList?.find((i) => i.key === item.key)?.value; setBoundInfo({ ...boundInfo, databaseName: _databaseName, schemaName: void 0, }); if (boundInfo.consoleId) { historyService.updateSavedConsole({ id: boundInfo.consoleId, databaseName: _databaseName, }); } }; // 选择schema const changeSchema = (item) => { const _schemaName = schemaList?.find((i) => i.key === item.key)?.value; setBoundInfo({ ...boundInfo, schemaName: _schemaName, }); if (boundInfo.consoleId) { historyService.updateSavedConsole({ id: boundInfo.consoleId, schemaName: _schemaName, }); } }; const getAllTableNameList = (dataSourceId, databaseName, schemaName?) => { sqlService .getAllTableList({ dataSourceId, databaseName, schemaName, }) .then((data) => { setAllTableList(data); }); }; return (
{boundInfo.dataSourceName || `<${'dataSource'}>`}
{supportDatabase && (
{boundInfo.databaseName || `<${'database'}>`}
)} {supportSchema && (
{boundInfo.schemaName || `<${'schema'}>`}
)}
); }); export default SelectBoundInfo; ================================================ FILE: chat2db-client/src/components/ConsoleEditor/hooks/useModuleData.ts ================================================ import {useState, useEffect} from 'react'; import sqlService from '@/service/sql'; import {IBoundInfo} from '../index' interface IProps { boundInfo: IBoundInfo; } export const useModuleData = (props: IProps) => { const { boundInfo } = props; const [selectedTables, setSelectedTables] = useState([]); const [tableNameList, setTableNameList] = useState([]); useEffect(() => { const { dataSourceId, databaseName, schemaName } = boundInfo; if( !databaseName && !schemaName){ setTableNameList([]); setSelectedTables([]); return } sqlService .getAllTableList({ dataSourceId, databaseName, schemaName, }) .then((data) => { const tableNameListTemp = data.map((t) => t.name); setTableNameList(tableNameListTemp); if (selectedTables.length === 0) { setSelectedTables(tableNameListTemp.slice(0, 1)); } }); }, []); return { selectedTables, setSelectedTables, tableNameList, setTableNameList } } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts ================================================ import {useState, useEffect, useRef} from 'react'; import { ConsoleStatus } from '@/constants'; import { message } from 'antd'; import indexedDB from '@/indexedDB'; import historyServer from '@/service/history'; import i18n from '@/i18n'; import { getCookie } from '@/utils'; import { getSavedConsoleList } from '@/pages/main/workspace/store/console'; interface IProps { isActive?: boolean; source?: string; editorRef: any; boundInfo: any; defaultValue?: string; } export const useSaveEditorData = (props: IProps) => { const { isActive, source, editorRef, boundInfo, defaultValue } = props; const timerRef = useRef(); // 上一次同步的console数据 const lastSyncConsole = useRef(defaultValue); const [saveStatus, setSaveStatus] = useState(boundInfo.status || ConsoleStatus.DRAFT); const saveConsole = (value?: string, noPrompting?: boolean) => { const p: any = { id: boundInfo.consoleId, status: ConsoleStatus.RELEASE, ddl: value, }; historyServer.updateSavedConsole(p).then(() => { getSavedConsoleList(); indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', boundInfo.consoleId!); lastSyncConsole.current = value; setSaveStatus(ConsoleStatus.RELEASE); if (noPrompting) { return; } message.success(i18n('common.tips.saveSuccessfully')); timingAutoSave(ConsoleStatus.RELEASE); }); }; function timingAutoSave(_status?: ConsoleStatus) { if (timerRef.current) { clearInterval(timerRef.current); } timerRef.current = setInterval(() => { const curValue = editorRef?.current?.getAllContent(); if (curValue === lastSyncConsole.current) { return; } if (saveStatus === ConsoleStatus.RELEASE || _status === ConsoleStatus.RELEASE) { saveConsole(curValue, true); } else { indexedDB .updateData('chat2db', 'workspaceConsoleDDL', { consoleId: boundInfo.consoleId!, ddl: curValue, userId: getCookie('CHAT2DB.USER_ID'), }) .then(() => { lastSyncConsole.current = curValue; }); } }, 5000); } useEffect(() => { if (source !== 'workspace') { return; } // 离开时保存 if (!isActive) { // 离开时清除定时器 if (timerRef.current) { clearInterval(timerRef.current); } const curValue = editorRef?.current?.getAllContent(); if (curValue === lastSyncConsole.current) { return; } if (saveStatus === ConsoleStatus.RELEASE) { saveConsole(curValue, true); } else { indexedDB .updateData('chat2db', 'workspaceConsoleDDL', { consoleId: boundInfo.consoleId!, ddl: curValue, userId: getCookie('CHAT2DB.USER_ID'), }) .then(() => { lastSyncConsole.current = curValue; }); } } else { timingAutoSave(); } return () => { if (timerRef.current) { clearInterval(timerRef.current); } }; }, [isActive]); useEffect(() => { if (saveStatus === ConsoleStatus.RELEASE) { editorRef?.current?.setValue(defaultValue, 'cover'); } else { indexedDB .getDataByCursor('chat2db', 'workspaceConsoleDDL', { consoleId: boundInfo.consoleId!, userId: getCookie('CHAT2DB.USER_ID'), }) .then((res: any) => { // oldValue是为了处理函数视图等,他们是带着值来的,不需要去数据库取值 const oldValue = editorRef?.current?.getAllContent(); if (!oldValue) { editorRef?.current?.setValue(res?.[0]?.ddl || '', 'cover'); } }); } }, []); return {saveConsole, saveStatus} } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/index.less ================================================ .console { position: relative; height: 100%; :global { .ant-spin-nested-loading { height: 100%; } .ant-spin-container { height: calc(100% - 40px); } } } .consoleEditor { height: 100%; } .consoleEditorWithChat { height: calc(100% - 42px); } .aiBlock { font-size: 14px; line-height: 20px; } ================================================ FILE: chat2db-client/src/components/ConsoleEditor/index.tsx ================================================ import React, { useEffect, useMemo, useRef, useState, useImperativeHandle, ForwardedRef, forwardRef, createContext, } from 'react'; import { formatParams } from '@/utils/url'; import connectToEventSource from '@/utils/eventSource'; import { Spin, Drawer, Modal } from 'antd'; import ChatInput, { SyncModelType } from './components/ChatInput'; import MonacoEditor, { IEditorOptions, IExportRefFunction, IRangeType } from '../MonacoEditor'; import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; import { IAiConfig, IBoundInfo } from '@/typings'; import Popularize from '@/components/Popularize'; import OperationLine from './components/OperationLine'; import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AIType } from '@/typings/ai'; import i18n from '@/i18n'; import configService from '@/service/config'; import styles from './index.less'; // ----- hooks ----- import { useSaveEditorData } from './hooks/useSaveEditorData'; // ----- store ----- import { useSettingStore, fetchRemainingUse, setAiConfig } from '@/store/setting'; // ----- function ----- import { handelCreateConsole } from '@/pages/main/workspace/functions/shortcutKeyCreateConsole'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', SQL_EXPLAIN = 'SQL_EXPLAIN', SQL_OPTIMIZER = 'SQL_OPTIMIZER', SQL_2_SQL = 'SQL_2_SQL', ChatRobot = 'ChatRobot', } export type IAppendValue = { text: any; range?: IRangeType; }; interface IProps { /** 调用来源 */ source?: 'workspace'; isActive: boolean; /** 添加或修改的内容 */ appendValue?: IAppendValue; defaultValue?: string; /** 是否开启AI输入 */ hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ hasAi2Lang?: boolean; /** 是否有 */ hasSaveBtn?: boolean; value?: string; boundInfo: IBoundInfo; setBoundInfo: (params: IBoundInfo) => void; editorOptions?: IEditorOptions; onExecuteSQL: (sql: string) => void; } export interface IConsoleRef { editorRef: IExportRefFunction | undefined; } interface IIntelligentEditorContext { isActive: boolean; tableNameList: string[]; setTableNameList: (tables: string[]) => void; selectedTables: string[]; setSelectedTables: (tables: string[]) => void; } export const IntelligentEditorContext = createContext({} as any); function ConsoleEditor(props: IProps, ref: ForwardedRef) { const { hasAiChat = true, boundInfo, setBoundInfo, appendValue, hasSaveBtn = true, source, defaultValue, isActive, } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); const [selectedTables, setSelectedTables] = useState([]); const [tableNameList, setTableNameList] = useState([]); const [syncTableModel, setSyncTableModel] = useState(0); const [isLoading, setIsLoading] = useState(false); const [aiContent, setAiContent] = useState(''); const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); const [popularizeModal, setPopularizeModal] = useState(false); const [modalProps, setModalProps] = useState({}); const [isStream, setIsStream] = useState(false); const aiFetchIntervalRef = useRef(); const closeEventSource = useRef(); const { aiConfig, hasWhite, remainingUse } = useSettingStore((state) => { return { aiConfig: state.aiConfig, hasWhite: state.hasWhite, remainingUse: state.remainingUse, }; }); // ---------------- new-code ---------------- const { saveConsole } = useSaveEditorData({ editorRef, isActive, boundInfo: props.boundInfo, source, defaultValue, }); // ---------------- new-code ---------------- /** * 当前选择的AI类型是Chat2DBAI */ const isChat2DBAI = useMemo(() => aiConfig?.aiSqlSource === AIType.CHAT2DBAI, [aiConfig?.aiSqlSource]); useEffect(() => { handleSelectTableSyncModel(); }, [hasWhite, localStorage.getItem('syncTableModel')]); useEffect(() => { if (appendValue) { editorRef?.current?.setValue(appendValue.text, appendValue.range); } }, [appendValue]); useImperativeHandle( ref, () => ({ editorRef: editorRef?.current, }), [editorRef?.current], ); const handleApiKeyEmptyOrGetQrCode = async (shouldPoll?: boolean) => { setIsLoading(true); try { const { wechatQrCodeUrl, token, tip } = await aiServer.getLoginQrCode({}); setIsLoading(false); setPopularizeModal(true); setModalProps({ imageUrl: wechatQrCodeUrl, token, tip, }); if (shouldPoll) { let pollCnt = 0; aiFetchIntervalRef.current = setInterval(async () => { const { apiKey } = (await aiServer.getLoginStatus({ token })) || {}; pollCnt++; if (apiKey || pollCnt >= 60) { clearInterval(aiFetchIntervalRef.current); } if (apiKey) { setPopularizeModal(false); setAiConfig({ ...(aiConfig || {}), apiKey, }); fetchRemainingUse(apiKey); } }, 3000); } } catch (e) { setIsLoading(false); } }; const handleAIChatInEditor = async (content: string, promptType: IPromptType, ext?: string) => { const _aiConfig = await configService.getAiSystemConfig({}); handleAiChat(content, promptType, _aiConfig, ext); }; const handleAiChat = async (content: string, promptType: IPromptType, _aiConfig?: IAiConfig, ext?: string) => { const { apiKey } = _aiConfig || aiConfig || {}; if (!apiKey && isChat2DBAI) { handleApiKeyEmptyOrGetQrCode(true); return; } const { dataSourceId, databaseName, schemaName } = boundInfo; const isNL2SQL = promptType === IPromptType.NL_2_SQL; if (isNL2SQL) { setIsLoading(true); } else { setAiContent(''); setIsAiDrawerOpen(true); setIsAiDrawerLoading(true); } const params = formatParams({ message: content, promptType, dataSourceId, databaseName, schemaName, tableNames: syncTableModel ? selectedTables : null, ext, }); const handleMessage = (_message: string) => { setIsLoading(false); setIsAiDrawerLoading(false); try { const isEOF = _message === '[DONE]'; if (isEOF) { closeEventSource.current(); setIsStream(false); if (isChat2DBAI) { fetchRemainingUse(apiKey); } if (isNL2SQL) { editorRef?.current?.setValue('\n'); } else { setIsAiDrawerLoading(false); chatResult.current += '\n'; setAiContent(chatResult.current); chatResult.current = ''; } return; } let hasErrorToLogin = false; chatErrorToLogin.forEach((err) => { if (_message.includes(err)) { hasErrorToLogin = true; } }); let hasKeyLimitedOrExpired = false; chatErrorForKey.forEach((err) => { if (_message.includes(err)) { hasKeyLimitedOrExpired = true; } }); if (hasKeyLimitedOrExpired) { closeEventSource.current(); setIsLoading(false); handlePopUp(); return; } if (hasErrorToLogin) { closeEventSource.current(); setIsLoading(false); hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(true); // hasErrorToInvite && handleClickRemainBtn(); fetchRemainingUse(apiKey); return; } if (isNL2SQL) { editorRef?.current?.setValue(JSON.parse(_message).content); } else { chatResult.current += JSON.parse(_message).content; setAiContent(chatResult.current); } } catch (error) { setIsLoading(false); setIsStream(false); setIsAiDrawerLoading(false); closeEventSource.current(); } }; const handleError = (error: any) => { console.error('Error:', error); setIsLoading(false); setIsAiDrawerLoading(false); setIsStream(false); closeEventSource.current(); }; closeEventSource.current = connectToEventSource({ url: `/api/ai/chat?${params}`, uid, onOpen: () => { setIsStream(true); }, onMessage: handleMessage, onError: handleError, }); }; const executeSQL = (sql?: string) => { const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); if (!sqlContent) { return; } props.onExecuteSQL && props.onExecuteSQL(sqlContent); }; const addAction = [ { id: 'explainSQL', label: i18n('common.text.explainSQL'), action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_EXPLAIN), }, { id: 'optimizeSQL', label: i18n('common.text.optimizeSQL'), action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_OPTIMIZER), }, { id: 'changeSQL', label: i18n('common.text.conversionSQL'), action: (selectedText: string, ext?: string) => { handleAIChatInEditor(selectedText, IPromptType.SQL_2_SQL, ext); }, }, ]; /** * 弹框 关注公众号 */ const handlePopUp = () => { setModalProps({ imageUrl: 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp', tip: ( <> {remainingUse?.remainingUses === 0 &&

Key次数用完或者过期

}

微信扫描二维码并关注公众号获得 AI 使用机会。

), }); setPopularizeModal(true); }; const handleSelectTableSyncModel = () => { const syncModel = localStorage.getItem('syncTableModel'); const hasAiAccess = hasWhite; if (syncModel !== null) { setSyncTableModel(Number(syncModel)); return; } setSyncTableModel(hasAiAccess ? SyncModelType.AUTO : SyncModelType.MANUAL); }; // 注册快捷键 const registerShortcutKey = (editor, monaco) => { // 保存 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { const value = editor?.getValue(); saveConsole(value || ''); }); // 执行 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, () => { const value = editorRef.current?.getCurrentSelectContent(); executeSQL(value); }); // 执行 editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyL, () => { handelCreateConsole(); }); }; return (
{hasAiChat && ( { handleAiChat(value, IPromptType.NL_2_SQL); }} selectedTables={selectedTables} onSelectTables={(tables: string[]) => { setSelectedTables(tables); }} syncTableModel={syncTableModel} onSelectTableSyncModel={(model: number) => { setSyncTableModel(model); localStorage.setItem('syncTableModel', String(model)); }} onCancelStream={() => { closeEventSource.current(); setIsStream(false); setIsLoading(false); }} /> )} { try { setIsAiDrawerOpen(false); setIsAiDrawerLoading(false); setIsStream(false); closeEventSource.current && closeEventSource.current(); } catch (error) { // console.log('close drawer', error); } }} >
{aiContent}
{ aiFetchIntervalRef.current && clearInterval(aiFetchIntervalRef.current); setPopularizeModal(false); }} >
); } export default forwardRef(ConsoleEditor); ================================================ FILE: chat2db-client/src/components/CreateDatabase/index.less ================================================ @import '../../styles/var.less'; .monacoEditorBox { height: 200px; border: 1px solid var(--color-border); border-radius: 4px; overflow: hidden; } .errorBox { margin-top: 10px; } .previewBox { // 伪元素画一条剧中的线条 position: relative; display: flex; margin-bottom: 4px; .previewText { background: var(--color-bg-base); flex-shrink: 0; margin-right: 10px; } .previewLine { flex: 1; position: relative; &::after { position: absolute; left: 0; right: 0px; top: 50%; content: ''; width: 100%; height: 1px; background: var(--color-border); transform: translateY(-50%); } } } ================================================ FILE: chat2db-client/src/components/CreateDatabase/index.tsx ================================================ import React, { useCallback, useMemo, useState, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Form, Input, Modal } from 'antd'; import MonacoEditor, { IExportRefFunction } from '@/components/MonacoEditor'; import { v4 as uuid } from 'uuid'; import sqlService from '@/service/sql'; import i18n from '@/i18n'; import { debounce } from 'lodash'; import { DatabaseTypeCode } from '@/constants'; import { setOpenCreateDatabaseModal } from '@/pages/main/workspace/store/modal'; interface IProps { relyOnParams: { databaseType: DatabaseTypeCode; dataSourceId: number; databaseName?: string; }; executedCallback?: () => void; } export type CreateType = 'database' | 'schema'; export interface ICreateDatabase { databaseName?: string; schemaName?: string; comment?: string; } // 创建database不支持注释的数据库 const noCommentDatabase = [DatabaseTypeCode.MYSQL]; const CreateDatabase = () => { const [form] = Form.useForm(); const monacoEditorUuid = useMemo(() => uuid(), []); const monacoEditorRef = React.useRef(null); const [open, setOpen] = useState(false); const [errorMessage, setErrorMessage] = useState<{ success: boolean; message: string; originalSql: string } | null>( null, ); const [confirmLoading, setConfirmLoading] = useState(false); const [createType, setCreateType] = useState('database'); const [relyOnParams, setRelyOnParams] = useState(null); const executedCallbackRef = React.useRef(); useEffect(() => { if (!open) { setErrorMessage(null); form.resetFields(); monacoEditorRef.current?.setValue('', 'cover'); } }, [open]); const config = useMemo(() => { return createType === 'database' ? { title: `${i18n('common.title.create')} Database`, api: sqlService.getCreateDatabaseSql, formName: 'databaseName', } : { title: `${i18n('common.title.create')} Schema`, api: sqlService.getCreateSchemaSql, formName: 'schemaName', }; }, [createType]); const labelCol = { flex: '70px' }; const handleFieldsChange = useCallback( debounce(() => { const formData: ICreateDatabase = form.getFieldsValue(); if (!formData.databaseName && createType === 'database') { return; } if (!formData.schemaName && createType === 'schema') { return; } const params = { databaseType: relyOnParams?.databaseType, dataSourceId: relyOnParams?.dataSourceId, databaseName: relyOnParams?.databaseName, ...formData, }; config.api(params as any).then((res) => { const { sql } = res; monacoEditorRef.current?.setValue(sql, 'cover'); }); }, 500), [relyOnParams, createType, monacoEditorRef, config], ); const executeUpdateDataSql = (sql: string) => { const params: any = { dataSourceId: relyOnParams?.dataSourceId, databaseType: relyOnParams?.databaseType, databaseName: relyOnParams?.databaseName, sql, }; setConfirmLoading(true); setErrorMessage(null); return sqlService .executeDDL(params) .then((res) => { if (res.success) { setOpen(false); executedCallbackRef.current?.(); } else { setErrorMessage(res); } }) .finally(() => { setConfirmLoading(false); }); }; const onOk = () => { const sql = monacoEditorRef.current?.getAllContent() || ''; executeUpdateDataSql(sql); }; const openCreateDatabaseModal = (params: { type: CreateType; relyOnParams: { databaseType: DatabaseTypeCode; dataSourceId: number; databaseName?: string; }; executedCallback?: () => void; }) => { setOpen(true); setCreateType(params.type); setRelyOnParams(params.relyOnParams); executedCallbackRef.current = params.executedCallback; }; useEffect(() => { setOpenCreateDatabaseModal(openCreateDatabaseModal); }, []); return (!!relyOnParams && ( { setOpen(false); }} title={config.title} destroyOnClose confirmLoading={confirmLoading} open={open} onOk={onOk} >
{noCommentDatabase.includes(relyOnParams.databaseType) ? null : ( )}
{i18n('common.title.preview')}
{errorMessage && ( <>
{i18n('common.title.errorMessage')}
{errorMessage.message}
)}
)) }; export default CreateDatabase; ================================================ FILE: chat2db-client/src/components/CustomLayout/index.less ================================================ @import '../../styles/var.less'; .customLayout { display: flex; align-items: center; margin: 0px -3px; .iconPanel { display: flex; align-items: center; justify-content: center; margin: 0px 3px; border-radius: 3px; height: 20px; width: 20px; cursor: pointer; &:hover { background-color: var(--color-hover-bg); } i { font-size: 15px; color: var(--color-text-secondary); } } } ================================================ FILE: chat2db-client/src/components/CustomLayout/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { togglePanelLeft, togglePanelRight } from '@/pages/main/workspace/store/config'; import Iconfont from '@/components/Iconfont'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; const { panelLeft, panelRight } = useWorkspaceStore((state) => { return { panelLeft: state.layout.panelLeft, panelRight: state.layout.panelRight, }; }); // 阻止事件冒泡 const stopPropagation = (e: React.MouseEvent) => { e.stopPropagation(); }; return (
{panelLeft ? : }
{panelRight ? : }
); }); ================================================ FILE: chat2db-client/src/components/CustomSelect/index.less ================================================ @import '../../styles/var.less'; .box { } ================================================ FILE: chat2db-client/src/components/CustomSelect/index.tsx ================================================ import React, { memo, useEffect, useState } from 'react'; import { Select } from 'antd'; interface IOption { label: string; value: string | number | null; } interface IProps { className?: string; options: IOption[]; onChange?: any; value?: any; } const CustomSelect = memo((props: IProps) => { const { options, onChange, value } = props; const [customOptions, setCustomOptions] = useState([]); const [customValue, setCustomValue] = useState(''); const [curSearch, setCurSearch] = useState(null); useEffect(() => { setCustomOptions([...options, { label: '', value: null }]); }, [options]); useEffect(() => { setCustomValue(value); }, [value]); // 1. 如果自定义的节点为null就过滤掉自定义节点 // 2. 如果自定义节点的值和前面的节点的值相同就过滤掉自定义节点 const filtrationCustomOptions = (list: IOption[]) => { const newList = [...list]; const lastItem = newList[newList.length - 1]; newList.forEach((item, index) => { if ((lastItem.value === item.value && index !== list.length - 1) || !item.value) { newList.pop(); } }); return newList; }; const onSearch = (v: string) => { setCurSearch(v); }; const customChange = (v: string) => { setCurSearch(null); setCustomValue(v); onChange?.(v); }; const onBlur = () => { if (curSearch) { onChange?.(curSearch); } setCurSearch(null); }; return (
); } ================================================ FILE: chat2db-client/src/components/SearchResult/components/RightClickMenu/index.less ================================================ @import '../../../../styles/var.less'; .dropdownOverlay { :global { .ant-dropdown-menu-submenu-title{ display: flex; align-items: center; } } } ================================================ FILE: chat2db-client/src/components/SearchResult/components/RightClickMenu/index.tsx ================================================ import React, { memo, useEffect, useMemo } from 'react'; import { Dropdown, ConfigProvider } from 'antd'; import i18n from '@/i18n'; import MenuLabel from '@/components/MenuLabel'; import styles from './index.less'; interface IProps { className?: string; children?: React.ReactNode; menuList: IMenu[] | null; } export interface IMenu { key: string; callback?: () => void; children?: { callback: () => void; hide?: boolean; }[]; } export enum AllSupportedMenusType { CopyCell = 'copy-cell', CopyRow = 'copy-row', CloneRow = 'clone-row', DeleteRow = 'delete-row', SetDefault = 'set-default', SetNull = 'set-null', ViewData = 'view-data', } export default memo((props) => { const { children, menuList } = props; const [open, setOpen] = React.useState(undefined); const [canContextmenu, setCanContextmenu] = React.useState(false); useEffect(() => { if (open === false) { setOpen(undefined); } }, [open]); useEffect(() => { const handleClick = (event) => { const targetElement = event.target as Element; if (targetElement.closest('[data-chat2db-edit-table-data-can-right-click]')) { setCanContextmenu(true); } else { setCanContextmenu(false); } }; document.addEventListener('contextmenu', handleClick); return () => { document.removeEventListener('contextmenu', handleClick); }; }, []); const allSupportedMenus = { [AllSupportedMenusType.CopyCell]: { label: , key: AllSupportedMenusType.CopyCell, }, [AllSupportedMenusType.CopyRow]: { label: , key: AllSupportedMenusType.CopyRow, children: [ { label: i18n('common.button.insertSql'), key: 'copy-row-1', }, { label: i18n('common.button.updateSql'), key: 'copy-row-2', }, { label: i18n('common.button.tabularSeparatedValues'), key: 'copy-row-3', }, { label: i18n('common.button.tabularSeparatedValuesFieldName'), key: 'copy-row-4', }, { label: i18n('common.button.tabularSeparatedValuesFieldNameAndData'), key: 'copy-row-5', }, ], }, [AllSupportedMenusType.CloneRow]: { label: , key: AllSupportedMenusType.CloneRow, }, [AllSupportedMenusType.DeleteRow]: { label: , key: AllSupportedMenusType.DeleteRow, }, [AllSupportedMenusType.SetDefault]: { label: , key: AllSupportedMenusType.SetDefault, }, [AllSupportedMenusType.SetNull]: { label: , key: AllSupportedMenusType.SetNull, }, [AllSupportedMenusType.ViewData]: { label: , key: AllSupportedMenusType.ViewData, }, }; const items = useMemo(() => { return menuList?.map((menu) => { return { ...allSupportedMenus[menu.key], onClick: () => { menu.callback?.(); setOpen(false); }, children: menu.children?.map((child, index) => { if (child.hide) return null; return { ...allSupportedMenus[menu.key]['children'][index], onClick: () => { child.callback(); setOpen(false); }, }; }), }; }); }, [menuList]); return ( { setOpen(_open); }} > {children} ); }); ================================================ FILE: chat2db-client/src/components/SearchResult/components/ScreeningResult/index.less ================================================ .screeningResult{ display: flex; align-items: center; height: 18px; padding: 4px 0px; border-bottom: 1px solid var(--color-border); .whereBox,.orderByBox{ width: 50%; display: flex; align-items: center; height: 100%; margin: 0px 4px; .titleBox{ display: flex; align-items: center; margin-right: 10px; width: fit-content; flex-shrink: 0; } .titleIcon{ margin-right: 4px; } .title{ color: var(--color-text-secondary); flex-shrink: 0; font-weight: 500; } .activeTitle{ color: var(--color-primary-text); } .monacoEditor{ flex: 1; height: 100%; width: 0px; } } :global { .decorationsOverviewRuler{ display: none !important; } } } ================================================ FILE: chat2db-client/src/components/SearchResult/components/ScreeningResult/index.tsx ================================================ import React, { memo, useEffect, useContext } from 'react'; import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import SingleFileMonacoEditor from '@/components/SingleFileMonacoEditor'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { IExecuteSqlParams } from '@/service/sql'; import { Context } from '@/components/SearchResult'; interface IProps { className?: string; promptWord: any[]; getTableData: (params?: Partial) => void; } const keywordHintList = [ 'AND', 'OR', 'NOT', 'IS', 'NULL', 'IN', 'IS NOT NULL', 'IS NULL', 'IS NOT', 'NOT IN', 'EXISTS', 'BETWEEN', 'LIKE', 'ASC', 'DESC', ] export default memo((props) => { const { promptWord, getTableData } = props; const { notChangedSql } = useContext(Context); const [isActive, setIsActive] = React.useState(false); const keywordHintRef = React.useRef(null); const fieldHintRef = React.useRef(null); const whereSingleFileMonacoEditorRef = React.useRef(null); const orderBySingleFileMonacoEditorRef = React.useRef(null); useEffect(() => { keywordHintRef.current && keywordHintRef.current.dispose(); fieldHintRef.current && fieldHintRef.current.dispose(); if (isActive) { registerPromptWord(); } }, [promptWord, isActive]); const registerPromptWord = () => { const suggestions = promptWord.slice(1).map((item) => { return { insertText: item.name, kind: monaco.languages.CompletionItemKind.Field, label: item.name, }; }); const provideCompletionItems: any = () => { return { suggestions, }; }; fieldHintRef.current = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems, triggerCharacters: [], }); keywordHintRef.current = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: keywordHintList.map((item) => { return { insertText: item, kind: monaco.languages.CompletionItemKind.Keyword, label: item, }; }), }; }, triggerCharacters: [], }); }; const search = () => { const whereValue = whereSingleFileMonacoEditorRef.current?.getAllContent().trim() || ''; const orderByValue = orderBySingleFileMonacoEditorRef.current?.getAllContent().trim() || ''; let sql = whereValue ? notChangedSql + ' WHERE ' + whereValue : notChangedSql; sql = orderByValue ? sql + ' ORDER BY ' + orderByValue : sql; getTableData({ sql }); }; const focusChange = (_isActive: boolean) => { setIsActive(_isActive); }; return (
WHERE
ORDER BY
); }); ================================================ FILE: chat2db-client/src/components/SearchResult/components/StatusBar/index.less ================================================ @import '../../../../styles/var.less'; .statusBar { height: 26px; box-sizing: border-box; padding: 4px 8px; font-size: 12px; display: flex; justify-content: start; align-items: center; border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-subtle); overflow: hidden; flex-shrink: 0; .f-single-line(); & > span { margin-right: 16px; } } ================================================ FILE: chat2db-client/src/components/SearchResult/components/StatusBar/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import i18n from '@/i18n'; interface IProps { className?: string; description?: string; duration?: number; dataLength?: number; } export default memo((props) => { const { className, description, duration, dataLength } = props; return (
{`【${i18n('common.text.result')}】${description}.`} {`【${i18n('common.text.timeConsuming')}】${duration}ms.`} {!!dataLength && {`【${i18n('common.text.searchRow')}】${dataLength} ${i18n('common.text.row')}.`}}
); }); ================================================ FILE: chat2db-client/src/components/SearchResult/components/TableBox/index.less ================================================ @import '../../../../styles/var.less'; .button-bar-item() { width: 24px; height: 24px; display: flex; justify-content: center; align-items: center; cursor: pointer; border-radius: 4px; i { color: var(--color-primary); } &:hover{ background-color: var(--color-hover-bg); } } .tableBox { height: 100%; overflow: hidden; display: flex; flex-direction: column; .allSelectBox{ width: calc(100% + 8px); height: 100%; transform: translateX(-4px); &:hover{ background-color: var(--color-hover-bg); } } :global { .table{ overflow: hidden; } .art-table { table colgroup col:nth-of-type(1) { min-width: 60px; } // th, // td { // overflow: hidden; // } .fnWppk { // word-break: break-all; // display: -webkit-box; // -webkit-box-orient: vertical; // box-orient: vertical; // -webkit-line-clamp: 1; // line-clamp: 1; // text-overflow: ellipsis; overflow: hidden; } th { font-weight: bold; } th.first { border-left: 0; } td.first { border-left: 0; } tr.first th { border-top: 0; } } .art-table-cell { position: relative; } // .art-sticky-scroll { // display: none !important; // } .art-table-row { height: 32px; } .art-table-header { background: transparent !important; overflow: hidden !important; top: 0px !important; } .art-table-body::-webkit-scrollbar { display: none; } .art-table-body-scroll { padding-right: 6px; } .art-table-header-cell { padding: 0px 4px; } .art-table-header-row .art-table-header-cell:nth-of-type(1){ .resize-handle{ display: none; } } } } .noDataTableBox { :global { .art-loading-wrapper, .art-loading-content-wrapper, .art-table { height: 100%; } .art-table-body { height: calc(100% - 32px); overflow-y: auto !important; } } table { height: 100%; } } .toolBar { z-index: 30; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--color-border-secondary); background-color: var(--color-bg-subtle); padding: 0px 4px 0px 0px; height: 30px; flex-shrink: 0; } .toolBarItem { height: 100%; display: flex; justify-content: start; align-items: center; &:not(:last-child) { border-right: 1px solid var(--color-border); } } .toolBarRight { flex: 1; display: flex; justify-content: end; .exportBar { cursor: pointer; } } .editTableDataBar { padding: 0px 4px; height: 30px; i { color: var(--color-text-disabled); } .editTableDataBarItem { .button-bar-item(); } .disableBar { cursor: default; i { color: var(--color-text-disabled); } &:hover{ background-color: transparent; } } .viewSqlBar { i { font-size: 15px; } } } .refreshBar{ padding: 0px 4px; .refreshIconBox{ .button-bar-item(); } } .supportBaseTableBox { flex: 1; overflow-y: auto; height: 0px; position: relative; .supportBaseTableSpin { position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; display: flex; justify-content: center; align-items: center; z-index: 10; background-color: var(--color-fill-quaternary); } } .supportBaseTableBoxHidden { overflow: hidden; } .table { height: 100%; } .tableItem { height: 31px; display: flex; align-items: center; justify-content: space-between; position: relative; padding-left: 4px; user-select: none; background-color: var(--color-bg-base); cursor: pointer; .tableItemContent { max-height: 31px; line-height: 31px; flex: 1; width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .previewTableItemContent{ display: none; position: absolute; left: 0px; top: 0px; bottom: 0px; width: fit-content; min-width: 100%; padding: 0px 4px; max-height: 31px; line-height: 31px; background-color: var(--color-bg-layout); z-index: 2; color: var(--color-text); white-space: nowrap; } &:hover { .previewTableItemContent { display: flex; } .previewTableItemContent { display: block; } } .cellValueNull { color: var(--color-text-tertiary); } input { background: none; } } .tableItemEdit { background-color: var(--color-success-bg); .previewTableItemContent { background-color: var(--color-success-bg); } } .tableItemHighlight { background-color: var(--color-bg-subtle); .previewTableItemContent { background-color: var(--color-bg-subtle); } } .tableItemFocus { background-color: var(--color-primary-hover); color: var(--color-bg-base); .previewTableItemContent { background-color: var(--color-primary-hover); } .previewTableItemContent{ background-color: var(--color-primary-hover); } .cellValueNull { color: var(--color-bg-base); } } .tableItemSuccess { background-color: var(--color-success-bg); .previewTableItemContent { background-color: var(--color-success-bg); } } .tableItemError { background-color: var(--color-error-bg); .previewTableItemContent { background-color: var(--color-error-bg); } } .tableItemNo { width: 100%; text-align: center; } .pagination { :global { .ant-pagination-prev, .ant-pagination-next, .ant-pagination-jump-prev, .ant-pagination-jump-next { min-width: 20px; } .ant-pagination-simple-pager { input { padding: 0px !important; font-size: 12px; height: 70% !important; } } } } .monacoEditor { border: 1px solid var(--color-border); border-radius: 4px; overflow: hidden; height: 60vh; margin-top: -15px; } .errorDetail { white-space: normal; } ================================================ FILE: chat2db-client/src/components/SearchResult/components/TableBox/index.tsx ================================================ import React, { useEffect, useMemo, useState } from 'react'; import { Dropdown, Input, MenuProps, message, Modal, Space, Popover, Spin, Button } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import styled from 'styled-components'; import classnames from 'classnames'; import lodash from 'lodash'; import { v4 as uuid } from 'uuid'; import i18n from '@/i18n'; import ScreeningResult from '@/components/SearchResult/components/ScreeningResult'; // import { Context } from '@/components/SearchResult'; // 样式 import styles from './index.less'; // 工具函数 import { compareStrings } from '@/utils/sort'; import { downloadFile } from '@/utils/file'; import { transformInputValue } from '../../utils'; // 类型定义 import { CRUD } from '@/constants'; import { TableDataType } from '@/constants/table'; import { IManageResultData, IResultConfig } from '@/typings/database'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; // api import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; // store import { setFocusedContent } from '@/store/common/copyFocusedContent'; // 依赖组件 import ExecuteSQL from '@/components/ExecuteSQL'; import { DownOutlined } from '@ant-design/icons'; import { copy, tableCopy } from '@/utils'; import Iconfont from '../../../Iconfont'; import StateIndicator from '../../../StateIndicator'; import MonacoEditor from '../../../MonacoEditor'; import MyPagination from '../Pagination'; import StatusBar from '../StatusBar'; import RightClickMenu, { AllSupportedMenusType } from '../RightClickMenu'; // 自定义hooks import useCurdTableData from '../../hooks/useCurdTableData'; import useMultipleSelect from '../../hooks/useMultipleSelect'; import usePasteData from '../../hooks/usePasteData'; interface ITableProps { className?: string; outerQueryResultData: IManageResultData; executeSqlParams: any; tableBoxId: string; isActive?: boolean; concealTabHeader?: boolean; // concealTabHeader 是否隐藏tab头部, 目前来说隐藏头部都是单表查询。需要显示筛选 } interface IViewTableCellData { name: string; value: any; colId: string; rowId: string; } export interface IUpdateData { oldDataList?: Array; dataList?: Array; type: CRUD; rowId: string; } export enum USER_FILLED_VALUE { DEFAULT = 'CHAT2DB_UPDATE_TABLE_DATA_USER_FILLED_DEFAULT', } const SupportBaseTable: any = styled(BaseTable)` &.supportBaseTable { --bgcolor: var(--color-bg-base); --header-bgcolor: var(--color-bg-subtle); --hover-bgcolor: transparent; --header-hover-bgcolor: var(--color-bg-subtle); --highlight-bgcolor: transparent; --header-highlight-bgcolor: var(--color-bg-subtle); --color: var(--color-text); --header-color: var(--color-text); --lock-shadow: rgb(37 37 37 / 0.5) 0 0 6px 2px; --border-color: var(--color-border-secondary); --cell-padding: 0px; --row-height: 32px; --lock-shadow: 0px 1px 2px 0px var(--color-border); } `; const preCode = '$$chat2db_'; // No列的code const colNoCode = `${preCode}0No.`; const defaultPaginationConfig: IResultConfig = { pageNo: 1, pageSize: 200, total: 0, hasNextPage: true, }; export const TableContext = React.createContext({} as any); export default function TableBox(props: ITableProps) { // const {} = useContext(Context); const { className, outerQueryResultData, isActive, concealTabHeader } = props; const [viewTableCellData, setViewTableCellData] = useState(null); const [, contextHolder] = message.useMessage(); const [paginationConfig, setPaginationConfig] = useState(defaultPaginationConfig); // sql查询结果 const [queryResultData, setQueryResultData] = useState(outerQueryResultData); // tableData:带列标识的表数据 可以传给Table组件 进行渲染 // 保存原始的表数据,用于撤销 const [oldTableData, setOldTableData] = useState<{ [key: string]: string }[]>([]); // 实时更新的表数据 const [tableData, setTableData] = useState<{ [key: string]: string | null }[]>([]); // DataList不带列标识的表数据 // 保存原始的表数据,用于对比新老数据看是否有变化 const [oldDataList, setOldDataList] = useState([]); // 当前聚焦的单元格的坐标,以及是否正在编辑,为false时,代表正在聚焦,但是没有编辑 const [editingCell, setEditingCell] = useState<[string, string, boolean] | null>(null); // input受控的正在编辑的数据 const [editingData, setEditingData] = useState(''); // 当前选中的行号 const [curOperationRowNo, setCurOperationRowNo] = useState | null>(null); // 操作过的数据列表 const [updateData, setUpdateData] = useState([]); // 更新数据的sql const [updateDataSql, setUpdateDataSql] = useState(null); // ExecuteSQL弹窗 初始化的错误信息 const [initError, setInitError] = useState(null); // 是否显示更新数据的sql const [viewUpdateDataSqlModal, setViewUpdateDataSqlModal] = useState(false); // 用于滚动到底部 const tableBoxRef = React.useRef(null); // 所有数据准备好了 const [allDataReady, setAllDataReady] = useState(false); // 编辑数据的inputRef const editDataInputRef = React.useRef(null); // monacoEditorRef const monacoEditorRef = React.useRef(null); // 表格loading const [tableLoading, setTableLoading] = useState(false); // 列宽数组 const [columnResize, setColumnResize] = useState([0]); // 表格的宽度 // const [tableBoxWidth, setTableBoxWidth] = useState(0); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { ...(props.executeSqlParams || {}), sql: queryResultData.sql, originalSql: queryResultData.originalSql, exportType, exportSize, }; downloadFile(window._BaseURL + '/api/rdb/dml/export', params); }; useEffect(() => { let total: any = queryResultData.fuzzyTotal; // 如果total是数字,且不为0,则还是使用原先的total if (lodash.isNumber(paginationConfig.total) && paginationConfig.total !== 0) { total = paginationConfig.total; } if (!lodash.isNumber(paginationConfig.total)) { const oldTotal = Number(paginationConfig.total.split('+')[0]); const newTotal = Number(queryResultData.fuzzyTotal.split('+')[0]); if (oldTotal > newTotal) { total = paginationConfig.total; } } setPaginationConfig({ ...paginationConfig, total, hasNextPage: queryResultData.hasNextPage, }); }, [queryResultData]); useEffect(() => { // 每次dataList变化,都需要重新计算tableData if (!columns?.length) { setTableData([]); } else { const newTableData = dataListTransformTableData(queryResultData.dataList); setTableData(newTableData); setOldTableData(newTableData); setAllDataReady(true); } // 每次data变化,都需要重新计算oldDataList if (queryResultData.dataList?.length) { setOldDataList(queryResultData.dataList); } }, [queryResultData.dataList]); // 导出sql的菜单项 const exportDropdownItems: MenuProps['items'] = useMemo( () => [ { label: i18n('workspace.table.export.all.csv'), key: '1', // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, }, { label: i18n('workspace.table.export.all.insert'), key: '2', // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, }, { label: i18n('workspace.table.export.cur.csv'), key: '3', // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, }, { label: i18n('workspace.table.export.cur.insert'), key: '4', // icon: , onClick: () => { handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, }, ], [queryResultData], ); const defaultSorts: SortItem[] = useMemo( () => (queryResultData.headerList || []).map((item) => ({ code: item.name, order: 'none', })), [queryResultData.headerList], ); function monacoEditorEditData() { const editorData = monacoEditorRef?.current?.getAllContent(); const { rowId, colId } = viewTableCellData!; oldTableData.forEach((item) => { if (item[colNoCode] === rowId) { if (item[colId] !== editorData) { const newTableData = lodash.cloneDeep(tableData); let newRowDataList: any = []; newTableData.forEach((i) => { if (i[colNoCode] === rowId) { i[colId] = editorData; newRowDataList = Object.keys(i).map((_i) => i[_i]); } }); setTableData(newTableData); // 添加更新记录 setUpdateData([ ...updateData, { type: CRUD.UPDATE, oldDataList: Object.keys(item).map((_i) => item[_i]), dataList: newRowDataList, rowId, }, ]); } return; } }); setViewTableCellData(null); } function handleCancel() { setViewTableCellData(null); } const handleClickTableItem = (colId, rowId, value, isEditing) => { // 1. 如果当前单元格正在编辑,则不需要再次编辑 // 2. 如果当前单元格正在编辑,则不需要聚焦 if (editingCell?.[0] === colId && editingCell?.[1] === rowId && editingCell?.[2]) { return; } setFocusedContent(value); // 聚焦当前单元格,取消对于行的聚焦 setCurOperationRowNo(null); // 当前聚焦或者编辑的单元格的数据 setEditingData(value); // 如果数据不支持修改,则该单元格不支持编辑 if (!queryResultData.canEdit) { setEditingCell([colId, rowId, false]); } else { setEditingCell([colId, rowId, isEditing]); } // 当前聚焦或者编辑的单元格的坐标 // 如果是编辑状态,则需要聚焦到input if (isEditing) { setTimeout(() => { editDataInputRef?.current?.focus(); }, 0); } }; // 渲染单元格的值 const renderTableCellValue = (value) => { if (value === null) { return {''}; } else if (value === USER_FILLED_VALUE.DEFAULT) { return {''}; } else if (!value) { // 如果为空需要展位 return ; } else { return value; } }; // 每个单元格的样式 const tableCellStyle = (value, rowId, colId) => { // 单元格的基础样式 const styleList = [styles.tableItem]; // 如果当前行中的单元格正在聚焦或编辑 if (editingCell?.[1] === rowId) { // 设置正在编辑或聚焦的单元格所在行的样式为高亮 styleList.push(styles.tableItemHighlight); // 精确找到列,设置正在编辑或聚焦的单元格的样式为Focus if (editingCell?.[0] === colId && !editingCell?.[2]) { styleList.push(styles.tableItemFocus); } return classnames(...styleList); } // 当前单元格所在的行被选中了(行聚焦) if (curOperationRowNo?.includes(rowId)) { // No列的高亮只需要用tableItemHighlight不需要用tableItemFocus if (colId === colNoCode) { styleList.push(styles.tableItemHighlight); } else { styleList.push(styles.tableItemFocus); } return classnames(...styleList); } // 新添加的行 const index2 = updateData.findIndex((item) => { return item.rowId === rowId && item.type === CRUD.CREATE; }); if (index2 !== -1) { styleList.push(styles.tableItemSuccess); return classnames(...styleList); } // 如果是删除过的行 const index = updateData.findIndex((item) => item.rowId === rowId && item.type === CRUD.DELETE); if (index !== -1) { styleList.push(styles.tableItemError); return classnames(...styleList); } // 编辑过的单元格的样式 let oldValue = ''; oldTableData.forEach((item) => { if (item[colNoCode] === rowId) { oldValue = item[colId]; } }); if (value !== oldValue && colId !== colNoCode) { // console.log('colId', colId, 'rowId', rowId) // console.log('oldValue', oldValue, 'value', value) styleList.push(styles.tableItemEdit); } return classnames(...styleList); }; // 纯数据的dataList 转换为 tableData const dataListTransformTableData = (myDataList: string[][]) => { const newTableData = (myDataList || []).map((item) => { const rowData: any = {}; item.map((i: string | null, colIndex: number) => { const colId = `${preCode}${colIndex}${columns[colIndex].name}`; rowData[colId] = i; }); return rowData; }); return newTableData; }; const onPageNoChange = (pageNo: number) => { const config = { ...paginationConfig, pageNo }; setPaginationConfig(config); getTableData({ pageNo }); }; const onPageSizeChange = (pageSize: number) => { const config = { ...paginationConfig, pageSize, pageNo: 1 }; setPaginationConfig(config); getTableData({ pageSize, pageNo: 1 }); }; const onClickTotalBtn = async () => { const res = await sqlService.getDMLCount({ sql: queryResultData.originalSql, ...(props.executeSqlParams || {}), }); setPaginationConfig({ ...paginationConfig, total: res }); return res; }; // 撤销按钮是否可用 const revokeDisableBarState = useMemo(() => { // 如果有聚焦的行,但是没有操作过的数据,则不可用 const operationType = [CRUD.CREATE, CRUD.UPDATE, CRUD.DELETE]; if (curOperationRowNo) { // 当前选中的行里面有没有操作过的数据 const hasOperationData = updateData.some((item) => { return operationType.includes(item.type) && curOperationRowNo.includes(item.rowId); }); if (hasOperationData) { return false; } } // 如果有聚焦的单元格 if (editingCell && editingCell[2] === false) { const oldRowDataList = oldDataList.find((item) => item[0] === editingCell[1]); const oldData = oldRowDataList?.[editingCell[0]]; // 如果当前单元格的数据和老数据一样,则可用 if (oldData !== editingData) { return false; } } // 如果都没,那撤销按钮不可用 return true; }, [curOperationRowNo, updateData, editingCell]); // 处理撤销 const handleRevoke = () => { if (revokeDisableBarState) { return; } // 多行撤销处理 if (curOperationRowNo?.length) { const _updateData = updateData.filter((item) => !curOperationRowNo?.includes(item.rowId)); let _tableData = tableData.map((item) => { const oldData = oldTableData.find((i) => i[colNoCode] === item[colNoCode])!; return curOperationRowNo.includes(item[colNoCode]!) ? oldData : item; }); _tableData = _tableData.filter((item) => item); setUpdateData(_updateData); setTableData(_tableData); setCurOperationRowNo(null); return; } // 聚焦单元格撤销 if (editingCell && editingCell[2] === false) { const oldRowTableData = oldTableData.find((item) => item[colNoCode] === editingCell[1])!; const oldData = oldRowTableData[editingCell[0]]; const _tableData = tableData.map((item) => { if (item[colNoCode] === editingCell[1]) { item[editingCell[0]] = oldData || ''; } return item; }); // 如果撤销后这一行的数据和原始数据一样,则删除这条更新记录 const newRowTableData = _tableData.find((item) => item[colNoCode] === editingCell[1])!; if (lodash.isEqual(newRowTableData, oldRowTableData)) { setUpdateData(updateData.filter((item) => item.rowId !== editingCell[1])); } setTableData(_tableData); } }; // 查看更新数据的sql const handleViewSql = () => { if (!updateData.length) { return; } getExecuteUpdateSql().then((res) => { setUpdateDataSql(res); setViewUpdateDataSqlModal(true); }); }; // 更新数据的sql const handleUpdateSubmit = () => { if (!updateData.length || tableLoading) { return; } setTableLoading(true); getExecuteUpdateSql() .then((res) => { executeUpdateDataSql(res); }) .catch(() => { setTableLoading(false); }); }; // 获取更新数据的sql const getExecuteUpdateSql = (_updateData?: any) => { return new Promise((resolve) => { const params = { databaseName: props.executeSqlParams?.databaseName, dataSourceId: props.executeSqlParams?.dataSourceId, schemaName: props.executeSqlParams?.schemaName, type: props.executeSqlParams?.databaseType, tableName: queryResultData.tableName, headerList: queryResultData.headerList, operations: _updateData || updateData, }; sqlService.getExecuteUpdateSql(params).then((res) => { resolve(res || ''); }); }); }; // 执行sql const executeUpdateDataSql = (sql: string) => { const executeSQLParams: IExecuteSqlParams = { sql, dataSourceId: props.executeSqlParams?.dataSourceId, databaseName: props.executeSqlParams?.databaseName, schemaName: props.executeSqlParams?.schemaName, tableName: queryResultData.tableName, }; sqlService .executeUpdateDataSql(executeSQLParams) .then((res) => { if (res?.success) { // 更新成功后,需要重新获取表格数据 getTableData().then(() => { message.success(i18n('common.text.successfulExecution')); setUpdateData([]); }); } else { setUpdateDataSql(res?.sql); setViewUpdateDataSqlModal(true); setInitError(res.message); } }) .finally(() => { setTableLoading(false); }); }; // 获取表格数据 接受一个参数params 包含IExecuteSqlParams中的一个或多个 const getTableData = (params?: Partial) => { setTableLoading(true); setCurOperationRowNo(null); setEditingCell(null); const executeSQLParams: IExecuteSqlParams = { sql: queryResultData.originalSql, dataSourceId: props.executeSqlParams?.dataSourceId, databaseName: props.executeSqlParams?.databaseName, schemaName: props.executeSqlParams?.schemaName, pageNo: paginationConfig.pageNo, pageSize: paginationConfig.pageSize, ...(params || {}), }; return sqlService.executeSql(executeSQLParams).then((res) => { setTableLoading(false); setQueryResultData(res?.[0]); setUpdateData([]); }); }; // sql执行成功后的回调 const executeSuccessCallBack = () => { getTableData().then(() => { setViewUpdateDataSqlModal(false); message.success(i18n('common.text.successfulExecution')); }); }; const { multipleSelect } = useMultipleSelect({ setCurOperationRowNo, tableData, colNoCode, curOperationRowNo, setFocusedContent, }); const handelRowNoClick = (rowId: string) => { multipleSelect(rowId); setEditingCell(null); // const newRowData = tableData.find((item) => item[colNoCode] === rowId)!; // const newRowDataList = Object.keys(newRowData).map((item) => newRowData[item]); // newRowDataList.splice(0, 1); }; // 表格 列配置 const columns: ArtColumn[] = useMemo(() => { return (queryResultData.headerList || []).map((item, colIndex) => { const { dataType, name } = item; const isNumber = dataType === TableDataType.NUMERIC; const isNumericalOrder = dataType === TableDataType.CHAT2DB_ROW_NUMBER; const colId = `${preCode}${colIndex}${name}`; if (isNumericalOrder) { return { code: colNoCode, name: 'No.', title: (
{ setEditingCell(null); if (curOperationRowNo) { setCurOperationRowNo(null); return; } // 全选列 const rowIds = tableData.map((i) => i[colNoCode]!); setCurOperationRowNo(rowIds); }} /> ), key: name, lock: true, // features: { sortable: compareStrings }, render: (value: any, rowData, rowIndex) => { const rowId = rowData[colNoCode]; return (
{ handelRowNoClick(rowId); }} onContextMenu={() => { if (!curOperationRowNo?.includes(rowId)) { handelRowNoClick(rowId); } }} className={tableCellStyle(value, rowId, colNoCode)} >
{rowIndex + 1}
); }, }; } return { code: colId, name: name, key: name, // title:
{name}
, render: (value: any, rowData) => { const rowId = rowData[colNoCode]; const content = renderTableCellValue(value); return (
{editingCell?.[0] === colId && editingCell?.[1] === rowId && editingCell?.[2] ? ( { setEditingData(e.target.value); }} onBlur={() => { setEditingCell([editingCell![0], editingCell![1], false]); updateTableData('setCell', editingData); }} /> ) : ( <>
{content}
{content}
)}
); }, // 如果是数字类型,因为后端返回的都是字符串,所以需要调用字符串对比函数来判断 features: { sortable: isNumber ? compareStrings : true }, }; }); }, [queryResultData.headerList, editingCell, editingData, curOperationRowNo, oldDataList]); const { updateTableData, handleCreateData, handleDeleteData } = useCurdTableData({ tableData, setTableData, preCode, editingCell, columns, curOperationRowNo, oldDataList, updateData, setUpdateData, queryResultData, setCurOperationRowNo, setEditingCell, tableBoxRef, oldTableData, colNoCode, }); // 处理粘贴的数据 hooks usePasteData({ updateTableData, curOperationRowNo, editingCell }); // 表格渲染的配置 const pipeline = useTablePipeline() .input({ dataSource: tableData, columns }) .use( features.sort({ mode: 'single', defaultSorts, highlightColumnWhenActive: true, // sorts, // onChangeSorts, }), ) .use( features.columnResize({ fallbackSize: 150, // handleBackground: '#ddd', handleHoverBackground: `var(--color-primary-bg-hover)`, handleActiveBackground: `var(--color-primary-bg-hover)`, minSize: 60, maxSize: 1080, sizes: columnResize, onChangeSizes: (sizes) => { sizes[0] = 0; setColumnResize(sizes); }, }), ); const getSelectTableRowData = () => { if (!curOperationRowNo && !editingCell) { return [[]]; } const rowIds = curOperationRowNo || [editingCell?.[1]]; const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); delete _item[colNoCode]; return Object.keys(_item).map((i) => _item[i]); }); return newRowDatasList; }; // 右键菜单配置项 const copyRow = { key: AllSupportedMenusType.CopyRow, children: [ { callback: () => { const rowIds = curOperationRowNo || [editingCell![1]]; const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); return Object.keys(_item).map((i) => _item[i]); }); const _updateDatas = newRowDatasList.map((item, index) => { return { type: CRUD.CREATE, dataList: item, rowId: (tableData.length + index + 1).toString(), }; }); getExecuteUpdateSql(_updateDatas).then((res) => { copy(res); }); }, hide: !queryResultData.canEdit, }, { callback: () => { const rowIds = curOperationRowNo || [editingCell![1]]; const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); return Object.keys(_item).map((i) => _item[i]); }); const _updateDatas = newRowDatasList.map((item, index) => { return { type: CRUD.UPDATE_COPY, dataList: item, rowId: (tableData.length + index + 1).toString(), }; }); getExecuteUpdateSql(_updateDatas).then((res) => { copy(res); }); }, hide: !queryResultData.canEdit, }, // 复制当前行的数据 { callback: () => { const selectTableRowData = getSelectTableRowData(); tableCopy(selectTableRowData); }, }, // 复制表头 { callback: () => { const headerList = queryResultData.headerList.map((item) => item.name); // 去掉No列 headerList.splice(0, 1); tableCopy([headerList]); }, }, // 复制表头和当前行的数据 { callback: () => { const rowIds = curOperationRowNo || [editingCell![1]]; const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); delete _item[colNoCode]; return Object.keys(_item).map((i) => _item[i]); }); const headerList = queryResultData.headerList.map((item) => item.name); // 去掉No列 headerList.splice(0, 1); tableCopy([headerList, ...newRowDatasList]); }, }, ], }; const cloneRow = { key: AllSupportedMenusType.CloneRow, callback: () => { const newTableData = lodash.cloneDeep(tableData); const rowIds = curOperationRowNo || [editingCell![1]]; // 在newTableData中找出 rowIds中所有的行 const newRowDatas = newTableData.filter((item) => rowIds.includes(item[colNoCode]!)); newRowDatas.map((t, i) => { t[colNoCode] = (newTableData.length + i + 1).toString(); }); handleCreateData(newRowDatas); }, }; const deleteRow = { key: AllSupportedMenusType.DeleteRow, callback: handleDeleteData, }; const copyCell = { key: AllSupportedMenusType.CopyCell, callback: () => { copy(editingData); }, }; const setDefault = { key: AllSupportedMenusType.SetDefault, callback: () => { updateTableData('setCell', USER_FILLED_VALUE.DEFAULT); }, }; const setNull = { key: AllSupportedMenusType.SetNull, callback: () => { updateTableData('setCell', null); }, }; const viewData = { key: AllSupportedMenusType.ViewData, callback: () => { setViewTableCellData({ name: columns.find((i) => i.code === editingCell![0])!.name, value: editingData, colId: editingCell![0], rowId: editingCell![1], }); }, }; const rowRightClickMenu = useMemo(() => { let rightClickMenu: any = []; if (curOperationRowNo) { rightClickMenu = [copyRow, cloneRow, deleteRow]; // 如果当前数据不可编辑,则不显示cloneRow和deleteRow if (!queryResultData.canEdit) { rightClickMenu = rightClickMenu.filter( (i) => i.key !== AllSupportedMenusType.CloneRow && i.key !== AllSupportedMenusType.DeleteRow, ); } } if (editingCell) { rightClickMenu = [viewData, copyCell, copyRow, cloneRow, setNull, setDefault, deleteRow]; // 判断是否有默认值,如果没有默认值,则不显示设置默认值的菜单 const colId = editingCell[0]; const hasDefaultValue = queryResultData.headerList.find((item) => { return item.name === columns.find((i) => i.code === colId)?.name; })?.defaultValue !== null; if (!hasDefaultValue) { rightClickMenu = rightClickMenu.filter((i) => i.key !== AllSupportedMenusType.SetDefault); } // 如果当前数据不可编辑,则不显示cloneRow和deleteRow if (!queryResultData.canEdit) { rightClickMenu = rightClickMenu.filter( (i) => i.key !== AllSupportedMenusType.CloneRow && i.key !== AllSupportedMenusType.DeleteRow && i.key !== AllSupportedMenusType.SetNull, ); } } if (!curOperationRowNo && !editingCell) { return null; } return rightClickMenu; }, [curOperationRowNo, editingCell, queryResultData]); const renderContent = () => { const bottomStatus = (
{`【${i18n('common.text.result')}】${queryResultData.description}.`} {`【${i18n('common.text.timeConsuming')}】${queryResultData.duration}ms.`} {`【${i18n('common.text.searchRow')}】${tableData.length} ${i18n('common.text.row')}.`}
); if (!columns.length) { return ( <>
{bottomStatus}
); } else { return ( <>
{/* 刷新 */}
{ getTableData(); }} className={classnames(styles.refreshIconBox)} >
{queryResultData.canEdit && (
{/* 新增行 */}
{ handleCreateData(); }} className={classnames(styles.createDataBar, styles.editTableDataBarItem)} >
{/* 删除行 */}
{ handleDeleteData(); }} className={classnames(styles.deleteDataBar, styles.editTableDataBarItem, { [styles.disableBar]: curOperationRowNo === null, })} >
{/* 撤销 */}
{/* 查看更改sql */}
{/* 提交 */}
)}
{i18n('common.text.export')}
{concealTabHeader && } {isActive ? (
{allDataReady && ( <> {tableLoading && }

{i18n('common.text.noData')}

}} isStickyHead stickyTop={31} {...pipeline.getProps()} /> )}
) : (
)} ); } }; const renderMonacoEditor = useMemo(() => { return (
); }, [queryResultData, viewTableCellData]); return (
{renderContent()} {i18n('common.button.modify')} , ] } > {renderMonacoEditor} { setViewUpdateDataSqlModal(false); setUpdateDataSql(''); setInitError(null); }} > {contextHolder}
); } ================================================ FILE: chat2db-client/src/components/SearchResult/hooks/useCurdTableData.ts ================================================ import lodash from 'lodash'; import { CRUD } from '@/constants'; import { USER_FILLED_VALUE, IUpdateData } from '../components/TableBox/index'; export interface IProps { preCode: string; // tableData: { [key: string]: string | null }[]; setTableData: (tableData: { [key: string]: string | null }[]) => void; // editingCell: [string, string, boolean] | null; setEditingCell: (editingCell: [string, string, boolean] | null) => void; // updateData: IUpdateData[]; setUpdateData: (updateData: IUpdateData[]) => void; // curOperationRowNo: Array | null; setCurOperationRowNo: (curOperationRowNo: Array | null) => void; // columns; oldDataList; queryResultData; tableBoxRef; oldTableData; colNoCode; } const useCurdTableData = (props: IProps) => { const { tableData, setTableData, preCode, editingCell, columns, curOperationRowNo, oldDataList, updateData, setUpdateData, queryResultData, setCurOperationRowNo, setEditingCell, tableBoxRef, oldTableData, colNoCode, } = props; // 编辑数据 const updateTableData = (type: 'setCell' | 'setRow', _data: string | null | Array) => { const newTableData = lodash.cloneDeep(tableData); let oldRowDataList: Array = []; let newRowDataList: Array = []; let curRowNo: string | null = null; if (type === 'setCell' && (typeof _data === 'string' || _data === null)) { const [colId, rowId] = editingCell!; curRowNo = rowId; newTableData.forEach((item) => { if (item[colNoCode] === rowId) { item[colId] = _data; newRowDataList = Object.keys(item).map((i) => item[i]); } }); } if (type === 'setRow' && Array.isArray(_data) && curOperationRowNo) { curRowNo = curOperationRowNo[0]; _data.unshift(curOperationRowNo[0]); newTableData.forEach((t) => { if (t[colNoCode] === curOperationRowNo[0]) { const dataLength = Object.keys(t).length; Object.keys(t).forEach((item, index) => { if (index > dataLength) return; t[item] = _data[index] || null; }); return; } }); newRowDataList = _data; } setTableData(newTableData); oldDataList.forEach((item) => { if (item[0] === curRowNo) { oldRowDataList = item; } }); const index = updateData.findIndex((item) => item.rowId === curRowNo); // 如果newRowDataList和oldRowDataList的数据一样,代表用户虽然编辑过,但是又改回去了,则不需要更新 if (oldRowDataList?.join(',') === newRowDataList?.join(',')) { if (index !== -1) { setUpdateData(updateData.filter((item) => item.rowId !== curRowNo && item.type !== CRUD.UPDATE)); } return; } if (index === -1) { setUpdateData([ ...updateData, { type: CRUD.UPDATE, oldDataList: oldRowDataList, dataList: newRowDataList, rowId: curRowNo!, }, ]); return; } const newRowUpdateData = { ...updateData[index], dataList: newRowDataList, }; // 如果是删除过的,则需要把type改为update if (newRowUpdateData.type === CRUD.DELETE) { newRowUpdateData.type = CRUD.UPDATE; } updateData[index] = newRowUpdateData; setUpdateData([...updateData]); }; // 处理创建数据 const handleCreateData = (_newData?: { [key in string]: any }[]) => { // 正常的新增 const newTableData = lodash.cloneDeep(tableData); let newData: { [key in string]: any }[] = [{}]; if (_newData) { newData = _newData; } else { newData.forEach((newDataItem, index) => { columns.forEach((t, i) => { if (t.name === 'No.') { newDataItem[`${preCode}${i}${t.name}`] = (newTableData.length + index + 1).toString(); } else { // 判断是否有默认值 const hasDefaultValue = queryResultData.headerList.find((item) => item.name === t.name)?.defaultValue !== null; if (hasDefaultValue) { newDataItem[`${preCode}${i}${t.name}`] = USER_FILLED_VALUE.DEFAULT; return; } newDataItem[`${preCode}${i}${t.name}`] = null; } }); }); } setTableData(newTableData.concat(newData)); const newUpdateData = newData.map((item, index) => { return { type: CRUD.CREATE, dataList: Object.keys(item).map((i) => item[i]), rowId: (newTableData.length + index + 1).toString(), }; }); setUpdateData([...updateData, ...newUpdateData]); setCurOperationRowNo([(newTableData.length + 1).toString()]); setEditingCell(null); // 新增一条数据,tableBox需要滚动到最下方 setTimeout(() => { tableBoxRef.current?.scrollTo(0, tableBoxRef.current?.scrollHeight + 31); }, 0); }; // 处理删除数据 const handleDeleteData = () => { if (!curOperationRowNo && !editingCell) { return; } const rowIds = curOperationRowNo || [editingCell![1]]; const needDeleteNewData:any = []; const needRecoverData:any = []; const needDeleteData:any = []; let _updateData = lodash.cloneDeep(updateData); let _tableData = lodash.cloneDeep(tableData); rowIds.forEach((rowId: string) => { let flag = false _updateData.forEach((item) => { if (item.rowId === rowId) { if (item.type === CRUD.CREATE) { flag = true needDeleteNewData.push(rowId); } else if(item.type === CRUD.UPDATE){ flag = true needRecoverData.push(rowId); } } }); if(!flag) { needDeleteData.push(rowId); } }); // 删除新增的行 needDeleteNewData.forEach((rowId) => { _updateData = _updateData.filter((item) => item.rowId !== rowId); _tableData = _tableData.filter((item) => item[colNoCode] !== rowId); }); // 删除编辑的行,需要把这一行的数据恢复 needRecoverData.forEach((rowId) => { const oldRowDataList = _updateData.find((item) => item.rowId === rowId)?.oldDataList; _tableData = _tableData.map((item) => { if (item[colNoCode] === rowId) { return oldTableData.find((i) => item[colNoCode] === i[colNoCode])!; } return item; }) const index = _updateData.findIndex((item) => item.rowId === rowId); _updateData[index] = { ..._updateData[index], type: CRUD.DELETE, oldDataList: oldRowDataList, }; }); needDeleteData.forEach((rowId) => { const oldRowData = oldDataList.find((item) => item[0] === rowId); _updateData.push({ type: CRUD.DELETE, oldDataList: Object.keys(oldRowData!).map((i) => oldRowData![i]), rowId, }) }); setUpdateData(_updateData); setTableData(_tableData); setEditingCell(null); setCurOperationRowNo(null); }; return { updateTableData, handleCreateData, handleDeleteData, }; }; export default useCurdTableData; ================================================ FILE: chat2db-client/src/components/SearchResult/hooks/useMultipleSelect.ts ================================================ import { useEffect, useState, useRef, useCallback } from 'react'; import lodash from 'lodash'; // 多选行 hooks const useMultipleSelect = (props: { curOperationRowNo: Array | null; setCurOperationRowNo: (rowNo: Array | null) => void; tableData: { [key: string]: string | null }[]; colNoCode: string; setFocusedContent: (content: any[][]) => void; }) => { const { curOperationRowNo, setCurOperationRowNo, tableData, colNoCode,setFocusedContent } = props; // 是否按下了shift键 const isShiftDownRef = useRef(false); // 是否按下了cmd键 const isCmdDownRef = useRef(false); // 如果用useState,因为异步会导致第一次点击失效 // const [isShiftDown, setIsShiftDown] = useState(false); // 第一次选中的行号,用于判断是否是连续选中 const [firstOperationRowNo, setFirstOperationRowNo] = useState(null); useEffect(()=>{ if(!curOperationRowNo){ setFirstOperationRowNo(null) } },[curOperationRowNo]) useEffect(() => { const handleKeyDown = (event) => { if (event.keyCode === 16) { isShiftDownRef.current = true; } if (event.keyCode === 91 || event.keyCode === 17) { isCmdDownRef.current = true; } }; const handleKeyUp = (event) => { if (event.keyCode === 16) { isShiftDownRef.current = false; } if (event.keyCode === 91) { isCmdDownRef.current = false; } }; document.addEventListener('keydown', handleKeyDown); document.addEventListener('keyup', handleKeyUp); return () => { document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keyup', handleKeyUp); }; }, []); const copyTableRowData = (rowIds)=>{ const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); delete _item[colNoCode]; return Object.keys(_item).map((i) => _item[i]); }); setFocusedContent(newRowDatasList); } const multipleSelect = useCallback( (newClickRowNo: string | null) => { if (newClickRowNo === null) { setCurOperationRowNo(null); return; } if (isShiftDownRef.current && firstOperationRowNo) { // 1. 在tableData中找到firstOperationRowNo所在的index const firstOperationRowIndex = tableData.findIndex((item) => item[colNoCode] === firstOperationRowNo); // 2. 在tableData中找到newClickRowNo所在的index const newClickRowIndex = tableData.findIndex((item) => item[colNoCode] === newClickRowNo); // 3. 从table中截取firstOperationRowIndex到newClickRowIndex的数据 // 前序号 let front = 0; // 后序号 let back = 0; if (firstOperationRowIndex < newClickRowIndex) { front = firstOperationRowIndex; back = newClickRowIndex; } else { front = newClickRowIndex; back = firstOperationRowIndex; } const newCurOperationRowNo = tableData.slice(front, back + 1).map((item) => item[colNoCode]!); setCurOperationRowNo(newCurOperationRowNo); copyTableRowData(newCurOperationRowNo) return; } if (isCmdDownRef.current) { // 如果是cmd键,就是多选 if (curOperationRowNo) { if (curOperationRowNo.includes(newClickRowNo)) { // 如果已经选中了,就取消选中 const newCurOperationRowNo = curOperationRowNo.filter((item) => item !== newClickRowNo); setCurOperationRowNo(newCurOperationRowNo); copyTableRowData(newCurOperationRowNo) return; } // 如果没有选中,就添加选中 const newCurOperationRowNo = [...curOperationRowNo, newClickRowNo]; setCurOperationRowNo(newCurOperationRowNo); copyTableRowData(newCurOperationRowNo) return; } // 如果没有选中,就添加选中 setCurOperationRowNo([newClickRowNo]); copyTableRowData([newClickRowNo]) return; } setFirstOperationRowNo(newClickRowNo); setCurOperationRowNo([newClickRowNo]); copyTableRowData([newClickRowNo]) }, [isShiftDownRef.current, firstOperationRowNo,curOperationRowNo], ); return { multipleSelect }; }; export default useMultipleSelect; ================================================ FILE: chat2db-client/src/components/SearchResult/hooks/usePasteData.ts ================================================ import { useEffect, useState } from 'react'; import { clipboardToArray } from '@/utils'; interface IUsePasteDataRelyData { curOperationRowNo: Array | null; editingCell; updateTableData; } // 处理粘贴的数据 hooks const usePasteData = (props: IUsePasteDataRelyData) => { const { curOperationRowNo, editingCell, updateTableData } = props; const [canPaste, setCanPaste] = useState(false); // 判断当前是否可以粘贴 useEffect(() => { const handleClick = (event) => { const targetElement = event.target as Element; if (targetElement.closest('[data-chat2db-edit-table-data-can-paste]')) { setCanPaste(true); } else { setCanPaste(false); } }; document.addEventListener('click', handleClick); document.addEventListener('contextmenu', handleClick); return () => { document.removeEventListener('click', handleClick); document.removeEventListener('contextmenu', handleClick); }; }, []); // 读取剪切板数据,更新表格数据 useEffect(() => { const handleCopy = () => { if (curOperationRowNo) { navigator.clipboard .readText() .then((text) => { const array2D = clipboardToArray(text); updateTableData('setRow', array2D[0]); }) .catch((err) => { console.error('Failed to read clipboard contents: ', err); }); } if (editingCell && editingCell[2] === false) { navigator.clipboard .readText() .then((text) => { updateTableData('setCell', text); }) .catch((err) => { console.error('Failed to read clipboard contents: ', err); }); } }; if (canPaste) { document.addEventListener('paste', handleCopy); } else { document.removeEventListener('paste', handleCopy); } return () => { document.removeEventListener('paste', handleCopy); }; }, [curOperationRowNo, editingCell, canPaste]); }; export default usePasteData; ================================================ FILE: chat2db-client/src/components/SearchResult/index.less ================================================ @import '../../styles/var.less'; .searchResult { height: 100%; display: flex; flex-direction: column; position: relative; } .tabs { height: 100%; } .recordIcon { font-size: 16px; margin-right: 4px; } .statusIcon { margin-right: 6px; font-size: 12px; } .successIcon { color: var(--color-primary); } .failIcon { color: var(--color-primary); } .tableIndex { width: 50px; } .monacoEditor { height: 300px; } .monacoEditor { margin: -15px; } .cursorStateIndicator { margin: 0 auto; max-width: 80%; display: flex; align-items: center; justify-content: center; } .noData { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 12px; overflow: hidden; } .outputPrefixIcon { margin-right: 4px; } .stateIndicator { width: 70%; margin: 0 auto; overflow: hidden; } .updateCountBox { height: 100%; } .successResult { height: 100%; .successResultContent { height: 100%; } .updateCount { height: calc(100% - 26px); display: flex; justify-content: center; align-items: center; } } .tableLoading { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; // height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; // background-color: var(--color-fill); .stopExecuteSql { cursor: pointer; margin-top: 30px; &:hover { color: var(--color-primary); } } } ================================================ FILE: chat2db-client/src/components/SearchResult/index.tsx ================================================ import React, { useCallback, useEffect, useMemo, useState, useRef, forwardRef, ForwardedRef, useImperativeHandle, Fragment, createContext, } from 'react'; import classnames from 'classnames'; import Tabs, { ITabItem } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; // import Output from '@/components/Output'; import { IManageResultData, IResultConfig } from '@/typings'; import TableBox from './components/TableBox'; import StatusBar from './components/StatusBar'; import styles from './index.less'; import EmptyImg from '@/assets/img/empty.svg'; import i18n from '@/i18n'; import sqlServer, { IExecuteSqlParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; import { Spin } from 'antd'; interface IProps { className?: string; sql?: string; executeSqlParams: any; concealTabHeader?: boolean; viewTable?: boolean; isActive?: boolean; } const defaultResultConfig: IResultConfig = { pageNo: 1, pageSize: 200, total: 0, hasNextPage: true, }; export interface ISearchResultRef { handleExecuteSQL: (sql: string) => void; } interface IContext { // 这里不用ref的话,会导致切换时闪动 activeTabId: string; notChangedSql: string; } export const Context = createContext({} as any); export default forwardRef((props: IProps, ref: ForwardedRef) => { const { className, sql, executeSqlParams, concealTabHeader, viewTable, isActive } = props; const [resultDataList, setResultDataList] = useState(); const [tableLoading, setTableLoading] = useState(false); const controllerRef = useRef(); const [activeTabId, setActiveTabId] = useState(''); const [notChangedSql, setNotChangedSql] = useState(''); useEffect(() => { if (sql) { handleExecuteSQL(sql); } }, [sql]); useImperativeHandle(ref, () => ({ handleExecuteSQL, })); /** * 执行SQL * @param sql */ const handleExecuteSQL = (_sql: string) => { setTableLoading(true); const api = viewTable ? sqlServer.viewTable : sqlServer.executeSql; const executeSQLParams: IExecuteSqlParams = { sql: _sql, tableName: executeSqlParams?.tableName, ...defaultResultConfig, ...executeSqlParams, type: executeSqlParams.databaseType, // 兼容写法,希望后端可以统一把type改成databaseType }; controllerRef.current = new AbortController(); // 获取当前SQL的查询结果 api(executeSQLParams, { signal: controllerRef.current.signal, }) .then((res) => { const sqlResult = res.map((_res) => ({ ..._res, uuid: uuidV4(), })); setResultDataList(sqlResult); if(!notChangedSql){ setNotChangedSql(_sql); } }) .finally(() => { setTableLoading(false); }); }; const onChange = useCallback((uuid) => { // activeTabIdRef.current = uuid; setActiveTabId(uuid); }, []); const renderResult = (queryResultData) => { function renderSuccessResult() { const needTable = queryResultData?.headerList?.length > 1; return (
{needTable ? ( ) : (
{i18n('common.text.affectedRows', queryResultData.updateCount)}
)}
); } return ( {queryResultData.success ? ( renderSuccessResult() ) : ( )} ); }; const tabsList = useMemo(() => { return resultDataList?.map((queryResultData, index) => { return { prefixIcon: ( ), popover: queryResultData.originalSql, label: i18n('common.text.executionResult', index + 1), key: queryResultData.uuid!, children: renderResult(queryResultData), }; }); }, [resultDataList, isActive]); const onEdit = useCallback( (type: 'add' | 'remove', data: ITabItem[]) => { if (type === 'remove') { const newResultDataList = resultDataList?.filter((d) => { return data.findIndex((item) => item.key === d.uuid) === -1; }); setResultDataList(newResultDataList); } }, [resultDataList], ); const stopExecuteSql = () => { controllerRef.current && controllerRef.current.abort(); setResultDataList([]); setTableLoading(false); }; return (
{tableLoading ? (
{i18n('common.button.cancelRequest')}
) : ( <> {tabsList?.length ? ( ) : (

{i18n('common.text.noData')}

)} )}
); }); ================================================ FILE: chat2db-client/src/components/SearchResult/utils.tsx ================================================ import { USER_FILLED_VALUE } from './components/TableBox/index'; // 在input中把USER_FILLED_VALUE转换为null export const transformInputValue = (value: string) => { if (value === USER_FILLED_VALUE.DEFAULT) { return null; } return value; }; ================================================ FILE: chat2db-client/src/components/ShortcutKey/index.less ================================================ @import '../../styles/var.less'; .box { display: flex; flex-direction: column; justify-content: center; align-items: center; .letterpress{ display: flex; justify-content: center; align-items: center; font-size: 80px; font-weight: 900; color: var(--color-text); opacity: 0.15; overflow: hidden; margin-bottom: 30px; } .shortcutsItem{ display: flex; justify-content: center; font-size: 14px; margin: 10px 0px; color: var(--color-text-tertiary); .title{ width: 200px; text-align: right; margin-right: 10px; } .plusSignBox{ width: 200px; } .plusSign{ margin: 0px 4px; } } } ================================================ FILE: chat2db-client/src/components/ShortcutKey/index.tsx ================================================ import React, { memo, Fragment } from 'react'; import i18n from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; import { osNow } from '@/utils'; interface IProps { className?: string; slot: any; } const keyboardKey = (function () { if (osNow().isMac) { return { command: 'Cmd', Shift: 'Shift', }; } return { command: 'Ctrl', Shift: 'Shift', }; })(); const shortcutsList = [ { title: i18n('common.text.textToSQL'), keys: ['Enter'], }, { title: i18n('common.text.optimizeSQL'), keys: [i18n('common.text.editorRightClick')], }, { title: i18n('common.text.executeSelectedSQL'), keys: [keyboardKey.command, 'R'], }, { title: i18n('common.text.saveConsole'), keys: [keyboardKey.command, 'S'], }, { title: i18n('common.button.createConsole'), keys: [keyboardKey.command, keyboardKey.Shift, 'L'], }, ]; export default memo((props) => { const { className, slot } = props; return (
Chat2DB
{shortcutsList.map((t, i) => { return (
{t.title}
{t.keys.map((item, index) => { return ( {item} {index + 1 < t.keys.length && +} ); })}
); })}
{slot()}
); }); ================================================ FILE: chat2db-client/src/components/SingleFileMonacoEditor/index.less ================================================ @import '../../styles/var.less'; .singleFileMonacoEditor { height: 18px; } ================================================ FILE: chat2db-client/src/components/SingleFileMonacoEditor/index.tsx ================================================ import React, { memo, useCallback, useMemo, ForwardedRef, forwardRef, useImperativeHandle, useRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import MonacoEditor, { IExportRefFunction } from '@/components/MonacoEditor'; import { v4 as uuid } from 'uuid'; interface IProps { className?: string; handelEnter?: (value: string) => void; focusChange?: (isActive: boolean) => void; ref: any; // ref不是写在这里吧??? } export interface ISingleFileMonacoEditorRefFunction { getAllContent?: () => string; } const options = { lineNumbers: false, renderLineHighlight: 'none', scrollBeyondLastLine: false, wordWrap: 'off', minimap: { enabled: false, }, // 不显示滚动条 scrollbar: { vertical: 'hidden', horizontal: 'hidden', }, overviewRulerBorder: false, glyphMargin: false, folding: false, lineDecorationsWidth: 0, // 行号宽度 lineNumbersMinChars: 0, // 行号最小宽度 }; const SingleFileMonacoEditor = memo( forwardRef((props, ref: ForwardedRef) => { const { className, handelEnter, focusChange } = props; const editorRef = useRef(null); const monacoEditorRef = useRef(null); const editorId = useMemo(() => { return uuid(); }, []); const handleKeydown = useCallback((event) => { if (event.key === 'Enter' && editorRef.current) { const controller = editorRef.current.getContribution('editor.contrib.suggestController') as any; const suggestWidget = controller._widget; if (suggestWidget && suggestWidget.suggestWidgetVisible.get()) { return; } // 否则,阻止回车键的默认行为 event.preventDefault(); const value = monacoEditorRef.current?.getAllContent().trim() || ''; handelEnter && handelEnter(value); } }, []); // 监听keydown事件,阻止回车键的默认行为 const registerShortcutKey = useCallback((_editor, _monaco, isActive) => { if (isActive) { editorRef.current = _editor; window.addEventListener('keydown', handleKeydown); } else { window.removeEventListener('keydown', handleKeydown); } }, []); const getAllContent = () => { return monacoEditorRef.current?.getAllContent() || ''; }; useImperativeHandle(ref, () => ({ getAllContent, })); return (
); }), ); export default SingleFileMonacoEditor; ================================================ FILE: chat2db-client/src/components/StateIndicator/index.less ================================================ @import '../../styles/var.less'; .box { height: 100%; display: flex; justify-content: center; align-items: center; } .empty { // width: 200px; // height: 200px; // background-image: url('../../assets/no-data.png'); background-size: cover; background-repeat: no-repeat; } .errorBox { display: flex; flex-direction: column; justify-content: center; align-items: center; } .error { width: 200px; height: 200px; // background-image: url('../../assets/error.png'); background-size: cover; background-repeat: no-repeat; } .errorText { font-size: 14px; color: var(--color-error); text-align: center; transform: translateY(-20px); white-space: pre-wrap; } .successBox { .errorBox { display: flex; flex-direction: column; justify-content: center; align-items: center; } // .success { // width: 200px; // height: 200px; // background-image: url('../../assets/error.png'); // background-size: cover; // background-repeat: no-repeat; // } .successText { font-size: 14px; color: var(--success-color); text-align: center; transform: translateY(-20px); } } ================================================ FILE: chat2db-client/src/components/StateIndicator/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Spin } from 'antd'; interface IProps { className?: string; state: 'loading' | 'empty' | 'error' | 'success'; text?: string; image?: boolean; } export const enum State { LOADING = 'loading', EMPTY = 'empty', ERROR = 'error', SUCCESS = 'success', } const config = { loading: { icon: '\ue6cd;', }, empty: { icon: '\ue760', }, error: { icon: '\ue755', }, success: { icon: '\ue62e', }, }; const StateIndicator = ({ className, state, text, image = false }: IProps) => { const renderState = () => { switch (state) { case 'loading': return ; case 'error': return (
{image &&
}
{text}
); case 'success': return (
{image &&
}
{text}
); default: return
; } }; return
{renderState()}
; }; export default memo(StateIndicator); ================================================ FILE: chat2db-client/src/components/Tabs/index.less ================================================ @import '../../styles/var.less'; .tab-focus() { background-color: var(--color-bg-subtle); // 添加内阴影 box-shadow: inset 0px -1px 0px var(--color-primary); .icon { opacity: 1; } } .tab-focus-line() { color: var(--color-primary); background-color: var(--color-bg-subtle); .icon { display: flex; } } .tabBox { display: flex; flex-direction: column; } .tabsNav { display: flex; overflow-x: hidden; position: relative; height: 32px; flex-shrink: 0; background-color: var(--color-bg-base); border-bottom: 1px solid var(--color-border); &:hover { overflow-x: scroll; } &::-webkit-scrollbar { display: none; } } .tabsContent { flex: 1; height: 0px; .tabsContentItem { height: 100%; width: 100%; display: none; position: relative; } .tabsContentItemActive { display: block; } } .activeContent { display: block; } .tabList { display: flex; } .tabItem { position: relative; display: flex; align-items: center; padding-left: 10px; line-height: 32px; height: 32px; cursor: pointer; user-select: none; border-right: 1px solid var(--color-border); &:last-child { border-right: 0; } .textBox { flex: 1; display: flex; align-items: center; } .text { flex: 1; width: fit-content; .f-single-line(); } .icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0px 4px; border-radius: 4px; cursor: pointer; color: var(--color-text-secondary); opacity: 0; i { font-size: 12px; } &:hover { color: var(--color-primary); background-color: var(--color-hover-bg); } } &:hover { // .tab-focus(); color: var(--color-primary); .icon { opacity: 1; } } } .activeTab { .tab-focus(); } .rightBox { // flex: 1; position: sticky; right: 0; top: 0; bottom: 0; background-color: var(--color-bg-base); // box-shadow: -1px 0 4px rgba(0, 0, 0, 0.1); display: flex; } .moreTabs { width: 30px; height: 100%; display: flex; justify-content: center; align-items: center; cursor: pointer; border-left: 1px solid var(--color-border); &:hover { color: var(--color-primary); } } .addIcon { width: 30px; height: 100%; display: flex; justify-content: center; align-items: center; cursor: pointer; border-left: 1px solid var(--color-border); &:hover { color: var(--color-primary); } } .addIconDisabled { color: var(--color-text-disabled); cursor: not-allowed; &:hover { color: var(--color-text-disabled); } } .input { border: 0; width: 200px; flex: 1; height: 20px; outline: none; font-size: 12px; font-weight: 400; background-color: var(--color-bg-subtle); color: var(--color-text); input:focus { outline: none; } } .prefixIcon { flex-shrink: 0; margin-right: 4px; } ================================================ FILE: chat2db-client/src/components/Tabs/index.tsx ================================================ import React, { memo, useEffect, useState, useRef } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import { Popover, Dropdown } from 'antd'; import i18n from '@/i18n'; import { isValid } from '@/utils/check'; import _ from 'lodash'; import styles from './index.less'; export interface ITabItem { prefixIcon?: string | React.ReactNode; label: React.ReactNode; key: number | string; popover?: string | React.ReactNode; children?: React.ReactNode; editableName?: boolean; canClosed?: boolean; styles?: React.CSSProperties; } export interface IOnchangeProps { type: 'add' | 'delete' | 'switch'; data?: ITabItem; } const MAX_TABS = 20; interface IProps { className?: string; items?: ITabItem[]; activeKey?: number | string | null; onChange?: (key: string | number | null) => void; onEdit?: (action: 'add' | 'remove', data?: ITabItem[], list?: ITabItem[]) => void; hideAdd?: boolean; editableNameOnBlur?: (option: ITabItem) => void; concealTabHeader?: boolean; // 最后一个tab不能关闭 lastTabCannotClosed?: boolean; destroyInactiveTabPane?: boolean; } export default memo((props) => { const { className, items, onChange, onEdit, activeKey, hideAdd, lastTabCannotClosed, editableNameOnBlur, concealTabHeader, destroyInactiveTabPane = false, } = props; const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(null); const [editingTab, setEditingTab] = useState(); const tabListBoxRef = useRef(null); const tabsNavRef = useRef(null); const isNumberKey = useRef(false); const [showMoreTabs, setShowMoreTabs] = useState(false); useEffect(() => { if (isValid(activeKey)) { setInternalActiveTab(activeKey!); } isNumberKey.current = typeof activeKey === 'number'; }, [activeKey]); useEffect(() => { setInternalTabs(items || []); if (items?.length && !isValid(internalActiveTab)) { setInternalActiveTab(items[0]?.key); } }, [items]); useEffect(() => { const fn = (e) => { if (e.deltaY) { e.preventDefault(); // 鼠标滚轮事件,让tab可以横向滚动 if (tabsNavRef.current) { tabsNavRef.current.scrollLeft -= e.deltaY; } } }; tabsNavRef.current?.addEventListener('wheel', fn); return () => { tabsNavRef.current?.removeEventListener('wheel', fn); }; }, []); useEffect(() => { onChange?.(internalActiveTab); // 聚焦的时候,聚焦的tab要在第一个 if (tabListBoxRef.current) { const activeTab = tabListBoxRef.current.querySelector(`.${styles.activeTab}`); if (activeTab) { activeTab.scrollIntoView({ block: 'nearest' }); } } }, [internalActiveTab]); useEffect(() => { // from copilot if (tabListBoxRef.current) { const tabsNavWidth = tabsNavRef.current?.getBoundingClientRect().width || 0; const tabListBoxWidth = tabListBoxRef.current?.getBoundingClientRect().width || 0; setShowMoreTabs(tabsNavWidth < tabListBoxWidth); } }, [internalTabs]); const deleteTab = (data: ITabItem) => { const newInternalTabs = internalTabs?.filter((t) => t.key !== data.key); let activeKeyTemp = internalActiveTab; // 删掉的是当前激活的tab,那么就切换到前一个,如果前一个没有就切换到后一个 if (data.key === internalActiveTab) { const index = internalTabs.findIndex((t) => t.key === data.key); if (index === 0) { activeKeyTemp = internalTabs[1]?.key; } else { activeKeyTemp = internalTabs[index - 1]?.key; } } changeTab(activeKeyTemp); setInternalTabs(newInternalTabs); onEdit?.('remove', [data], newInternalTabs); }; const deleteOtherTab = (data: ITabItem) => { const newInternalTabs = internalTabs?.filter((t) => t.key === data.key); const deleteTabs = internalTabs?.filter((t) => t.key !== data.key); changeTab(data.key); setInternalTabs(newInternalTabs); onEdit?.('remove', deleteTabs, newInternalTabs); }; // 关闭所有tab const deleteAllTab = () => { changeTab(null); setInternalTabs([]); onEdit?.('remove', [...internalTabs]); }; const changeTab = (key: string | number | null) => { setInternalActiveTab(key); }; const handleAdd = () => { if (internalTabs.length >= MAX_TABS) { return; } onEdit?.('add'); }; const onDoubleClick = (t: ITabItem) => { if (t.editableName) { setEditingTab(t.key); } }; const renderTabItem = (t: ITabItem, index: number) => { function inputOnChange(value: string) { internalTabs[index].label = value; setInternalTabs([...internalTabs]); } function onBlur() { editableNameOnBlur?.(t); setEditingTab(undefined); } function showClosed() { if (lastTabCannotClosed && internalTabs.length === 1) { return false; } if (t.canClosed === true) { return false; } return true; } const closeTabsMenu = [ { label: i18n('common.button.close'), key: 'close', onClick: () => { deleteTab(t); }, }, { label: i18n('common.button.closeOthers'), key: 'closeOther', onClick: () => { deleteOtherTab(t); }, }, { label: i18n('common.button.closeAll'), key: 'closeAll', onClick: () => { deleteAllTab(); }, }, ]; return (
{ onDoubleClick(t); }} style={t.styles} className={classnames(styles.tabItem, { [styles.activeTab]: t.key === internalActiveTab })} > {t.key === editingTab ? ( { inputOnChange(e.target.value); }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> ) : (
{t.prefixIcon && (typeof t.prefixIcon == 'string' ? ( ) : ( t.prefixIcon ))}
{t.label}
)} {showClosed() && (
)}
); }; const moreTabsMenu = (internalTabs || []).map((t) => { return { label: t.label, key: t.key.toString(), value: t.key, }; }); return (
{!concealTabHeader && (
{!!internalTabs?.length && (
{internalTabs.map((t, index) => { return renderTabItem(t, index); })}
)} {
{showMoreTabs && (
{ const key = moreTabsMenu.find((t) => t?.key === v.key)?.value || null; changeTab(key); }, }} trigger={['click']} > e.preventDefault()}>
)} {!hideAdd && (
= MAX_TABS, })} onClick={handleAdd} >
)}
}
)} {/* 隐藏的方案 */} {!destroyInactiveTabPane ? (
{internalTabs?.map((t) => { return (
{t.children}
); })}
) : (
{internalTabs.find((t) => t.key === internalActiveTab)?.children}
)}
); }); ================================================ FILE: chat2db-client/src/components/UploadDriver/index.less ================================================ @import '../../styles/var.less'; .box { } ================================================ FILE: chat2db-client/src/components/UploadDriver/index.tsx ================================================ import React, { memo, useEffect, useState, useRef } from 'react'; import i18n from '@/i18n' import styles from './index.less'; import classnames from 'classnames'; import { Button, message, Upload, Form, Input } from 'antd'; import type { UploadProps } from 'antd'; import connectionService from '@/service/connection'; import { DatabaseTypeCode } from '@/constants' interface IProps { className?: string; databaseType: DatabaseTypeCode; formChange: Function; jdbcDriverClass: string | undefined; } export default memo(function UploadDriver(props) { const { className, databaseType = DatabaseTypeCode.MYSQL, formChange, jdbcDriverClass } = props; const [formData, setFormData] = useState({ dbType: databaseType, jdbcDriverClass: jdbcDriverClass, jdbcDriver: [] }); const uploadProps: UploadProps = { name: 'multipartFiles', action: `${window._BaseURL}/api/jdbc/driver/upload`, multiple: true, onChange(info) { if (info.file.percent === 100 && info.file?.response?.data?.[0]) { setFormData({ ...formData, jdbcDriver: [...(formData.jdbcDriver), info.file?.response?.data?.[0]] }) } }, accept: "application/java-archive" }; useEffect(() => { formChange(formData) }, [formData]) function onChange(e: any) { setFormData({ ...formData, jdbcDriverClass: e.target.value }) } return
}) ================================================ FILE: chat2db-client/src/components/ViewDDL/index.less ================================================ @import '../../styles/var.less'; .viewDDL { height: 100%; } ================================================ FILE: chat2db-client/src/components/ViewDDL/index.tsx ================================================ import React, { memo, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import MonacoEditor, { IExportRefFunction } from '@/components/MonacoEditor'; import { v4 as uuid } from 'uuid'; import sqlServer from '@/service/sql'; interface IProps { className?: string; data: any; } export default memo((props) => { const { className, data } = props; const [monacoEditorId] = React.useState(uuid()); const monacoEditorRef = React.useRef(null); const [sql,setSql] = React.useState(''); useEffect(() => { if(data){ sqlServer .exportCreateTableSql({ ...data, } as any) .then((res) => { setSql(res); }); } }, [data]); useEffect(() => { monacoEditorRef.current?.setValue(sql || '', 'reset'); }, [sql]); return (
); }); ================================================ FILE: chat2db-client/src/components/XXXX_FN/index.less ================================================ @import '../../styles/var.less'; .box { } ================================================ FILE: chat2db-client/src/components/XXXX_FN/index.tsx ================================================ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; return
demo
; }); ================================================ FILE: chat2db-client/src/constants/IntelliSense/index.ts ================================================ import mysql from './mysql'; import oracle from './oracle'; import postgresql from './pgsql'; import redis from './redis'; import sqlserver from './sqlserver' export default { mysql, oracle, postgresql, redis, sqlserver }; ================================================ FILE: chat2db-client/src/constants/IntelliSense/mysql.ts ================================================ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.MYSQL, keywords: [ 'ACCESSIBLE', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'BEFORE', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'CONDITION', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CUBE', 'CUME_DIST', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DENSE_RANK', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'EMPTY', 'ENCLOSED', 'ESCAPED', 'EXCEPT', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FIRST_VALUE', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'FUNCTION', 'GENERATED', 'GET', 'GRANT', 'GROUP', 'GROUPING', 'GROUPS', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERVAL', 'INTO', 'IO_AFTER_GTIDS', 'IO_BEFORE_GTIDS', 'IS', 'ITERATE', 'JOIN', 'JSON_TABLE', 'KEY', 'KEYS', 'KILL', 'LAG', 'LAST_VALUE', 'LATERAL', 'LEAD', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_BIND', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NOT', 'NO_WRITE_TO_BINLOG', 'NTH_VALUE', 'NTILE', 'NULL', 'NUMERIC', 'OF', 'ON', 'OPTIMIZE', 'OPTIMIZER_COSTS', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'OVER', 'PARTITION', 'PERCENT_RANK', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'RANGE', 'RANK', 'READ', 'READS', 'READ_WRITE', 'REAL', 'RECURSIVE', 'REFERENCES', 'REGEXP', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQL_NO_CACHE', 'SSL', 'STARTING', 'STORED', 'STRAIGHT_JOIN', 'SYSTEM', 'TABLE', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WINDOW', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', ], functions: [ 'ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_DECRYPT', 'AES_ENCRYPT', 'ANY_VALUE', 'Area', 'AsBinary', 'AsWKB', 'ASCII', 'ASIN', 'AsText', 'AsWKT', 'ASYMMETRIC_DECRYPT', 'ASYMMETRIC_DERIVE', 'ASYMMETRIC_ENCRYPT', 'ASYMMETRIC_SIGN', 'ASYMMETRIC_VERIFY', 'ATAN', 'ATAN2', 'ATAN', 'AVG', 'BENCHMARK', 'BIN', 'BIT_AND', 'BIT_COUNT', 'BIT_LENGTH', 'BIT_OR', 'BIT_XOR', 'Buffer', 'CAST', 'CEIL', 'CEILING', 'Centroid', 'CHAR', 'CHAR_LENGTH', 'CHARACTER_LENGTH', 'CHARSET', 'COALESCE', 'COERCIBILITY', 'COLLATION', 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONNECTION_ID', 'Contains', 'CONV', 'CONVERT', 'CONVERT_TZ', 'ConvexHull', 'COS', 'COT', 'COUNT', 'CRC32', 'CREATE_ASYMMETRIC_PRIV_KEY', 'CREATE_ASYMMETRIC_PUB_KEY', 'CREATE_DH_PARAMETERS', 'CREATE_DIGEST', 'Crosses', 'CUME_DIST', 'CURDATE', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURTIME', 'DATABASE', 'DATE', 'DATE_ADD', 'DATE_FORMAT', 'DATE_SUB', 'DATEDIFF', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'DECODE', 'DEFAULT', 'DEGREES', 'DES_DECRYPT', 'DES_ENCRYPT', 'DENSE_RANK', 'Dimension', 'Disjoint', 'Distance', 'ELT', 'ENCODE', 'ENCRYPT', 'EndPoint', 'Envelope', 'Equals', 'EXP', 'EXPORT_SET', 'ExteriorRing', 'EXTRACT', 'ExtractValue', 'FIELD', 'FIND_IN_SET', 'FIRST_VALUE', 'FLOOR', 'FORMAT', 'FORMAT_BYTES', 'FORMAT_PICO_TIME', 'FOUND_ROWS', 'FROM_BASE64', 'FROM_DAYS', 'FROM_UNIXTIME', 'GEN_RANGE', 'GEN_RND_EMAIL', 'GEN_RND_PAN', 'GEN_RND_SSN', 'GEN_RND_US_PHONE', 'GeomCollection', 'GeomCollFromText', 'GeometryCollectionFromText', 'GeomCollFromWKB', 'GeometryCollectionFromWKB', 'GeometryCollection', 'GeometryN', 'GeometryType', 'GeomFromText', 'GeometryFromText', 'GeomFromWKB', 'GeometryFromWKB', 'GET_FORMAT', 'GET_LOCK', 'GLength', 'GREATEST', 'GROUP_CONCAT', 'GROUPING', 'GTID_SUBSET', 'GTID_SUBTRACT', 'HEX', 'HOUR', 'ICU_VERSION', 'IF', 'IFNULL', 'INET_ATON', 'INET_NTOA', 'INET6_ATON', 'INET6_NTOA', 'INSERT', 'INSTR', 'InteriorRingN', 'Intersects', 'INTERVAL', 'IS_FREE_LOCK', 'IS_IPV4', 'IS_IPV4_COMPAT', 'IS_IPV4_MAPPED', 'IS_IPV6', 'IS_USED_LOCK', 'IS_UUID', 'IsClosed', 'IsEmpty', 'ISNULL', 'IsSimple', 'JSON_APPEND', 'JSON_ARRAY', 'JSON_ARRAY_APPEND', 'JSON_ARRAY_INSERT', 'JSON_ARRAYAGG', 'JSON_CONTAINS', 'JSON_CONTAINS_PATH', 'JSON_DEPTH', 'JSON_EXTRACT', 'JSON_INSERT', 'JSON_KEYS', 'JSON_LENGTH', 'JSON_MERGE', 'JSON_MERGE_PATCH', 'JSON_MERGE_PRESERVE', 'JSON_OBJECT', 'JSON_OBJECTAGG', 'JSON_OVERLAPS', 'JSON_PRETTY', 'JSON_QUOTE', 'JSON_REMOVE', 'JSON_REPLACE', 'JSON_SCHEMA_VALID', 'JSON_SCHEMA_VALIDATION_REPORT', 'JSON_SEARCH', 'JSON_SET', 'JSON_STORAGE_FREE', 'JSON_STORAGE_SIZE', 'JSON_TABLE', 'JSON_TYPE', 'JSON_UNQUOTE', 'JSON_VALID', 'LAG', 'LAST_DAY', 'LAST_INSERT_ID', 'LAST_VALUE', 'LCASE', 'LEAD', 'LEAST', 'LEFT', 'LENGTH', 'LineFromText', 'LineStringFromText', 'LineFromWKB', 'LineStringFromWKB', 'LineString', 'LN', 'LOAD_FILE', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATE', 'LOG', 'LOG10', 'LOG2', 'LOWER', 'LPAD', 'LTRIM', 'MAKE_SET', 'MAKEDATE', 'MAKETIME', 'MASK_INNER', 'MASK_OUTER', 'MASK_PAN', 'MASK_PAN_RELAXED', 'MASK_SSN', 'MASTER_POS_WAIT', 'MAX', 'MBRContains', 'MBRCoveredBy', 'MBRCovers', 'MBRDisjoint', 'MBREqual', 'MBREquals', 'MBRIntersects', 'MBROverlaps', 'MBRTouches', 'MBRWithin', 'MD5', 'MEMBER OF', 'MICROSECOND', 'MID', 'MIN', 'MINUTE', 'MLineFromText', 'MultiLineStringFromText', 'MLineFromWKB', 'MultiLineStringFromWKB', 'MOD', 'MONTH', 'MONTHNAME', 'MPointFromText', 'MultiPointFromText', 'MPointFromWKB', 'MultiPointFromWKB', 'MPolyFromText', 'MultiPolygonFromText', 'MPolyFromWKB', 'MultiPolygonFromWKB', 'MultiLineString', 'MultiPoint', 'MultiPolygon', 'NAME_CONST', 'NOT IN', 'NOW', 'NTH_VALUE', 'NTILE', 'NULLIF', 'NumGeometries', 'NumInteriorRings', 'NumPoints', 'OCT', 'OCTET_LENGTH', 'OLD_PASSWORD', 'ORD', 'Overlaps', 'PASSWORD', 'PERCENT_RANK', 'PERIOD_ADD', 'PERIOD_DIFF', 'PI', 'Point', 'PointFromText', 'PointFromWKB', 'PointN', 'PolyFromText', 'PolygonFromText', 'PolyFromWKB', 'PolygonFromWKB', 'Polygon', 'POSITION', 'POW', 'POWER', 'PS_CURRENT_THREAD_ID', 'PS_THREAD_ID', 'PROCEDURE ANALYSE', 'QUARTER', 'QUOTE', 'RADIANS', 'RAND', 'RANDOM_BYTES', 'RANK', 'REGEXP_INSTR', 'REGEXP_LIKE', 'REGEXP_REPLACE', 'REGEXP_REPLACE', 'RELEASE_ALL_LOCKS', 'RELEASE_LOCK', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'ROLES_GRAPHML', 'ROUND', 'ROW_COUNT', 'ROW_NUMBER', 'RPAD', 'RTRIM', 'SEC_TO_TIME', 'SECOND', 'SESSION_USER', 'SHA1', 'SHA', 'SHA2', 'SIGN', 'SIN', 'SLEEP', 'SOUNDEX', 'SOURCE_POS_WAIT', 'SPACE', 'SQRT', 'SRID', 'ST_Area', 'ST_AsBinary', 'ST_AsWKB', 'ST_AsGeoJSON', 'ST_AsText', 'ST_AsWKT', 'ST_Buffer', 'ST_Buffer_Strategy', 'ST_Centroid', 'ST_Collect', 'ST_Contains', 'ST_ConvexHull', 'ST_Crosses', 'ST_Difference', 'ST_Dimension', 'ST_Disjoint', 'ST_Distance', 'ST_Distance_Sphere', 'ST_EndPoint', 'ST_Envelope', 'ST_Equals', 'ST_ExteriorRing', 'ST_FrechetDistance', 'ST_GeoHash', 'ST_GeomCollFromText', 'ST_GeometryCollectionFromText', 'ST_GeomCollFromTxt', 'ST_GeomCollFromWKB', 'ST_GeometryCollectionFromWKB', 'ST_GeometryN', 'ST_GeometryType', 'ST_GeomFromGeoJSON', 'ST_GeomFromText', 'ST_GeometryFromText', 'ST_GeomFromWKB', 'ST_GeometryFromWKB', 'ST_HausdorffDistance', 'ST_InteriorRingN', 'ST_Intersection', 'ST_Intersects', 'ST_IsClosed', 'ST_IsEmpty', 'ST_IsSimple', 'ST_IsValid', 'ST_LatFromGeoHash', 'ST_Length', 'ST_LineFromText', 'ST_LineStringFromText', 'ST_LineFromWKB', 'ST_LineStringFromWKB', 'ST_LineInterpolatePoint', 'ST_LineInterpolatePoints', 'ST_LongFromGeoHash', 'ST_Longitude', 'ST_MakeEnvelope', 'ST_MLineFromText', 'ST_MultiLineStringFromText', 'ST_MLineFromWKB', 'ST_MultiLineStringFromWKB', 'ST_MPointFromText', 'ST_MultiPointFromText', 'ST_MPointFromWKB', 'ST_MultiPointFromWKB', 'ST_MPolyFromText', 'ST_MultiPolygonFromText', 'ST_MPolyFromWKB', 'ST_MultiPolygonFromWKB', 'ST_NumGeometries', 'ST_NumInteriorRing', 'ST_NumInteriorRings', 'ST_NumPoints', 'ST_Overlaps', 'ST_PointAtDistance', 'ST_PointFromGeoHash', 'ST_PointFromText', 'ST_PointFromWKB', 'ST_PointN', 'ST_PolyFromText', 'ST_PolygonFromText', 'ST_PolyFromWKB', 'ST_PolygonFromWKB', 'ST_Simplify', 'ST_SRID', 'ST_StartPoint', 'ST_SwapXY', 'ST_SymDifference', 'ST_Touches', 'ST_Transform', 'ST_Union', 'ST_Validate', 'ST_Within', 'ST_X', 'ST_Y', 'StartPoint', 'STATEMENT_DIGEST', 'STATEMENT_DIGEST_TEXT', 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 'STR_TO_DATE', 'STRCMP', 'SUBDATE', 'SUBSTR', 'SUBSTRING', 'SUBSTRING_INDEX', 'SUBTIME', 'SUM', 'SYSDATE', 'SYSTEM_USER', 'TAN', 'TIME', 'TIME_FORMAT', 'TIME_TO_SEC', 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TO_BASE64', 'TO_DAYS', 'TO_SECONDS', 'Touches', 'TRIM', 'TRUNCATE', 'UCASE', 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UNIX_TIMESTAMP', 'UpdateXML', 'UPPER', 'USER', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'UUID', 'UUID_SHORT', 'UUID_TO_BIN', 'VALIDATE_PASSWORD_STRENGTH', 'VALUES', 'VAR_POP', 'VAR_SAMP', 'VARIANCE', 'VERSION', 'WAIT_FOR_EXECUTED_GTID_SET', 'WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'WEIGHT_STRING', 'Within', 'X', 'Y', 'YEAR', 'YEARWEEK', ], }; ================================================ FILE: chat2db-client/src/constants/IntelliSense/oracle.ts ================================================ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.ORACLE, keywords: [ 'ACCESS', 'ADD', 'ALL', 'ALTER', 'AND', 'ANY', 'AS', 'ASC', 'AUDIT', 'BETWEEN', 'BY', 'CHAR', 'CHECK', 'CLUSTER', 'COLUMN', 'COMMENT', 'COMPRESS', 'CONNECT', 'CREATE', 'CURRENT', 'DATE', 'DECIMAL', 'DEFAULT', 'DELETE', 'DESC', 'DISTINCT', 'DROP', 'ELSE', 'EXCLUSIVE', 'EXISTS', 'FILE', 'FLOAT', 'FOR', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IDENTIFIED', 'IMMEDIATE', 'IN', 'INCREMENT', 'INDEX', 'INITIAL', 'INSERT', 'INTEGER', 'INTERSECT', 'INTO', 'IS', 'LEVEL', 'LIKE', 'LOCK', 'LONG', 'MAXEXTENTS', 'MINUS', 'MLSLABEL', 'MODE', 'MODIFY', 'NOAUDIT', 'NOCOMPRESS', 'NOT', 'NOWAIT', 'NULL', 'NUMBER', 'OF', 'OFFLINE', 'ON', 'ONLINE', 'OPTION', 'OR', 'ORDER', 'PCTFREE', 'PRIOR', 'PRIVILEGES', 'PUBLIC', 'RAW', 'RENAME', 'RESOURCE', 'REVOKE', 'ROW', 'ROWID', 'ROWNUM', 'ROWS', 'SELECT', 'SESSION', 'SET', 'SHARE', 'SIZE', 'SMALLINT', 'START', 'SUCCESSFUL', 'SYNONYM', 'SYSDATE', 'TABLE', 'THEN', 'TO', 'TRIGGER', 'UID', 'UNION', 'UNIQUE', 'UPDATE', 'USER', 'VALIDATE', 'VALUES', 'VARCHAR', 'VARCHAR2', 'VIEW', 'WHENEVER', 'WHERE', 'WITH', ], functions: [ 'CONNECT BY', 'START WITH', 'ORDER SIBLINGS BY', 'SIBLINGS', 'PRIOR', 'NOCYCLE', 'LEVEL', 'GROUP BY', 'GROUPING SETS', 'CUBE', 'ROLLUP', 'PIVOT', 'UNPIVOT', 'MODEL', 'DIMENSION', 'MEASURES', 'RULES', 'ITERATE', 'INCREMENT', 'PRESENTV', 'ABSENTV', 'SQLQUERY', 'ARRAY', 'VARRAY', 'ASSOCIATIVE ARRAY', 'RECORD', 'OBJECT', 'TABLE', 'VIEW', 'MATERIALIZED VIEW', 'CONTEXT', 'DIRECTORY', 'EDITION', 'Profile', 'ROLE', 'SEQUENCE', 'SYNONYM', 'INDEXTYPE', 'LIBRARY', 'OPERATOR', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'TYPE', 'TRIGGER', 'LINK', 'VIEW LOG', 'CONSTRAINT', 'CLUSTER', 'COMMENT', 'AUDIT', 'NOAUDIT', 'GRANT', 'REVOKE', 'FLASHBACK', 'PURGE', 'MERGE', 'ALTER SESSION', 'COMMIT', 'ROLLBACK', 'SAVEPOINT', 'SET TRANSACTION', 'LOCK', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE', 'SHARE', 'EXCLUSIVE', 'NOWAIT', 'SKIP LOCKED', 'PL/SQL', 'AUTONOMOUS_TRANSACTION', 'PRAGMA', 'EXCEPTION', 'SERIALLY_REUSABLE', ], }; ================================================ FILE: chat2db-client/src/constants/IntelliSense/pgsql.ts ================================================ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.POSTGRESQL, keywords: [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'AUTHORIZATION', 'BINARY', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLLATION', 'COLUMN', 'CONCURRENTLY', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_SCHEMA', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FREEZE', 'FROM', 'FULL', 'GRANT', 'GROUP', 'HAVING', 'ILIKE', 'IN', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'JOIN', 'LATERAL', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NATURAL', 'NOT', 'NOTNULL', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER', 'OUTER', 'OVERLAPS', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'RIGHT', 'SELECT', 'SESSION_USER', 'SIMILAR', 'SOME', 'SYMMETRIC', 'TABLE', 'TABLESAMPLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'VERBOSE', 'WHEN', 'WHERE', 'WINDOW', 'WITH', ], functions: [ 'abbrev', 'abs', 'acldefault', 'aclexplode', 'acos', 'acosd', 'acosh', 'age', 'any', 'area', 'array_agg', 'array_append', 'array_cat', 'array_dims', 'array_fill', 'array_length', 'array_lower', 'array_ndims', 'array_position', 'array_positions', 'array_prepend', 'array_remove', 'array_replace', 'array_to_json', 'array_to_string', 'array_to_tsvector', 'array_upper', 'ascii', 'asin', 'asind', 'asinh', 'atan', 'atan2', 'atan2d', 'atand', 'atanh', 'avg', 'bit', 'bit_and', 'bit_count', 'bit_length', 'bit_or', 'bit_xor', 'bool_and', 'bool_or', 'bound_box', 'box', 'brin_desummarize_range', 'brin_summarize_new_values', 'brin_summarize_range', 'broadcast', 'btrim', 'cardinality', 'cbrt', 'ceil', 'ceiling', 'center', 'char_length', 'character_length', 'chr', 'circle', 'clock_timestamp', 'coalesce', 'col_description', 'concat', 'concat_ws', 'convert', 'convert_from', 'convert_to', 'corr', 'cos', 'cosd', 'cosh', 'cot', 'cotd', 'count', 'covar_pop', 'covar_samp', 'cume_dist', 'current_catalog', 'current_database', 'current_date', 'current_query', 'current_role', 'current_schema', 'current_schemas', 'current_setting', 'current_time', 'current_timestamp', 'current_user', 'currval', 'cursor_to_xml', 'cursor_to_xmlschema', 'date_bin', 'date_part', 'date_trunc', 'database_to_xml', 'database_to_xml_and_xmlschema', 'database_to_xmlschema', 'decode', 'degrees', 'dense_rank', 'diagonal', 'diameter', 'div', 'encode', 'enum_first', 'enum_last', 'enum_range', 'every', 'exp', 'extract', 'factorial', 'family', 'first_value', 'floor', 'format', 'format_type', 'gcd', 'gen_random_uuid', 'generate_series', 'generate_subscripts', 'get_bit', 'get_byte', 'get_current_ts_config', 'gin_clean_pending_list', 'greatest', 'grouping', 'has_any_column_privilege', 'has_column_privilege', 'has_database_privilege', 'has_foreign_data_wrapper_privilege', 'has_function_privilege', 'has_language_privilege', 'has_schema_privilege', 'has_sequence_privilege', 'has_server_privilege', 'has_table_privilege', 'has_tablespace_privilege', 'has_type_privilege', 'height', 'host', 'hostmask', 'inet_client_addr', 'inet_client_port', 'inet_merge', 'inet_same_family', 'inet_server_addr', 'inet_server_port', 'initcap', 'isclosed', 'isempty', 'isfinite', 'isopen', 'json_agg', 'json_array_elements', 'json_array_elements_text', 'json_array_length', 'json_build_array', 'json_build_object', 'json_each', 'json_each_text', 'json_extract_path', 'json_extract_path_text', 'json_object', 'json_object_agg', 'json_object_keys', 'json_populate_record', 'json_populate_recordset', 'json_strip_nulls', 'json_to_record', 'json_to_recordset', 'json_to_tsvector', 'json_typeof', 'jsonb_agg', 'jsonb_array_elements', 'jsonb_array_elements_text', 'jsonb_array_length', 'jsonb_build_array', 'jsonb_build_object', 'jsonb_each', 'jsonb_each_text', 'jsonb_extract_path', 'jsonb_extract_path_text', 'jsonb_insert', 'jsonb_object', 'jsonb_object_agg', 'jsonb_object_keys', 'jsonb_path_exists', 'jsonb_path_match', 'jsonb_path_query', 'jsonb_path_query_array', 'jsonb_path_exists_tz', 'jsonb_path_query_first', 'jsonb_path_query_array_tz', 'jsonb_path_query_first_tz', 'jsonb_path_query_tz', 'jsonb_path_match_tz', 'jsonb_populate_record', 'jsonb_populate_recordset', 'jsonb_pretty', 'jsonb_set', 'jsonb_set_lax', 'jsonb_strip_nulls', 'jsonb_to_record', 'jsonb_to_recordset', 'jsonb_to_tsvector', 'jsonb_typeof', 'justify_days', 'justify_hours', 'justify_interval', 'lag', 'last_value', 'lastval', 'lcm', 'lead', 'least', 'left', 'length', 'line', 'ln', 'localtime', 'localtimestamp', 'log', 'log10', 'lower', 'lower_inc', 'lower_inf', 'lpad', 'lseg', 'ltrim', 'macaddr8_set7bit', 'make_date', 'make_interval', 'make_time', 'make_timestamp', 'make_timestamptz', 'makeaclitem', 'masklen', 'max', 'md5', 'min', 'min_scale', 'mod', 'mode', 'multirange', 'netmask', 'network', 'nextval', 'normalize', 'now', 'npoints', 'nth_value', 'ntile', 'nullif', 'num_nonnulls', 'num_nulls', 'numnode', 'obj_description', 'octet_length', 'overlay', 'parse_ident', 'path', 'pclose', 'percent_rank', 'percentile_cont', 'percentile_disc', 'pg_advisory_lock', 'pg_advisory_lock_shared', 'pg_advisory_unlock', 'pg_advisory_unlock_all', 'pg_advisory_unlock_shared', 'pg_advisory_xact_lock', 'pg_advisory_xact_lock_shared', 'pg_backend_pid', 'pg_backup_start_time', 'pg_blocking_pids', 'pg_cancel_backend', 'pg_client_encoding', 'pg_collation_actual_version', 'pg_collation_is_visible', 'pg_column_compression', 'pg_column_size', 'pg_conf_load_time', 'pg_control_checkpoint', 'pg_control_init', 'pg_control_recovery', 'pg_control_system', 'pg_conversion_is_visible', 'pg_copy_logical_replication_slot', 'pg_copy_physical_replication_slot', 'pg_create_logical_replication_slot', 'pg_create_physical_replication_slot', 'pg_create_restore_point', 'pg_current_logfile', 'pg_current_snapshot', 'pg_current_wal_flush_lsn', 'pg_current_wal_insert_lsn', 'pg_current_wal_lsn', 'pg_current_xact_id', 'pg_current_xact_id_if_assigned', 'pg_current_xlog_flush_location', 'pg_current_xlog_insert_location', 'pg_current_xlog_location', 'pg_database_size', 'pg_describe_object', 'pg_drop_replication_slot', 'pg_event_trigger_ddl_commands', 'pg_event_trigger_dropped_objects', 'pg_event_trigger_table_rewrite_oid', 'pg_event_trigger_table_rewrite_reason', 'pg_export_snapshot', 'pg_filenode_relation', 'pg_function_is_visible', 'pg_get_catalog_foreign_keys', 'pg_get_constraintdef', 'pg_get_expr', 'pg_get_function_arguments', 'pg_get_function_identity_arguments', 'pg_get_function_result', 'pg_get_functiondef', 'pg_get_indexdef', 'pg_get_keywords', 'pg_get_object_address', 'pg_get_owned_sequence', 'pg_get_ruledef', 'pg_get_serial_sequence', 'pg_get_statisticsobjdef', 'pg_get_triggerdef', 'pg_get_userbyid', 'pg_get_viewdef', 'pg_get_wal_replay_pause_state', 'pg_has_role', 'pg_identify_object', 'pg_identify_object_as_address', 'pg_import_system_collations', 'pg_index_column_has_property', 'pg_index_has_property', 'pg_indexam_has_property', 'pg_indexes_size', 'pg_is_in_backup', 'pg_is_in_recovery', 'pg_is_other_temp_schema', 'pg_is_wal_replay_paused', 'pg_is_xlog_replay_paused', 'pg_jit_available', 'pg_last_committed_xact', 'pg_last_wal_receive_lsn', 'pg_last_wal_replay_lsn', 'pg_last_xact_replay_timestamp', 'pg_last_xlog_receive_location', 'pg_last_xlog_replay_location', 'pg_listening_channels', 'pg_log_backend_memory_contexts', 'pg_logical_emit_message', 'pg_logical_slot_get_binary_changes', 'pg_logical_slot_get_changes', 'pg_logical_slot_peek_binary_changes', 'pg_logical_slot_peek_changes', 'pg_ls_archive_statusdir', 'pg_ls_dir', 'pg_ls_logdir', 'pg_ls_tmpdir', 'pg_ls_waldir', 'pg_mcv_list_items', 'pg_my_temp_schema', 'pg_notification_queue_usage', 'pg_opclass_is_visible', 'pg_operator_is_visible', 'pg_opfamily_is_visible', 'pg_options_to_table', 'pg_partition_ancestors', 'pg_partition_root', 'pg_partition_tree', 'pg_postmaster_start_time', 'pg_promote', 'pg_read_binary_file', 'pg_read_file', 'pg_relation_filenode', 'pg_relation_filepath', 'pg_relation_size', 'pg_reload_conf', 'pg_replication_origin_advance', 'pg_replication_origin_create', 'pg_replication_origin_drop', 'pg_replication_origin_oid', 'pg_replication_origin_progress', 'pg_replication_origin_session_is_setup', 'pg_replication_origin_session_progress', 'pg_replication_origin_session_reset', 'pg_replication_origin_session_setup', 'pg_replication_origin_xact_reset', 'pg_replication_origin_xact_setup', 'pg_replication_slot_advance', 'pg_rotate_logfile', 'pg_safe_snapshot_blocking_pids', 'pg_size_bytes', 'pg_size_pretty', 'pg_sleep', 'pg_sleep_for', 'pg_sleep_until', 'pg_snapshot_xip', 'pg_snapshot_xmax', 'pg_snapshot_xmin', 'pg_start_backup', 'pg_stat_file', 'pg_statistics_obj_is_visible', 'pg_stop_backup', 'pg_switch_wal', 'pg_switch_xlog', 'pg_table_is_visible', 'pg_table_size', 'pg_tablespace_databases', 'pg_tablespace_location', 'pg_tablespace_size', 'pg_terminate_backend', 'pg_total_relation_size', 'pg_trigger_depth', 'pg_try_advisory_lock', 'pg_try_advisory_lock_shared', 'pg_try_advisory_xact_lock', 'pg_try_advisory_xact_lock_shared', 'pg_ts_config_is_visible', 'pg_ts_dict_is_visible', 'pg_ts_parser_is_visible', 'pg_ts_template_is_visible', 'pg_type_is_visible', 'pg_typeof', 'pg_visible_in_snapshot', 'pg_wal_lsn_diff', 'pg_wal_replay_pause', 'pg_wal_replay_resume', 'pg_walfile_name', 'pg_walfile_name_offset', 'pg_xact_commit_timestamp', 'pg_xact_commit_timestamp_origin', 'pg_xact_status', 'pg_xlog_location_diff', 'pg_xlog_replay_pause', 'pg_xlog_replay_resume', 'pg_xlogfile_name', 'pg_xlogfile_name_offset', 'phraseto_tsquery', 'pi', 'plainto_tsquery', 'point', 'polygon', 'popen', 'position', 'power', 'pqserverversion', 'query_to_xml', 'query_to_xml_and_xmlschema', 'query_to_xmlschema', 'querytree', 'quote_ident', 'quote_literal', 'quote_nullable', 'radians', 'radius', 'random', 'range_agg', 'range_intersect_agg', 'range_merge', 'rank', 'regexp_match', 'regexp_matches', 'regexp_replace', 'regexp_split_to_array', 'regexp_split_to_table', 'regr_avgx', 'regr_avgy', 'regr_count', 'regr_intercept', 'regr_r2', 'regr_slope', 'regr_sxx', 'regr_sxy', 'regr_syy', 'repeat', 'replace', 'reverse', 'right', 'round', 'row_number', 'row_security_active', 'row_to_json', 'rpad', 'rtrim', 'scale', 'schema_to_xml', 'schema_to_xml_and_xmlschema', 'schema_to_xmlschema', 'session_user', 'set_bit', 'set_byte', 'set_config', 'set_masklen', 'setseed', 'setval', 'setweight', 'sha224', 'sha256', 'sha384', 'sha512', 'shobj_description', 'sign', 'sin', 'sind', 'sinh', 'slope', 'split_part', 'sprintf', 'sqrt', 'starts_with', 'statement_timestamp', 'stddev', 'stddev_pop', 'stddev_samp', 'string_agg', 'string_to_array', 'string_to_table', 'strip', 'strpos', 'substr', 'substring', 'sum', 'suppress_redundant_updates_trigger', 'table_to_xml', 'table_to_xml_and_xmlschema', 'table_to_xmlschema', 'tan', 'tand', 'tanh', 'text', 'timeofday', 'timezone', 'to_ascii', 'to_char', 'to_date', 'to_hex', 'to_json', 'to_number', 'to_regclass', 'to_regcollation', 'to_regnamespace', 'to_regoper', 'to_regoperator', 'to_regproc', 'to_regprocedure', 'to_regrole', 'to_regtype', 'to_timestamp', 'to_tsquery', 'to_tsvector', 'transaction_timestamp', 'translate', 'trim', 'trim_array', 'trim_scale', 'trunc', 'ts_debug', 'ts_delete', 'ts_filter', 'ts_headline', 'ts_lexize', 'ts_parse', 'ts_rank', 'ts_rank_cd', 'ts_rewrite', 'ts_stat', 'ts_token_type', 'tsquery_phrase', 'tsvector_to_array', 'tsvector_update_trigger', 'tsvector_update_trigger_column', 'txid_current', 'txid_current_if_assigned', 'txid_current_snapshot', 'txid_snapshot_xip', 'txid_snapshot_xmax', 'txid_snapshot_xmin', 'txid_status', 'txid_visible_in_snapshot', 'unistr', 'unnest', 'upper', 'upper_inc', 'upper_inf', 'user', 'var_pop', 'var_samp', 'variance', 'version', 'websearch_to_tsquery', 'width', 'width_bucket', 'xml_is_well_formed', 'xml_is_well_formed_content', 'xml_is_well_formed_document', 'xmlagg', 'xmlcomment', 'xmlconcat', 'xmlelement', 'xmlexists', 'xmlforest', 'xmlparse', 'xmlpi', 'xmlroot', 'xmlserialize', 'xpath', 'xpath_exists', ], }; ================================================ FILE: chat2db-client/src/constants/IntelliSense/redis.ts ================================================ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.REDIS, keywords: [ 'APPEND', 'AUTH', 'BGREWRITEAOF', 'BGSAVE', 'BITCOUNT', 'BITFIELD', 'BITOP', 'BITPOS', 'BLPOP', 'BRPOP', 'BRPOPLPUSH', 'CLIENT', 'KILL', 'LIST', 'GETNAME', 'PAUSE', 'REPLY', 'SETNAME', 'CLUSTER', 'ADDSLOTS', 'COUNT-FAILURE-REPORTS', 'COUNTKEYSINSLOT', 'DELSLOTS', 'FAILOVER', 'FORGET', 'GETKEYSINSLOT', 'INFO', 'KEYSLOT', 'MEET', 'NODES', 'REPLICATE', 'RESET', 'SAVECONFIG', 'SET-CONFIG-EPOCH', 'SETSLOT', 'SLAVES', 'SLOTS', 'COMMAND', 'COUNT', 'GETKEYS', 'CONFIG', 'GET', 'REWRITE', 'SET', 'RESETSTAT', 'DBSIZE', 'DEBUG', 'OBJECT', 'SEGFAULT', 'DECR', 'DECRBY', 'DEL', 'DISCARD', 'DUMP', 'ECHO', 'EVAL', 'EVALSHA', 'EXEC', 'EXISTS', 'EXPIRE', 'EXPIREAT', 'FLUSHALL', 'FLUSHDB', 'GEOADD', 'GEOHASH', 'GEOPOS', 'GEODIST', 'GEORADIUS', 'GEORADIUSBYMEMBER', 'GETBIT', 'GETRANGE', 'GETSET', 'HDEL', 'HEXISTS', 'HGET', 'HGETALL', 'HINCRBY', 'HINCRBYFLOAT', 'HKEYS', 'HLEN', 'HMGET', 'HMSET', 'HSET', 'HSETNX', 'HSTRLEN', 'HVALS', 'INCR', 'INCRBY', 'INCRBYFLOAT', 'KEYS', 'LASTSAVE', 'LINDEX', 'LINSERT', 'LLEN', 'LPOP', 'LPUSH', 'LPUSHX', 'LRANGE', 'LREM', 'LSET', 'LTRIM', 'MGET', 'MIGRATE', 'MONITOR', 'MOVE', 'MSET', 'MSETNX', 'MULTI', 'PERSIST', 'PEXPIRE', 'PEXPIREAT', 'PFADD', 'PFCOUNT', 'PFMERGE', 'PING', 'PSETEX', 'PSUBSCRIBE', 'PUBSUB', 'PTTL', 'PUBLISH', 'PUNSUBSCRIBE', 'QUIT', 'RANDOMKEY', 'READONLY', 'READWRITE', 'RENAME', 'RENAMENX', 'RESTORE', 'ROLE', 'RPOP', 'RPOPLPUSH', 'RPUSH', 'RPUSHX', 'SADD', 'SAVE', 'SCARD', 'SCRIPT', 'FLUSH', 'LOAD', 'SDIFF', 'SDIFFSTORE', 'SELECT', 'SETBIT', 'SETEX', 'SETNX', 'SETRANGE', 'SHUTDOWN', 'SINTER', 'SINTERSTORE', 'SISMEMBER', 'SLAVEOF', 'SLOWLOG', 'SMEMBERS', 'SMOVE', 'SORT', 'SPOP', 'SRANDMEMBER', 'SREM', 'STRLEN', 'SUBSCRIBE', 'SUNION', 'SUNIONSTORE', 'SWAPDB', 'SYNC', 'TIME', 'TOUCH', 'TTL', 'TYPE', 'UNSUBSCRIBE', 'UNLINK', 'UNWATCH', 'WAIT', 'WATCH', 'ZADD', 'ZCARD', 'ZCOUNT', 'ZINCRBY', 'ZINTERSTORE', 'ZLEXCOUNT', 'ZRANGE', 'ZRANGEBYLEX', 'ZREVRANGEBYLEX', 'ZRANGEBYSCORE', 'ZRANK', 'ZREM', 'ZREMRANGEBYLEX', 'ZREMRANGEBYRANK', 'ZREMRANGEBYSCORE', 'ZREVRANGE', 'ZREVRANGEBYSCORE', 'ZREVRANK', 'ZSCORE', 'ZUNIONSTORE', 'SCAN', 'SSCAN', 'HSCAN', 'ZSCAN', ], functions: [], }; ================================================ FILE: chat2db-client/src/constants/IntelliSense/sqlserver.ts ================================================ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.SQLSERVER, keywords: [ 'ABSOLUTE', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ALWAYS', 'AND', 'ANY', 'APPLY', 'AS', 'ASC', 'AUTHORIZATION', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINARY', 'BREAK', 'BROWSE', 'BULK', 'BY', 'CASCADE', 'CASE', 'CAST', 'CATCH', 'CHECK', 'CHECKPOINT', 'CLOSE', 'CLUSTERED', 'COALESCE', 'COLLATE', 'COLUMN', 'COMMIT', 'COMPUTE', 'CONSTRAINT', 'CONTAINS', 'CONTAINSTABLE', 'CONTINUE', 'CONVERT', 'CORRESPONDING', 'CREATE', 'CROSS', 'CURRENT', 'CURSOR', 'CYCLE', 'DATA', 'DATABASE', 'DBCC', 'DEALLOCATE', 'DECLARE', 'DEFAULT', 'DELETE', 'DENY', 'DESC', 'DESCRIPTION', 'DISABLE', 'DISALLOW', 'DISCONNECT', 'DISTINCT', 'DISTRIBUTED', 'DOUBLE', 'DROP', 'DUMP', 'ELSE', 'ENABLE', 'END', 'ERRLVL', 'ESCAPE', 'EXCEPT', 'EXEC', 'EXECUTE', 'EXISTS', 'EXIT', 'EXTERNAL', 'FETCH', 'FILE', 'FILLFACTOR', 'FOLLOWING', 'FOR', 'FOREIGN', 'FROM', 'FULL', 'FUNCTION', 'GOTO', 'GRANT', 'GROUP', 'HAVING', 'HOLDLOCK', 'IDENTITY', 'IF', 'IN', 'INDEX', 'INNER', 'INSERT', 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'KEY', 'KILL', 'LAST', 'LEFT', 'LIKE', 'LIMIT', 'LINENO', 'LOAD', 'LOCAL', 'MERGE', 'NATIONAL', 'NOCHECK', 'NONCLUSTERED', 'NOT', 'NULL', 'NULLIF', 'OF', 'OFF', 'OFFSETS', 'ON', 'ONLY', 'OPEN', 'OPTION', 'OR', 'ORDER', 'OUTER', 'OUTPUT', 'OVER', 'PARTITION', 'PERCENT', 'PLAN', 'PRECEDING', 'PRECISION', 'PRIMARY', 'PRINT', 'PROC', 'PROCEDURE', 'PUBLIC', 'RAISERROR', 'READ', 'READONLY', 'READTEXT', 'RECONFIGURE', 'REFERENCES', 'REPLICATION', 'RESTORE', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'ROLLBACK', 'ROWCOUNT', 'ROWGUIDCOL', 'RULE', 'SAVE', 'SCHEMA', 'SECURITYAUDIT', 'SELECT', 'SEMANTICKEYPHRASETABLE', 'SEMANTICSIMILARITYDETAILSTABLE', 'SEMANTICSIMILARITYTABLE', 'SESSION_USER', 'SET', 'SETUSER', 'SHUTDOWN', 'SOME', 'STATISTICS', 'SYSTEM_USER', 'TABLE', 'TABLESAMPLE', 'TEXTSIZE', 'THEN', 'THROW', 'TO', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'TRY', 'TSEQUAL', 'UNION', 'UNIQUE', 'UNPIVOT', 'UPDATE', 'UPDATETEXT', 'USE', 'USER', 'VALUES', 'VARYING', 'VIEW', 'WAITFOR', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WITHIN', 'WRITETEXT', 'XML', ], functions: [ 'ABS', 'ACOS', 'APP_NAME', 'ASCII', 'ASIN', 'ATAN', 'ATN2', 'AVG', 'BIGCOUNT', 'BINARY_CHECKSUM', 'CAST', 'CEILING', 'CHAR', 'CHARINDEX', 'CHECKSUM', 'CHECKSUM_AGG', 'COALESCE', 'COL_LENGTH', 'COL_NAME', 'CONCAT', 'CONCAT_WS', 'CONNECTIONPROPERTY', 'CONTEXT_INFO', 'CONVERT', 'COS', 'COT', 'COUNT', 'COUNT_BIG', 'CRYPT_GEN_RANDOM', 'CRYPT_PROPERTY', 'CURSOR_STATUS', 'DATABASEPROPERTYEX', 'DATEADD', 'DATEDIFF', 'DATENAME', 'DATEPART', 'DAY', 'DB_ID', 'DB_NAME', 'DEGREES', 'DIFFERENCE', 'EXP', 'FILE_ID', 'FILE_NAME', 'FILEGROUP_ID', 'FILEGROUP_NAME', 'FILEGROUPPROPERTY', 'FILEPROPERTY', 'FLOOR', 'FORMAT', 'GETANSINULL', 'GETDATE', 'GETUTCDATE', 'HASHBYTES', 'HOST_ID', 'HOST_NAME', 'IDENT_CURRENT', 'IDENT_INCR', 'IDENT_SEED', 'IIF', 'INDEX_COL', 'INDEXKEY_PROPERTY', 'INDEXPROPERTY', 'ISDATE', 'ISNULL', 'ISNUMERIC', 'IS_MEMBER', 'IS_OBJECTSIGNED', 'IS_SRVROLEMEMBER', 'ISNULL', 'LEFT', 'LEN', 'LOG', 'LOG10', 'LOWER', 'LTRIM', 'MAX', 'MIN', 'MONTH', 'NEWID', 'NULLIF', 'OBJECT_DEFINITION', 'OBJECT_ID', 'OBJECT_NAME', 'OBJECT_SCHEMA_NAME', 'OBJECTPROPERTY', 'OBJECTPROPERTYEX', 'PARSE', 'PATINDEX', 'PERCENT_RANK', 'PERCENTILE_CONT', 'PERCENTILE_DISC', 'PI', 'POWER', 'RADIANS', 'RANK', 'REPLACE', 'REPLICATE', 'REVERSE', 'RIGHT', 'ROUND', 'ROW_NUMBER', 'RTRIM', 'SCOPE_IDENTITY', 'SERVERPROPERTY', 'SESSIONPROPERTY', 'SIGN', 'SIN', 'SOUNDEX', 'SPACE', 'SQRT', 'SQUARE', 'STATS_DATE', 'STDEV', 'STDEVP', 'STUFF', 'SUBSTRING', 'SUM', 'SUSER_ID', 'SUSER_NAME', 'SUSER_SID', 'SUSER_SNAME', 'SYSTEM_USER', 'TAN', 'TEXTPTR', 'TEXTVALID', 'TRY_CAST', 'TRY_CONVERT', 'TRY_PARSE', 'TYPE_ID', 'TYPE_NAME', 'TYPEPROPERTY', 'UNICODE', 'UPPER', 'USER_ID', 'USER_NAME', 'YEAR', 'GET_FILESTREAM_TRANSACTION_CONTEXT', 'CURRENT_TRANSACTION_ID', 'XACT_STATE', 'STRING_AGG', 'STRING_ESCAPE', 'STRING_SPLIT', 'FORMATMESSAGE', 'XML_SCHEMA_NAMESPACE', 'ISJSON', 'JSON_VALUE', 'JSON_QUERY', 'JSON_MODIFY', 'JSON_EXISTS', 'JSON_TEXTCONTAINS', 'CHOOSE', 'IIF', 'FORMAT', 'PARSE', 'SEQUENCE_SCHEMA', 'SEQUENCE_NAME', 'SPATIAL_ST_GEOMETRYTYPE', 'SPATIAL_ST_DIMENSION', 'SPATIAL_ST_SRID', 'SPATIAL_ST_ISVALID', 'SPATIAL_ST_ASTEXT', 'SPATIAL_ST_ASBINARY', 'SPATIAL_ST_AREA', 'SPATIAL_ST_LENGTH', 'SPATIAL_ST_ISCLOSED', 'SPATIAL_ST_NUMPOINTS', 'SPATIAL_ST_X', 'SPATIAL_ST_Y', 'SPATIAL_ST_NUMGEOMETRIES', 'SPATIAL_ST_BOUNDARY', 'SPATIAL_ST_BUFFER', 'SPATIAL_ST_CENTROID', 'SPATIAL_ST_CONTAINS', 'SPATIAL_ST_CONVEXHULL', 'SPATIAL_ST_CROSSES', 'SPATIAL_ST_DIFFERENCE', 'SPATIAL_ST_DISJOINT', 'SPATIAL_ST_DISTANCE', 'SPATIAL_ST_ENVELOPE', 'SPATIAL_ST_EQUALS', 'SPATIAL_ST_INTERSECTION', 'SPATIAL_ST_INTERSECTS', 'SPATIAL_ST_ISRING', 'SPATIAL_ST_ISSIMPLE', 'SPATIAL_ST_OVERLAPS', 'SPATIAL_ST_SYMDIFFERENCE', 'SPATIAL_ST_TOUCHES', 'SPATIAL_ST_UNION', 'SPATIAL_ST_WITHIN', ], }; ================================================ FILE: chat2db-client/src/constants/appConfig.ts ================================================ export const APP_NAME = 'Chat2DB'; export const GITHUB_URL = 'https://github.com/chat2db/Chat2DB/blob/main/CHANGELOG.md' export const WEBSITE_DOC = 'https://doc.sqlgpt.cn/changelog/' ================================================ FILE: chat2db-client/src/constants/chat.ts ================================================ export const chatError = { CHAT2DB_KEY_INVALID: 'apikey 不在我们的数据库中需要扫码登录', CHAT2DB_KEY_LIMIT: '次数用完了,需要发起推广', CHAT2DB_KEY_EXPIRED: '到过期时间了', CHAT2DB_SERVICE_BUSY: '这个异常就稍后重试就行了', CHAT2DB_AUTH_HEADER_MISSING: '这个是 http 请求 header 没传 Authorization 字段,这个你看要怎么处理', CHAT2DB_AUTH_TOKEN_MISSING: '这个是 http 请求 header 中 Authorization 后面没有以 Bearer 开头,也是传的认证信息有问题,你看要怎么处理', CHAT2DB_SERVICE_ERROR: '这个是出了意料之外的异常要联系管理员', CHAT2DB_BAD_JSON_FORMAT: '这个是传的请求不是 json 格式', CHAT2DB_HTTP_METHOD_INVALID: '这个是传的请求不是 post 请求,目前给 openai 的请求必须是 post', }; export const chatErrorCodeArr = Object.keys(chatError); export const chatErrorToLogin = ['CHAT2DB_KEY_INVALID', 'CHAT2DB_AUTH_HEADER_MISSING', 'CHAT2DB_AUTH_TOKEN_MISSING']; export const chatErrorForKey = ['CHAT2DB_KEY_LIMIT', 'CHAT2DB_KEY_EXPIRED']; ================================================ FILE: chat2db-client/src/constants/common.ts ================================================ export enum DatabaseTypeCode { MYSQL = 'MYSQL', ORACLE = 'ORACLE', DB2 = 'DB2', MONGODB = 'MONGODB', REDIS = 'REDIS', H2 = 'H2', POSTGRESQL = 'POSTGRESQL', SQLSERVER = 'SQLSERVER', SQLITE = 'SQLITE', MARIADB = 'MARIADB', CLICKHOUSE = 'CLICKHOUSE', DM = 'DM', OCEANBASE = 'OCEANBASE', PRESTO = 'PRESTO', HIVE = 'HIVE', KINGBASE = 'KINGBASE', TIMEPLUS = 'TIMEPLUS', } export enum ConsoleStatus { DRAFT = 'DRAFT', RELEASE = 'RELEASE', } export enum OSType { WIN = 'Win', MAC = 'Mac', RESTS = 'rests', } export enum ConnectionKind { Private = 'PRIVATE', Shared = 'SHARED', } // 通用的增删改查枚举 export enum CRUD { CREATE = 'CREATE', READ = 'READ', UPDATE = 'UPDATE', DELETE = 'DELETE', UPDATE_COPY = 'UPDATE_COPY', } ================================================ FILE: chat2db-client/src/constants/console.ts ================================================ export enum ConsoleOpenedStatus { IS_OPEN = 'y', NOT_OPEN = 'n', } ================================================ FILE: chat2db-client/src/constants/database.ts ================================================ import mysqlLogo from '@/assets/img/databaseImg/mysql.png'; import redisLogo from '@/assets/img/databaseImg/redis.png'; import h2Logo from '@/assets/img/databaseImg/h2.png'; import moreDBLogo from '@/assets/img/databaseImg/other.png'; import { IDatabase } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; export enum ConnectionEnvType { DAILY = 'DAILY', PRODUCT = 'PRODUCT', } export const databaseMap: { [keys: string]: IDatabase; } = { [DatabaseTypeCode.MYSQL]: { name: 'MySQL', img: mysqlLogo, code: DatabaseTypeCode.MYSQL, // port: 3306, icon: '\uec6d', }, [DatabaseTypeCode.H2]: { name: 'H2', img: h2Logo, code: DatabaseTypeCode.H2, // port: 9092, icon: '\ue61c', }, [DatabaseTypeCode.ORACLE]: { name: 'Oracle', img: moreDBLogo, code: DatabaseTypeCode.ORACLE, // port: 1521, icon: '\uec48', }, [DatabaseTypeCode.POSTGRESQL]: { name: 'PostgreSql', img: moreDBLogo, code: DatabaseTypeCode.POSTGRESQL, // port: 5432, icon: '\uec5d', }, [DatabaseTypeCode.SQLSERVER]: { name: 'SQLServer', img: moreDBLogo, code: DatabaseTypeCode.SQLSERVER, // port: 1521, icon: '\ue664', }, [DatabaseTypeCode.SQLITE]: { name: 'SQLite', img: moreDBLogo, code: DatabaseTypeCode.SQLITE, // port: 5432, icon: '\ue65a', }, [DatabaseTypeCode.MARIADB]: { name: 'Mariadb', img: moreDBLogo, code: DatabaseTypeCode.MARIADB, // port: 3306, icon: '\ue6f5', }, [DatabaseTypeCode.CLICKHOUSE]: { name: 'ClickHouse', img: moreDBLogo, code: DatabaseTypeCode.CLICKHOUSE, // port: 8123, icon: '\ue8f4', }, [DatabaseTypeCode.DM]: { name: 'DM', img: moreDBLogo, code: DatabaseTypeCode.DM, // port: 5236, icon: '\ue655', }, [DatabaseTypeCode.PRESTO]: { name: 'Presto', img: moreDBLogo, code: DatabaseTypeCode.PRESTO, // port: 8080, icon: '\ue60b', }, [DatabaseTypeCode.DB2]: { name: 'DB2', img: moreDBLogo, code: DatabaseTypeCode.DB2, // port: 50000, icon: '\ue60a', }, [DatabaseTypeCode.OCEANBASE]: { name: 'OceanBase', img: moreDBLogo, code: DatabaseTypeCode.OCEANBASE, // port: 2883, icon: '\ue982', }, [DatabaseTypeCode.HIVE]: { name: 'Hive', img: moreDBLogo, code: DatabaseTypeCode.HIVE, // port: 10000, icon: '\ue60e', }, [DatabaseTypeCode.KINGBASE]: { name: 'KingBase', img: moreDBLogo, code: DatabaseTypeCode.KINGBASE, // port: 54321, icon: '\ue6a0', }, [DatabaseTypeCode.MONGODB]: { name: 'MongoDB', img: moreDBLogo, code: DatabaseTypeCode.MONGODB, // port: 27017, icon: '\uec21', }, [DatabaseTypeCode.TIMEPLUS]: { name: 'Timeplus', img: moreDBLogo, code: DatabaseTypeCode.TIMEPLUS, // port: 8123, icon: '\ue8f4', }, // [DatabaseTypeCode.REDIS]: { // name: 'Redis', // img: moreDBLogo, // code: DatabaseTypeCode.REDIS, // // port: 6379, // icon: '\ue6a2', // }, }; export const databaseTypeList = Object.keys(databaseMap).map((keys) => { return databaseMap[keys]; }); ================================================ FILE: chat2db-client/src/constants/editTable.ts ================================================ export enum EditColumnOperationType { // 新增 Add = 'ADD', // 修改 Modify = 'MODIFY', // 删除 Delete = 'DELETE', } // nullable export enum NullableType { // 不可为空 NotNull = 0, // 可为空 Null = 1, } ================================================ FILE: chat2db-client/src/constants/environment.ts ================================================ ================================================ FILE: chat2db-client/src/constants/index.ts ================================================ export * from './appConfig'; export * from './common'; export * from './database'; export * from './environment'; export * from './table'; export * from './theme'; export * from './tree'; export * from './workspace'; export * from './editTable'; export * from './console'; ================================================ FILE: chat2db-client/src/constants/table.ts ================================================ export enum TableDataType { BOOLEAN = 'BOOLEAN', NUMERIC = 'NUMERIC', STRING = 'STRING', DATETIME = 'DATETIME', // 暂时不适配 BINARY = 'BINARY', CONTENT = 'CONTENT', STRUCT = 'STRUCT', DOCUMENT = 'DOCUMENT', ARRAY = 'ARRAY', OBJECT = 'OBJECT', REFERENCE = 'REFERENCE', ROWID = 'ROWID', ANY = 'ANY', UNKNOWN = 'UNKNOWN', CHAT2DB_ROW_NUMBER = 'CHAT2DB_ROW_NUMBER', } export enum StatusType { SUCCESS = 'success', FAIL = 'fail', } ================================================ FILE: chat2db-client/src/constants/theme.ts ================================================ export enum ThemeType { Light = 'light', Dark = 'dark', DarkDimmed = 'darkDimmed', FollowOs = 'followOs', } export enum EditorThemeType { DashboardLightTheme = 'DashboardLightTheme', DashboardBlackTheme = 'DashboardBlackTheme', } export enum PrimaryColorType { Polar_Green = 'polar-green', Golden_Purple = 'golden-purple', Polar_Blue = 'polar-blue', Silver = 'silver', Red = 'red', Orange = 'orange', Blue2 = 'blue2', Gold = 'gold', } export enum LangType { EN_US = 'en-us', ZH_CN = 'zh-cn', TR_TR = 'tr-tr', JA_JP = 'ja-jp', } ================================================ FILE: chat2db-client/src/constants/tree.ts ================================================ export enum TreeNodeType { DATA_SOURCES = 'dataSources', DATA_SOURCE = 'dataSource', DATABASE = 'database', SCHEMAS = 'schemas', TABLES = 'tables', TABLE = 'table', COLUMNS = 'columns', COLUMN = 'column', KEYS = 'keys', KEY = 'key', INDEXES = 'indexes', INDEX = 'index', VIEWS = 'views', // 视图组 VIEW = 'view', // 视图 VIEWCOLUMN = 'viewColumn', VIEWCOLUMNS = 'viewColumns', FUNCTIONS = 'functions', // 函数组 FUNCTION = 'function', // 函数 PROCEDURES = 'procedures', // procedure组 PROCEDURE = 'procedure', // procedure TRIGGERS = 'triggers', // trigger组 TRIGGER = 'trigger', // trigger SEQUENCES = 'sequences', SEQUENCE = 'sequence', } // 树右键支持的功能 export enum OperationColumn { ShiftOut = 'shiftOut', // 移出数据源 Refresh = 'refresh', // 刷新各级菜单 CreateTable = 'createTable', //创建表 CreateConsole = 'createConsole', // 新建console DeleteTable = 'deleteTable', // 删除表 OpenTable = 'openTable', // 打开表 ViewDDL = 'viewDDL', // 查看ddl EditSource = 'editSource', // 编辑数据源 Pin = 'pin', // 置顶 EditTable = 'editTable', // 编辑表 EditTableData = 'editTableData', // 编辑表数据 CopyName = 'copyName', // 复制名称 EditView = 'editView', // 编辑视图 OpenView = 'openView', // 打开视图 OpenFunction = 'openFunction', // 打开函数 OpenProcedure = 'openProcedure', // 打开存储过程 OpenTrigger = 'openTrigger', // 打开触发器 CreateSchema = 'createSchema', // 新建schema CreateDatabase = 'createDatabase', // 新建database ViewAllTable = 'viewAllTable', // 查看所有的表 OpenSequence = 'openSequence', // 打开序列 CreateSequence = 'createSequence', // 新建序列 EditSequence = 'editSequence', /// 编辑序列 DeleteSequence = 'deleteSequence' // 删除序列 } ================================================ FILE: chat2db-client/src/constants/workspace.ts ================================================ export enum CreateTabIntroType { EditorTable = 'editorTable', EditTableData = 'editTableData', } // 工作台Tab的类型 export enum WorkspaceTabType { CONSOLE = 'console', FUNCTION = 'function', PROCEDURE = 'procedure', VIEW = 'view', TRIGGER = 'trigger', SEQUENCE = 'sequence', EditTable = 'editTable', CreateTable = 'createTable', EditTableData = 'editTableData', ViewAllTable = 'viewAllTable', CreateSequence = 'createSequence', EditSequence = 'editSequence', } // 工作台Tab的类型对应的一些配置 export const workspaceTabConfig: { [key in WorkspaceTabType]: { icon: string }; } = { [WorkspaceTabType.CONSOLE]: { icon: '\uec83' }, [WorkspaceTabType.VIEW]: { icon: '\ue70c' }, [WorkspaceTabType.FUNCTION]: { icon: '\ue76a' }, [WorkspaceTabType.PROCEDURE]: { icon: '\ue73c' }, [WorkspaceTabType.TRIGGER]: { icon: '\ue64a' }, [WorkspaceTabType.SEQUENCE]: { icon: '\ue63e' }, [WorkspaceTabType.EditTable]: { icon: '\ue6f3' }, [WorkspaceTabType.CreateTable]: { icon: '\ue6b6' }, [WorkspaceTabType.EditTableData]: { icon: '\ue618' }, [WorkspaceTabType.ViewAllTable]: { icon: '\ue611' }, [WorkspaceTabType.CreateSequence]: { icon: '\ue63e' }, [WorkspaceTabType.EditSequence]: { icon: '\ue63e' } } ================================================ FILE: chat2db-client/src/hooks/getConnection.ts ================================================ import connectionService from '@/service/connection'; import { setConnectionEnvList, getConnectionList } from '@/pages/main/store/connection'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; const getConnectionEnvList = () => { connectionService.getEnvList().then((res) => { setConnectionEnvList(res); }); }; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; const getConnection = () => { const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; getConnectionList().then((res) => { // 如果连接列表为空,则设置当前连接为空 if (res.length === 0) { setCurrentConnectionDetails(null); return; } // 如果当前连接不存在,则设置当前连接为第一个连接 if (!currentConnectionDetails?.id) { setCurrentConnectionDetails(res[0]); return; } // 如果存在但是不在列表中,则设置当前连接为第一个连接 const currentConnection = res.find((item) => item.id === currentConnectionDetails?.id); if (!currentConnection) { setCurrentConnectionDetails(res[0]); } }); getConnectionEnvList(); }; export default getConnection; ================================================ FILE: chat2db-client/src/hooks/index.ts ================================================ export * from './useTheme'; export * from './useUpdateEffect'; export * from './useEventSource'; ================================================ FILE: chat2db-client/src/hooks/useClickAndDoubleClick.ts ================================================ import { useState, useEffect, useCallback } from 'react'; const useClickAndDoubleClick = (singleClickCallback, doubleClickCallback, delay = 250) => { const [clickCount, setClickCount] = useState(0); const [eventData, setEventData] = useState(null); const handleClick = useCallback((data) => { setEventData(data); setClickCount((prev) => prev + 1); }, []); useEffect(() => { if (clickCount === 1) { const singleClickTimer = setTimeout(() => { singleClickCallback(eventData); setClickCount(0); }, delay); return () => clearTimeout(singleClickTimer); } else if (clickCount === 2) { doubleClickCallback(eventData); setClickCount(0); } }, [clickCount, eventData, singleClickCallback, doubleClickCallback, delay]); return handleClick; }; export default useClickAndDoubleClick; ================================================ FILE: chat2db-client/src/hooks/useEventSource.ts ================================================ import React, { useEffect, useMemo, useState } from 'react'; import { EventSourcePolyfill } from 'event-source-polyfill'; import { v4 as uuidv4 } from 'uuid'; function useEventSource({ url }) { const uid = useMemo(() => uuidv4(), []); const [messages, setMessage] = useState(''); useEffect(() => { handleEventSource(); }, []); const handleEventSource = () => { const eventSource = new EventSourcePolyfill(url, { headers: { uid, }, }); eventSource.onmessage = (event) => { setMessage(event.data); const isEOF = event.data === '[DONE]'; if (isEOF) { eventSource.close(); } }; eventSource.onerror = (error) => { console.error('EventSourcePolyfill error:', error); }; }; return messages; } export default useEventSource; ================================================ FILE: chat2db-client/src/hooks/useFocusData.ts ================================================ import { useEffect } from 'react'; import { useCommonStore } from '@/store/common'; import { tableCopy, copy } from '@/utils' import { setFocusedContent } from '@/store/common/copyFocusedContent'; // 如果用户点击的不是可复制的元素,就清空选中的内容 function useCopyFocusData() { const { focusedContent } = useCommonStore((state) => { return { focusedContent: state.focusedContent } }); // 注册快捷键监听cmd+c或ctrl+c复制focusedContent useEffect(() => { const handleCopy = (e: KeyboardEvent) => { if (e.key === 'c' && (e.metaKey || e.ctrlKey)) { if (!focusedContent) return // 如果是数据是数组,就调用tableCopy if (Array.isArray(focusedContent)) { tableCopy(focusedContent as any) return } copy(focusedContent as any); } }; document.addEventListener('keydown', handleCopy); return () => { document.removeEventListener('keydown', handleCopy); }; }, [focusedContent]); useEffect(() => { const handleClick = (event) => { const targetElement = event.target as Element; if (!targetElement.closest('[data-chat2db-general-can-copy-element]')) { setFocusedContent(null) } }; document.addEventListener('click', handleClick); document.addEventListener('contextmenu', handleClick); return () => { document.removeEventListener('click', handleClick); document.removeEventListener('contextmenu', handleClick); }; }, [focusedContent]); } export default useCopyFocusData; ================================================ FILE: chat2db-client/src/hooks/usePollRequestService.ts ================================================ import { useState, useEffect, useRef } from 'react'; interface IProps { /** Maximum number of requests */ maxAttempts?: number; /** Request interval ms */ interval?: number; /** demand service */ loopService: (...rest) => Promise; } export enum ServiceStatus { PENDING = 'PENDING', SUCCESS = 'SUCCESS', FAILURE = 'FAILURE', } /** * Polling request back-end service */ const usePollRequestService = ({ maxAttempts = 200, interval = 200, loopService }: IProps) => { const [serviceStatus, setServiceStatus] = useState(ServiceStatus.PENDING); const [restart, setRestart] = useState(false); const attempts = useRef(0); const startupDate = useRef(new Date().getTime()); const serviceFn = async () => { // The first request fails. Start the service if (attempts.current === 1 && ServiceStatus.SUCCESS !== serviceStatus) { window.electronApi?.startServerForSpawn(); } if (attempts.current >= maxAttempts) { setServiceStatus(ServiceStatus.FAILURE); return; } attempts.current = attempts.current + 1; loopService().then((res) => { if (res) { const now = new Date().getTime(); setTimeout(() => { setServiceStatus(ServiceStatus.SUCCESS); }, startupDate.current + 1000 - now); } }) .catch(() => { setTimeout(serviceFn, interval); }); }; useEffect(() => { serviceFn(); }, [maxAttempts, interval, restart]); // Newly added reset function const restartPolling = () => { setServiceStatus(ServiceStatus.PENDING); attempts.current = 0; setRestart(!restart); }; return { serviceStatus, restartPolling }; }; export default usePollRequestService; ================================================ FILE: chat2db-client/src/hooks/useTheme.ts ================================================ import { useEffect, useState } from 'react'; import { getOsTheme } from '@/utils'; import { ITheme } from '@/typings'; import { ThemeType, PrimaryColorType } from '@/constants'; import { getPrimaryColor, getTheme, setPrimaryColor, setTheme } from '@/utils/localStorage'; import { v4 as uuidv4 } from 'uuid'; const colorSchemeListeners: { [key: string]: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void; } = {}; const addColorSchemeListener = ( callback: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void, ) => { const uuid = uuidv4(); colorSchemeListeners[uuid] = callback; return uuid; }; const initialTheme = () => { const localStorageTheme = getTheme(); const localStoragePrimaryColor = getPrimaryColor(); // 判断localStorage的theme在不在ThemeType中, 如果存在就用localStorageTheme let backgroundColor = ThemeType.Light; if (Object.values(ThemeType).includes(localStorageTheme)) { backgroundColor = localStorageTheme; } let primaryColor = PrimaryColorType.Golden_Purple; if (Object.values(PrimaryColorType).includes(localStoragePrimaryColor)) { primaryColor = localStoragePrimaryColor; } if (backgroundColor === ThemeType.FollowOs) { backgroundColor = getOsTheme(); } document.documentElement.setAttribute('theme', backgroundColor); document.documentElement.setAttribute('primary-color', primaryColor); return { backgroundColor, primaryColor, }; }; export function useTheme(): [T, React.Dispatch>] { const [appTheme, setAppTheme] = useState(initialTheme()); // const isDark = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); useEffect(() => { const uuid = addColorSchemeListener(setAppTheme as any); return () => { delete colorSchemeListeners[uuid]; }; }, []); function handleAppThemeChange(theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) { if (theme.backgroundColor === ThemeType.FollowOs) { theme.backgroundColor = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeType.DarkDimmed : ThemeType.Light; } Object.keys(colorSchemeListeners)?.forEach((t) => { colorSchemeListeners[t]?.(theme); }); document.documentElement.setAttribute('theme', theme.backgroundColor); setTheme(theme.backgroundColor); document.documentElement.setAttribute('primary-color', theme.primaryColor); setPrimaryColor(theme.primaryColor); } return [appTheme, handleAppThemeChange] as any; } ================================================ FILE: chat2db-client/src/hooks/useUpdateEffect.ts ================================================ import { useRef, useEffect } from 'react'; /** * 第一次Effect更新不执行 * @param fn * @param arr */ export function useUpdateEffect(fn: Function, arr: any[]) { const first = useRef(true); useEffect(() => { if (first.current) { first.current = false; } else { fn(); } }, arr); } ================================================ FILE: chat2db-client/src/i18n/en-us/chat.ts ================================================ export default { 'chat.input.remain': '{1} remaining', 'chat.input.tableSelect.placeholder': 'Please choose tables', 'chat.input.tableSelect.error.TooManyTable': 'You can only select up to 8 tables', 'chat.input.remain.dialog.tips': 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', 'chat.input.syncTable.tips': 'The automatically synchronize all table structures to the AI context', 'chat.input.remain.tooltip': 'The manually selected table will be synchronized to the AI context', 'chat.input.syncTable.tempTips': '🎉Update: Automatically synchronize all table structures to the AI context', }; ================================================ FILE: chat2db-client/src/i18n/en-us/common.ts ================================================ export default { 'common.text.no': 'no', 'common.text.is': 'is', 'common.button.affirm': 'Affirm', 'common.button.edit': 'Edit', 'common.button.modify': 'Modify', 'common.button.confirm': 'Confirm', 'common.button.cancel': 'Cancel', 'common.data.hour': '{1} {hour|hours}', 'common.data.minute': '{1} {minute|minutes}', 'common.tip.yesterday': '{1} yesterday', 'common.tip.tomorrow': '{1} tomorrow', 'common.tip.ago': ' ago', 'common.tip.later': ' later', 'common.tip.now': 'Now', 'common.tip.justNow': 'Just now', 'common.text.search': 'search', 'common.placeholder.select': 'Place Select {1}', 'common.text.serviceStarting': 'Service Starting ...', 'common.text.serviceFail': 'Service startup failed. Please try refreshing the page...', 'common.text.column': 'column', 'common.text.row': 'row', 'common.text.indexes': 'indexes', 'common.button.save': 'Save', 'common.button.open': 'Open', 'common.button.refresh': 'Refresh', 'common.button.execute': 'Run', 'common.button.import': 'Import SQL', 'common.button.format': 'Format', 'common.message.successfulConfig': 'Successful configuration', 'common.text.successful': 'successful', 'common.text.failure': 'failure', 'common.message.modifySuccessfully': 'modify successfully', 'common.message.addedSuccessfully': 'successfully added', 'common.text.custom': 'custom', 'common.button.delete': 'Delete', 'common.text.executionResult': 'Result {1}', 'common.tips.deleteTable': 'Are you sure delete this Table?', 'common.text.tableName': 'Table Name', 'common.text.submittedSuccessfully': 'Successfully submitted', 'common.text.successfullyDelete': 'Successfully Delete', 'common.text.explainSQL': 'Explain SQL', 'common.text.optimizeSQL': 'Optimize SQL', 'common.text.conversionSQL': 'Conversion SQL', 'common.text.table': 'Table', 'common.tips.saveSuccessfully': 'Save Successfully', 'common.button.copy': 'Copy', 'common.button.copyName': 'Copy name', 'common.button.copySuccessfully': 'Copy Successfully', 'common.button.createConsole': 'Create Console', 'common.button.exportWord': 'Export to Word', 'common.button.exportExcel': 'Export to Excel', 'common.button.exportHtml': 'Export to Html', 'common.button.exportMarkdown': 'Export to Markdown', 'common.button.exportPdf': 'Export to Pdf', 'common.text.successfulExecution': 'Successful Execution', 'common.text.result': 'Result', 'common.text.timeConsuming': 'Time Consumed', 'common.text.searchRow': 'Query Result', 'common.text.noData': 'No Data', 'common.text.remindMeLater': 'Remind Me Later', 'common.text.goToUpdate': 'Go To Update', 'common.text.updateReminder': 'Update Reminder', 'common.text.detectionLatestVersion': 'The latest version is monitored', 'common.text.setting': 'Setting', 'common.text.tryToRestart': 'Try To Restart', 'common.text.contactUs': 'Contact Us', 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', 'common.text.export': 'Export', 'common.notification.detail': 'More details', 'common.notification.solution': 'Solution', 'common.button.copyError': 'Copy error report', 'common.button.copyErrorTips': '(The interface information and detailed parameters will be copied here. If there are sensitive parameters, please parse JSON first and then send them)', 'common.tips.formatError': 'Formatting failed, please check whether the sql is correct', 'common.text.executeSelectedSQL': 'Execute selected SQL', 'common.text.refreshPage': 'Refresh page', 'common.text.saveConsole': 'Save console', 'common.text.textToSQL': 'Plain text to SQL', 'common.text.editorRightClick': 'Editor right click', 'common.form.error.required': 'This field is required!', 'common.form.error.email': 'The input is not a valid email!', 'common.tips.delete.confirm': 'Are you sure to delete it?', 'common.tips.updateSuccess': 'Update Successfully', 'common.tips.createSuccess': 'Create Successfully', 'common.text.action': 'Action', 'common.button.add': 'Add', 'common.text.errorMessage': 'Error Message', 'common.button.cancelRequest': 'Cancel Request', 'common.button.executionError': 'Execution Error', 'common.text.affectedRows': 'Affected rows: {1}', 'common.text.selectFile': 'Select File', 'common.text.noTableFoundUp': 'No tables in this database', 'common.text.noTableFoundDown': 'Switch databases at the top', 'common.title.preview': 'Preview', 'common.title.errorMessage': 'Error message', 'common.label.comment': 'Comment', 'common.label.name': 'Name', 'common.title.create': 'Create', 'common.title.executiveLogging': 'Runtime logs', 'common.text.executionTime': 'Done with {1} ms', 'common.button.copyRowAs': 'Copy the row as', 'common.button.insertSql': 'Insert SQL', 'common.button.updateSql': 'Update SQL', 'common.button.tabularSeparatedValues': 'TAB delimited (data)', 'common.button.tabularSeparatedValuesFieldName': 'TAB delimited (field name)', 'common.button.tabularSeparatedValuesFieldNameAndData': 'Tab-separated (field names and data)', 'common.button.cloneRow': 'Clone row', 'common.button.deleteRow': 'Delete row', 'common.button.setNull': 'Set NULL', 'common.button.setDefault': 'Set DEFAULT', 'common.button.viewData': 'View/Edit Data', 'common.button.close': 'Close', 'common.button.closeAll': 'Close all', 'common.button.closeOthers': 'Close others', 'common.label.tcp': 'TCP', 'common.label.LocalFile': 'LocalFile', 'common.text.rename': 'Rename', 'common.title.info': 'Info', }; ================================================ FILE: chat2db-client/src/i18n/en-us/connection.ts ================================================ export default { 'connection.title': 'Connections', 'connection.title.connections': 'Connections', 'connection.title.createConnection': 'New Connection', 'connection.title.editConnection': 'Edit Connection', 'connection.title.importConnection': 'Import Connection', 'connection.label.name': 'name', 'connection.label.host': 'host', 'connection.label.authentication': 'authentication', 'connection.label.database': 'database', 'connection.label.JDBCDrive': 'JDBC Driver', 'connection.label.port': 'port', 'connection.button.testConnection': 'Test', 'connection.label.advancedConfiguration': 'Advanced Configuration', 'connection.label.sshConfiguration': 'SSH Configuration', 'connection.button.addConnection': 'Add Connection', 'connection.button.connect': 'Connect', 'connection.button.remove': 'Remove', 'connection.message.testConnectResult': 'Test connection is {1}', 'connection.message.testSshConnection': 'Test the ssh connection', 'connection.tableHeader.name': 'Name', 'connection.tableHeader.value': 'Value', 'connection.title.uploadDriver': 'Upload', 'connection.tips.customUpload': "Upload driver", 'connection.title.driver': 'Driver', 'connection.button.clickUpload': 'Click to Upload', 'connection.text.downloadDriver': 'Download Driver', 'connection.text.downloadSuccess': 'Download Success', 'connection.text.tryAgainDownload': 'Try again download', 'connection.text.downloading': 'Downloading...', 'connection.label.private': 'Private', 'connection.label.shared': 'Shared', 'connection.button.createConnection': 'Create connection', 'connection.tips.noConnection': 'You have not created any connections yet', 'connection.tips.noConnectionTips': 'You do not have permission to view the connection details, but you can connect to the connection directly', 'connection.title.importTitle': 'Import file,.ncx(navicat) or.dbp(dbever)', }; ================================================ FILE: chat2db-client/src/i18n/en-us/dashboard.ts ================================================ export default { 'dashboard.title': 'Dashboard', 'dashboard.edit': 'Edit', 'dashboard.modal.editTitle': 'Edit Dashboard', 'dashboard.modal.addTitle': 'Add Dashboard', 'dashboard.modal.name.placeholder': "Please enter dashboard's name.", 'dashboard.export2image': 'Export to image', 'dashboard.delete': 'Delete', 'dashboard.editor.cascader.placeholder': 'Please select a connection pool', 'dashboard.editor.execute.noDataSource': 'Please select a data source first', 'dashboard.editor.execute.success': 'Successful, Please select Chart Configuration', }; ================================================ FILE: chat2db-client/src/i18n/en-us/editSequence.ts ================================================ export default { 'editSequence.button.createSequence': 'New Sequence', 'editSequence.button.editSequence': 'Edit Sequence', 'editSequence.label.comment': 'Comment', 'editSequence.label.relname': 'Sequence Name', 'editSequence.label.typname': 'Data Type', 'editSequence.label.seqcache': 'Cache', 'editSequence.label.rolname': 'Owner', 'editSequence.label.seqstart': 'Start Value', 'editSequence.label.seqincrement': 'Increment Value', 'editSequence.label.seqmax': 'Max Value', 'editSequence.label.seqmin': 'Min Value', 'editSequence.label.seqcycle': 'Cycle', 'editSequence.title.sqlPreview': 'SQL Preview', }; ================================================ FILE: chat2db-client/src/i18n/en-us/editTable.ts ================================================ export default { 'editTable.tab.basicInfo': 'Basic', 'editTable.tab.columnInfo': 'Column', 'editTable.tab.indexInfo': 'Index', 'editTable.label.tableName': 'Table name', 'editTable.label.comment': 'Comment', 'editTable.button.add': 'Add', 'editTable.button.delete': 'Delete', 'editTable.button.up': 'Up', 'editTable.button.down': 'Down', 'editTable.label.indexName': 'Name', 'editTable.label.indexType': 'Type', 'editTable.label.indexMethod': 'Index method', 'editTable.label.includeColumn': 'Include column', 'editTable.button.createTable': 'Create Table', 'editTable.button.importTable': 'Export Table', 'editTable.label.index': 'Index', 'editTable.label.columnName': 'Name', 'editTable.label.columnSize': 'Size', 'editTable.label.columnType': 'Type', 'editTable.label.nullable': 'Nullable', 'editTable.label.prefixLength': 'Prefix length', 'editTable.label.defaultValue': 'Default value', 'editTable.label.sparse': 'Sparse', 'editTable.label.characterSet': 'Character set', 'editTable.label.collation': 'Collation', 'editTable.label.decimalPoint': 'Decimal point', 'editTable.label.unit': 'Unit', 'editTable.label.value': 'Value', 'editTable.label.autoIncrement': 'Auto increment', 'editTable.label.engine': 'Engine', 'editTable.label.incrementValue': 'Increment value', 'editTable.label.order': 'Order', 'editTable.label.primaryKey': 'Key', 'editTable.title.sqlPreview': 'SQL preview', 'editTable.button.addColumn': 'Add column', 'editTable.button.addIndex': 'Add Index', }; ================================================ FILE: chat2db-client/src/i18n/en-us/editTableData.ts ================================================ export default { 'editTableData.tips.addRow': 'Add Row', 'editTableData.tips.deleteRow': 'Delete Row', 'editTableData.tips.revert': 'Revert', 'editTableData.tips.previewPendingChanges': 'Preview Pending Changes', 'editTableData.tips.submit': 'Submit', }; ================================================ FILE: chat2db-client/src/i18n/en-us/index.ts ================================================ import common from './common'; import connection from './connection'; import menu from './menu'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' import editSequence from './editSequence'; export default { lang: 'en', ...common, ...setting, ...connection, ...workspace, ...menu, ...dashboard, ...chat, ...team, ...login, ...editTable, ...editTableData, ...sqlEditor, ...editSequence }; ================================================ FILE: chat2db-client/src/i18n/en-us/login.ts ================================================ export default { 'login.text.logout': 'Logout', 'login.text.welcome': 'Welcome to Chat2DB', 'login.text.tips': 'The Chat2DB account is only for team collaboration management.', 'login.text.tips.title': 'Why need login?', 'login.text.setting': 'Setting', 'login.form.user': 'UserName', 'login.form.user.placeholder': 'Please enter your username', 'login.form.password': 'Password', 'login.form.password.placeholder': 'Please enter your password', 'login.button.login': 'Login', 'login.tips.defaultPassword': 'The default user name and password are: chat2db', }; ================================================ FILE: chat2db-client/src/i18n/en-us/menu.ts ================================================ export default { 'menu.file' : 'File' } ================================================ FILE: chat2db-client/src/i18n/en-us/setting.ts ================================================ export default { 'setting.title.setting': 'Setting', 'setting.nav.basic': 'Basic', 'setting.nav.customAi': 'Custom Ai', 'setting.nav.proxy': 'Service Path', 'setting.nav.aboutUs': 'About Us', 'setting.title.backgroundColor': 'Background Color', 'setting.title.themeColor': 'Theme Color', 'setting.title.sqlEditorFontSize': 'SQL Editor Font Size', 'setting.label.blue': 'Blue', 'setting.label.green': 'Green', 'setting.label.violet': 'Violet', 'setting.text.dark': 'Dark', 'setting.text.dark2': 'Dark-2', 'setting.text.light': 'Light', 'setting.text.followOS': 'FollowOS', 'setting.title.language': 'Language', 'setting.title.aiSource': 'AI Source', 'setting.tab.custom': 'Custom', 'setting.tab.aiType.zhipu': 'ZhiPu AI', 'setting.tab.aiType.baichuan': 'BaiChuan AI', 'setting.tab.aiType.wenxin': 'WenXin AI', 'setting.tab.aiType.tongyiqianwen': 'TongYiQianWen AI', 'setting.tab.aiType.custom.tips': 'The API format is consistent with the OpenAI API format', 'setting.label.serviceAddress': 'Service Address', 'setting.button.apply': 'Apply', 'setting.text.currentEnv': 'Current Env', 'setting.text.currentVersion': 'Current Version', 'setting.text.viewingUpdateLogs': 'Viewing Update Logs', 'setting.label.isStreamOutput': 'Whether the interface streams output', 'setting.label.customAiUrl': 'User-defined interface Url', 'setting.placeholder.httpsProxy': 'Not required. Set HTTP proxy {1} when requesting OPENAI interface.', 'setting.placeholder.apiKey': 'OpenAI official website to view the APIKEY', 'setting.placeholder.chat2dbApiKey': 'Use the APIKEY provided by Chat2DB', 'setting.placeholder.customUrl': 'URL of the REST interface of the AI', 'setting.placeholder.apiHost': 'This parameter is mandatory. The default value is https://api.openai.com/', 'setting.message.urlTestError': 'The interface test failed. Procedure', 'setting.placeholder.azureOpenAIKey': 'Get Azure OpenAI key credential from the Azure Portal', 'setting.placeholder.azureEndpoint': 'Get Azure OpenAI endpoint from the Azure Portal', 'setting.placeholder.azureDeployment': 'Deployment id of the deployed model', 'setting.ai.tips': 'Please log in and select AI configuration', 'setting.ai.user.hidden': 'Please contact the administrator to set ApiKey in "Settings ->Custom Ai"', 'setting.button.startDownloading': 'Start Downloading', 'setting.button.beDownloading': 'Downloading', 'setting.button.redownload': 'Redownload', 'setting.button.restart': 'Restart', 'setting.text.discoverNewVersion': 'Discover new version {1}', 'setting.text.isLatestVersion': 'This is the latest version', 'setting.button.changeLog': 'Changelog', 'setting.title.updateRule': 'Update rule', 'setting.text.autoUpdate': 'The new version will be automatically downloaded and installed', 'setting.text.manualUpdate': 'Only alert me when a new version is released', 'setting.button.iSee': 'I see', 'setting.text.newEditionIsReady': 'New version to download completed, restart the software will install the new version', 'setting.button.goToUpdate': 'Go to update', 'setting.text.UpdatedLatestVersion': 'Updated to the latest version {1}', 'setting.title.holdingService': 'Holding Service', 'setting.text.holdingService': 'Keep the service when exiting the application to speed up startup', 'setting.chat2db.ai.button': 'Please visit Chat2DB Pro for more powerful AI features', 'setting.title.goto.chat2db.pro': 'Go to Chat2DB Pro', }; ================================================ FILE: chat2db-client/src/i18n/en-us/sqlEditor.ts ================================================ export default { 'sqlEditor.text.keyword': 'Keyword', 'sqlEditor.text.function': 'Function', 'sqlEditor.text.tableName': 'TableName', 'sqlEditor.text.databaseName': 'DatabaseName', 'sqlEditor.text.schemaName': 'Schema', 'sqlEditor.text.viewName': 'ViewName', 'sqlEditor.text.fieldName': 'FieldName', }; ================================================ FILE: chat2db-client/src/i18n/en-us/team.ts ================================================ export default { 'team.title': 'Team Management', 'team.tab.datasource': 'DataSource Management', 'team.tab.user': 'User Management', 'team.tab.team': 'Team Management', 'team.action.rightManagement': 'Right Management', 'team.action.editDatasource': 'Edit DataSource', 'team.action.addDatasource': 'Add DataSource', 'team.action.addDatasource.placeholder': 'Search DataSource', 'team.action.editUser': 'Edit user', 'team.action.addUser': 'Add User', 'team.action.addUser.placeholder': 'Search User', 'team.action.editTeam': 'Edit Team', 'team.action.addTeam': 'Add Team', 'team.action.addTeam.placeholder': 'Search Team', 'team.action.affiliation.user': 'Affiliation User', 'team.action.affiliation.team': 'Affiliation Team', 'team.action.affiliation.datasource': 'Affiliation DataSource', 'team.action.addUserAndTeam': 'Add User/Team', 'team.action.addUserAndTeam.placeholder': 'Search User/Team', 'team.input.search.placeholder': 'Please enter keywords to search', 'team.datasource.rightManagement': 'Right Management', 'team.datasource.alias': 'DataSource Name', 'team.datasource.url': 'DataSource URL', 'team.datasource.code': 'Code', 'team.datasource.name': 'Name', 'team.datasource.status': 'Status', 'team.user.name': 'User', 'team.user.userName': 'UserName', 'team.user.nickName': 'NickName', 'team.user.status': 'Status', 'team.user.addForm.userName': 'UserName', 'team.user.addForm.nickName': 'NickName', 'team.user.addForm.email': 'Email', 'team.user.addForm.password': 'Password', 'team.user.addForm.roleCode': 'Role', 'team.user.addForm.roleCode.admin': 'Admin', 'team.user.addForm.roleCode.user': 'User', 'team.user.addForm.status': 'Status', 'team.user.addForm.status.valid': 'Valid', 'team.user.addForm.status.invalid': 'Invalid', 'team.team.name': 'Team', 'team.team.addForm.code': 'Team Code', 'team.team.addForm.name': 'Team Name', 'team.team.addForm.status': 'Status', 'team.team.addForm.status.valid': 'Valid', 'team.team.addForm.status.invalid': 'Invalid', 'team.team.addForm.description': 'Description', } ================================================ FILE: chat2db-client/src/i18n/en-us/workspace.ts ================================================ export default { 'workspace.title': 'Workspace', 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', 'workspace.title.savedConsole': 'Saved console', 'workspace.menu.ViewDDL': 'View DDL', 'workspace.menu.deleteTable': 'Delete Table', 'workspace.menu.openTable': 'Open Table', 'workspace.menu.editTable': 'Edit Table', 'workspace.menu.view': 'View', 'workspace.menu.pin': 'Pin', 'workspace.menu.unPin': 'Unpin', 'workspace.menu.editTableData': 'Edit Table Data', 'workspace.menu.queryConsole': 'Query console', 'workspace.menu.viewAllTable': 'View all table', 'workspace.menu.createDatabase': 'Create database', 'workspace.menu.createSchema': 'Create schema', 'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete', 'workspace.menu.createSequence': 'Create sequence', 'workspace.menu.editSequence': 'Edit sequence', 'workspace.menu.deleteSequence': 'Delete sequence', 'workspace.tips.affirmDeleteTable': 'The table name you entered is not the same as the table name you want to delete, please confirm again', 'workspace.table.total': 'Total', 'workspace.table.total.tip': 'Load total number of rows', 'workspace.table.export.all.csv': 'Export results as a CSV', 'workspace.table.export.cur.csv': 'Export results on the current page as a CSV', 'workspace.table.export.all.insert': 'Export results as INSERT SQL', 'workspace.table.export.cur.insert': 'Export results on the current page as INSERT SQL', 'workspace.tree.view': 'View', 'workspace.tree.trigger': 'Trigger', 'workspace.tree.function': 'Function', 'workspace.tree.procedure': 'Procedure', 'workspace.tree.search.placeholder': 'Search in the expand node', 'workspace.tree.delete.tip': 'I understand that this operation is permanently deleted', 'workspace.tree.delete.table.tip': 'Are you sure you want to delete the table {1}?', 'workspace.tips.noConnection': 'You have not created a connection yet', 'workspace.tips.maxConsole': 'You can only open up to 20 consoles', 'workspace.tips.openExecutiveLogging': 'Open this executive logging', 'workspace.tree.delete.sequence.tip': 'Are you sure you want to delete the sequence {1}?', }; ================================================ FILE: chat2db-client/src/i18n/index.tsx ================================================ import React, { Fragment } from 'react'; import { getLang } from '@/utils/localStorage'; import { LangType } from '@/constants'; import zhCN from './zh-cn'; import enUS from './en-us'; import trTR from './tr-tr'; import jaJp from './ja-jp'; const locale = { 'en-us': enUS, 'zh-cn': zhCN, 'tr-tr': trTR, 'ja-jp': jaJp, }; export const currentLang: LangType = getLang() || LangType.EN_US; export const isEn = currentLang === LangType.EN_US; export const isZH = currentLang === LangType.ZH_CN; export const isTR = currentLang === LangType.TR_TR; export const isJA = currentLang === LangType.JA_JP; const langSet: Record = locale[currentLang]; function i18n(key: keyof typeof zhCN, ...args: any[]) { let result = langSet[key]; if (result === undefined) { return `[${key}]`; } else { args.forEach((arg, i) => { result = result.replace(new RegExp(`\\{${i + 1}\\}`, 'g'), arg); }); if (args.length) { result = result.replace(/\{(.+?)\|(.+?)\}/g, (_, singular, plural) => { const n = args[0]; return n == 1 ? singular : plural; }); } return result; } } function i18nElement(key: keyof typeof zhCN, ...args: React.ReactNode[]) { const str = langSet[key]; if (str === undefined) { return `[${key}]`; } else { const result: React.ReactNode[] = []; str.split(/(\{\d\})/).forEach((item, i) => { if (/^\{\d\}$/.test(item)) { result.push( {args[parseInt(item.substring(1, item.length - 1)) - 1]} , ); } else { result.push( {item.replace(/\{(.+?)\|(.+?)\}/g, (_, singular, plural) => { const n = args[0]; return n == 1 ? singular : plural; })} , ); } }); return result; } } export default i18n; export { i18n, i18nElement }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/chat.ts ================================================ export default { 'chat.input.remain': '残り {1} 回', 'chat.input.tableSelect.placeholder': 'テーブルを選択してください', 'chat.input.tableSelect.error.TooManyTable': '最大で8つのテーブルを選択できます', 'chat.input.remain.dialog.tips': '公式アカウントをフォローし、"プロモーション"を送信して体験回数を増やす', 'chat.input.syncTable.tips': 'すべてのテーブル構造をAIコンテキストに自動同期(Chat2DBAIモデルのみで使用可能、グループ内でグループオーナーに連絡し、Chat2DBAIのホワイトリストに申請)', 'chat.input.remain.tooltip': '手動で選択したテーブルの構造はAIコンテキストに同期されます', 'chat.input.syncTable.tempTips': '🎉リリース:すべてのテーブル構造をAIコンテキストに自動同期', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/common.ts ================================================ export default { 'common.text.no': 'いいえ', 'common.text.is': 'は', 'common.button.affirm': '確認する', 'common.button.edit': '編集', 'common.button.modify': '修正', 'common.button.confirm': '確認', 'common.button.cancel': 'キャンセル', 'common.data.hour': '{1}時間', 'common.data.minute': '{1}分', 'common.tip.yesterday': '昨日{1}', 'common.tip.tomorrow': '明日{1}', 'common.tip.ago': '前', 'common.tip.later': '後', 'common.tip.now': '今', 'common.tip.justNow': 'ちょうど今', 'common.text.search': '検索', 'common.placeholder.select': '{1}を選択してください', 'common.text.serviceStarting': 'サービス開始中...', 'common.text.serviceFail': 'サービスの開始に失敗しました', 'common.text.column': '列', 'common.text.row': '行', 'common.text.indexes': 'インデックス', 'common.button.save': '保存', 'common.button.open': '開く', 'common.button.refresh': '更新', 'common.button.execute': '実行', "common.button.import": 'SQLをインポート', 'common.button.format': 'フォーマット', 'common.message.successfulConfig': '設定成功', 'common.text.successful': '成功', 'common.text.failure': '失敗', 'common.message.modifySuccessfully': '修正成功', 'common.message.addedSuccessfully': '追加成功', 'common.text.custom': 'カスタム', 'common.button.delete': '削除', 'common.text.executionResult': '結果 {1}', 'common.tips.deleteTable': 'このテーブルを削除してもよろしいですか?', 'common.text.tableName': 'テーブル名', 'common.text.submittedSuccessfully': '正常に送信されました', 'common.text.successfullyDelete': '削除成功', 'common.text.explainSQL': 'SQLを説明する', 'common.text.optimizeSQL': 'SQLを最適化する', 'common.text.conversionSQL': 'SQLを変換する', 'common.text.table': 'テーブル', 'common.tips.saveSuccessfully': '保存成功', 'common.button.copy': 'コピー', 'common.button.copyName': '名前をコピー', 'common.button.copySuccessfully': 'コピー成功', 'common.button.createConsole': '新しいコンソールを作成', 'common.button.exportWord': 'Wordにエクスポート', 'common.button.exportExcel': 'Excelにエクスポート', 'common.button.exportHtml': 'Htmlにエクスポート', 'common.button.exportMarkdown': 'Markdownにエクスポート', 'common.button.exportPdf': 'Pdfにエクスポート', 'common.text.successfulExecution': '実行成功', 'common.text.result': '結果', 'common.text.timeConsuming': '時間がかかる', 'common.text.searchRow': '検索行数', 'common.text.noData': 'データなし', 'common.text.remindMeLater': '後でリマインド', 'common.text.goToUpdate': '更新に進む', 'common.text.updateReminder': '更新リマインダー', 'common.text.detectionLatestVersion': '最新バージョンを検出', 'common.text.setting': '設定', 'common.text.tryToRestart': '再起動を試みる', 'common.text.contactUs': 'お問い合わせ', 'common.text.wechatPopularizeAi': 'WeChat公式アカウントをフォローし、「AI」を送信して無料体験を取得。', 'common.text.wechatPopularizeAi2': 'WeChat公式アカウントをフォローし、「AI」を送信して無料でApiKeyを取得し、体験回数をプレゼント。', 'common.text.wechatPopularize': '「プロモーション」を送信して、さらに無料体験を取得。', 'common.text.export': 'エクスポート', 'common.notification.detail': '詳細を見る', 'common.notification.solution': '解決策', 'common.button.copyError': 'エラーレポートをコピー', 'common.button.copyErrorTips': '(ここではAPI情報と詳細なパラメータがコピーされます。機密情報がある場合は、JSONを解析してから送信してください)', 'common.tips.formatError': 'フォーマット失敗、SQLが正しいかどうかを確認してください', 'common.text.executeSelectedSQL': '選択したSQLを実行', 'common.text.refreshPage': 'ページを更新', 'common.text.saveConsole': 'コンソールを保存', 'common.text.textToSQL': 'プレーンテキストをSQLに変換', 'common.text.editorRightClick': 'エディタの右クリック', 'common.form.error.required': '必須項目です!', 'common.form.error.email': '正しいメール形式ではありません', 'common.tips.delete.confirm': '削除してもよろしいですか?', 'common.tips.updateSuccess': '更新成功', 'common.tips.createSuccess': '作成成功', 'common.text.action': '操作', 'common.button.add': '追加', 'common.text.errorMessage': 'エラーメッセージ', 'common.button.cancelRequest': 'リクエストをキャンセル', 'common.button.executionError': '実行エラー', 'common.text.affectedRows': '影響を受けた行:{1}', 'common.text.selectFile' : 'ファイルを選択', 'common.text.noTableFoundUp' : '現在のデータベースにテーブルが見つかりません', 'common.text.noTableFoundDown' : 'データベースを切り替えることができます', 'common.text.updateNow' : 'すぐに更新', 'common.title.preview' : 'プレビュー', 'common.title.errorMessage': 'エラーメッセージ', 'common.label.comment': 'コメント', 'common.label.name': '名前', 'common.title.create': '作成', 'common.title.executiveLogging': '実行ログ', 'common.text.executionTime': '{1}ms 完了', 'common.button.copyRowAs': '行をコピー', 'common.button.insertSql': 'Insert文', 'common.button.updateSql': 'Update文', 'common.button.tabularSeparatedValues': 'タブ区切り値', 'common.button.tabularSeparatedValuesFieldName': 'タブ区切り値(フィールド名)', 'common.button.tabularSeparatedValuesFieldNameAndData': 'タブ区切り値(フィールド名とデータ)', 'common.button.cloneRow': '行を複製', 'common.button.deleteRow': '行を削除', 'common.button.setNull': 'NULLに設定', 'common.button.setDefault': 'デフォルトに設定', 'common.button.viewData': 'データを見る/修正する', 'common.button.close': '閉じる', 'common.button.closeAll': 'すべて閉じる', 'common.button.closeOthers': '他を閉じる', 'common.label.tcp': 'オンライン', 'common.label.LocalFile': 'ローカルファイル', 'common.text.rename': '名前を変更', 'common.title.info': '情報', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/connection.ts ================================================ export default { 'connection.title': 'データソース', 'connection.title.connections': '接続', 'connection.title.createConnection': 'データソースを作成', 'connection.title.editConnection': 'データソースを編集', 'connection.title.importConnection': 'データソースをインポート', 'connection.label.name': '名前', 'connection.label.host': 'ホスト', 'connection.label.authentication': '認証', 'connection.label.database': 'データベース', 'connection.label.JDBCDrive': 'JDBCドライバ', 'connection.label.port': 'ポート', 'connection.button.testConnection': '接続をテスト', 'connection.label.advancedConfiguration': '高度な設定', 'connection.label.sshConfiguration': 'SSH', 'connection.button.addConnection': '接続を追加', 'connection.button.connect': '接続', 'connection.button.remove': '接続を削除', 'connection.message.testConnectResult': '接続テスト{1}', 'connection.message.testSshConnection': 'SSH接続をテスト', 'connection.tableHeader.name': '名前', 'connection.tableHeader.value': '値', 'connection.title.uploadDriver': 'アップロード', 'connection.tips.customUpload': 'ドライバをアップロード', 'connection.title.driver': 'ドライバ', 'connection.button.clickUpload': 'クリックしてアップロード', 'connection.text.downloadDriver': 'ドライバをダウンロード', 'connection.text.downloadSuccess': 'ダウンロード成功', 'connection.text.tryAgainDownload': '再ダウンロードを試みる', 'connection.text.downloading': 'ダウンロード中...', 'connection.label.private': 'プライベート', 'connection.label.shared': '共有', 'connection.button.createConnection': '接続を作成', 'connection.tips.noConnection': '現在、接続は作成されていません', 'connection.tips.noConnectionTips': 'この接続の詳細を表示する権限がありませんが、直接接続することはできます', 'connection.title.importTitle': 'ファイルをインポート, .ncx(navicat) または .dbp(dbever)', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/dashboard.ts ================================================ export default { "dashboard.title": "ダッシュボード", "dashboard.edit": "編集", "dashboard.modal.editTitle": "ダッシュボードの編集", "dashboard.modal.addTitle": "ダッシュボードの追加", "dashboard.modal.name.placeholder": "ダッシュボード名を入力してください", "dashboard.delete": "削除", "dashboard.export2image": "画像への出力", "dashboard.editor.cascader.placeholder": "接続を選択してください", "dashboard.editor.execute.noDataSource": "まず、データソースを選択してください", "dashboard.editor.execute.success": "実行成功。チャートの設定を選択してください" } ================================================ FILE: chat2db-client/src/i18n/ja-jp/editSequence.ts ================================================ export default { 'editSequence.button.createSequence': '新規シーケンス', 'editSequence.button.editSequence': 'シーケンスの編集', 'editSequence.label.comment': 'コメント', 'editSequence.label.relname': 'シーケンス名', 'editSequence.label.typname': 'データ型', 'editSequence.label.seqcache': 'キャッシュ', 'editSequence.label.rolname': '所有者', 'editSequence.label.seqstart': '開始値', 'editSequence.label.seqincrement': '増分値', 'editSequence.label.seqmax': '最大値', 'editSequence.label.seqmin': '最小値', 'editSequence.label.seqcycle': '周期', 'editSequence.title.sqlPreview': 'SQLプレビュー', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/editTable.ts ================================================ export default { 'editTable.tab.basicInfo': '基本情報', 'editTable.tab.columnInfo': '列情報', 'editTable.tab.indexInfo': 'インデックス情報', 'editTable.label.tableName': 'テーブル名', 'editTable.label.comment': 'コメント', 'editTable.button.add': '追加', 'editTable.button.delete': '削除', 'editTable.button.up': '上に移動', 'editTable.button.down': '下に移動', 'editTable.label.indexName': 'インデックス名', 'editTable.label.indexType': 'インデックスタイプ', 'editTable.label.indexMethod': 'インデックス方法', 'editTable.label.includeColumn': '含まれる列', 'editTable.button.createTable': 'テーブルの作成', 'editTable.button.importTable': 'テーブルのインポート', 'editTable.label.index': '番号', 'editTable.label.columnName': '列名', 'editTable.label.columnSize': 'サイズ', 'editTable.label.columnType': 'タイプ', 'editTable.label.nullable': 'NULL可能', 'editTable.label.prefixLength': '接頭辞の長さ', 'editTable.label.defaultValue': 'デフォルト値', 'editTable.label.sparse': 'スパース', 'editTable.label.characterSet': '文字セット', 'editTable.label.collation': '照合順序', 'editTable.label.decimalPoint': '小数点', 'editTable.label.unit': '単位', 'editTable.label.value': '値', 'editTable.label.autoIncrement': '自動増分', 'editTable.label.engine': 'エンジン', 'editTable.label.incrementValue': '増分値', 'editTable.label.order': '順序', 'editTable.label.primaryKey': '主キー', 'editTable.title.sqlPreview': 'SQLプレビュー', 'editTable.button.addColumn': '列を追加', 'editTable.button.addIndex': 'インデックスを追加', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/editTableData.ts ================================================ export default { 'editTableData.tips.addRow': '行を追加', 'editTableData.tips.deleteRow': '行を削除', 'editTableData.tips.revert': '取り消す', 'editTableData.tips.previewPendingChanges': '保留中の変更をプレビュー', 'editTableData.tips.submit': '変更を提出', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/index.ts ================================================ import { LangType } from '@/constants' import menu from './menu'; import common from './common'; import connection from './connection'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' import editSequence from './editSequence'; export default { lang: LangType.ZH_CN, ...connection, ...common, ...setting, ...workspace, ...menu, ...connection, ...dashboard, ...chat, ...team, ...login, ...editTable, ...editTableData, ...sqlEditor, ...editSequence }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/login.ts ================================================ export default { 'login.text.logout': 'ログアウト', 'login.text.welcome': 'Chat2DBをご利用いただき、ありがとうございます', 'login.text.tips': 'Chat2DBのアカウントはチームの協力と管理のためにのみ使用されます', 'login.text.tips.title': 'なぜログインが必要ですか?', 'login.text.setting': '設定', 'login.form.user': 'ユーザー名', 'login.form.user.placeholder': 'ユーザー名を入力してください', 'login.form.password': 'パスワード', 'login.form.password.placeholder': 'パスワードを入力してください', 'login.button.login': 'ログイン', 'login.tips.defaultPassword': 'デフォルトのユーザー名とパスワードは両方とも chat2db です', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/menu.ts ================================================ export default { 'menu.file': 'File' } ================================================ FILE: chat2db-client/src/i18n/ja-jp/setting.ts ================================================ export default { 'setting.title.setting': '設定', 'setting.nav.basic': '基本設定', 'setting.nav.customAi': 'カスタムAI', 'setting.nav.proxy': 'サーバーアドレス', 'setting.nav.aboutUs': '私たちについて', 'setting.title.backgroundColor': '背景色', 'setting.title.themeColor': 'テーマカラー', 'setting.title.sqlEditorFontSize': 'SQLエディタのフォントサイズ', 'setting.label.blue': 'ブルー', 'setting.label.green': 'グリーン', 'setting.label.violet': 'バイオレット', 'setting.text.dark': 'ダーク', 'setting.text.dark2': 'ダーク2', 'setting.text.light': 'ライト', 'setting.text.followOS': '自動', 'setting.title.language': '言語', 'setting.title.aiSource': 'AIのソース', 'setting.tab.custom': 'カスタム', 'setting.tab.aiType.zhipu': '智譜', 'setting.tab.aiType.baichuan': '百川', 'setting.tab.aiType.wenxin': '文心一言', 'setting.tab.aiType.tongyiqianwen': '通義千問', 'setting.tab.aiType.custom.tips': 'インターフェース形式はOpenAIのものと互換性があります', 'setting.label.serviceAddress': 'サービスアドレス', 'setting.button.apply': '適用', 'setting.text.currentEnv': '現在の環境', 'setting.text.currentVersion': '現在のバージョン', 'setting.text.viewingUpdateLogs': '更新ログを見る', 'setting.label.isStreamOutput': 'ストリーム出力を行うか', 'setting.label.customAiUrl': 'カスタムAIのURL', 'setting.placeholder.httpsProxy': '任意項目、OpenAIインターフェースをリクエストする際のHTTPプロキシを設定するためのもの{1}', 'setting.placeholder.apiKey': 'OpenAIインターフェースを使用する場合は必須です。APIキーはOpenAI公式サイトで確認できます', 'setting.placeholder.chat2dbApiKey': 'Chat2DBが提供するAPIキーを使用します', 'setting.placeholder.customUrl': 'カスタムAIを選択する場合は必須で、カスタムAIのRESTインターフェースのURLを設定します', 'setting.placeholder.apiHost': '任意項目、デフォルト値はhttps://api.openai.com/', 'setting.message.urlTestError': 'インターフェースのテストに合格しません', 'setting.placeholder.azureOpenAIKey': 'AzureポータルからAzure OpenAIキーを取得します', 'setting.placeholder.azureEndpoint': 'AzureポータルからAzure OpenAIエンドポイントを取得します', 'setting.placeholder.azureDeployment': 'モデルのデプロイIDを指定します', 'setting.ai.tips': 'AI設定を選択するにはログインしてください', 'setting.ai.user.hidden': '管理者に連絡して、「設定->カスタムAI」でAPIキーを設定してください', 'setting.button.startDownloading': 'ダウンロード開始', 'setting.button.beDownloading': 'ダウンロード中', 'setting.button.redownload': '再ダウンロード', 'setting.button.restart': 'すぐに再起動', 'setting.text.discoverNewVersion': '新しいバージョン {1} が見つかりました', 'setting.text.isLatestVersion': '最新バージョンです', 'setting.button.changeLog': '更新ログを見る', 'setting.title.updateRule': '更新ルール', 'setting.text.autoUpdate': '新しいバージョンを自動でダウンロードしてインストールする', 'setting.text.manualUpdate': '新しいバージョンがリリースされたときにのみ通知する', 'setting.button.iSee': '了解しました', 'setting.text.newEditionIsReady': '新しいバージョンがダウンロードされました。ソフトウェアを再起動すると新しいバージョンがインストールされます', 'setting.button.goToUpdate': '更新に進む', 'setting.text.UpdatedLatestVersion': '最新バージョン {1} に更新しました', 'setting.title.holdingService': 'サービスを維持', 'setting.text.holdingService': 'アプリケーションを終了してもサービスを維持し、起動速度を向上させます', 'setting.chat2db.ai.button': 'より強力なAI機能を体験するために、Chat2DB Proをご利用ください', 'setting.title.goto.chat2db.pro': 'Chat2DB Proへ行く', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/sqlEditor.ts ================================================ export default { 'sqlEditor.text.keyword': 'キーワード', 'sqlEditor.text.function': '関数', 'sqlEditor.text.tableName': 'テーブル名', 'sqlEditor.text.databaseName': 'データベース名', 'sqlEditor.text.schemaName': 'スキーマ名', 'sqlEditor.text.viewName': 'ビュー名', 'sqlEditor.text.fieldName': 'フィールド名', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/team.ts ================================================ export default { 'team.title': 'チーム管理', 'team.tab.datasource': 'リンク管理', 'team.tab.user': 'ユーザー管理', 'team.tab.team': 'チーム管理', 'team.action.rightManagement': '権限管理', 'team.action.editDatasource': 'リンクの編集', 'team.action.addDatasource': 'リンクの追加', 'team.action.addDatasource.placeholder': 'リンクを検索', 'team.action.editUser': 'ユーザーの編集', 'team.action.addUser': 'ユーザーの追加', 'team.action.addUser.placeholder': 'ユーザーを検索', 'team.action.editTeam': 'チームの編集', 'team.action.addTeam': 'チームの追加', 'team.action.addTeam.placeholder': 'チームを検索', 'team.action.affiliation.user': '含まれるユーザー', 'team.action.affiliation.team': '所属チーム', 'team.action.affiliation.datasource': '所属リンク', 'team.action.addUserAndTeam': 'ユーザー/チームの追加', 'team.action.addUserAndTeam.placeholder': '人員/チームを検索', 'team.input.search.placeholder': 'キーワードを入力して検索', 'team.datasource.rightManagement': '権限管理', 'team.datasource.alias': 'リンク名', 'team.datasource.url': 'リンクアドレス', 'team.datasource.code': 'コード', 'team.datasource.name': '名前', 'team.datasource.status': 'ステータス', 'team.user.name': 'ユーザー', 'team.user.userName': 'ユーザー名', 'team.user.nickName': 'ニックネーム', 'team.user.status': 'ステータス', 'team.user.addForm.userName': 'ユーザー名', 'team.user.addForm.nickName': 'ニックネーム', 'team.user.addForm.email': 'メールアドレス', 'team.user.addForm.password': 'パスワード', 'team.user.addForm.roleCode': '権限', 'team.user.addForm.roleCode.admin': '管理者', 'team.user.addForm.roleCode.user': 'ユーザー', 'team.user.addForm.status': 'ステータス', 'team.user.addForm.status.valid': '有効', 'team.user.addForm.status.invalid': '無効', 'team.team.name': 'チーム', 'team.team.addForm.code': 'コード', 'team.team.addForm.name': '名前', 'team.team.addForm.status': 'ステータス', 'team.team.addForm.status.valid': '有効', 'team.team.addForm.status.invalid': '無効', 'team.team.addForm.description': '説明', }; ================================================ FILE: chat2db-client/src/i18n/ja-jp/workspace.ts ================================================ export default { 'workspace.title': 'ワークスペース', 'workspace.cascader.placeholder': '選択してください', 'workspace.ai.input.placeholder': 'ここにプレーンテキストを入力してください', 'workspace.title.savedConsole': '保存されたコンソール', 'workspace.menu.ViewDDL': 'DDLを見る', 'workspace.menu.deleteTable': 'テーブルを削除', 'workspace.menu.openTable': 'テーブルを開く', 'workspace.menu.editTable': 'テーブルを編集', 'workspace.menu.view': '見る', 'workspace.menu.pin': 'ピン', 'workspace.menu.unPin': 'ピンを外す', 'workspace.menu.editTableData': 'テーブルデータを編集', 'workspace.menu.queryConsole': '新しいクエリ', 'workspace.menu.viewAllTable': 'すべてのテーブルを見る', 'workspace.menu.createDatabase': 'データベースを作成', 'workspace.menu.createSchema': 'スキーマを作成', 'workspace.menu.deleteTablePlaceHolder': '削除するテーブル名を入力してください', 'workspace.menu.createSequence': 'シーケンスを作成', 'workspace.menu.editSequence': 'シーケンスを編集', 'workspace.menu.deleteSequence': 'シーケンスを削除', 'workspace.tips.affirmDeleteTable': '入力したテーブル名と削除するテーブル名が一致しません。再確認してください', 'workspace.table.total': '合計', 'workspace.table.total.tip': '合計行数をロード', 'workspace.table.export.all.csv': '結果セットをcsvでエクスポート', 'workspace.table.export.cur.csv': '現在のページの結果セットをcsvでエクスポート', 'workspace.table.export.all.insert': '結果セットをinsert sqlでエクスポート', 'workspace.table.export.cur.insert': '現在のページの結果セットをinsert sqlでエクスポート', 'workspace.tree.view': 'ビュー', 'workspace.tree.trigger': 'トリガー', 'workspace.tree.function': '関数', 'workspace.tree.procedure': 'ストアドプロシージャ', 'workspace.tree.search.placeholder': '展開されたノードで検索', 'workspace.tree.delete.tip': 'この操作は永久的に削除されることを理解しています', 'workspace.tree.delete.table.tip': 'テーブル{1}を削除してもよろしいですか?', 'workspace.tips.noConnection': 'まだ接続が作成されていません', 'workspace.tips.maxConsole': 'コンソールは最大20個まで開くことができます', 'workspace.tips.openExecutiveLogging': '実行ログを開く', 'workspace.tree.delete.sequence.tip': 'シーケンス{1}を削除してもよろしいですか?', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/chat.ts ================================================ export default { 'chat.input.remain': 'Kalan {1}', 'chat.input.tableSelect.placeholder': 'Lütfen tabloları seçin', 'chat.input.tableSelect.error.TooManyTable': 'En fazla 8 tablo seçebilirsiniz', 'chat.input.remain.dialog.tips': 'Resmi WeChat hesabımıza abone olun, daha fazla deneyim şansı için 推广 gönderin.', 'chat.input.syncTable.tips': 'Otomatik olarak tüm tablo yapılarını AI bağlamına senkronize eder', 'chat.input.remain.tooltip': 'Manuel olarak seçilen tablo, AI bağlamına senkronize edilecektir', 'chat.input.syncTable.tempTips': '🎉Güncelleme: Otomatik olarak tüm tablo yapılarını AI bağlamına senkronize etme', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/common.ts ================================================ export default { 'common.text.no': 'hayır', 'common.text.is': 'dir', 'common.button.affirm': 'Onayla', 'common.button.edit': 'Düzenle', 'common.button.modify': 'Değiştir', 'common.button.confirm': 'Onayla', 'common.button.cancel': 'İptal', 'common.data.hour': '{1} {saat|saat}', 'common.data.minute': '{1} {dakika|dakika}', 'common.tip.yesterday': '{1} dün', 'common.tip.tomorrow': '{1} yarın', 'common.tip.ago': ' önce', 'common.tip.later': ' sonra', 'common.tip.now': 'Şimdi', 'common.tip.justNow': 'Şu an', 'common.text.search': 'arama', 'common.placeholder.select': 'Lütfen Seçin {1}', 'common.text.serviceStarting': 'Hizmet Başlatılıyor ...', 'common.text.serviceFail': 'Hizmet başlatma başarısız oldu. Lütfen sayfayı yeniden yenilemeyi deneyin...', 'common.text.column': 'kolon', 'common.text.row': 'satır', 'common.text.indexes': 'indeksler', 'common.button.save': 'Kaydet', 'common.button.open': 'Aç', 'common.button.refresh': 'Yenile', 'common.button.execute': 'Çalıştır', "common.button.import": 'SQL İçe Aktar', 'common.button.format': 'Biçimlendir', 'common.message.successfulConfig': 'Başarılı yapılandırma', 'common.text.successful': 'başarılı', 'common.text.failure': 'başarısızlık', 'common.message.modifySuccessfully': 'başarıyla değiştirildi', 'common.message.addedSuccessfully': 'başarıyla eklendi', 'common.text.custom': 'özel', 'common.button.delete': 'Sil', 'common.text.executionResult': 'Sonuç {1}', 'common.tips.deleteTable': 'Bu Tabloyu Silmek İstediğinizden Emin Misiniz?', 'common.text.tableName': 'Tablo Adı', 'common.text.submittedSuccessfully': 'Başarıyla Gönderildi', 'common.text.successfullyDelete': 'Başarıyla Silindi', 'common.text.explainSQL': 'SQL Açıkla', 'common.text.optimizeSQL': 'SQL Optimizasyonu', 'common.text.conversionSQL': 'SQL Dönüşümü', 'common.text.table': 'Tablo', 'common.tips.saveSuccessfully': 'Başarıyla Kaydedildi', 'common.button.copy': 'Kopyala', 'common.button.copyName': 'Adı Kopyala', 'common.button.copySuccessfully': 'Başarıyla Kopyalandı', 'common.button.createConsole': 'Konsol Oluştur', 'common.button.exportWord': 'Word\'e Aktar', 'common.button.exportExcel': 'Excel\'e Aktar', 'common.button.exportHtml': 'Html\'e Aktar', 'common.button.exportMarkdown': 'Markdown\'a Aktar', 'common.button.exportPdf': 'Pdf\'e Aktar', 'common.text.successfulExecution': 'Başarılı İşlem', 'common.text.result': 'Sonuç', 'common.text.timeConsuming': 'Zaman Harcama', 'common.text.searchRow': 'Satır Ara', 'common.text.noData': 'Veri Yok', 'common.text.remindMeLater': 'Beni Sonra Hatırlat', 'common.text.goToUpdate': 'Güncellemeye Git', 'common.text.updateReminder': 'Güncelleme Hatırlatıcısı', 'common.text.detectionLatestVersion': 'Son sürüm izleniyor', 'common.text.setting': 'Ayar', 'common.text.tryToRestart': 'Yeniden Başlatmayı Deneyin', 'common.text.contactUs': 'Bize Ulaşın', 'common.text.wechatPopularizeAi': 'WeChat resmi hesabımızı takip edin, "AI" göndererek ücretsiz deneyimler alın.', 'common.text.wechatPopularizeAi2': 'WeChat resmi hesabımızı takip edin, "AI" göndererek ücretsiz ApiKey alın ve deneyim sayısını hediye edin.', 'common.text.wechatPopularize': 'Daha fazla ücretsiz deneyim almak için "promotion" gönderebilirsiniz.', 'common.text.export': 'Dışa Aktar', 'common.notification.detail': 'Daha fazla detay', 'common.notification.solution': 'Çözüm', 'common.button.copyError': 'Hata raporunu kopyala', 'common.button.copyErrorTips': '(Arayüz bilgileri ve ayrıntılı parametreler buraya kopyalanacaktır. Hassas parametreler varsa önce JSON ayrıştırın ve sonra gönderin)', 'common.tips.formatError': 'Biçimlendirme başarısız, sql\'in doğru olup olmadığını kontrol edin', 'common.text.executeSelectedSQL': 'Seçilen SQL\'i Çalıştır', 'common.text.refreshPage': 'Sayfayı Yenile', 'common.text.saveConsole': 'Konsolu Kaydet', 'common.text.textToSQL': 'Düz metinleri SQL\'e', 'common.text.editorRightClick': 'Düzenleyici sağ tıklama', 'common.form.error.required': 'Bu alan zorunludur!', 'common.form.error.email': 'Girdi geçerli bir e-posta değil!', 'common.tips.delete.confirm': 'Emin misiniz silmek için?', 'common.tips.updateSuccess': 'Başarıyla Güncellendi', 'common.tips.createSuccess': 'Başarıyla Oluşturuldu', 'common.text.action': 'Eylem', 'common.button.add': 'Ekle', 'common.text.errorMessage': 'Hata Mesajı', 'common.button.cancelRequest': 'İsteği İptal Et', 'common.button.executionError': 'Çalıştırma Hatası', 'common.text.affectedRows': 'Etkilenen satırlar: {1}', 'common.text.selectFile' : 'Dosya Seç', 'common.text.noTableFoundUp' : 'Bu veritabanında tablo bulunmamaktadır', 'common.text.noTableFoundDown': 'Üstte veritabanını değiştirin', 'common.title.preview': 'Önizleme', 'common.title.errorMessage': 'Hata mesajı', 'common.label.comment': 'Yorum', 'common.label.name': 'Ad', 'common.title.create': 'Oluştur', 'common.title.executiveLogging': 'Yürütme Günlüğü', 'common.text.executionTime': '{1} ms içinde etkilendi', 'common.button.copyRowAs': 'Satırı Kopyala', 'common.button.insertSql': 'SQL Ekle', 'common.button.updateSql': 'SQL Güncelle', 'common.button.tabularSeparatedValues': 'TAB sınırlı (veri)', 'common.button.tabularSeparatedValuesFieldName': 'TAB sınırlı (alan adı)', 'common.button.tabularSeparatedValuesFieldNameAndData': 'TAB ayrılmış (alan adları ve veri)', 'common.button.cloneRow': 'Satırı Kopyala', 'common.button.deleteRow': 'Satırı Sil', 'common.button.setNull': 'NULL Ayarla', 'common.button.setDefault': 'DEFAULT Ayarla', 'common.button.viewData': 'Veriyi Görüntüle/Düzenle', 'common.button.close': 'Kapat', 'common.button.closeAll': 'Tümünü Kapat', 'common.button.closeOthers': 'Diğerlerini Kapat', 'common.label.tcp': 'TCP', 'common.label.LocalFile': 'Yerel Dosya', 'common.text.rename': 'Yeniden Adlandır', 'common.title.info': 'Bilgi', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/connection.ts ================================================ export default { 'connection.title': 'Bağlantılar', 'connection.title.connections': 'Bağlantılar', 'connection.title.createConnection': 'Yeni Bağlantı', 'connection.title.editConnection': 'Bağlantıyı Düzenle', 'connection.title.importConnection': 'Bağlantı İçe Aktar', 'connection.label.name': 'ad', 'connection.label.host': 'ana bilgisayar', 'connection.label.authentication': 'doğrulama', 'connection.label.database': 'veritabanı', 'connection.label.JDBCDrive': 'JDBC Sürücüsü', 'connection.label.port': 'port', 'connection.button.testConnection': 'Test Et', 'connection.label.advancedConfiguration': 'Gelişmiş Yapılandırma', 'connection.label.sshConfiguration': 'SSH Yapılandırma', 'connection.button.addConnection': 'Bağlantı Ekle', 'connection.button.connect': 'Bağlan', 'connection.button.remove': 'Kaldır', 'connection.message.testConnectResult': 'Test bağlantısı {1}', 'connection.message.testSshConnection': 'SSH bağlantısını test et', 'connection.tableHeader.name': 'Adı', 'connection.tableHeader.value': 'Değer', 'connection.title.uploadDriver': 'Sürücü Yükle', 'connection.tips.customUpload': "Sürücüyü yükle", 'connection.title.driver': 'Sürücü', 'connection.button.clickUpload': 'Yükleme için Tıklayın', 'connection.text.downloadDriver': 'Sürücü İndir', 'connection.text.downloadSuccess': 'İndirme Başarılı', 'connection.text.tryAgainDownload': 'Yeniden İndir', 'connection.text.downloading': 'İndiriliyor...', 'connection.label.private': 'Özel', 'connection.label.shared': 'Paylaşılan', 'connection.button.createConnection': 'Bağlantı Oluştur', 'connection.tips.noConnection': 'Henüz hiç bağlantı oluşturmadınız', 'connection.tips.noConnectionTips': 'Bağlantı ayrıntılarını görüntüleme izniniz yok, ancak bağlantıya doğrudan bağlanabilirsiniz', 'connection.title.importTitle': 'Dosya İçe Aktar,.ncx(navicat) veya.dbp(dbever)', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/dashboard.ts ================================================ export default { 'dashboard.title': 'Kontrol Paneli', 'dashboard.edit': 'Düzenle', 'dashboard.modal.editTitle': 'Kontrol Panelini Düzenle', 'dashboard.modal.addTitle': 'Kontrol Paneli Ekle', 'dashboard.modal.name.placeholder': "Lütfen kontrol paneli adını girin.", 'dashboard.export2image': 'Resme Aktar', 'dashboard.delete': 'Sil', 'dashboard.editor.cascader.placeholder': 'Lütfen bir bağlantı havuzu seçin', 'dashboard.editor.execute.noDataSource': 'Lütfen önce bir veri kaynağı seçin', 'dashboard.editor.execute.success': 'Başarılı, Lütfen Grafik Yapılandırmasını Seçin', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/editSequence.ts ================================================ export default { 'editSequence.button.createSequence': 'Sıradan oluştur', 'editSequence.button.editSequence': 'Sıralamayı Düzenle', 'editSequence.label.comment': 'Yorum', 'editSequence.label.relname': 'Sıra Adı', 'editSequence.label.typname': 'Veri Türü', 'editSequence.label.seqcache': 'Önbelleğe Alma', 'editSequence.label.rolname': 'Sahibi', 'editSequence.label.seqstart': 'Başlangıç Değeri', 'editSequence.label.seqincrement': 'Artış Değeri', 'editSequence.label.seqmax': 'En Büyük Değer', 'editSequence.label.seqmin': 'En Küçük Değer', 'editSequence.label.seqcycle': 'bisiklet', 'editSequence.title.sqlPreview': 'SQL Önizleme', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/editTable.ts ================================================ export default { 'editTable.tab.basicInfo': 'Temel Bilgi', 'editTable.tab.columnInfo': 'Kolon', 'editTable.tab.indexInfo': 'İndeks', 'editTable.label.tableName': 'Tablo adı', 'editTable.label.comment': 'Yorum', 'editTable.button.add': 'Ekle', 'editTable.button.delete': 'Sil', 'editTable.button.up': 'Yukarı', 'editTable.button.down': 'Aşağı', 'editTable.label.indexName': 'Adı', 'editTable.label.indexType': 'Türü', 'editTable.label.indexMethod': 'İndeks yöntemi', 'editTable.label.includeColumn': 'Kolonu içerir', 'editTable.button.createTable': 'Tablo Oluştur', 'editTable.button.importTable': 'Tabloyu İçe Aktar', 'editTable.label.index': 'İndeks', 'editTable.label.columnName': 'Adı', 'editTable.label.columnSize': 'Boyutu', 'editTable.label.columnType': 'Türü', 'editTable.label.nullable': 'Boş bırakılabilir', 'editTable.label.prefixLength': 'Önek uzunluğu', 'editTable.label.defaultValue': 'Varsayılan değer', 'editTable.label.sparse': 'Düzensiz', 'editTable.label.characterSet': 'Karakter kümesi', 'editTable.label.collation': 'Düzenleme', 'editTable.label.decimalPoint': 'Ondalık nokta', 'editTable.label.unit': 'Birim', 'editTable.label.value': 'Değer', 'editTable.label.autoIncrement': 'Otomatik artır', 'editTable.label.engine': 'Motor', 'editTable.label.incrementValue': 'Artış değeri', 'editTable.label.order': 'Sıra', 'editTable.label.primaryKey': 'Anahtar', 'editTable.title.sqlPreview': 'SQL önizleme', 'editTable.button.addColumn': 'Kolon Ekle', 'editTable.button.addIndex': 'İndeks Ekle', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/editTableData.ts ================================================ export default { 'editTableData.tips.addRow': 'Satır Ekle', 'editTableData.tips.deleteRow': 'Satırı Sil', 'editTableData.tips.revert': 'Geri Al', 'editTableData.tips.previewPendingChanges': 'Bekleyen Değişiklikleri Önizle', 'editTableData.tips.submit': 'Gönder', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/index.ts ================================================ import common from './common'; import connection from './connection'; import menu from './menu'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' import editSequence from './editSequence'; export default { lang: 'tr', ...common, ...setting, ...connection, ...workspace, ...menu, ...dashboard, ...chat, ...team, ...login, ...editTable, ...editTableData, ...sqlEditor, ...editSequence }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/login.ts ================================================ export default { 'login.text.logout': 'Çıkış Yap', 'login.text.welcome': 'Chat2DB\'ye Hoş Geldiniz', 'login.text.tips': 'Chat2DB hesabı yalnızca takım işbirliği yönetimi içindir.', 'login.text.tips.title': 'Neden giriş yapmalı?', 'login.text.setting': 'Ayarlar', 'login.form.user': 'Kullanıcı Adı', 'login.form.user.placeholder': 'Lütfen kullanıcı adınızı girin', 'login.form.password': 'Şifre', 'login.form.password.placeholder': 'Lütfen şifrenizi girin', 'login.button.login': 'Giriş Yap', 'login.tips.defaultPassword': 'Varsayılan kullanıcı adı ve şifre: chat2db', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/menu.ts ================================================ export default { 'menu.file' : 'Dosya' } ================================================ FILE: chat2db-client/src/i18n/tr-tr/setting.ts ================================================ export default { 'setting.title.setting': 'Ayarlar', 'setting.nav.basic': 'Temel', 'setting.nav.customAi': 'Özel AI', 'setting.nav.proxy': 'Hizmet Yolu', 'setting.nav.aboutUs': 'Hakkımızda', 'setting.title.backgroundColor': 'Arka Plan Rengi', 'setting.title.themeColor': 'Tema Rengi', 'setting.title.sqlEditorFontSize': 'SQL Düzenleyici Yazı Tipi Boyutu', 'setting.label.blue': 'Mavi', 'setting.label.green': 'Yeşil', 'setting.label.violet': 'Menekşe', 'setting.text.dark': 'Karanlık', 'setting.text.dark2': 'Karanlık-2', 'setting.text.light': 'Açık', 'setting.text.followOS': 'İşletim Sistemini Takip Et', 'setting.title.language': 'Dil', 'setting.title.aiSource': 'AI Kaynağı', 'setting.tab.custom': 'Özel', 'setting.tab.aiType.zhipu': 'ZhiPu AI', 'setting.tab.aiType.baichuan': 'BaiChuan AI', 'setting.tab.aiType.wenxin': 'WenXin AI', 'setting.tab.aiType.tongyiqianwen': 'TongYiQianWen AI', 'setting.tab.aiType.custom.tips': 'API formatı OpenAI API formatıyla uyumludur', 'setting.label.serviceAddress': 'Hizmet Adresi', 'setting.button.apply': 'Uygula', 'setting.text.currentEnv': 'Geçerli Ortam', 'setting.text.currentVersion': 'Geçerli Sürüm', 'setting.text.viewingUpdateLogs': 'Güncelleme Günlüklerini Görüntüleme', 'setting.label.isStreamOutput': 'Arayüzün çıktıyı akıtıp akıtmadığı', 'setting.label.customAiUrl': "Kullanıcı tanımlı arayüz URL'si", 'setting.placeholder.httpsProxy': 'Gerekli değil. OPENAI arayüzünü istemek için HTTP proxy {1} ayarlayın.', 'setting.placeholder.apiKey': "APIKEY'i görüntülemek için OpenAI resmi web sitesine gidin", 'setting.placeholder.chat2dbApiKey': "Chat2DB tarafından sağlanan APIKEY'i kullanın", 'setting.placeholder.customUrl': "AI REST arayüzünün URL'si", 'setting.placeholder.apiHost': 'Bu parametre zorunludur. Varsayılan değer https://api.openai.com/', 'setting.message.urlTestError': 'Arayüz testi başarısız oldu. İşlem', 'setting.placeholder.azureOpenAIKey': "Azure Portal'dan Azure OpenAI anahtar kimlik bilgilerini alın", 'setting.placeholder.azureEndpoint': "Azure Portal'dan Azure OpenAI uç noktasını alın", 'setting.placeholder.azureDeployment': 'Dağıtılan modelin dağıtım kimliği', 'setting.ai.tips': 'Lütfen oturum açın ve AI yapılandırmasını seçin', 'setting.ai.user.hidden': '"Ayarlar -> Özel AI" içinde ApiKey\'i ayarlamak için lütfen yönetici ile iletişime geçin', 'setting.button.startDownloading': 'İndirmeye Başla', 'setting.button.beDownloading': 'İndiriliyor', 'setting.button.redownload': 'Yeniden İndir', 'setting.button.restart': 'Yeniden Başlat', 'setting.text.discoverNewVersion': 'Yeni sürümü keşfedin {1}', 'setting.text.isLatestVersion': 'Bu en son sürüm', 'setting.button.changeLog': 'Değişiklik Günlüğü', 'setting.title.updateRule': 'Güncelleme Kuralı', 'setting.text.autoUpdate': 'Yeni sürüm otomatik olarak indirilir ve yükler', 'setting.text.manualUpdate': 'Yalnızca yeni bir sürüm yayınlandığında beni uyar', 'setting.button.iSee': 'Görüyorum', 'setting.text.newEditionIsReady': 'Yeni sürüm indirme tamamlandı, yazılımı yeniden başlatarak yeni sürümü kurabilirsiniz', 'setting.button.goToUpdate': 'Güncellemeye Git', 'setting.text.UpdatedLatestVersion': 'En son sürüme güncellendi {1}', 'setting.title.holdingService': 'Hizmeti Tutma', 'setting.text.holdingService': 'Uygulamadan çıkarken hizmeti tutarak başlangıcı hızlandırın', 'setting.chat2db.ai.button': `Daha güçlü AI özellikleri için lütfen Chat2DB Pro'yu ziyaret edin`, 'setting.title.goto.chat2db.pro': `Chat2DB Pro'ya Git`, }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/sqlEditor.ts ================================================ export default { 'sqlEditor.text.keyword': 'Anahtar Kelime', 'sqlEditor.text.function': 'Fonksiyon', 'sqlEditor.text.tableName': 'Tablo Adı', 'sqlEditor.text.databaseName': 'Veritabanı Adı', 'sqlEditor.text.schemaName': 'Şema', 'sqlEditor.text.viewName': 'Görünüm Adı', 'sqlEditor.text.fieldName': 'Alan Adı', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/team.ts ================================================ export default { 'team.title': 'Takım Yönetimi', 'team.tab.datasource': 'Veri Kaynağı Yönetimi', 'team.tab.user': 'Kullanıcı Yönetimi', 'team.tab.team': 'Takım Yönetimi', 'team.action.rightManagement': 'Yetki Yönetimi', 'team.action.editDatasource': 'Veri Kaynağını Düzenle', 'team.action.addDatasource': 'Veri Kaynağı Ekle', 'team.action.addDatasource.placeholder': 'Veri Kaynağı Ara', 'team.action.editUser': 'Kullanıcıyı Düzenle', 'team.action.addUser': 'Kullanıcı Ekle', 'team.action.addUser.placeholder': 'Kullanıcı Ara', 'team.action.editTeam': 'Takımı Düzenle', 'team.action.addTeam': 'Takım Ekle', 'team.action.addTeam.placeholder': 'Takım Ara', 'team.action.affiliation.user': 'Kullanıcıya Bağla', 'team.action.affiliation.team': 'Takıma Bağla', 'team.action.affiliation.datasource': 'Veri Kaynağına Bağla', 'team.action.addUserAndTeam': 'Kullanıcı/Takım Ekle', 'team.action.addUserAndTeam.placeholder': 'Kullanıcı/Takım Ara', 'team.input.search.placeholder': 'Arama yapmak için anahtar kelimeleri girin', 'team.datasource.rightManagement': 'Yetki Yönetimi', 'team.datasource.alias': 'Veri Kaynağı Adı', 'team.datasource.url': 'Veri Kaynağı URL\'si', 'team.datasource.code': 'Kod', 'team.datasource.name': 'Ad', 'team.datasource.status': 'Durum', 'team.user.name': 'Kullanıcı', 'team.user.userName': 'Kullanıcı Adı', 'team.user.nickName': 'Takma Ad', 'team.user.status': 'Durum', 'team.user.addForm.userName': 'Kullanıcı Adı', 'team.user.addForm.nickName': 'Takma Ad', 'team.user.addForm.email': 'E-posta', 'team.user.addForm.password': 'Şifre', 'team.user.addForm.roleCode': 'Rol', 'team.user.addForm.roleCode.admin': 'Yönetici', 'team.user.addForm.roleCode.user': 'Kullanıcı', 'team.user.addForm.status': 'Durum', 'team.user.addForm.status.valid': 'Geçerli', 'team.user.addForm.status.invalid': 'Geçersiz', 'team.team.name': 'Takım', 'team.team.addForm.code': 'Takım Kodu', 'team.team.addForm.name': 'Takım Adı', 'team.team.addForm.status': 'Durum', 'team.team.addForm.status.valid': 'Geçerli', 'team.team.addForm.status.invalid': 'Geçersiz', 'team.team.addForm.description': 'Açıklama', }; ================================================ FILE: chat2db-client/src/i18n/tr-tr/workspace.ts ================================================ export default { 'workspace.title': 'Çalışma Alanı', 'workspace.cascader.placeholder': 'Buradan Seçin', 'workspace.ai.input.placeholder': 'Düz metin ifadenizi buraya girin', 'workspace.title.savedConsole': 'Kaydedilmiş konsol', 'workspace.menu.ViewDDL': 'DDL Görüntüle', 'workspace.menu.deleteTable': 'Tabloyu Sil', 'workspace.menu.openTable': 'Tabloyu Aç', 'workspace.menu.editTable': 'Tabloyu Düzenle', 'workspace.menu.view': 'Görüntüle', 'workspace.menu.pin': 'Sabitle', 'workspace.menu.unPin': 'Sabitlemeyi Kaldır', 'workspace.menu.editTableData': 'Tablo Verilerini Düzenle', 'workspace.menu.queryConsole': 'Sorgu Konsolu', 'workspace.menu.viewAllTable': 'Tüm Tabloları Görüntüle', 'workspace.menu.createDatabase': 'Veritabanı Oluştur', 'workspace.menu.createSchema': 'Şema Oluştur', 'workspace.menu.deleteTablePlaceHolder': 'Silmek istediğiniz tablonun adını giriniz', 'workspace.menu.createSequence': 'Sıra Oluştur', 'workspace.menu.editSequence': 'Sırayı Düzenle', 'workspace.menu.deleteSequence': 'Sıradan Sil', 'workspace.tips.affirmDeleteTable': 'Girdiğiniz tablo adı, silmek istediğiniz tablo adı ile aynı değil, lütfen tekrar doğrulayın', 'workspace.table.total': 'Toplam', 'workspace.table.total.tip': 'Toplam satır sayısını yükle', 'workspace.table.export.all.csv': 'Sonuç kümesini CSV olarak dışa aktar', 'workspace.table.export.cur.csv': 'Geçerli sayfa sonucunu CSV olarak dışa aktar', 'workspace.table.export.all.insert': 'Sonuç kümesini INSERT SQL olarak dışa aktar', 'workspace.table.export.cur.insert': 'Geçerli sayfa sonucunu INSERT SQL olarak dışa aktar', 'workspace.tree.view': 'Görüntüle', 'workspace.tree.trigger': 'Tetikleyici', 'workspace.tree.function': 'Fonksiyon', 'workspace.tree.procedure': 'Prosedür', 'workspace.tree.search.placeholder': 'Genişletilmiş düğümde arama yapın', 'workspace.tree.delete.tip': 'Bu işlemin kalıcı olarak silindiğini anlıyorum', 'workspace.tree.delete.table.tip': '{1} tablosunu silmek istediğinizden emin misiniz?', 'workspace.tips.noConnection': 'Henüz bir bağlantı oluşturmadınız', 'workspace.tree.delete.sequence.tip': '{1} i silmek istediğinize emin misiniz?' }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/chat.ts ================================================ export default { 'chat.input.remain': '剩余 {1} 次', 'chat.input.tableSelect.placeholder': '请选择表', 'chat.input.tableSelect.error.TooManyTable': '最多选择8张表', 'chat.input.remain.dialog.tips': '关注公众号,发送"推广"获取更多体验次数', 'chat.input.syncTable.tips': '自动同步所有表结构给AI上下文(在群内联系群主,申请Chat2DBAI白名单后,仅在Chat2DBAI模型下可用)', 'chat.input.remain.tooltip': '手动选中的表的结构将会同步给AI上下文', 'chat.input.syncTable.tempTips': '🎉上线:自动同步所有表结构到AI上下文', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/common.ts ================================================ export default { 'common.text.no': '否', 'common.text.is': '是', 'common.button.affirm': '确认', 'common.button.edit': '编辑', 'common.button.modify': '修改', 'common.button.confirm': '确认', 'common.button.cancel': '取消', 'common.data.hour': '{1}小时', 'common.data.minute': '{1}分钟', 'common.tip.yesterday': '昨天{1}', 'common.tip.tomorrow': '明天{1}', 'common.tip.ago': '前', 'common.tip.later': '后', 'common.tip.now': '现在', 'common.tip.justNow': '刚刚', 'common.text.search': '搜索', 'common.placeholder.select': '请选择{1}', 'common.text.serviceStarting': '服务启动中...', 'common.text.serviceFail': '服务启动失败', 'common.text.column': '列', 'common.text.row': '行', 'common.text.indexes': '索引', 'common.button.save': '保存', 'common.button.open': '打开', 'common.button.refresh': '刷新', 'common.button.execute': '执行', "common.button.import": '导入SQL', 'common.button.format': '格式化', 'common.message.successfulConfig': '配置成功', 'common.text.successful': '成功', 'common.text.failure': '失败', 'common.message.modifySuccessfully': '修改成功', 'common.message.addedSuccessfully': '添加成功', 'common.text.custom': '自定义', 'common.button.delete': '删除', 'common.text.executionResult': '结果 {1}', 'common.tips.deleteTable': '你确定好删除这张表吗?', 'common.text.tableName': '表名称', 'common.text.submittedSuccessfully': 'Successfully submitted', 'common.text.successfullyDelete': '删除成功', 'common.text.explainSQL': '解释SQL', 'common.text.optimizeSQL': '优化SQL', 'common.text.conversionSQL': '转化SQL', 'common.text.table': '表', 'common.tips.saveSuccessfully': '保存成功', 'common.button.copy': '复制', 'common.button.copyName': '复制名称', 'common.button.copySuccessfully': '复制成功', 'common.button.createConsole': '新建控制台', 'common.button.exportWord': '导出到Word', 'common.button.exportExcel': '导出到Excel', 'common.button.exportHtml': '导出到Html', 'common.button.exportMarkdown': '导出到Markdown', 'common.button.exportPdf': '导出到Pdf', 'common.text.successfulExecution': '执行成功', 'common.text.result': '结果', 'common.text.timeConsuming': '耗时', 'common.text.searchRow': '查询行数', 'common.text.noData': '暂无数据', 'common.text.remindMeLater': '稍后提醒我', 'common.text.goToUpdate': '前往更新', 'common.text.updateReminder': '更新提醒', 'common.text.detectionLatestVersion': '监测到最新版本', 'common.text.setting': '设置', 'common.text.tryToRestart': '尝试重新启动', 'common.text.contactUs': '联系我们', 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', 'common.text.export': '导出', 'common.notification.detail': '查看详情', 'common.notification.solution': '解决办法', 'common.button.copyError': '复制错误报告', 'common.button.copyErrorTips': '(这里会复制接口信息以及详细参数,如有敏感参数,请先解析JSON处理后在发送)', 'common.tips.formatError': '格式化失败,请看sql是否正确', 'common.text.executeSelectedSQL': '执行选中SQL', 'common.text.refreshPage': '刷新页面', 'common.text.saveConsole': '保存控制台', 'common.text.textToSQL': '纯文本转SQL', 'common.text.editorRightClick': '编辑器右键', 'common.form.error.required': '此项必填!', 'common.form.error.email': '不是正确的邮箱格式', 'common.tips.delete.confirm': '确认要删除吗?', 'common.tips.updateSuccess': '更新成功', 'common.tips.createSuccess': '创建成功', 'common.text.action': '操作', 'common.button.add': '添加', 'common.text.errorMessage': '错误信息', 'common.button.cancelRequest': '取消请求', 'common.button.executionError': '执行错误', 'common.text.affectedRows': '受影响行:{1}', 'common.text.selectFile' : '选择文件', 'common.text.noTableFoundUp' : '当前库没有查询到表', 'common.text.noTableFoundDown' : '你可以在顶部切换数据库', 'common.text.updateNow' : '立即更新', 'common.title.preview' : '预览', 'common.title.errorMessage': '错误信息', 'common.label.comment': '备注', 'common.label.name': '名称', 'common.title.create': '创建', 'common.title.executiveLogging': '执行记录', 'common.text.executionTime': '{1}ms 执行完毕', 'common.button.copyRowAs': '复制行为', 'common.button.insertSql': 'Insert 语句', 'common.button.updateSql': 'Update 语句', 'common.button.tabularSeparatedValues': '制表符分隔值', 'common.button.tabularSeparatedValuesFieldName': '制表符分隔值(字段名)', 'common.button.tabularSeparatedValuesFieldNameAndData': '制表符分隔值(字段名和数据)', 'common.button.cloneRow': '克隆行', 'common.button.deleteRow': '删除行', 'common.button.setNull': '设置为NULL', 'common.button.setDefault': '设置为默认值', 'common.button.viewData': '查看/修改数据', 'common.button.close': '关闭', 'common.button.closeAll': '全部关闭', 'common.button.closeOthers': '关闭其他', 'common.label.tcp': '线上', 'common.label.LocalFile': '本地', 'common.text.rename': '重命名', 'common.title.info': '信息', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/connection.ts ================================================ export default { 'connection.title': '数据源', 'connection.title.connections': '连接', 'connection.title.createConnection': '创建数据源', 'connection.title.editConnection': '修改数据源', 'connection.title.importConnection': '导入数据源', 'connection.label.name': '名称', 'connection.label.host': '主机', 'connection.label.authentication': '身份验证', 'connection.label.database': '数据库', 'connection.label.JDBCDrive': 'JDBC驱动', 'connection.label.port': '端口', 'connection.button.testConnection': '测试链接', 'connection.label.advancedConfiguration': '高级配置', 'connection.label.sshConfiguration': 'SSH', 'connection.button.addConnection': '新增连接', 'connection.button.connect': '连接', 'connection.button.remove': '删除链接', 'connection.message.testConnectResult': '测试连接{1}', 'connection.message.testSshConnection': '测试ssh连接', 'connection.tableHeader.name': '名称', 'connection.tableHeader.value': '值', 'connection.title.uploadDriver': '上传', 'connection.tips.customUpload': '上传驱动', 'connection.title.driver': '驱动', 'connection.button.clickUpload': '点击上传', 'connection.text.downloadDriver': '下载驱动', 'connection.text.downloadSuccess': '下载成功', 'connection.text.tryAgainDownload': '尝试重新下载', 'connection.text.downloading': '下载中...', 'connection.label.private': '私有', 'connection.label.shared': '共享', 'connection.button.createConnection': '创建连接', 'connection.tips.noConnection': '您当前还没有创建任何连接', 'connection.tips.noConnectionTips': '无权限查看该连接详情,但你可以直接连接该连接', 'connection.title.importTitle': '导入文件, .ncx(navicat) 或 .dbp(dbever)', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/dashboard.ts ================================================ export default { 'dashboard.title': '仪表盘', 'dashboard.edit': '编辑', 'dashboard.modal.editTitle': '编辑仪表盘', 'dashboard.modal.addTitle': '新增仪表盘', 'dashboard.modal.name.placeholder': '请输入仪表盘名', 'dashboard.delete': '删除', 'dashboard.export2image': '导出图片', 'dashboard.editor.cascader.placeholder': '请选择连接', 'dashboard.editor.execute.noDataSource': '请先选择数据源', 'dashboard.editor.execute.success': '执行成功,请选择图表配置', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/editSequence.ts ================================================ export default { 'editSequence.button.createSequence': '新建序列', 'editSequence.button.editSequence': '编辑序列', 'editSequence.label.comment': '注释', 'editSequence.label.relname': '序列名称', 'editSequence.label.typname': '数据类型', 'editSequence.label.seqcache': '缓存', 'editSequence.label.rolname': '所有者', 'editSequence.label.seqstart': '起始值', 'editSequence.label.seqincrement': '步长值', 'editSequence.label.seqmax': '最大值', 'editSequence.label.seqmin': '最小值', 'editSequence.label.seqcycle': '周期', 'editSequence.title.sqlPreview': 'sql预览' }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/editTable.ts ================================================ export default { 'editTable.tab.basicInfo': '基本信息', 'editTable.tab.columnInfo': '列信息', 'editTable.tab.indexInfo': '索引信息', 'editTable.label.tableName': '表名', 'editTable.label.comment': '注释', 'editTable.button.add': '新增', 'editTable.button.delete': '删除', 'editTable.button.up': '上移', 'editTable.button.down': '下移', 'editTable.label.indexName': '索引名称', 'editTable.label.indexType': '索引类型', 'editTable.label.indexMethod': '索引方法', 'editTable.label.includeColumn': '包含列', 'editTable.button.createTable': '新建表', 'editTable.button.importTable': '导出表', 'editTable.label.index': '序号', 'editTable.label.columnName': '列名', 'editTable.label.columnSize': '长度', 'editTable.label.columnType': '类型', 'editTable.label.nullable': '可空', 'editTable.label.prefixLength': '前缀长度', 'editTable.label.defaultValue': '默认值', 'editTable.label.sparse': '稀疏', 'editTable.label.characterSet': '字符集', 'editTable.label.collation': '排序规则', 'editTable.label.decimalPoint': '小数点', 'editTable.label.unit': '单位', 'editTable.label.value': '值', 'editTable.label.autoIncrement': '是否自增', 'editTable.label.engine': '引擎', 'editTable.label.incrementValue': '自增值', 'editTable.label.order': '排序', 'editTable.label.primaryKey': '键', 'editTable.title.sqlPreview': 'sql预览', 'editTable.button.addColumn': '添加列', 'editTable.button.addIndex': '添加索引', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/editTableData.ts ================================================ export default { 'editTableData.tips.addRow': '添加行', 'editTableData.tips.deleteRow': '删除行', 'editTableData.tips.revert': '撤销', 'editTableData.tips.previewPendingChanges': '预览待提交的修改', 'editTableData.tips.submit': '提交更改', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/index.ts ================================================ import { LangType } from '@/constants' import menu from './menu'; import common from './common'; import connection from './connection'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; import sqlEditor from './sqlEditor' import editSequence from './editSequence'; export default { lang: LangType.ZH_CN, ...connection, ...common, ...setting, ...workspace, ...menu, ...connection, ...dashboard, ...chat, ...team, ...login, ...editTable, ...editTableData, ...sqlEditor, ...editSequence }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/login.ts ================================================ export default { 'login.text.logout': '退出登录', 'login.text.welcome': '欢迎使用 Chat2DB', 'login.text.tips': 'Chat2DB 账号仅用于团队协作管理', 'login.text.tips.title': '为什么需要登录?', 'login.text.setting': '设 置', 'login.form.user': '用户名', 'login.form.user.placeholder': '请输入用户名', 'login.form.password': '密码', 'login.form.password.placeholder': '请输入密码', 'login.button.login': '登 录', 'login.tips.defaultPassword': '默认用户名和密码均为: chat2db', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/menu.ts ================================================ export default { 'menu.file': 'File' } ================================================ FILE: chat2db-client/src/i18n/zh-cn/setting.ts ================================================ export default { 'setting.title.setting': '设置', 'setting.nav.basic': '基础设置', 'setting.nav.customAi': '自定义AI', 'setting.nav.proxy': '服务端地址', 'setting.nav.aboutUs': '关于我们', 'setting.title.backgroundColor': '背景色', 'setting.title.themeColor': '主题色', 'setting.title.sqlEditorFontSize': 'SQL编辑器字体大小', 'setting.label.blue': '蓝蓬釉', 'setting.label.green': '极光绿', 'setting.label.violet': '酱紫', 'setting.text.dark': '暗色', 'setting.text.dark2': '暗色2', 'setting.text.light': '亮色', 'setting.text.followOS': '自动', 'setting.title.language': '语言', 'setting.title.aiSource': 'AI 来源', 'setting.tab.custom': '自定义', 'setting.tab.aiType.zhipu': '智谱', 'setting.tab.aiType.baichuan': '百川', 'setting.tab.aiType.wenxin': '文心一言', 'setting.tab.aiType.tongyiqianwen': '通义千问', 'setting.tab.aiType.custom.tips': '接口格式与OpenAI接口格式一致', 'setting.label.serviceAddress': '服务地址', 'setting.button.apply': '应用', 'setting.text.currentEnv': '当前环境', 'setting.text.currentVersion': '当前版本', 'setting.text.viewingUpdateLogs': '查看更新日志', 'setting.label.isStreamOutput': '接口是否流式输出', 'setting.label.customAiUrl': '自定义接口Url', 'setting.placeholder.httpsProxy': '非必填,用于设置请求OPENAI接口时的HTTP代理{1}', 'setting.placeholder.apiKey': '使用OpenAi接口时必填,可前往OpenAI官网查看APIKEY', 'setting.placeholder.chat2dbApiKey': '使用Chat2DB提供的APIKEY', 'setting.placeholder.customUrl': '选择自定义AI时必填,用于设置自定义AI的REST接口URL', 'setting.placeholder.apiHost': '非必填,默认值为 https://api.openai.com/', 'setting.message.urlTestError': '接口测试不通过', 'setting.placeholder.azureOpenAIKey': '从Azure门户获取Azure OpenAI密钥凭证', 'setting.placeholder.azureEndpoint': '从Azure门户获取Azure OpenA端口', 'setting.placeholder.azureDeployment': '部署模型的部署id', 'setting.ai.tips': '请登录后选择AI配置', 'setting.ai.user.hidden': '请联系管理员在"设置->自定义AI"中设置ApiKey', 'setting.button.startDownloading': '开始下载', 'setting.button.beDownloading': '下载中', 'setting.button.redownload': '重新下载', 'setting.button.restart': '立即重启', 'setting.text.discoverNewVersion': '发现新版本 {1}', 'setting.text.isLatestVersion': '已是最新版本', 'setting.button.changeLog': '查看更新日志', 'setting.title.updateRule': '更新规则', 'setting.text.autoUpdate': '新版自动下载并安装更新', 'setting.text.manualUpdate': '仅在新版本发布时提醒我', 'setting.button.iSee': '我知道了', 'setting.text.newEditionIsReady': '新版本已下载完成, 重启软件将会安装新版本', 'setting.button.goToUpdate': '前往更新', 'setting.text.UpdatedLatestVersion': '已更新到最新版本 {1}', 'setting.title.holdingService': '保持服务', 'setting.text.holdingService': '退出应用时保持服务,加快启动速度', 'setting.chat2db.ai.button': '请前往 Chat2DB Pro 体验更为强大的AI功能', 'setting.title.goto.chat2db.pro': '前往Chat2DB Pro', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/sqlEditor.ts ================================================ export default { 'sqlEditor.text.keyword': '关键词', 'sqlEditor.text.function': '函数', 'sqlEditor.text.tableName': '表名', 'sqlEditor.text.databaseName': '数据库', 'sqlEditor.text.schemaName': 'Schema', 'sqlEditor.text.viewName': '视图名', 'sqlEditor.text.fieldName': '字段名', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/team.ts ================================================ export default { 'team.title': '团队管理', 'team.tab.datasource': '链接管理', 'team.tab.user': '用户管理', 'team.tab.team': '团队管理', 'team.action.rightManagement': '权限管理', 'team.action.editDatasource': '编辑链接', 'team.action.addDatasource': '添加链接', 'team.action.addDatasource.placeholder': '搜索链接', 'team.action.editUser': '编辑用户', 'team.action.addUser': '添加用户', 'team.action.addUser.placeholder': '搜索用户', 'team.action.editTeam': '编辑团队', 'team.action.addTeam': '添加团队', 'team.action.addTeam.placeholder': '搜索团队', 'team.action.affiliation.user': '包含用户', 'team.action.affiliation.team': '所属团队', 'team.action.affiliation.datasource': '归属链接', 'team.action.addUserAndTeam': '添加用户/团队', 'team.action.addUserAndTeam.placeholder': '搜索人员/团队', 'team.input.search.placeholder': '输入关键字进行搜索', 'team.datasource.rightManagement': '权限管理', 'team.datasource.alias': '链接名称', 'team.datasource.url': '链接地址', 'team.datasource.code': '编码', 'team.datasource.name': '名称', 'team.datasource.status': '状态', 'team.user.name': '用户', 'team.user.userName': '用户名', 'team.user.nickName': '昵称', 'team.user.status': '状态', 'team.user.addForm.userName': '用户名', 'team.user.addForm.nickName': '昵称', 'team.user.addForm.email': '邮箱', 'team.user.addForm.password': '密码', 'team.user.addForm.roleCode': '角色', 'team.user.addForm.roleCode.admin': '管理员', 'team.user.addForm.roleCode.user': '用户', 'team.user.addForm.status': '状态', 'team.user.addForm.status.valid': '有效', 'team.user.addForm.status.invalid': '无效', 'team.team.name': '团队', 'team.team.addForm.code': '团队编码', 'team.team.addForm.name': '团队名', 'team.team.addForm.status': '状态', 'team.team.addForm.status.valid': '有效', 'team.team.addForm.status.invalid': '无效', 'team.team.addForm.description': '描述', }; ================================================ FILE: chat2db-client/src/i18n/zh-cn/workspace.ts ================================================ export default { 'workspace.title': '工作台', 'workspace.cascader.placeholder': '请选择', 'workspace.ai.input.placeholder': '在这里输入纯文本语句', 'workspace.title.savedConsole': '保存记录', 'workspace.menu.ViewDDL': '查看DDL', 'workspace.menu.deleteTable': '删除表', 'workspace.menu.openTable': '打开表', 'workspace.menu.editTable': '修改表', 'workspace.menu.view': '查看', 'workspace.menu.pin': '置顶', 'workspace.menu.unPin': '取消置顶', 'workspace.menu.editTableData': '编辑表数据', 'workspace.menu.queryConsole': '新建查询', 'workspace.menu.viewAllTable': '查看所有表', 'workspace.menu.createDatabase': '创建数据库', 'workspace.menu.createSchema': '创建Schema', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', 'workspace.menu.createSequence': '创建序列', 'workspace.menu.editSequence': '修改序列', 'workspace.menu.deleteSequence': '删除序列', 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', 'workspace.table.total.tip': '加载总行数', 'workspace.table.export.all.csv': '导出结果集 csv', 'workspace.table.export.cur.csv': '导出当前页结果集 csv', 'workspace.table.export.all.insert': '导出结果集 insert sql', 'workspace.table.export.cur.insert': '导出当前页结果集 insert sql', 'workspace.tree.view': '视图', 'workspace.tree.trigger': '触发器', 'workspace.tree.function': '函数', 'workspace.tree.procedure': '存储过程', 'workspace.tree.search.placeholder': '在展开节点中搜索', 'workspace.tree.delete.tip': '我了解该操作是永久性删除', 'workspace.tree.delete.table.tip': '确定要删除表{1}吗?', 'workspace.tips.noConnection': '你还没有创建连接', 'workspace.tips.maxConsole': '最多只能打开20个控制台', 'workspace.tips.openExecutiveLogging': '打开执行记录', 'workspace.tree.delete.sequence.tip': '确定要删除序列{1}吗?', }; ================================================ FILE: chat2db-client/src/indexedDB/index.ts ================================================ import { tableList } from './table'; // 创建数据库的方法 export const createDB = (dbName: string, version: number) => { return new Promise((resolve, reject) => { const request = window.indexedDB.open(dbName, version); request.onerror = (event: any) => { reject(event.target.error); }; request.onsuccess = (event: any) => { resolve(event.target.result); }; request.onupgradeneeded = (event: any) => { const db = event.target.result; // 数据库对象 // 创建存储库 tableList.forEach((item: any) => { const { tableDetails } = item; const objectStore = db.createObjectStore(tableDetails.name, tableDetails.primaryKey); tableDetails.column.forEach((i: any) => { if (i.isIndex) { objectStore.createIndex(i.name, i.keyPath, i.options); } }); }); }; }); }; type TableType = 'workspaceConsoleDDL'; type DBType = 'chat2db'; // 添加数据 export const addData = (db: DBType, tableName: TableType, data: any) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.add(data); request.onsuccess = () => { resolve(true); }; request.onerror = (error) => { reject(error); }; }); }; // 通过索引删除数据 export const deleteDataByIndex = (db: DBType, tableName: TableType, indexName, indexValue) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.index(indexName).delete(indexValue); request.onsuccess = () => { resolve(true); }; request.onerror = () => { reject(false); }; }); }; // 通过主键删除数据 export const deleteData = (db: DBType, tableName: TableType, key: any) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.delete(key); request.onsuccess = () => { resolve(true); }; request.onerror = () => { reject(false); }; }); }; // 通过索引查询数据,支持传入多个索引 export const getDataByIndex = (db: DBType, tableName: TableType, indexName: string, indexValue: any) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.index(indexName).get(indexValue); request.onsuccess = () => { resolve(request.result); }; request.onerror = () => { reject(false); }; }); }; // 通过游标查询数据,支持传入多个条件 export const getDataByCursor = (db: DBType, tableName: TableType, condition: {[key in string]: any} ) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.openCursor(); const result: any[] = []; request.onsuccess = (event: any) => { const cursor = event.target.result; if (cursor) { let flag = true; Object.keys(condition).forEach((key) => { if (cursor.value[key] !== condition[key]) { flag = false; } }); if (flag) { result.push(cursor.value); } cursor.continue(); } else { resolve(result); } }; request.onerror = () => { reject(false); }; }); }; // 修改数据 export const updateData = (db: DBType, tableName: TableType, data: any) => { return new Promise((resolve, reject) => { const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); const objectStore = transaction.objectStore(tableName); const request = objectStore.put(data); request.onsuccess = () => { resolve(true); }; request.onerror = () => { reject(false); }; }); }; // 关闭数据库 export const closeDB = (db: DBType) => { return new Promise((resolve) => { window._indexedDB[db].close(); resolve(true); }); }; export default { createDB, addData, deleteDataByIndex, deleteData, getDataByIndex, getDataByCursor, updateData, closeDB, }; ================================================ FILE: chat2db-client/src/indexedDB/table.ts ================================================ export interface IWorkspaceConsoleDDL { consoleId: string; // 控制台的id 唯一 ddl: string; // 数据源ddl userId?: string; // 用户的唯一id } // 工作区console表 export const workspaceConsoleDDL = { name: 'workspaceConsoleDDL', primaryKey: { keyPath: 'consoleId', autoIncrement: true, }, column: [ { name: 'consoleId', isIndex: true, keyPath: 'consoleId', options: { unique: true, }, }, { name: 'userId', isIndex: true, keyPath: 'userId', options: { unique: false, }, }, { name: 'ddl', isIndex: true, keyPath: 'ddl', options: { unique: false, }, }, ], } export const tableList = [ { tableDetails: workspaceConsoleDDL, } ] ================================================ FILE: chat2db-client/src/layouts/GlobalLayout/index.less ================================================ @import '@/styles/global.less'; @import '@/styles/var.less'; .loadingBox { flex: 1; width: 100vw; display: flex; justify-content: center; align-items: center; font-size: 16px; flex-direction: column; .contact { line-height: 32px; display: flex; align-items: center; .icon { cursor: pointer; font-size: 32px; margin-right: 20px; } } } .app { width: 100vw; height: 100vh; display: flex; flex-direction: column; .appBody { flex: 1; position: relative; } } :global { #root { height: 100%; .codicon-symbol-text:before { content: '\eb11'; width: 16px; height: 16px; } .codicon-symbol-function:before { content: '\ebb8'; width: 16px; height: 16px; // background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAKZJREFUOE+lkgEOgzAMA83LNl7G9rJtL2O6qkZZlRYGkSpacGzHdNLFmi72a0TwkLQEgRTbI3DzLOk9ctkjWCU9JUE0rBHBoXwi6BWk7tX6p7rgTDEOe1ZxFwkMIjgaPTtPwLc6FkLbeJlNAFaO8/MekZ9sMgICzNL/i6AltivGYb8JtED//zYbcqGJch7lnBEYtHcFyvee1d0LZPaMwFZPOTjUFEFfU0IlESizFzUAAAAASUVORK5CYII="); } .codicon-symbol-folder:before { content: '\ebb7'; width: 16px; height: 16px; // background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAHxJREFUOE/Fk10OgCAMgz9Opp5MPZl4Mk0JS4AEJWK0L+OvZWzF0QkX+SMwA4ot8MAKeBPYgB1YWtjx3ABMJnAANm7UIHBeFWi9OT1XzcBqUYsSuXzCPwLqq0EtEtRaoZxrTb7JatAtkPrg+xqUVr7LQPuZlbs/0xMXBs4Jp5IvERhfiDgAAAAASUVORK5CYII="); } .codicon-symbol-field:before { content: '\eb17'; width: 16px; height: 16px; // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); } .codicon-symbol-unit:before { content: '\ea70'; width: 16px; height: 16px; // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); } .codicon-symbol-property:before { content: '\eace'; width: 16px; height: 16px; // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); } } } ================================================ FILE: chat2db-client/src/layouts/GlobalLayout/index.tsx ================================================ import React, { useEffect, useLayoutEffect, useState } from 'react'; import usePollRequestService, { ServiceStatus } from '@/hooks/usePollRequestService'; import i18n, { isEn } from '@/i18n'; import { Button, ConfigProvider, Spin, Tooltip } from 'antd'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import service from '@/service/misc'; import useCopyFocusData from '@/hooks/useFocusData'; import { useTheme } from '@/hooks/useTheme'; import { getAntdThemeConfig } from '@/theme'; import { Outlet } from 'umi'; import init from '../init/init'; import { GithubOutlined, SyncOutlined, WechatOutlined } from '@ant-design/icons'; import { ThemeType } from '@/constants'; import GlobalComponent from '../init/GlobalComponent'; import styles from './index.less'; import { useUserStore, queryCurUser } from '@/store/user'; const GlobalLayout = () => { const [appTheme, setAppTheme] = useTheme(); const [antdTheme, setAntdTheme] = useState({}); const { curUser } = useUserStore((state) => { return { curUser: state.curUser, }; }); const { serviceStatus, restartPolling } = usePollRequestService({ loopService: service.testService, }); useCopyFocusData(); useLayoutEffect(() => { setAntdTheme(getAntdThemeConfig(appTheme)); }, [appTheme]); useLayoutEffect(() => { init(); monitorOsTheme(); }, []); useEffect(() => { if (serviceStatus === ServiceStatus.SUCCESS) { queryCurUser(); } }, [serviceStatus]); // 监听系统(OS)主题变化 const monitorOsTheme = () => { function change(e: any) { if (appTheme.backgroundColor === ThemeType.FollowOs) { setAppTheme({ ...appTheme, backgroundColor: e.matches ? ThemeType.Dark : ThemeType.Light, }); } } const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); matchMedia.onchange = change; }; // 等待状态页面 if (serviceStatus === ServiceStatus.PENDING || curUser === null) { return (
); } // 错误状态页面 if (serviceStatus === ServiceStatus.FAILURE) { return (
{i18n('common.text.contactUs')}: window.open('https://github.com/chat2db/Chat2DB')} /> } >
); } return (
); }; export default GlobalLayout; ================================================ FILE: chat2db-client/src/layouts/init/GlobalComponent.tsx ================================================ import React from 'react'; import MyNotification from '@/components/MyNotification'; import Modal from '@/components/Modal/BaseModal'; const GlobalComponent = () => { return <> } export default GlobalComponent; ================================================ FILE: chat2db-client/src/layouts/init/init.ts ================================================ import { clearOlderLocalStorage } from '@/utils'; import initIndexedDB from './initIndexedDB'; import registerElectronApi from './registerElectronApi'; import registerMessage from './registerMessage'; import registerNotification from './registerNotification'; import { getLang, setLang } from '@/utils/localStorage'; import { LangType } from '@/constants'; const init = () => { clearOlderLocalStorage(); initLang(); initIndexedDB(); registerElectronApi(); registerMessage(); registerNotification(); }; // 初始化语言 const initLang = () => { const lang = getLang(); if (!lang) { setLang(LangType.EN_US); document.documentElement.setAttribute('lang', LangType.EN_US); const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`; } }; export default init; ================================================ FILE: chat2db-client/src/layouts/init/initIndexedDB.ts ================================================ import indexedDB from '@/indexedDB'; /** 初始化indexedDB */ const initIndexedDB = () => { indexedDB.createDB('chat2db', 1).then((db) => { window._indexedDB = { chat2db: db, }; }); }; export default initIndexedDB; ================================================ FILE: chat2db-client/src/layouts/init/registerElectronApi.ts ================================================ // 注册Electron关闭时,关闭服务 import { useSettingStore } from '@/store/setting' const registerElectronApi = () => { window.electronApi?.registerAppMenu({ version: __APP_VERSION__, }); window.electronApi?.setBaseURL?.(window._BaseURL); window.electronApi?.setForceQuitCode?.(useSettingStore.getState().holdingService); }; export default registerElectronApi; ================================================ FILE: chat2db-client/src/layouts/init/registerMessage.ts ================================================ import { message } from 'antd'; export default () => { message.config({ maxCount: 1, duration: 3, }); }; ================================================ FILE: chat2db-client/src/layouts/init/registerNotification.ts ================================================ import { notification } from 'antd'; export default () => { notification.config({ placement: 'BottomRight', maxCount: 2, duration: null, }); }; ================================================ FILE: chat2db-client/src/main/analysis.js ================================================ const os = require('os'); const { readVersion } = require('./utils'); const Analytics4 = require('./ga4'); function registerAnalytics() { const analytics = new Analytics4('G-V8M4E5SF61', 'LShbzC_vRka5Sw5AWco7Tw'); const customParams = { platform: 'DESKTOP', version: readVersion(), os: os.platform(), }; analytics.setParams(customParams).event('first_enter'); } module.exports = registerAnalytics; ================================================ FILE: chat2db-client/src/main/constants.js ================================================ const DEV_WEB_URL = 'http://localhost:8000/'; /** jar包名 */ const JAVA_APP_NAME = 'chat2db-server-start.jar'; const JAVA_PATH = 'jre/bin/java'; module.exports = { DEV_WEB_URL, JAVA_APP_NAME, JAVA_PATH, }; ================================================ FILE: chat2db-client/src/main/ga4.js ================================================ const { net } = require('electron'); const { v4: uuidv4 } = require('uuid'); const { machineIdSync } = require('node-machine-id'); const log = require('electron-log'); class Analytics4 { constructor(trackingID, secretKey, clientID = machineIdSync(), sessionID = uuidv4()) { this.trackingID = trackingID; this.secretKey = secretKey; this.clientID = clientID; this.sessionID = sessionID; this.customParams = {}; this.userProperties = null; this.baseURL = 'https://google-analytics.com/mp'; this.collectURL = '/collect'; } set(key, value) { if (value !== null) { this.customParams[key] = value; } else { delete this.customParams[key]; } return this; } setParams(params) { if (typeof params === 'object' && Object.keys(params).length > 0) { Object.assign(this.customParams, params); } else { this.customParams = {}; } return this; } setUserProperties(upValue) { if (typeof upValue === 'object' && Object.keys(upValue).length > 0) { this.userProperties = upValue; } else { this.userProperties = null; } return this; } event(eventName) { const payload = { client_id: this.clientID, events: [ { name: eventName, params: { session_id: this.sessionID, ...this.customParams, }, }, ], }; if (this.userProperties) { Object.assign(payload, { user_properties: this.userProperties }); } const url = `${this.baseURL}${this.collectURL}?measurement_id=${this.trackingID}&api_secret=${this.secretKey}`; const request = net.request({ method: 'POST', url, }); request.on('response', (response) => { let responseData = ''; response.on('data', (chunk) => { responseData += chunk; }); response.on('end', () => { if (response.statusCode >= 200 && response.statusCode < 300) { log.info('success', responseData); } else { log.error('response error', response.statusCode); } }); }); request.on('error', (error) => { log.error('Error posting data:', error); }); request.write(JSON.stringify(payload)); request.end(); } } module.exports = Analytics4; ================================================ FILE: chat2db-client/src/main/i18n/en/index.js ================================================ const locale = { 'menu.file': 'File', 'menu.edit': 'Edit', }; module.exports = locale; ================================================ FILE: chat2db-client/src/main/i18n/index.js ================================================ const zhCN = require('./zh-cn'); const en = require('./en'); // TODO: 需要获取渲染进程的语言环境 const isZH = false; const locale = isZH ? zhCN : en; const i18n = (key) => locale[key] || key; module.exports = i18n; ================================================ FILE: chat2db-client/src/main/i18n/zh-cn/index.js ================================================ const locale = { 'menu.file': '文件', 'menu.edit': '编辑', }; module.exports = locale; ================================================ FILE: chat2db-client/src/main/index.js ================================================ const { app, BrowserWindow, shell, net, ipcMain, globalShortcut } = require('electron'); const path = require('path'); const registerAppMenu = require('./menu'); const registerAnalysis = require('./analysis'); const store = require('./store'); const { loadMainResource, isMac } = require('./utils'); let mainWindow = null; let baseUrl = null; let _forceQuitCode = false; /** * Initial window options */ function createWindow() { const { width, height, x, y } = store.get('windowBounds', { width: 1440, height: 800 }); const options = { x, y, height, width, minWidth: 1080, minHeight: 720, show: false, webPreferences: { webSecurity: false, spellcheck: false, // 禁用拼写检查器 nodeIntegration: true, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), }, }; mainWindow = new BrowserWindow(options); mainWindow.show(); // 加载应用----- loadMainResource(mainWindow); mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); mainWindow.on('resize', () => { store.set('windowBounds', mainWindow.getBounds()); }); mainWindow.on('move', () => { store.set('windowBounds', mainWindow.getBounds()); }); // 注册快捷键Ctrl+Shift+I打开开发者工具 globalShortcut.register('CommandOrControl+Shift+I', () => { mainWindow.webContents.openDevTools() }) } // const menu = Menu.buildFromTemplate(menuBar); // Menu.setApplicationMenu(menu); app.commandLine.appendSwitch('--disable-gpu-sandbox'); app.on('ready', () => { createWindow(); registerAppMenu(mainWindow); registerAnalysis(); }); app.on('activate', () => { if (!mainWindow) { createWindow(); } else { if (mainWindow.isMinimized()) { mainWindow.restore(); } if (mainWindow.isVisible()) { mainWindow.focus(); } else { mainWindow.show(); } } }); app.on('window-all-closed', (e) => { mainWindow = null if (isMac) return; app.quit(); }); app.on('before-quit', () => { if (baseUrl) { try { const request = net.request({ headers: { 'Content-Type': 'application/json', }, method: 'POST', url: `${baseUrl}/api/system/stop?forceQuit=${_forceQuitCode}`, }); request.end(); } catch (error) {} } }); ipcMain.handle('get-product-name', () => { const exePath = app.getPath('exe'); const { name } = path.parse(exePath); return name; }); // 重启app ipcMain.on('quit-app', () => { app.relaunch(); app.quit(); }); // 放大或还原窗口 ipcMain.on('set-maximize', () => { if (mainWindow.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow.maximize(); } }); ipcMain.on('register-app-menu', (event, orgs) => { registerAppMenu(mainWindow, orgs); }); ipcMain.on('set-base-url', (event, _baseUrl) => { baseUrl = _baseUrl; }); ipcMain.on('set-force-quit-code', (event, _forceQuitCode) => { forceQuitCode = _forceQuitCode; }); ipcMain.on('close-window', () => { mainWindow.close(); }); // 最小化窗口 ipcMain.on('minimize-window', () => { mainWindow.minimize(); }); // 获取当前窗口是否是最大化 ipcMain.on('is-maximized', () => { return mainWindow.isMaximized(); }); ================================================ FILE: chat2db-client/src/main/main.js ================================================ /*! For license information please see main.js.LICENSE.txt */ (()=>{var e={6084:(e,t,r)=>{const n=r(2037),{readVersion:s}=r(4288),o=r(7504);e.exports=function(){const e=new o("G-V8M4E5SF61","LShbzC_vRka5Sw5AWco7Tw"),t={platform:"DESKTOP",version:s(),os:n.platform()};e.setParams(t).event("first_enter")}},6381:e=>{e.exports={DEV_WEB_URL:"http://localhost:8000/",JAVA_APP_NAME:"chat2db-server-start.jar",JAVA_PATH:"jre/bin/java"}},7504:(e,t,r)=>{const{net:n}=r(2298),{v4:s}=r(2600),{machineIdSync:o}=r(1572),i=r(7377);e.exports=class{constructor(e,t,r=o(),n=s()){this.trackingID=e,this.secretKey=t,this.clientID=r,this.sessionID=n,this.customParams={},this.userProperties=null,this.baseURL="https://google-analytics.com/mp",this.collectURL="/collect"}set(e,t){return null!==t?this.customParams[e]=t:delete this.customParams[e],this}setParams(e){return"object"==typeof e&&Object.keys(e).length>0?Object.assign(this.customParams,e):this.customParams={},this}setUserProperties(e){return"object"==typeof e&&Object.keys(e).length>0?this.userProperties=e:this.userProperties=null,this}event(e){const t={client_id:this.clientID,events:[{name:e,params:{session_id:this.sessionID,...this.customParams}}]};this.userProperties&&Object.assign(t,{user_properties:this.userProperties});const r=`${this.baseURL}${this.collectURL}?measurement_id=${this.trackingID}&api_secret=${this.secretKey}`,s=n.request({method:"POST",url:r});s.on("response",(e=>{let t="";e.on("data",(e=>{t+=e})),e.on("end",(()=>{e.statusCode>=200&&e.statusCode<300?i.info("success",t):i.error("response error",e.statusCode)}))})),s.on("error",(e=>{i.error("Error posting data:",e)})),s.write(JSON.stringify(t)),s.end()}}},6010:(e,t,r)=>{const{app:n,BrowserWindow:s,shell:o,net:i,ipcMain:a,globalShortcut:c}=r(2298),l=r(1017),u=r(5316),d=r(6084),f=r(4234),{loadMainResource:h,isMac:p}=r(4288);let m=null,y=null;function g(){const{width:e,height:t,x:r,y:n}=f.get("windowBounds",{width:1440,height:800}),i={x:r,y:n,height:t,width:e,minWidth:1080,minHeight:720,show:!1,webPreferences:{webSecurity:!1,spellcheck:!1,nodeIntegration:!0,contextIsolation:!0,preload:l.join(__dirname,"preload.js")}};m=new s(i),m.show(),h(m),m.webContents.setWindowOpenHandler((({url:e})=>(o.openExternal(e),{action:"deny"}))),m.on("resize",(()=>{f.set("windowBounds",m.getBounds())})),m.on("move",(()=>{f.set("windowBounds",m.getBounds())})),c.register("CommandOrControl+Shift+I",(()=>{m.webContents.openDevTools()}))}n.commandLine.appendSwitch("--disable-gpu-sandbox"),n.on("ready",(()=>{g(),u(m),d()})),n.on("activate",(()=>{m?(m.isMinimized()&&m.restore(),m.isVisible()?m.focus():m.show()):g()})),n.on("window-all-closed",(e=>{m=null,p||n.quit()})),n.on("before-quit",(()=>{if(y)try{i.request({headers:{"Content-Type":"application/json"},method:"POST",url:`${y}/api/system/stop?forceQuit=false`}).end()}catch(e){}})),a.handle("get-product-name",(()=>{const e=n.getPath("exe"),{name:t}=l.parse(e);return t})),a.on("quit-app",(()=>{n.relaunch(),n.quit()})),a.on("set-maximize",(()=>{m.isMaximized()?m.unmaximize():m.maximize()})),a.on("register-app-menu",((e,t)=>{u(m,t)})),a.on("set-base-url",((e,t)=>{y=t})),a.on("set-force-quit-code",((e,t)=>{forceQuitCode=t})),a.on("close-window",(()=>{m.close()})),a.on("minimize-window",(()=>{m.minimize()})),a.on("is-maximized",(()=>m.isMaximized()))},5316:(e,t,r)=>{const{shell:n,app:s,dialog:o,BrowserWindow:i,Menu:a}=r(2298),c=r(2037),l=r(1017),{isMac:u}=r(4288);e.exports=(e,t)=>{if(!u)return void a.setApplicationMenu(null);const r=[{label:"Chat2DB",submenu:[{label:"关于Chat2DB",click(){o.showMessageBox({title:"关于Chat2DB",message:`关于Chat2DB v${t?.version||s.getVersion()}`,detail:"一个集成AI能力的智能数据库客户端和智能BI报表工具。",icon:"./logo/icon.png"})}},{type:"separator"},{label:"重新启动",click(){s.relaunch(),s.quit()}},{label:"退出",accelerator:"darwin"===process.platform?"Cmd+Q":"Alt+F4",click(){s.quit()}}]},{label:"编辑",submenu:[{label:"撤销",role:"undo"},{label:"重做",role:"redo"},{type:"separator"},{label:"剪切",role:"cut"},{label:"复制",role:"copy"},{label:"粘贴",role:"paste"},{label:"全选",role:"selectAll"}]},{label:"视图",submenu:[{type:"separator"},{label:"放大",accelerator:"CmdOrCtrl+=",role:"zoomIn"},{label:"缩小",accelerator:"CmdOrCtrl+-",role:"zoomOut"},{label:"重置",accelerator:"CmdOrCtrl+0",role:"resetZoom"},{type:"separator"},{label:"全屏",role:"togglefullscreen"}]},{label:"窗口",role:"window",submenu:[{label:"最小化",role:"minimize",accelerator:"Command+W"},{label:"关闭",role:"close"}]},{label:"帮助",submenu:[{label:"打开日志",accelerator:"darwin"===process.platform?"Cmd+Shift+T":"Ctrl+Shift+T",click(){const e=l.join(c.homedir(),".chat2db/logs/application.log");n.openPath(e).then((e=>console.log("err:",e)))}},{label:"打开控制台",accelerator:"darwin"===process.platform?"Cmd+Shift+I":"Ctrl+Shift+I",click(){const e=i.getFocusedWindow();e&&e.toggleDevTools()}},{label:"访问官网",click(){n.openExternal("https://www.sqlgpt.cn/zh")}},{label:"查看文档",click(){n.openExternal("https://doc.sqlgpt.cn/zh/")}},{label:"查看更新日志",click(){n.openExternal("https://doc.sqlgpt.cn/zh/changelog/")}}]}];a.setApplicationMenu(a.buildFromTemplate(r))}},6540:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NOOP=t.LIMIT_FILES_DESCRIPTORS=t.LIMIT_BASENAME_LENGTH=t.IS_USER_ROOT=t.IS_POSIX=t.DEFAULT_TIMEOUT_SYNC=t.DEFAULT_TIMEOUT_ASYNC=t.DEFAULT_WRITE_OPTIONS=t.DEFAULT_READ_OPTIONS=t.DEFAULT_FOLDER_MODE=t.DEFAULT_FILE_MODE=t.DEFAULT_ENCODING=void 0,t.DEFAULT_ENCODING="utf8",t.DEFAULT_FILE_MODE=438,t.DEFAULT_FOLDER_MODE=511,t.DEFAULT_READ_OPTIONS={},t.DEFAULT_WRITE_OPTIONS={},t.DEFAULT_TIMEOUT_ASYNC=5e3,t.DEFAULT_TIMEOUT_SYNC=100;const r=!!process.getuid;t.IS_POSIX=r;const n=!!process.getuid&&!process.getuid();t.IS_USER_ROOT=n,t.LIMIT_BASENAME_LENGTH=128,t.LIMIT_FILES_DESCRIPTORS=1e4,t.NOOP=()=>{}},2582:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.writeFileSync=t.writeFile=t.readFileSync=t.readFile=void 0;const n=r(1017),s=r(6540),o=r(1788),i=r(4033),a=r(1729),c=r(2046);t.readFile=function e(t,r=s.DEFAULT_READ_OPTIONS){var n;if(i.default.isString(r))return e(t,{encoding:r});const a=Date.now()+(null!==(n=r.timeout)&&void 0!==n?n:s.DEFAULT_TIMEOUT_ASYNC);return o.default.readFileRetry(a)(t,r)},t.readFileSync=function e(t,r=s.DEFAULT_READ_OPTIONS){var n;if(i.default.isString(r))return e(t,{encoding:r});const a=Date.now()+(null!==(n=r.timeout)&&void 0!==n?n:s.DEFAULT_TIMEOUT_SYNC);return o.default.readFileSyncRetry(a)(t,r)};const l=(e,t,r,n)=>{if(i.default.isFunction(r))return l(e,t,s.DEFAULT_WRITE_OPTIONS,r);const o=u(e,t,r);return n&&o.then(n,n),o};t.writeFile=l;const u=async(e,t,r=s.DEFAULT_WRITE_OPTIONS)=>{var l;if(i.default.isString(r))return u(e,t,{encoding:r});const d=Date.now()+(null!==(l=r.timeout)&&void 0!==l?l:s.DEFAULT_TIMEOUT_ASYNC);let f=null,h=null,p=null,m=null,y=null;try{r.schedule&&(f=await r.schedule(e)),h=await a.default.schedule(e),e=await o.default.realpathAttempt(e)||e,[m,p]=c.default.get(e,r.tmpCreate||c.default.create,!(!1===r.tmpPurge));const l=s.IS_POSIX&&i.default.isUndefined(r.chown),u=i.default.isUndefined(r.mode);if(l||u){const t=await o.default.statAttempt(e);t&&(r={...r},l&&(r.chown={uid:t.uid,gid:t.gid}),u&&(r.mode=t.mode))}const g=n.dirname(e);await o.default.mkdirAttempt(g,{mode:s.DEFAULT_FOLDER_MODE,recursive:!0}),y=await o.default.openRetry(d)(m,"w",r.mode||s.DEFAULT_FILE_MODE),r.tmpCreated&&r.tmpCreated(m),i.default.isString(t)?await o.default.writeRetry(d)(y,t,0,r.encoding||s.DEFAULT_ENCODING):i.default.isUndefined(t)||await o.default.writeRetry(d)(y,t,0,t.length,0),!1!==r.fsync&&(!1!==r.fsyncWait?await o.default.fsyncRetry(d)(y):o.default.fsyncAttempt(y)),await o.default.closeRetry(d)(y),y=null,r.chown&&await o.default.chownAttempt(m,r.chown.uid,r.chown.gid),r.mode&&await o.default.chmodAttempt(m,r.mode);try{await o.default.renameRetry(d)(m,e)}catch(t){if("ENAMETOOLONG"!==t.code)throw t;await o.default.renameRetry(d)(m,c.default.truncate(e))}p(),m=null}finally{y&&await o.default.closeAttempt(y),m&&c.default.purge(m),f&&f(),h&&h()}},d=(e,t,r=s.DEFAULT_WRITE_OPTIONS)=>{var a;if(i.default.isString(r))return d(e,t,{encoding:r});const l=Date.now()+(null!==(a=r.timeout)&&void 0!==a?a:s.DEFAULT_TIMEOUT_SYNC);let u=null,f=null,h=null;try{e=o.default.realpathSyncAttempt(e)||e,[f,u]=c.default.get(e,r.tmpCreate||c.default.create,!(!1===r.tmpPurge));const a=s.IS_POSIX&&i.default.isUndefined(r.chown),d=i.default.isUndefined(r.mode);if(a||d){const t=o.default.statSyncAttempt(e);t&&(r={...r},a&&(r.chown={uid:t.uid,gid:t.gid}),d&&(r.mode=t.mode))}const p=n.dirname(e);o.default.mkdirSyncAttempt(p,{mode:s.DEFAULT_FOLDER_MODE,recursive:!0}),h=o.default.openSyncRetry(l)(f,"w",r.mode||s.DEFAULT_FILE_MODE),r.tmpCreated&&r.tmpCreated(f),i.default.isString(t)?o.default.writeSyncRetry(l)(h,t,0,r.encoding||s.DEFAULT_ENCODING):i.default.isUndefined(t)||o.default.writeSyncRetry(l)(h,t,0,t.length,0),!1!==r.fsync&&(!1!==r.fsyncWait?o.default.fsyncSyncRetry(l)(h):o.default.fsyncAttempt(h)),o.default.closeSyncRetry(l)(h),h=null,r.chown&&o.default.chownSyncAttempt(f,r.chown.uid,r.chown.gid),r.mode&&o.default.chmodSyncAttempt(f,r.mode);try{o.default.renameSyncRetry(l)(f,e)}catch(t){if("ENAMETOOLONG"!==t.code)throw t;o.default.renameSyncRetry(l)(f,c.default.truncate(e))}u(),f=null}finally{h&&o.default.closeSyncAttempt(h),f&&c.default.purge(f)}};t.writeFileSync=d},7121:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.attemptifySync=t.attemptifyAsync=void 0;const n=r(6540);t.attemptifyAsync=(e,t=n.NOOP)=>function(){return e.apply(void 0,arguments).catch(t)},t.attemptifySync=(e,t=n.NOOP)=>function(){try{return e.apply(void 0,arguments)}catch(e){return t(e)}}},1788:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(7147),s=r(3837),o=r(7121),i=r(1197),a=r(3823),c={chmodAttempt:o.attemptifyAsync(s.promisify(n.chmod),i.default.onChangeError),chownAttempt:o.attemptifyAsync(s.promisify(n.chown),i.default.onChangeError),closeAttempt:o.attemptifyAsync(s.promisify(n.close)),fsyncAttempt:o.attemptifyAsync(s.promisify(n.fsync)),mkdirAttempt:o.attemptifyAsync(s.promisify(n.mkdir)),realpathAttempt:o.attemptifyAsync(s.promisify(n.realpath)),statAttempt:o.attemptifyAsync(s.promisify(n.stat)),unlinkAttempt:o.attemptifyAsync(s.promisify(n.unlink)),closeRetry:a.retryifyAsync(s.promisify(n.close),i.default.isRetriableError),fsyncRetry:a.retryifyAsync(s.promisify(n.fsync),i.default.isRetriableError),openRetry:a.retryifyAsync(s.promisify(n.open),i.default.isRetriableError),readFileRetry:a.retryifyAsync(s.promisify(n.readFile),i.default.isRetriableError),renameRetry:a.retryifyAsync(s.promisify(n.rename),i.default.isRetriableError),statRetry:a.retryifyAsync(s.promisify(n.stat),i.default.isRetriableError),writeRetry:a.retryifyAsync(s.promisify(n.write),i.default.isRetriableError),chmodSyncAttempt:o.attemptifySync(n.chmodSync,i.default.onChangeError),chownSyncAttempt:o.attemptifySync(n.chownSync,i.default.onChangeError),closeSyncAttempt:o.attemptifySync(n.closeSync),mkdirSyncAttempt:o.attemptifySync(n.mkdirSync),realpathSyncAttempt:o.attemptifySync(n.realpathSync),statSyncAttempt:o.attemptifySync(n.statSync),unlinkSyncAttempt:o.attemptifySync(n.unlinkSync),closeSyncRetry:a.retryifySync(n.closeSync,i.default.isRetriableError),fsyncSyncRetry:a.retryifySync(n.fsyncSync,i.default.isRetriableError),openSyncRetry:a.retryifySync(n.openSync,i.default.isRetriableError),readFileSyncRetry:a.retryifySync(n.readFileSync,i.default.isRetriableError),renameSyncRetry:a.retryifySync(n.renameSync,i.default.isRetriableError),statSyncRetry:a.retryifySync(n.statSync,i.default.isRetriableError),writeSyncRetry:a.retryifySync(n.writeSync,i.default.isRetriableError)};t.default=c},1197:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(6540),s={isChangeErrorOk:e=>{const{code:t}=e;return"ENOSYS"===t||!(n.IS_USER_ROOT||"EINVAL"!==t&&"EPERM"!==t)},isRetriableError:e=>{const{code:t}=e;return"EMFILE"===t||"ENFILE"===t||"EAGAIN"===t||"EBUSY"===t||"EACCESS"===t||"EACCS"===t||"EPERM"===t},onChangeError:e=>{if(!s.isChangeErrorOk(e))throw e}};t.default=s},4033:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={isFunction:e=>"function"==typeof e,isString:e=>"string"==typeof e,isUndefined:e=>void 0===e}},3823:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.retryifySync=t.retryifyAsync=void 0;const n=r(2068);t.retryifyAsync=(e,t)=>function(r){return function s(){return n.default.schedule().then((n=>e.apply(void 0,arguments).then((e=>(n(),e)),(e=>{if(n(),Date.now()>=r)throw e;if(t(e)){const e=Math.round(100+400*Math.random());return new Promise((t=>setTimeout(t,e))).then((()=>s.apply(void 0,arguments)))}throw e}))))}},t.retryifySync=(e,t)=>function(r){return function n(){try{return e.apply(void 0,arguments)}catch(e){if(Date.now()>r)throw e;if(t(e))return n.apply(void 0,arguments);throw e}}}},2068:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n={interval:25,intervalId:void 0,limit:r(6540).LIMIT_FILES_DESCRIPTORS,queueActive:new Set,queueWaiting:new Set,init:()=>{n.intervalId||(n.intervalId=setInterval(n.tick,n.interval))},reset:()=>{n.intervalId&&(clearInterval(n.intervalId),delete n.intervalId)},add:e=>{n.queueWaiting.add(e),n.queueActive.size{n.queueWaiting.delete(e),n.queueActive.delete(e)},schedule:()=>new Promise((e=>{const t=()=>n.remove(r),r=()=>e(t);n.add(r)})),tick:()=>{if(!(n.queueActive.size>=n.limit)){if(!n.queueWaiting.size)return n.reset();for(const e of n.queueWaiting){if(n.queueActive.size>=n.limit)break;n.queueWaiting.delete(e),n.queueActive.add(e),e()}}}};t.default=n},1729:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r={},n={next:e=>{const t=r[e];if(!t)return;t.shift();const s=t[0];s?s((()=>n.next(e))):delete r[e]},schedule:e=>new Promise((t=>{let s=r[e];s||(s=r[e]=[]),s.push(t),s.length>1||t((()=>n.next(e)))}))};t.default=n},2046:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(1017),s=r(6540),o=r(1788),i={store:{},create:e=>{const t=`000000${Math.floor(16777215*Math.random()).toString(16)}`.slice(-6);return`${e}.tmp-${Date.now().toString().slice(-10)}${t}`},get:(e,t,r=!0)=>{const n=i.truncate(t(e));return n in i.store?i.get(e,t,r):(i.store[n]=r,[n,()=>delete i.store[n]])},purge:e=>{i.store[e]&&(delete i.store[e],o.default.unlinkAttempt(e))},purgeSync:e=>{i.store[e]&&(delete i.store[e],o.default.unlinkSyncAttempt(e))},purgeSyncAll:()=>{for(const e in i.store)i.purgeSync(e)},truncate:e=>{const t=n.basename(e);if(t.length<=s.LIMIT_BASENAME_LENGTH)return e;const r=/^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(t);if(!r)return e;const o=t.length-s.LIMIT_BASENAME_LENGTH;return`${e.slice(0,-t.length)}${r[1]}${r[2].slice(0,-o)}${r[3]}`}};process.on("exit",i.purgeSyncAll),t.default=i},9658:function(e,t,r){"use strict";e=r.nmd(e);var n,s,o,i,a,c,l=this&&this.__classPrivateFieldSet||function(e,t,r,n,s){if("m"===n)throw new TypeError("Private method is not writable");if("a"===n&&!s)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!s:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===n?s.call(e,r):s?s.value=r:t.set(e,r),r},u=this&&this.__classPrivateFieldGet||function(e,t,r,n){if("a"===r&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)};Object.defineProperty(t,"__esModule",{value:!0});const d=r(3837),f=r(7147),h=r(1017),p=r(6113),m=r(9491),y=r(2361),g=r(3517),v=r(8866),w=r(1766),E=r(2582),$=r(5133),_=r(6838),b=r(7319),S=r(1249),O=r(7678),P="aes-256-cbc",x=()=>Object.create(null);let N="";try{delete r.c[__filename],N=h.dirname(null!==(s=null===(n=e.parent)||void 0===n?void 0:n.filename)&&void 0!==s?s:".")}catch(e){}const I="__internal__",j=`${I}.migrations.version`;class R{constructor(e={}){var t;o.set(this,void 0),i.set(this,void 0),a.set(this,void 0),c.set(this,{}),this._deserialize=e=>JSON.parse(e),this._serialize=e=>JSON.stringify(e,void 0,"\t");const r={configName:"config",fileExtension:"json",projectSuffix:"nodejs",clearInvalidConfig:!1,accessPropertiesByDotNotation:!0,configFileMode:438,...e},n=O((()=>{const e=v.sync({cwd:N}),t=e&&JSON.parse(f.readFileSync(e,"utf8"));return null!=t?t:{}}));if(!r.cwd){if(r.projectName||(r.projectName=n().name),!r.projectName)throw new Error("Project name could not be inferred. Please specify the `projectName` option.");r.cwd=w(r.projectName,{suffix:r.projectSuffix}).config}if(l(this,a,r,"f"),r.schema){if("object"!=typeof r.schema)throw new TypeError("The `schema` option must be an object.");const e=new $.default({allErrors:!0,useDefaults:!0});(0,_.default)(e);const t={type:"object",properties:r.schema};l(this,o,e.compile(t),"f");for(const[e,t]of Object.entries(r.schema))(null==t?void 0:t.default)&&(u(this,c,"f")[e]=t.default)}r.defaults&&l(this,c,{...u(this,c,"f"),...r.defaults},"f"),r.serialize&&(this._serialize=r.serialize),r.deserialize&&(this._deserialize=r.deserialize),this.events=new y.EventEmitter,l(this,i,r.encryptionKey,"f");const s=r.fileExtension?`.${r.fileExtension}`:"";this.path=h.resolve(r.cwd,`${null!==(t=r.configName)&&void 0!==t?t:"config"}${s}`);const d=this.store,p=Object.assign(x(),r.defaults,d);this._validate(p);try{m.deepEqual(d,p)}catch(e){this.store=p}if(r.watch&&this._watch(),r.migrations){if(r.projectVersion||(r.projectVersion=n().version),!r.projectVersion)throw new Error("Project version could not be inferred. Please specify the `projectVersion` option.");this._migrate(r.migrations,r.projectVersion,r.beforeEachMigration)}}get(e,t){if(u(this,a,"f").accessPropertiesByDotNotation)return this._get(e,t);const{store:r}=this;return e in r?r[e]:t}set(e,t){if("string"!=typeof e&&"object"!=typeof e)throw new TypeError("Expected `key` to be of type `string` or `object`, got "+typeof e);if("object"!=typeof e&&void 0===t)throw new TypeError("Use `delete()` to clear values");if(this._containsReservedKey(e))throw new TypeError(`Please don't use the ${I} key, as it's used to manage this module internal operations.`);const{store:r}=this,n=(e,t)=>{((e,t)=>{const r=typeof t;if(new Set(["undefined","symbol","function"]).has(r))throw new TypeError(`Setting a value of type \`${r}\` for key \`${e}\` is not allowed as it's not supported by JSON`)})(e,t),u(this,a,"f").accessPropertiesByDotNotation?g.set(r,e,t):r[e]=t};if("object"==typeof e){const t=e;for(const[e,r]of Object.entries(t))n(e,r)}else n(e,t);this.store=r}has(e){return u(this,a,"f").accessPropertiesByDotNotation?g.has(this.store,e):e in this.store}reset(...e){for(const t of e)null!=u(this,c,"f")[t]&&this.set(t,u(this,c,"f")[t])}delete(e){const{store:t}=this;u(this,a,"f").accessPropertiesByDotNotation?g.delete(t,e):delete t[e],this.store=t}clear(){this.store=x();for(const e of Object.keys(u(this,c,"f")))this.reset(e)}onDidChange(e,t){if("string"!=typeof e)throw new TypeError("Expected `key` to be of type `string`, got "+typeof e);if("function"!=typeof t)throw new TypeError("Expected `callback` to be of type `function`, got "+typeof t);return this._handleChange((()=>this.get(e)),t)}onDidAnyChange(e){if("function"!=typeof e)throw new TypeError("Expected `callback` to be of type `function`, got "+typeof e);return this._handleChange((()=>this.store),e)}get size(){return Object.keys(this.store).length}get store(){try{const e=f.readFileSync(this.path,u(this,i,"f")?null:"utf8"),t=this._encryptData(e),r=this._deserialize(t);return this._validate(r),Object.assign(x(),r)}catch(e){if("ENOENT"===(null==e?void 0:e.code))return this._ensureDirectory(),x();if(u(this,a,"f").clearInvalidConfig&&"SyntaxError"===e.name)return x();throw e}}set store(e){this._ensureDirectory(),this._validate(e),this._write(e),this.events.emit("change")}*[(o=new WeakMap,i=new WeakMap,a=new WeakMap,c=new WeakMap,Symbol.iterator)](){for(const[e,t]of Object.entries(this.store))yield[e,t]}_encryptData(e){if(!u(this,i,"f"))return e.toString();try{if(u(this,i,"f"))try{if(":"===e.slice(16,17).toString()){const t=e.slice(0,16),r=p.pbkdf2Sync(u(this,i,"f"),t.toString(),1e4,32,"sha512"),n=p.createDecipheriv(P,r,t);e=Buffer.concat([n.update(Buffer.from(e.slice(17))),n.final()]).toString("utf8")}else{const t=p.createDecipher(P,u(this,i,"f"));e=Buffer.concat([t.update(Buffer.from(e)),t.final()]).toString("utf8")}}catch(e){}}catch(e){}return e.toString()}_handleChange(e,t){let r=e();const n=()=>{const n=r,s=e();(0,d.isDeepStrictEqual)(s,n)||(r=s,t.call(this,s,n))};return this.events.on("change",n),()=>this.events.removeListener("change",n)}_validate(e){if(!u(this,o,"f"))return;if(u(this,o,"f").call(this,e)||!u(this,o,"f").errors)return;const t=u(this,o,"f").errors.map((({instancePath:e,message:t=""})=>`\`${e.slice(1)}\` ${t}`));throw new Error("Config schema violation: "+t.join("; "))}_ensureDirectory(){f.mkdirSync(h.dirname(this.path),{recursive:!0})}_write(e){let t=this._serialize(e);if(u(this,i,"f")){const e=p.randomBytes(16),r=p.pbkdf2Sync(u(this,i,"f"),e.toString(),1e4,32,"sha512"),n=p.createCipheriv(P,r,e);t=Buffer.concat([e,Buffer.from(":"),n.update(Buffer.from(t)),n.final()])}if(process.env.SNAP)f.writeFileSync(this.path,t,{mode:u(this,a,"f").configFileMode});else try{E.writeFileSync(this.path,t,{mode:u(this,a,"f").configFileMode})}catch(e){if("EXDEV"===(null==e?void 0:e.code))return void f.writeFileSync(this.path,t,{mode:u(this,a,"f").configFileMode});throw e}}_watch(){this._ensureDirectory(),f.existsSync(this.path)||this._write(x()),"win32"===process.platform?f.watch(this.path,{persistent:!1},b((()=>{this.events.emit("change")}),{wait:100})):f.watchFile(this.path,{persistent:!1},b((()=>{this.events.emit("change")}),{wait:5e3}))}_migrate(e,t,r){let n=this._get(j,"0.0.0");const s=Object.keys(e).filter((e=>this._shouldPerformMigration(e,n,t)));let o={...this.store};for(const i of s)try{r&&r(this,{fromVersion:n,toVersion:i,finalVersion:t,versions:s}),(0,e[i])(this),this._set(j,i),n=i,o={...this.store}}catch(e){throw this.store=o,new Error(`Something went wrong during the migration! Changes applied to the store until this failed migration will be restored. ${e}`)}!this._isVersionInRangeFormat(n)&&S.eq(n,t)||this._set(j,t)}_containsReservedKey(e){return"object"==typeof e&&Object.keys(e)[0]===I||"string"==typeof e&&!!u(this,a,"f").accessPropertiesByDotNotation&&!!e.startsWith(`${I}.`)}_isVersionInRangeFormat(e){return null===S.clean(e)}_shouldPerformMigration(e,t,r){return this._isVersionInRangeFormat(e)?("0.0.0"===t||!S.satisfies(t,e))&&S.satisfies(r,e):!S.lte(e,t)&&!S.gt(e,r)}_get(e,t){return g.get(this.store,e,t)}_set(e,t){const{store:r}=this;g.set(r,e,t),this.store=r}}t.default=R,e.exports=R,e.exports.default=R},5583:(e,t)=>{"use strict";function r(e,t){return{validate:e,compare:t}}Object.defineProperty(t,"__esModule",{value:!0}),t.formatNames=t.fastFormats=t.fullFormats=void 0,t.fullFormats={date:r(o,i),time:r(c,l),"date-time":r((function(e){const t=e.split(u);return 2===t.length&&o(t[0])&&c(t[1],!0)}),d),duration:/^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/,uri:function(e){return f.test(e)&&h.test(e)},"uri-reference":/^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i,"uri-template":/^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i,url:/^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu,email:/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,hostname:/^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i,ipv4:/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,ipv6:/^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i,regex:function(e){if(v.test(e))return!1;try{return new RegExp(e),!0}catch(e){return!1}},uuid:/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,"json-pointer":/^(?:\/(?:[^~/]|~0|~1)*)*$/,"json-pointer-uri-fragment":/^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,"relative-json-pointer":/^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/,byte:function(e){return p.lastIndex=0,p.test(e)},int32:{type:"number",validate:function(e){return Number.isInteger(e)&&e<=y&&e>=m}},int64:{type:"number",validate:function(e){return Number.isInteger(e)}},float:{type:"number",validate:g},double:{type:"number",validate:g},password:!0,binary:!0},t.fastFormats={...t.fullFormats,date:r(/^\d\d\d\d-[0-1]\d-[0-3]\d$/,i),time:r(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i,l),"date-time":r(/^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i,d),uri:/^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i,"uri-reference":/^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i,email:/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i},t.formatNames=Object.keys(t.fullFormats);const n=/^(\d\d\d\d)-(\d\d)-(\d\d)$/,s=[0,31,28,31,30,31,30,31,31,30,31,30,31];function o(e){const t=n.exec(e);if(!t)return!1;const r=+t[1],o=+t[2],i=+t[3];return o>=1&&o<=12&&i>=1&&i<=(2===o&&function(e){return e%4==0&&(e%100!=0||e%400==0)}(r)?29:s[o])}function i(e,t){if(e&&t)return e>t?1:e(t=n[1]+n[2]+n[3]+(n[4]||""))?1:e{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5583),s=r(1984),o=r(5899),i=new o.Name("fullFormats"),a=new o.Name("fastFormats"),c=(e,t={keywords:!0})=>{if(Array.isArray(t))return l(e,t,n.fullFormats,i),e;const[r,o]="fast"===t.mode?[n.fastFormats,a]:[n.fullFormats,i];return l(e,t.formats||n.formatNames,r,o),t.keywords&&s.default(e),e};function l(e,t,r,n){var s,i;null!==(s=(i=e.opts.code).formats)&&void 0!==s||(i.formats=o._`require("ajv-formats/dist/formats").${n}`);for(const n of t)e.addFormat(n,r[n])}c.get=(e,t="full")=>{const r=("fast"===t?n.fastFormats:n.fullFormats)[e];if(!r)throw new Error(`Unknown format "${e}"`);return r},e.exports=t=c,Object.defineProperty(t,"__esModule",{value:!0}),t.default=c},1984:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.formatLimitDefinition=void 0;const n=r(5133),s=r(5899),o=s.operators,i={formatMaximum:{okStr:"<=",ok:o.LTE,fail:o.GT},formatMinimum:{okStr:">=",ok:o.GTE,fail:o.LT},formatExclusiveMaximum:{okStr:"<",ok:o.LT,fail:o.GTE},formatExclusiveMinimum:{okStr:">",ok:o.GT,fail:o.LTE}},a={message:({keyword:e,schemaCode:t})=>s.str`should be ${i[e].okStr} ${t}`,params:({keyword:e,schemaCode:t})=>s._`{comparison: ${i[e].okStr}, limit: ${t}}`};t.formatLimitDefinition={keyword:Object.keys(i),type:"string",schemaType:"string",$data:!0,error:a,code(e){const{gen:t,data:r,schemaCode:o,keyword:a,it:c}=e,{opts:l,self:u}=c;if(!l.validateFormats)return;const d=new n.KeywordCxt(c,u.RULES.all.format.definition,"format");function f(e){return s._`${e}.compare(${r}, ${o}) ${i[a].fail} 0`}d.$data?function(){const r=t.scopeValue("formats",{ref:u.formats,code:l.code.formats}),n=t.const("fmt",s._`${r}[${d.schemaCode}]`);e.fail$data(s.or(s._`typeof ${n} != "object"`,s._`${n} instanceof RegExp`,s._`typeof ${n}.compare != "function"`,f(n)))}():function(){const r=d.schema,n=u.formats[r];if(!n||!0===n)return;if("object"!=typeof n||n instanceof RegExp||"function"!=typeof n.compare)throw new Error(`"${a}": format "${r}" does not define "compare" function`);const o=t.scopeValue("formats",{key:r,ref:n,code:l.code.formats?s._`${l.code.formats}${s.getProperty(r)}`:void 0});e.fail$data(f(o))}()},dependencies:["format"]},t.default=e=>(e.addKeyword(t.formatLimitDefinition),e)},5133:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.MissingRefError=t.ValidationError=t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;const n=r(3579),s=r(6568),o=r(8402),i=r(115),a=["/properties"],c="http://json-schema.org/draft-07/schema";class l extends n.default{_addVocabularies(){super._addVocabularies(),s.default.forEach((e=>this.addVocabulary(e))),this.opts.discriminator&&this.addKeyword(o.default)}_addDefaultMetaSchema(){if(super._addDefaultMetaSchema(),!this.opts.meta)return;const e=this.opts.$data?this.$dataMetaSchema(i,a):i;this.addMetaSchema(e,c,!1),this.refs["http://json-schema.org/schema"]=c}defaultMeta(){return this.opts.defaultMeta=super.defaultMeta()||(this.getSchema(c)?c:void 0)}}e.exports=t=l,Object.defineProperty(t,"__esModule",{value:!0}),t.default=l;var u=r(5032);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return u.KeywordCxt}});var d=r(5899);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return d._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return d.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return d.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return d.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return d.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return d.CodeGen}});var f=r(7173);Object.defineProperty(t,"ValidationError",{enumerable:!0,get:function(){return f.default}});var h=r(433);Object.defineProperty(t,"MissingRefError",{enumerable:!0,get:function(){return h.default}})},6796:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regexpCode=t.getEsmExportName=t.getProperty=t.safeStringify=t.stringify=t.strConcat=t.addCodeArg=t.str=t._=t.nil=t._Code=t.Name=t.IDENTIFIER=t._CodeOrName=void 0;class r{}t._CodeOrName=r,t.IDENTIFIER=/^[a-z$_][a-z$_0-9]*$/i;class n extends r{constructor(e){if(super(),!t.IDENTIFIER.test(e))throw new Error("CodeGen: name must be a valid identifier");this.str=e}toString(){return this.str}emptyStr(){return!1}get names(){return{[this.str]:1}}}t.Name=n;class s extends r{constructor(e){super(),this._items="string"==typeof e?[e]:e}toString(){return this.str}emptyStr(){if(this._items.length>1)return!1;const e=this._items[0];return""===e||'""'===e}get str(){var e;return null!==(e=this._str)&&void 0!==e?e:this._str=this._items.reduce(((e,t)=>`${e}${t}`),"")}get names(){var e;return null!==(e=this._names)&&void 0!==e?e:this._names=this._items.reduce(((e,t)=>(t instanceof n&&(e[t.str]=(e[t.str]||0)+1),e)),{})}}function o(e,...t){const r=[e[0]];let n=0;for(;n{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.or=t.and=t.not=t.CodeGen=t.operators=t.varKinds=t.ValueScopeName=t.ValueScope=t.Scope=t.Name=t.regexpCode=t.stringify=t.getProperty=t.nil=t.strConcat=t.str=t._=void 0;const n=r(6796),s=r(2321);var o=r(6796);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return o._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return o.str}}),Object.defineProperty(t,"strConcat",{enumerable:!0,get:function(){return o.strConcat}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return o.nil}}),Object.defineProperty(t,"getProperty",{enumerable:!0,get:function(){return o.getProperty}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return o.stringify}}),Object.defineProperty(t,"regexpCode",{enumerable:!0,get:function(){return o.regexpCode}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return o.Name}});var i=r(2321);Object.defineProperty(t,"Scope",{enumerable:!0,get:function(){return i.Scope}}),Object.defineProperty(t,"ValueScope",{enumerable:!0,get:function(){return i.ValueScope}}),Object.defineProperty(t,"ValueScopeName",{enumerable:!0,get:function(){return i.ValueScopeName}}),Object.defineProperty(t,"varKinds",{enumerable:!0,get:function(){return i.varKinds}}),t.operators={GT:new n._Code(">"),GTE:new n._Code(">="),LT:new n._Code("<"),LTE:new n._Code("<="),EQ:new n._Code("==="),NEQ:new n._Code("!=="),NOT:new n._Code("!"),OR:new n._Code("||"),AND:new n._Code("&&"),ADD:new n._Code("+")};class a{optimizeNodes(){return this}optimizeNames(e,t){return this}}class c extends a{constructor(e,t,r){super(),this.varKind=e,this.name=t,this.rhs=r}render({es5:e,_n:t}){const r=e?s.varKinds.var:this.varKind,n=void 0===this.rhs?"":` = ${this.rhs}`;return`${r} ${this.name}${n};`+t}optimizeNames(e,t){if(e[this.name.str])return this.rhs&&(this.rhs=R(this.rhs,e,t)),this}get names(){return this.rhs instanceof n._CodeOrName?this.rhs.names:{}}}class l extends a{constructor(e,t,r){super(),this.lhs=e,this.rhs=t,this.sideEffects=r}render({_n:e}){return`${this.lhs} = ${this.rhs};`+e}optimizeNames(e,t){if(!(this.lhs instanceof n.Name)||e[this.lhs.str]||this.sideEffects)return this.rhs=R(this.rhs,e,t),this}get names(){return j(this.lhs instanceof n.Name?{}:{...this.lhs.names},this.rhs)}}class u extends l{constructor(e,t,r,n){super(e,r,n),this.op=t}render({_n:e}){return`${this.lhs} ${this.op}= ${this.rhs};`+e}}class d extends a{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`${this.label}:`+e}}class f extends a{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`break${this.label?` ${this.label}`:""};`+e}}class h extends a{constructor(e){super(),this.error=e}render({_n:e}){return`throw ${this.error};`+e}get names(){return this.error.names}}class p extends a{constructor(e){super(),this.code=e}render({_n:e}){return`${this.code};`+e}optimizeNodes(){return`${this.code}`?this:void 0}optimizeNames(e,t){return this.code=R(this.code,e,t),this}get names(){return this.code instanceof n._CodeOrName?this.code.names:{}}}class m extends a{constructor(e=[]){super(),this.nodes=e}render(e){return this.nodes.reduce(((t,r)=>t+r.render(e)),"")}optimizeNodes(){const{nodes:e}=this;let t=e.length;for(;t--;){const r=e[t].optimizeNodes();Array.isArray(r)?e.splice(t,1,...r):r?e[t]=r:e.splice(t,1)}return e.length>0?this:void 0}optimizeNames(e,t){const{nodes:r}=this;let n=r.length;for(;n--;){const s=r[n];s.optimizeNames(e,t)||(A(e,s.names),r.splice(n,1))}return r.length>0?this:void 0}get names(){return this.nodes.reduce(((e,t)=>I(e,t.names)),{})}}class y extends m{render(e){return"{"+e._n+super.render(e)+"}"+e._n}}class g extends m{}class v extends y{}v.kind="else";class w extends y{constructor(e,t){super(t),this.condition=e}render(e){let t=`if(${this.condition})`+super.render(e);return this.else&&(t+="else "+this.else.render(e)),t}optimizeNodes(){super.optimizeNodes();const e=this.condition;if(!0===e)return this.nodes;let t=this.else;if(t){const e=t.optimizeNodes();t=this.else=Array.isArray(e)?new v(e):e}return t?!1===e?t instanceof w?t:t.nodes:this.nodes.length?this:new w(T(e),t instanceof w?[t]:t.nodes):!1!==e&&this.nodes.length?this:void 0}optimizeNames(e,t){var r;if(this.else=null===(r=this.else)||void 0===r?void 0:r.optimizeNames(e,t),super.optimizeNames(e,t)||this.else)return this.condition=R(this.condition,e,t),this}get names(){const e=super.names;return j(e,this.condition),this.else&&I(e,this.else.names),e}}w.kind="if";class E extends y{}E.kind="for";class $ extends E{constructor(e){super(),this.iteration=e}render(e){return`for(${this.iteration})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iteration=R(this.iteration,e,t),this}get names(){return I(super.names,this.iteration.names)}}class _ extends E{constructor(e,t,r,n){super(),this.varKind=e,this.name=t,this.from=r,this.to=n}render(e){const t=e.es5?s.varKinds.var:this.varKind,{name:r,from:n,to:o}=this;return`for(${t} ${r}=${n}; ${r}<${o}; ${r}++)`+super.render(e)}get names(){const e=j(super.names,this.from);return j(e,this.to)}}class b extends E{constructor(e,t,r,n){super(),this.loop=e,this.varKind=t,this.name=r,this.iterable=n}render(e){return`for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iterable=R(this.iterable,e,t),this}get names(){return I(super.names,this.iterable.names)}}class S extends y{constructor(e,t,r){super(),this.name=e,this.args=t,this.async=r}render(e){return`${this.async?"async ":""}function ${this.name}(${this.args})`+super.render(e)}}S.kind="func";class O extends m{render(e){return"return "+super.render(e)}}O.kind="return";class P extends y{render(e){let t="try"+super.render(e);return this.catch&&(t+=this.catch.render(e)),this.finally&&(t+=this.finally.render(e)),t}optimizeNodes(){var e,t;return super.optimizeNodes(),null===(e=this.catch)||void 0===e||e.optimizeNodes(),null===(t=this.finally)||void 0===t||t.optimizeNodes(),this}optimizeNames(e,t){var r,n;return super.optimizeNames(e,t),null===(r=this.catch)||void 0===r||r.optimizeNames(e,t),null===(n=this.finally)||void 0===n||n.optimizeNames(e,t),this}get names(){const e=super.names;return this.catch&&I(e,this.catch.names),this.finally&&I(e,this.finally.names),e}}class x extends y{constructor(e){super(),this.error=e}render(e){return`catch(${this.error})`+super.render(e)}}x.kind="catch";class N extends y{render(e){return"finally"+super.render(e)}}function I(e,t){for(const r in t)e[r]=(e[r]||0)+(t[r]||0);return e}function j(e,t){return t instanceof n._CodeOrName?I(e,t.names):e}function R(e,t,r){return e instanceof n.Name?o(e):(s=e)instanceof n._Code&&s._items.some((e=>e instanceof n.Name&&1===t[e.str]&&void 0!==r[e.str]))?new n._Code(e._items.reduce(((e,t)=>(t instanceof n.Name&&(t=o(t)),t instanceof n._Code?e.push(...t._items):e.push(t),e)),[])):e;var s;function o(e){const n=r[e.str];return void 0===n||1!==t[e.str]?e:(delete t[e.str],n)}}function A(e,t){for(const r in t)e[r]=(e[r]||0)-(t[r]||0)}function T(e){return"boolean"==typeof e||"number"==typeof e||null===e?!e:n._`!${L(e)}`}N.kind="finally",t.CodeGen=class{constructor(e,t={}){this._values={},this._blockStarts=[],this._constants={},this.opts={...t,_n:t.lines?"\n":""},this._extScope=e,this._scope=new s.Scope({parent:e}),this._nodes=[new g]}toString(){return this._root.render(this.opts)}name(e){return this._scope.name(e)}scopeName(e){return this._extScope.name(e)}scopeValue(e,t){const r=this._extScope.value(e,t);return(this._values[r.prefix]||(this._values[r.prefix]=new Set)).add(r),r}getScopeValue(e,t){return this._extScope.getValue(e,t)}scopeRefs(e){return this._extScope.scopeRefs(e,this._values)}scopeCode(){return this._extScope.scopeCode(this._values)}_def(e,t,r,n){const s=this._scope.toName(t);return void 0!==r&&n&&(this._constants[s.str]=r),this._leafNode(new c(e,s,r)),s}const(e,t,r){return this._def(s.varKinds.const,e,t,r)}let(e,t,r){return this._def(s.varKinds.let,e,t,r)}var(e,t,r){return this._def(s.varKinds.var,e,t,r)}assign(e,t,r){return this._leafNode(new l(e,t,r))}add(e,r){return this._leafNode(new u(e,t.operators.ADD,r))}code(e){return"function"==typeof e?e():e!==n.nil&&this._leafNode(new p(e)),this}object(...e){const t=["{"];for(const[r,s]of e)t.length>1&&t.push(","),t.push(r),(r!==s||this.opts.es5)&&(t.push(":"),(0,n.addCodeArg)(t,s));return t.push("}"),new n._Code(t)}if(e,t,r){if(this._blockNode(new w(e)),t&&r)this.code(t).else().code(r).endIf();else if(t)this.code(t).endIf();else if(r)throw new Error('CodeGen: "else" body without "then" body');return this}elseIf(e){return this._elseNode(new w(e))}else(){return this._elseNode(new v)}endIf(){return this._endBlockNode(w,v)}_for(e,t){return this._blockNode(e),t&&this.code(t).endFor(),this}for(e,t){return this._for(new $(e),t)}forRange(e,t,r,n,o=(this.opts.es5?s.varKinds.var:s.varKinds.let)){const i=this._scope.toName(e);return this._for(new _(o,i,t,r),(()=>n(i)))}forOf(e,t,r,o=s.varKinds.const){const i=this._scope.toName(e);if(this.opts.es5){const e=t instanceof n.Name?t:this.var("_arr",t);return this.forRange("_i",0,n._`${e}.length`,(t=>{this.var(i,n._`${e}[${t}]`),r(i)}))}return this._for(new b("of",o,i,t),(()=>r(i)))}forIn(e,t,r,o=(this.opts.es5?s.varKinds.var:s.varKinds.const)){if(this.opts.ownProperties)return this.forOf(e,n._`Object.keys(${t})`,r);const i=this._scope.toName(e);return this._for(new b("in",o,i,t),(()=>r(i)))}endFor(){return this._endBlockNode(E)}label(e){return this._leafNode(new d(e))}break(e){return this._leafNode(new f(e))}return(e){const t=new O;if(this._blockNode(t),this.code(e),1!==t.nodes.length)throw new Error('CodeGen: "return" should have one node');return this._endBlockNode(O)}try(e,t,r){if(!t&&!r)throw new Error('CodeGen: "try" without "catch" and "finally"');const n=new P;if(this._blockNode(n),this.code(e),t){const e=this.name("e");this._currNode=n.catch=new x(e),t(e)}return r&&(this._currNode=n.finally=new N,this.code(r)),this._endBlockNode(x,N)}throw(e){return this._leafNode(new h(e))}block(e,t){return this._blockStarts.push(this._nodes.length),e&&this.code(e).endBlock(t),this}endBlock(e){const t=this._blockStarts.pop();if(void 0===t)throw new Error("CodeGen: not in self-balancing block");const r=this._nodes.length-t;if(r<0||void 0!==e&&r!==e)throw new Error(`CodeGen: wrong number of nodes: ${r} vs ${e} expected`);return this._nodes.length=t,this}func(e,t=n.nil,r,s){return this._blockNode(new S(e,t,r)),s&&this.code(s).endFunc(),this}endFunc(){return this._endBlockNode(S)}optimize(e=1){for(;e-- >0;)this._root.optimizeNodes(),this._root.optimizeNames(this._root.names,this._constants)}_leafNode(e){return this._currNode.nodes.push(e),this}_blockNode(e){this._currNode.nodes.push(e),this._nodes.push(e)}_endBlockNode(e,t){const r=this._currNode;if(r instanceof e||t&&r instanceof t)return this._nodes.pop(),this;throw new Error(`CodeGen: not in block "${t?`${e.kind}/${t.kind}`:e.kind}"`)}_elseNode(e){const t=this._currNode;if(!(t instanceof w))throw new Error('CodeGen: "else" without "if"');return this._currNode=t.else=e,this}get _root(){return this._nodes[0]}get _currNode(){const e=this._nodes;return e[e.length-1]}set _currNode(e){const t=this._nodes;t[t.length-1]=e}},t.not=T;const C=k(t.operators.AND);t.and=function(...e){return e.reduce(C)};const D=k(t.operators.OR);function k(e){return(t,r)=>t===n.nil?r:r===n.nil?t:n._`${L(t)} ${e} ${L(r)}`}function L(e){return e instanceof n.Name?e:n._`(${e})`}t.or=function(...e){return e.reduce(D)}},2321:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ValueScope=t.ValueScopeName=t.Scope=t.varKinds=t.UsedValueState=void 0;const n=r(6796);class s extends Error{constructor(e){super(`CodeGen: "code" for ${e} not defined`),this.value=e.value}}var o;!function(e){e[e.Started=0]="Started",e[e.Completed=1]="Completed"}(o=t.UsedValueState||(t.UsedValueState={})),t.varKinds={const:new n.Name("const"),let:new n.Name("let"),var:new n.Name("var")};class i{constructor({prefixes:e,parent:t}={}){this._names={},this._prefixes=e,this._parent=t}toName(e){return e instanceof n.Name?e:this.name(e)}name(e){return new n.Name(this._newName(e))}_newName(e){return`${e}${(this._names[e]||this._nameGroup(e)).index++}`}_nameGroup(e){var t,r;if((null===(r=null===(t=this._parent)||void 0===t?void 0:t._prefixes)||void 0===r?void 0:r.has(e))||this._prefixes&&!this._prefixes.has(e))throw new Error(`CodeGen: prefix "${e}" is not allowed in this scope`);return this._names[e]={prefix:e,index:0}}}t.Scope=i;class a extends n.Name{constructor(e,t){super(t),this.prefix=e}setValue(e,{property:t,itemIndex:r}){this.value=e,this.scopePath=n._`.${new n.Name(t)}[${r}]`}}t.ValueScopeName=a;const c=n._`\n`;t.ValueScope=class extends i{constructor(e){super(e),this._values={},this._scope=e.scope,this.opts={...e,_n:e.lines?c:n.nil}}get(){return this._scope}name(e){return new a(e,this._newName(e))}value(e,t){var r;if(void 0===t.ref)throw new Error("CodeGen: ref must be passed in value");const n=this.toName(e),{prefix:s}=n,o=null!==(r=t.key)&&void 0!==r?r:t.ref;let i=this._values[s];if(i){const e=i.get(o);if(e)return e}else i=this._values[s]=new Map;i.set(o,n);const a=this._scope[s]||(this._scope[s]=[]),c=a.length;return a[c]=t.ref,n.setValue(t,{property:s,itemIndex:c}),n}getValue(e,t){const r=this._values[e];if(r)return r.get(t)}scopeRefs(e,t=this._values){return this._reduceValues(t,(t=>{if(void 0===t.scopePath)throw new Error(`CodeGen: name "${t}" has no value`);return n._`${e}${t.scopePath}`}))}scopeCode(e=this._values,t,r){return this._reduceValues(e,(e=>{if(void 0===e.value)throw new Error(`CodeGen: name "${e}" has no value`);return e.value.code}),t,r)}_reduceValues(e,r,i={},a){let c=n.nil;for(const l in e){const u=e[l];if(!u)continue;const d=i[l]=i[l]||new Map;u.forEach((e=>{if(d.has(e))return;d.set(e,o.Started);let i=r(e);if(i){const r=this.opts.es5?t.varKinds.var:t.varKinds.const;c=n._`${c}${r} ${e} = ${i};${this.opts._n}`}else{if(!(i=null==a?void 0:a(e)))throw new s(e);c=n._`${c}${i}${this.opts._n}`}d.set(e,o.Completed)}))}return c}}},4677:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendErrors=t.resetErrorsCount=t.reportExtraError=t.reportError=t.keyword$DataError=t.keywordError=void 0;const n=r(5899),s=r(320),o=r(6934);function i(e,t){const r=e.const("err",t);e.if(n._`${o.default.vErrors} === null`,(()=>e.assign(o.default.vErrors,n._`[${r}]`)),n._`${o.default.vErrors}.push(${r})`),e.code(n._`${o.default.errors}++`)}function a(e,t){const{gen:r,validateName:s,schemaEnv:o}=e;o.$async?r.throw(n._`new ${e.ValidationError}(${t})`):(r.assign(n._`${s}.errors`,t),r.return(!1))}t.keywordError={message:({keyword:e})=>n.str`must pass "${e}" keyword validation`},t.keyword$DataError={message:({keyword:e,schemaType:t})=>t?n.str`"${e}" keyword must be ${t} ($data)`:n.str`"${e}" keyword is invalid ($data)`},t.reportError=function(e,r=t.keywordError,s,o){const{it:c}=e,{gen:u,compositeRule:d,allErrors:f}=c,h=l(e,r,s);(null!=o?o:d||f)?i(u,h):a(c,n._`[${h}]`)},t.reportExtraError=function(e,r=t.keywordError,n){const{it:s}=e,{gen:c,compositeRule:u,allErrors:d}=s;i(c,l(e,r,n)),u||d||a(s,o.default.vErrors)},t.resetErrorsCount=function(e,t){e.assign(o.default.errors,t),e.if(n._`${o.default.vErrors} !== null`,(()=>e.if(t,(()=>e.assign(n._`${o.default.vErrors}.length`,t)),(()=>e.assign(o.default.vErrors,null)))))},t.extendErrors=function({gen:e,keyword:t,schemaValue:r,data:s,errsCount:i,it:a}){if(void 0===i)throw new Error("ajv implementation error");const c=e.name("err");e.forRange("i",i,o.default.errors,(i=>{e.const(c,n._`${o.default.vErrors}[${i}]`),e.if(n._`${c}.instancePath === undefined`,(()=>e.assign(n._`${c}.instancePath`,(0,n.strConcat)(o.default.instancePath,a.errorPath)))),e.assign(n._`${c}.schemaPath`,n.str`${a.errSchemaPath}/${t}`),a.opts.verbose&&(e.assign(n._`${c}.schema`,r),e.assign(n._`${c}.data`,s))}))};const c={keyword:new n.Name("keyword"),schemaPath:new n.Name("schemaPath"),params:new n.Name("params"),propertyName:new n.Name("propertyName"),message:new n.Name("message"),schema:new n.Name("schema"),parentSchema:new n.Name("parentSchema")};function l(e,t,r){const{createErrors:s}=e.it;return!1===s?n._`{}`:function(e,t,r={}){const{gen:s,it:i}=e,a=[u(i,r),d(e,r)];return function(e,{params:t,message:r},s){const{keyword:i,data:a,schemaValue:l,it:u}=e,{opts:d,propertyName:f,topSchemaRef:h,schemaPath:p}=u;s.push([c.keyword,i],[c.params,"function"==typeof t?t(e):t||n._`{}`]),d.messages&&s.push([c.message,"function"==typeof r?r(e):r]),d.verbose&&s.push([c.schema,l],[c.parentSchema,n._`${h}${p}`],[o.default.data,a]),f&&s.push([c.propertyName,f])}(e,t,a),s.object(...a)}(e,t,r)}function u({errorPath:e},{instancePath:t}){const r=t?n.str`${e}${(0,s.getErrorPath)(t,s.Type.Str)}`:e;return[o.default.instancePath,(0,n.strConcat)(o.default.instancePath,r)]}function d({keyword:e,it:{errSchemaPath:t}},{schemaPath:r,parentSchema:o}){let i=o?t:n.str`${t}/${e}`;return r&&(i=n.str`${i}${(0,s.getErrorPath)(r,s.Type.Str)}`),[c.schemaPath,i]}},7760:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resolveSchema=t.getCompilingSchema=t.resolveRef=t.compileSchema=t.SchemaEnv=void 0;const n=r(5899),s=r(7173),o=r(6934),i=r(6885),a=r(320),c=r(5032);class l{constructor(e){var t;let r;this.refs={},this.dynamicAnchors={},"object"==typeof e.schema&&(r=e.schema),this.schema=e.schema,this.schemaId=e.schemaId,this.root=e.root||this,this.baseId=null!==(t=e.baseId)&&void 0!==t?t:(0,i.normalizeId)(null==r?void 0:r[e.schemaId||"$id"]),this.schemaPath=e.schemaPath,this.localRefs=e.localRefs,this.meta=e.meta,this.$async=null==r?void 0:r.$async,this.refs={}}}function u(e){const t=f.call(this,e);if(t)return t;const r=(0,i.getFullPath)(this.opts.uriResolver,e.root.baseId),{es5:a,lines:l}=this.opts.code,{ownProperties:u}=this.opts,d=new n.CodeGen(this.scope,{es5:a,lines:l,ownProperties:u});let h;e.$async&&(h=d.scopeValue("Error",{ref:s.default,code:n._`require("ajv/dist/runtime/validation_error").default`}));const p=d.scopeName("validate");e.validateName=p;const m={gen:d,allErrors:this.opts.allErrors,data:o.default.data,parentData:o.default.parentData,parentDataProperty:o.default.parentDataProperty,dataNames:[o.default.data],dataPathArr:[n.nil],dataLevel:0,dataTypes:[],definedProperties:new Set,topSchemaRef:d.scopeValue("schema",!0===this.opts.code.source?{ref:e.schema,code:(0,n.stringify)(e.schema)}:{ref:e.schema}),validateName:p,ValidationError:h,schema:e.schema,schemaEnv:e,rootId:r,baseId:e.baseId||r,schemaPath:n.nil,errSchemaPath:e.schemaPath||(this.opts.jtd?"":"#"),errorPath:n._`""`,opts:this.opts,self:this};let y;try{this._compilations.add(e),(0,c.validateFunctionCode)(m),d.optimize(this.opts.code.optimize);const t=d.toString();y=`${d.scopeRefs(o.default.scope)}return ${t}`,this.opts.code.process&&(y=this.opts.code.process(y,e));const r=new Function(`${o.default.self}`,`${o.default.scope}`,y)(this,this.scope.get());if(this.scope.value(p,{ref:r}),r.errors=null,r.schema=e.schema,r.schemaEnv=e,e.$async&&(r.$async=!0),!0===this.opts.code.source&&(r.source={validateName:p,validateCode:t,scopeValues:d._values}),this.opts.unevaluated){const{props:e,items:t}=m;r.evaluated={props:e instanceof n.Name?void 0:e,items:t instanceof n.Name?void 0:t,dynamicProps:e instanceof n.Name,dynamicItems:t instanceof n.Name},r.source&&(r.source.evaluated=(0,n.stringify)(r.evaluated))}return e.validate=r,e}catch(t){throw delete e.validate,delete e.validateName,y&&this.logger.error("Error compiling schema, function code:",y),t}finally{this._compilations.delete(e)}}function d(e){return(0,i.inlineRef)(e.schema,this.opts.inlineRefs)?e.schema:e.validate?e:u.call(this,e)}function f(e){for(const n of this._compilations)if(r=e,(t=n).schema===r.schema&&t.root===r.root&&t.baseId===r.baseId)return n;var t,r}function h(e,t){let r;for(;"string"==typeof(r=this.refs[t]);)t=r;return r||this.schemas[t]||p.call(this,e,t)}function p(e,t){const r=this.opts.uriResolver.parse(t),n=(0,i._getFullPath)(this.opts.uriResolver,r);let s=(0,i.getFullPath)(this.opts.uriResolver,e.baseId,void 0);if(Object.keys(e.schema).length>0&&n===s)return y.call(this,r,e);const o=(0,i.normalizeId)(n),a=this.refs[o]||this.schemas[o];if("string"==typeof a){const t=p.call(this,e,a);if("object"!=typeof(null==t?void 0:t.schema))return;return y.call(this,r,t)}if("object"==typeof(null==a?void 0:a.schema)){if(a.validate||u.call(this,a),o===(0,i.normalizeId)(t)){const{schema:t}=a,{schemaId:r}=this.opts,n=t[r];return n&&(s=(0,i.resolveUrl)(this.opts.uriResolver,s,n)),new l({schema:t,schemaId:r,root:e,baseId:s})}return y.call(this,r,a)}}t.SchemaEnv=l,t.compileSchema=u,t.resolveRef=function(e,t,r){var n;r=(0,i.resolveUrl)(this.opts.uriResolver,t,r);const s=e.refs[r];if(s)return s;let o=h.call(this,e,r);if(void 0===o){const s=null===(n=e.localRefs)||void 0===n?void 0:n[r],{schemaId:i}=this.opts;s&&(o=new l({schema:s,schemaId:i,root:e,baseId:t}))}return void 0!==o?e.refs[r]=d.call(this,o):void 0},t.getCompilingSchema=f,t.resolveSchema=p;const m=new Set(["properties","patternProperties","enum","dependencies","definitions"]);function y(e,{baseId:t,schema:r,root:n}){var s;if("/"!==(null===(s=e.fragment)||void 0===s?void 0:s[0]))return;for(const n of e.fragment.slice(1).split("/")){if("boolean"==typeof r)return;const e=r[(0,a.unescapeFragment)(n)];if(void 0===e)return;const s="object"==typeof(r=e)&&r[this.opts.schemaId];!m.has(n)&&s&&(t=(0,i.resolveUrl)(this.opts.uriResolver,t,s))}let o;if("boolean"!=typeof r&&r.$ref&&!(0,a.schemaHasRulesButRef)(r,this.RULES)){const e=(0,i.resolveUrl)(this.opts.uriResolver,t,r.$ref);o=p.call(this,n,e)}const{schemaId:c}=this.opts;return o=o||new l({schema:r,schemaId:c,root:n,baseId:t}),o.schema!==o.root.schema?o:void 0}},6934:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s={data:new n.Name("data"),valCxt:new n.Name("valCxt"),instancePath:new n.Name("instancePath"),parentData:new n.Name("parentData"),parentDataProperty:new n.Name("parentDataProperty"),rootData:new n.Name("rootData"),dynamicAnchors:new n.Name("dynamicAnchors"),vErrors:new n.Name("vErrors"),errors:new n.Name("errors"),this:new n.Name("this"),self:new n.Name("self"),scope:new n.Name("scope"),json:new n.Name("json"),jsonPos:new n.Name("jsonPos"),jsonLen:new n.Name("jsonLen"),jsonPart:new n.Name("jsonPart")};t.default=s},433:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(6885);class s extends Error{constructor(e,t,r,s){super(s||`can't resolve reference ${r} from id ${t}`),this.missingRef=(0,n.resolveUrl)(e,t,r),this.missingSchema=(0,n.normalizeId)((0,n.getFullPath)(e,this.missingRef))}}t.default=s},6885:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getSchemaRefs=t.resolveUrl=t.normalizeId=t._getFullPath=t.getFullPath=t.inlineRef=void 0;const n=r(320),s=r(4063),o=r(5528),i=new Set(["type","format","pattern","maxLength","minLength","maxProperties","minProperties","maxItems","minItems","maximum","minimum","uniqueItems","multipleOf","required","enum","const"]);t.inlineRef=function(e,t=!0){return"boolean"==typeof e||(!0===t?!c(e):!!t&&l(e)<=t)};const a=new Set(["$ref","$recursiveRef","$recursiveAnchor","$dynamicRef","$dynamicAnchor"]);function c(e){for(const t in e){if(a.has(t))return!0;const r=e[t];if(Array.isArray(r)&&r.some(c))return!0;if("object"==typeof r&&c(r))return!0}return!1}function l(e){let t=0;for(const r in e){if("$ref"===r)return 1/0;if(t++,!i.has(r)&&("object"==typeof e[r]&&(0,n.eachItem)(e[r],(e=>t+=l(e))),t===1/0))return 1/0}return t}function u(e,t="",r){!1!==r&&(t=h(t));const n=e.parse(t);return d(e,n)}function d(e,t){return e.serialize(t).split("#")[0]+"#"}t.getFullPath=u,t._getFullPath=d;const f=/#\/?$/;function h(e){return e?e.replace(f,""):""}t.normalizeId=h,t.resolveUrl=function(e,t,r){return r=h(r),e.resolve(t,r)};const p=/^[a-z_][-a-z0-9._]*$/i;t.getSchemaRefs=function(e,t){if("boolean"==typeof e)return{};const{schemaId:r,uriResolver:n}=this.opts,i=h(e[r]||t),a={"":i},c=u(n,i,!1),l={},d=new Set;return o(e,{allKeys:!0},((e,t,n,s)=>{if(void 0===s)return;const o=c+t;let i=a[s];function u(t){const r=this.opts.uriResolver.resolve;if(t=h(i?r(i,t):t),d.has(t))throw m(t);d.add(t);let n=this.refs[t];return"string"==typeof n&&(n=this.refs[n]),"object"==typeof n?f(e,n.schema,t):t!==h(o)&&("#"===t[0]?(f(e,l[t],t),l[t]=e):this.refs[t]=o),t}function y(e){if("string"==typeof e){if(!p.test(e))throw new Error(`invalid anchor "${e}"`);u.call(this,`#${e}`)}}"string"==typeof e[r]&&(i=u.call(this,e[r])),y.call(this,e.$anchor),y.call(this,e.$dynamicAnchor),a[t]=i})),l;function f(e,t,r){if(void 0!==t&&!s(e,t))throw m(r)}function m(e){return new Error(`reference "${e}" resolves to more than one schema`)}}},6777:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRules=t.isJSONType=void 0;const r=new Set(["string","number","integer","boolean","null","object","array"]);t.isJSONType=function(e){return"string"==typeof e&&r.has(e)},t.getRules=function(){const e={number:{type:"number",rules:[]},string:{type:"string",rules:[]},array:{type:"array",rules:[]},object:{type:"object",rules:[]}};return{types:{...e,integer:!0,boolean:!0,null:!0},rules:[{rules:[]},e.number,e.string,e.array,e.object],post:{rules:[]},all:{},keywords:{}}}},320:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.checkStrictMode=t.getErrorPath=t.Type=t.useFunc=t.setEvaluated=t.evaluatedPropsToName=t.mergeEvaluated=t.eachItem=t.unescapeJsonPointer=t.escapeJsonPointer=t.escapeFragment=t.unescapeFragment=t.schemaRefOrVal=t.schemaHasRulesButRef=t.schemaHasRules=t.checkUnknownRules=t.alwaysValidSchema=t.toHash=void 0;const n=r(5899),s=r(6796);function o(e,t=e.schema){const{opts:r,self:n}=e;if(!r.strictSchema)return;if("boolean"==typeof t)return;const s=n.RULES.keywords;for(const r in t)s[r]||p(e,`unknown keyword: "${r}"`)}function i(e,t){if("boolean"==typeof e)return!e;for(const r in e)if(t[r])return!0;return!1}function a(e){return"number"==typeof e?`${e}`:e.replace(/~/g,"~0").replace(/\//g,"~1")}function c(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function l({mergeNames:e,mergeToName:t,mergeValues:r,resultToName:s}){return(o,i,a,c)=>{const l=void 0===a?i:a instanceof n.Name?(i instanceof n.Name?e(o,i,a):t(o,i,a),a):i instanceof n.Name?(t(o,a,i),i):r(i,a);return c!==n.Name||l instanceof n.Name?l:s(o,l)}}function u(e,t){if(!0===t)return e.var("props",!0);const r=e.var("props",n._`{}`);return void 0!==t&&d(e,r,t),r}function d(e,t,r){Object.keys(r).forEach((r=>e.assign(n._`${t}${(0,n.getProperty)(r)}`,!0)))}t.toHash=function(e){const t={};for(const r of e)t[r]=!0;return t},t.alwaysValidSchema=function(e,t){return"boolean"==typeof t?t:0===Object.keys(t).length||(o(e,t),!i(t,e.self.RULES.all))},t.checkUnknownRules=o,t.schemaHasRules=i,t.schemaHasRulesButRef=function(e,t){if("boolean"==typeof e)return!e;for(const r in e)if("$ref"!==r&&t.all[r])return!0;return!1},t.schemaRefOrVal=function({topSchemaRef:e,schemaPath:t},r,s,o){if(!o){if("number"==typeof r||"boolean"==typeof r)return r;if("string"==typeof r)return n._`${r}`}return n._`${e}${t}${(0,n.getProperty)(s)}`},t.unescapeFragment=function(e){return c(decodeURIComponent(e))},t.escapeFragment=function(e){return encodeURIComponent(a(e))},t.escapeJsonPointer=a,t.unescapeJsonPointer=c,t.eachItem=function(e,t){if(Array.isArray(e))for(const r of e)t(r);else t(e)},t.mergeEvaluated={props:l({mergeNames:(e,t,r)=>e.if(n._`${r} !== true && ${t} !== undefined`,(()=>{e.if(n._`${t} === true`,(()=>e.assign(r,!0)),(()=>e.assign(r,n._`${r} || {}`).code(n._`Object.assign(${r}, ${t})`)))})),mergeToName:(e,t,r)=>e.if(n._`${r} !== true`,(()=>{!0===t?e.assign(r,!0):(e.assign(r,n._`${r} || {}`),d(e,r,t))})),mergeValues:(e,t)=>!0===e||{...e,...t},resultToName:u}),items:l({mergeNames:(e,t,r)=>e.if(n._`${r} !== true && ${t} !== undefined`,(()=>e.assign(r,n._`${t} === true ? true : ${r} > ${t} ? ${r} : ${t}`))),mergeToName:(e,t,r)=>e.if(n._`${r} !== true`,(()=>e.assign(r,!0===t||n._`${r} > ${t} ? ${r} : ${t}`))),mergeValues:(e,t)=>!0===e||Math.max(e,t),resultToName:(e,t)=>e.var("items",t)})},t.evaluatedPropsToName=u,t.setEvaluated=d;const f={};var h;function p(e,t,r=e.opts.strictSchema){if(r){if(t=`strict mode: ${t}`,!0===r)throw new Error(t);e.self.logger.warn(t)}}t.useFunc=function(e,t){return e.scopeValue("func",{ref:t,code:f[t.code]||(f[t.code]=new s._Code(t.code))})},function(e){e[e.Num=0]="Num",e[e.Str=1]="Str"}(h=t.Type||(t.Type={})),t.getErrorPath=function(e,t,r){if(e instanceof n.Name){const s=t===h.Num;return r?s?n._`"[" + ${e} + "]"`:n._`"['" + ${e} + "']"`:s?n._`"/" + ${e}`:n._`"/" + ${e}.replace(/~/g, "~0").replace(/\\//g, "~1")`}return r?(0,n.getProperty)(e).toString():"/"+a(e)},t.checkStrictMode=p},8149:(e,t)=>{"use strict";function r(e,t){return t.rules.some((t=>n(e,t)))}function n(e,t){var r;return void 0!==e[t.keyword]||(null===(r=t.definition.implements)||void 0===r?void 0:r.some((t=>void 0!==e[t])))}Object.defineProperty(t,"__esModule",{value:!0}),t.shouldUseRule=t.shouldUseGroup=t.schemaHasRulesForType=void 0,t.schemaHasRulesForType=function({schema:e,self:t},n){const s=t.RULES.types[n];return s&&!0!==s&&r(e,s)},t.shouldUseGroup=r,t.shouldUseRule=n},473:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.boolOrEmptySchema=t.topBoolOrEmptySchema=void 0;const n=r(4677),s=r(5899),o=r(6934),i={message:"boolean schema is false"};function a(e,t){const{gen:r,data:s}=e,o={gen:r,keyword:"false schema",data:s,schema:!1,schemaCode:!1,schemaValue:!1,params:{},it:e};(0,n.reportError)(o,i,void 0,t)}t.topBoolOrEmptySchema=function(e){const{gen:t,schema:r,validateName:n}=e;!1===r?a(e,!1):"object"==typeof r&&!0===r.$async?t.return(o.default.data):(t.assign(s._`${n}.errors`,null),t.return(!0))},t.boolOrEmptySchema=function(e,t){const{gen:r,schema:n}=e;!1===n?(r.var(t,!1),a(e)):r.var(t,!0)}},2292:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportTypeError=t.checkDataTypes=t.checkDataType=t.coerceAndCheckDataType=t.getJSONTypes=t.getSchemaTypes=t.DataType=void 0;const n=r(6777),s=r(8149),o=r(4677),i=r(5899),a=r(320);var c;function l(e){const t=Array.isArray(e)?e:e?[e]:[];if(t.every(n.isJSONType))return t;throw new Error("type must be JSONType or JSONType[]: "+t.join(","))}!function(e){e[e.Correct=0]="Correct",e[e.Wrong=1]="Wrong"}(c=t.DataType||(t.DataType={})),t.getSchemaTypes=function(e){const t=l(e.type);if(t.includes("null")){if(!1===e.nullable)throw new Error("type: null contradicts nullable: false")}else{if(!t.length&&void 0!==e.nullable)throw new Error('"nullable" cannot be used without "type"');!0===e.nullable&&t.push("null")}return t},t.getJSONTypes=l,t.coerceAndCheckDataType=function(e,t){const{gen:r,data:n,opts:o}=e,a=function(e,t){return t?e.filter((e=>u.has(e)||"array"===t&&"array"===e)):[]}(t,o.coerceTypes),l=t.length>0&&!(0===a.length&&1===t.length&&(0,s.schemaHasRulesForType)(e,t[0]));if(l){const s=f(t,n,o.strictNumbers,c.Wrong);r.if(s,(()=>{a.length?function(e,t,r){const{gen:n,data:s,opts:o}=e,a=n.let("dataType",i._`typeof ${s}`),c=n.let("coerced",i._`undefined`);"array"===o.coerceTypes&&n.if(i._`${a} == 'object' && Array.isArray(${s}) && ${s}.length == 1`,(()=>n.assign(s,i._`${s}[0]`).assign(a,i._`typeof ${s}`).if(f(t,s,o.strictNumbers),(()=>n.assign(c,s))))),n.if(i._`${c} !== undefined`);for(const e of r)(u.has(e)||"array"===e&&"array"===o.coerceTypes)&&l(e);function l(e){switch(e){case"string":return void n.elseIf(i._`${a} == "number" || ${a} == "boolean"`).assign(c,i._`"" + ${s}`).elseIf(i._`${s} === null`).assign(c,i._`""`);case"number":return void n.elseIf(i._`${a} == "boolean" || ${s} === null || (${a} == "string" && ${s} && ${s} == +${s})`).assign(c,i._`+${s}`);case"integer":return void n.elseIf(i._`${a} === "boolean" || ${s} === null || (${a} === "string" && ${s} && ${s} == +${s} && !(${s} % 1))`).assign(c,i._`+${s}`);case"boolean":return void n.elseIf(i._`${s} === "false" || ${s} === 0 || ${s} === null`).assign(c,!1).elseIf(i._`${s} === "true" || ${s} === 1`).assign(c,!0);case"null":return n.elseIf(i._`${s} === "" || ${s} === 0 || ${s} === false`),void n.assign(c,null);case"array":n.elseIf(i._`${a} === "string" || ${a} === "number" || ${a} === "boolean" || ${s} === null`).assign(c,i._`[${s}]`)}}n.else(),p(e),n.endIf(),n.if(i._`${c} !== undefined`,(()=>{n.assign(s,c),function({gen:e,parentData:t,parentDataProperty:r},n){e.if(i._`${t} !== undefined`,(()=>e.assign(i._`${t}[${r}]`,n)))}(e,c)}))}(e,t,a):p(e)}))}return l};const u=new Set(["string","number","integer","boolean","null"]);function d(e,t,r,n=c.Correct){const s=n===c.Correct?i.operators.EQ:i.operators.NEQ;let o;switch(e){case"null":return i._`${t} ${s} null`;case"array":o=i._`Array.isArray(${t})`;break;case"object":o=i._`${t} && typeof ${t} == "object" && !Array.isArray(${t})`;break;case"integer":o=a(i._`!(${t} % 1) && !isNaN(${t})`);break;case"number":o=a();break;default:return i._`typeof ${t} ${s} ${e}`}return n===c.Correct?o:(0,i.not)(o);function a(e=i.nil){return(0,i.and)(i._`typeof ${t} == "number"`,e,r?i._`isFinite(${t})`:i.nil)}}function f(e,t,r,n){if(1===e.length)return d(e[0],t,r,n);let s;const o=(0,a.toHash)(e);if(o.array&&o.object){const e=i._`typeof ${t} != "object"`;s=o.null?e:i._`!${t} || ${e}`,delete o.null,delete o.array,delete o.object}else s=i.nil;o.number&&delete o.integer;for(const e in o)s=(0,i.and)(s,d(e,t,r,n));return s}t.checkDataType=d,t.checkDataTypes=f;const h={message:({schema:e})=>`must be ${e}`,params:({schema:e,schemaValue:t})=>"string"==typeof e?i._`{type: ${e}}`:i._`{type: ${t}}`};function p(e){const t=function(e){const{gen:t,data:r,schema:n}=e,s=(0,a.schemaRefOrVal)(e,n,"type");return{gen:t,keyword:"type",data:r,schema:n.type,schemaCode:s,schemaValue:s,parentSchema:n,params:{},it:e}}(e);(0,o.reportError)(t,h)}t.reportTypeError=p},162:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.assignDefaults=void 0;const n=r(5899),s=r(320);function o(e,t,r){const{gen:o,compositeRule:i,data:a,opts:c}=e;if(void 0===r)return;const l=n._`${a}${(0,n.getProperty)(t)}`;if(i)return void(0,s.checkStrictMode)(e,`default is ignored for: ${l}`);let u=n._`${l} === undefined`;"empty"===c.useDefaults&&(u=n._`${u} || ${l} === null || ${l} === ""`),o.if(u,n._`${l} = ${(0,n.stringify)(r)}`)}t.assignDefaults=function(e,t){const{properties:r,items:n}=e.schema;if("object"===t&&r)for(const t in r)o(e,t,r[t].default);else"array"===t&&Array.isArray(n)&&n.forEach(((t,r)=>o(e,r,t.default)))}},5032:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getData=t.KeywordCxt=t.validateFunctionCode=void 0;const n=r(473),s=r(2292),o=r(8149),i=r(2292),a=r(162),c=r(9653),l=r(9282),u=r(5899),d=r(6934),f=r(6885),h=r(320),p=r(4677);function m({gen:e,validateName:t,schema:r,schemaEnv:n,opts:s},o){s.code.es5?e.func(t,u._`${d.default.data}, ${d.default.valCxt}`,n.$async,(()=>{e.code(u._`"use strict"; ${y(r,s)}`),function(e,t){e.if(d.default.valCxt,(()=>{e.var(d.default.instancePath,u._`${d.default.valCxt}.${d.default.instancePath}`),e.var(d.default.parentData,u._`${d.default.valCxt}.${d.default.parentData}`),e.var(d.default.parentDataProperty,u._`${d.default.valCxt}.${d.default.parentDataProperty}`),e.var(d.default.rootData,u._`${d.default.valCxt}.${d.default.rootData}`),t.dynamicRef&&e.var(d.default.dynamicAnchors,u._`${d.default.valCxt}.${d.default.dynamicAnchors}`)}),(()=>{e.var(d.default.instancePath,u._`""`),e.var(d.default.parentData,u._`undefined`),e.var(d.default.parentDataProperty,u._`undefined`),e.var(d.default.rootData,d.default.data),t.dynamicRef&&e.var(d.default.dynamicAnchors,u._`{}`)}))}(e,s),e.code(o)})):e.func(t,u._`${d.default.data}, ${function(e){return u._`{${d.default.instancePath}="", ${d.default.parentData}, ${d.default.parentDataProperty}, ${d.default.rootData}=${d.default.data}${e.dynamicRef?u._`, ${d.default.dynamicAnchors}={}`:u.nil}}={}`}(s)}`,n.$async,(()=>e.code(y(r,s)).code(o)))}function y(e,t){const r="object"==typeof e&&e[t.schemaId];return r&&(t.code.source||t.code.process)?u._`/*# sourceURL=${r} */`:u.nil}function g({schema:e,self:t}){if("boolean"==typeof e)return!e;for(const r in e)if(t.RULES.all[r])return!0;return!1}function v(e){return"boolean"!=typeof e.schema}function w(e){(0,h.checkUnknownRules)(e),function(e){const{schema:t,errSchemaPath:r,opts:n,self:s}=e;t.$ref&&n.ignoreKeywordsWithRef&&(0,h.schemaHasRulesButRef)(t,s.RULES)&&s.logger.warn(`$ref: keywords ignored in schema at path "${r}"`)}(e)}function E(e,t){if(e.opts.jtd)return _(e,[],!1,t);const r=(0,s.getSchemaTypes)(e.schema);_(e,r,!(0,s.coerceAndCheckDataType)(e,r),t)}function $({gen:e,schemaEnv:t,schema:r,errSchemaPath:n,opts:s}){const o=r.$comment;if(!0===s.$comment)e.code(u._`${d.default.self}.logger.log(${o})`);else if("function"==typeof s.$comment){const r=u.str`${n}/$comment`,s=e.scopeValue("root",{ref:t.root});e.code(u._`${d.default.self}.opts.$comment(${o}, ${r}, ${s}.schema)`)}}function _(e,t,r,n){const{gen:s,schema:a,data:c,allErrors:l,opts:f,self:p}=e,{RULES:m}=p;function y(h){(0,o.shouldUseGroup)(a,h)&&(h.type?(s.if((0,i.checkDataType)(h.type,c,f.strictNumbers)),b(e,h),1===t.length&&t[0]===h.type&&r&&(s.else(),(0,i.reportTypeError)(e)),s.endIf()):b(e,h),l||s.if(u._`${d.default.errors} === ${n||0}`))}!a.$ref||!f.ignoreKeywordsWithRef&&(0,h.schemaHasRulesButRef)(a,m)?(f.jtd||function(e,t){!e.schemaEnv.meta&&e.opts.strictTypes&&(function(e,t){t.length&&(e.dataTypes.length?(t.forEach((t=>{S(e.dataTypes,t)||O(e,`type "${t}" not allowed by context "${e.dataTypes.join(",")}"`)})),function(e,t){const r=[];for(const n of e.dataTypes)S(t,n)?r.push(n):t.includes("integer")&&"number"===n&&r.push("integer");e.dataTypes=r}(e,t)):e.dataTypes=t)}(e,t),e.opts.allowUnionTypes||function(e,t){t.length>1&&(2!==t.length||!t.includes("null"))&&O(e,"use allowUnionTypes to allow union type keyword")}(e,t),function(e,t){const r=e.self.RULES.all;for(const n in r){const s=r[n];if("object"==typeof s&&(0,o.shouldUseRule)(e.schema,s)){const{type:r}=s.definition;r.length&&!r.some((e=>{return n=e,(r=t).includes(n)||"number"===n&&r.includes("integer");var r,n}))&&O(e,`missing type "${r.join(",")}" for keyword "${n}"`)}}}(e,e.dataTypes))}(e,t),s.block((()=>{for(const e of m.rules)y(e);y(m.post)}))):s.block((()=>x(e,"$ref",m.all.$ref.definition)))}function b(e,t){const{gen:r,schema:n,opts:{useDefaults:s}}=e;s&&(0,a.assignDefaults)(e,t.type),r.block((()=>{for(const r of t.rules)(0,o.shouldUseRule)(n,r)&&x(e,r.keyword,r.definition,t.type)}))}function S(e,t){return e.includes(t)||"integer"===t&&e.includes("number")}function O(e,t){t+=` at "${e.schemaEnv.baseId+e.errSchemaPath}" (strictTypes)`,(0,h.checkStrictMode)(e,t,e.opts.strictTypes)}t.validateFunctionCode=function(e){v(e)&&(w(e),g(e))?function(e){const{schema:t,opts:r,gen:n}=e;m(e,(()=>{r.$comment&&t.$comment&&$(e),function(e){const{schema:t,opts:r}=e;void 0!==t.default&&r.useDefaults&&r.strictSchema&&(0,h.checkStrictMode)(e,"default is ignored in the schema root")}(e),n.let(d.default.vErrors,null),n.let(d.default.errors,0),r.unevaluated&&function(e){const{gen:t,validateName:r}=e;e.evaluated=t.const("evaluated",u._`${r}.evaluated`),t.if(u._`${e.evaluated}.dynamicProps`,(()=>t.assign(u._`${e.evaluated}.props`,u._`undefined`))),t.if(u._`${e.evaluated}.dynamicItems`,(()=>t.assign(u._`${e.evaluated}.items`,u._`undefined`)))}(e),E(e),function(e){const{gen:t,schemaEnv:r,validateName:n,ValidationError:s,opts:o}=e;r.$async?t.if(u._`${d.default.errors} === 0`,(()=>t.return(d.default.data)),(()=>t.throw(u._`new ${s}(${d.default.vErrors})`))):(t.assign(u._`${n}.errors`,d.default.vErrors),o.unevaluated&&function({gen:e,evaluated:t,props:r,items:n}){r instanceof u.Name&&e.assign(u._`${t}.props`,r),n instanceof u.Name&&e.assign(u._`${t}.items`,n)}(e),t.return(u._`${d.default.errors} === 0`))}(e)}))}(e):m(e,(()=>(0,n.topBoolOrEmptySchema)(e)))};class P{constructor(e,t,r){if((0,c.validateKeywordUsage)(e,t,r),this.gen=e.gen,this.allErrors=e.allErrors,this.keyword=r,this.data=e.data,this.schema=e.schema[r],this.$data=t.$data&&e.opts.$data&&this.schema&&this.schema.$data,this.schemaValue=(0,h.schemaRefOrVal)(e,this.schema,r,this.$data),this.schemaType=t.schemaType,this.parentSchema=e.schema,this.params={},this.it=e,this.def=t,this.$data)this.schemaCode=e.gen.const("vSchema",j(this.$data,e));else if(this.schemaCode=this.schemaValue,!(0,c.validSchemaType)(this.schema,t.schemaType,t.allowUndefined))throw new Error(`${r} value must be ${JSON.stringify(t.schemaType)}`);("code"in t?t.trackErrors:!1!==t.errors)&&(this.errsCount=e.gen.const("_errs",d.default.errors))}result(e,t,r){this.failResult((0,u.not)(e),t,r)}failResult(e,t,r){this.gen.if(e),r?r():this.error(),t?(this.gen.else(),t(),this.allErrors&&this.gen.endIf()):this.allErrors?this.gen.endIf():this.gen.else()}pass(e,t){this.failResult((0,u.not)(e),void 0,t)}fail(e){if(void 0===e)return this.error(),void(this.allErrors||this.gen.if(!1));this.gen.if(e),this.error(),this.allErrors?this.gen.endIf():this.gen.else()}fail$data(e){if(!this.$data)return this.fail(e);const{schemaCode:t}=this;this.fail(u._`${t} !== undefined && (${(0,u.or)(this.invalid$data(),e)})`)}error(e,t,r){if(t)return this.setParams(t),this._error(e,r),void this.setParams({});this._error(e,r)}_error(e,t){(e?p.reportExtraError:p.reportError)(this,this.def.error,t)}$dataError(){(0,p.reportError)(this,this.def.$dataError||p.keyword$DataError)}reset(){if(void 0===this.errsCount)throw new Error('add "trackErrors" to keyword definition');(0,p.resetErrorsCount)(this.gen,this.errsCount)}ok(e){this.allErrors||this.gen.if(e)}setParams(e,t){t?Object.assign(this.params,e):this.params=e}block$data(e,t,r=u.nil){this.gen.block((()=>{this.check$data(e,r),t()}))}check$data(e=u.nil,t=u.nil){if(!this.$data)return;const{gen:r,schemaCode:n,schemaType:s,def:o}=this;r.if((0,u.or)(u._`${n} === undefined`,t)),e!==u.nil&&r.assign(e,!0),(s.length||o.validateSchema)&&(r.elseIf(this.invalid$data()),this.$dataError(),e!==u.nil&&r.assign(e,!1)),r.else()}invalid$data(){const{gen:e,schemaCode:t,schemaType:r,def:n,it:s}=this;return(0,u.or)(function(){if(r.length){if(!(t instanceof u.Name))throw new Error("ajv implementation error");const e=Array.isArray(r)?r:[r];return u._`${(0,i.checkDataTypes)(e,t,s.opts.strictNumbers,i.DataType.Wrong)}`}return u.nil}(),function(){if(n.validateSchema){const r=e.scopeValue("validate$data",{ref:n.validateSchema});return u._`!${r}(${t})`}return u.nil}())}subschema(e,t){const r=(0,l.getSubschema)(this.it,e);(0,l.extendSubschemaData)(r,this.it,e),(0,l.extendSubschemaMode)(r,e);const s={...this.it,...r,items:void 0,props:void 0};return function(e,t){v(e)&&(w(e),g(e))?function(e,t){const{schema:r,gen:n,opts:s}=e;s.$comment&&r.$comment&&$(e),function(e){const t=e.schema[e.opts.schemaId];t&&(e.baseId=(0,f.resolveUrl)(e.opts.uriResolver,e.baseId,t))}(e),function(e){if(e.schema.$async&&!e.schemaEnv.$async)throw new Error("async schema in sync schema")}(e);const o=n.const("_errs",d.default.errors);E(e,o),n.var(t,u._`${o} === ${d.default.errors}`)}(e,t):(0,n.boolOrEmptySchema)(e,t)}(s,t),s}mergeEvaluated(e,t){const{it:r,gen:n}=this;r.opts.unevaluated&&(!0!==r.props&&void 0!==e.props&&(r.props=h.mergeEvaluated.props(n,e.props,r.props,t)),!0!==r.items&&void 0!==e.items&&(r.items=h.mergeEvaluated.items(n,e.items,r.items,t)))}mergeValidEvaluated(e,t){const{it:r,gen:n}=this;if(r.opts.unevaluated&&(!0!==r.props||!0!==r.items))return n.if(t,(()=>this.mergeEvaluated(e,u.Name))),!0}}function x(e,t,r,n){const s=new P(e,r,t);"code"in r?r.code(s,n):s.$data&&r.validate?(0,c.funcKeywordCode)(s,r):"macro"in r?(0,c.macroKeywordCode)(s,r):(r.compile||r.validate)&&(0,c.funcKeywordCode)(s,r)}t.KeywordCxt=P;const N=/^\/(?:[^~]|~0|~1)*$/,I=/^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;function j(e,{dataLevel:t,dataNames:r,dataPathArr:n}){let s,o;if(""===e)return d.default.rootData;if("/"===e[0]){if(!N.test(e))throw new Error(`Invalid JSON-pointer: ${e}`);s=e,o=d.default.rootData}else{const i=I.exec(e);if(!i)throw new Error(`Invalid JSON-pointer: ${e}`);const a=+i[1];if(s=i[2],"#"===s){if(a>=t)throw new Error(c("property/index",a));return n[t-a]}if(a>t)throw new Error(c("data",a));if(o=r[t-a],!s)return o}let i=o;const a=s.split("/");for(const e of a)e&&(o=u._`${o}${(0,u.getProperty)((0,h.unescapeJsonPointer)(e))}`,i=u._`${i} && ${o}`);return i;function c(e,r){return`Cannot access ${e} ${r} levels up, current level is ${t}`}}t.getData=j},9653:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateKeywordUsage=t.validSchemaType=t.funcKeywordCode=t.macroKeywordCode=void 0;const n=r(5899),s=r(6934),o=r(7470),i=r(4677);function a(e){const{gen:t,data:r,it:s}=e;t.if(s.parentData,(()=>t.assign(r,n._`${s.parentData}[${s.parentDataProperty}]`)))}function c(e,t,r){if(void 0===r)throw new Error(`keyword "${t}" failed to compile`);return e.scopeValue("keyword","function"==typeof r?{ref:r}:{ref:r,code:(0,n.stringify)(r)})}t.macroKeywordCode=function(e,t){const{gen:r,keyword:s,schema:o,parentSchema:i,it:a}=e,l=t.macro.call(a.self,o,i,a),u=c(r,s,l);!1!==a.opts.validateSchema&&a.self.validateSchema(l,!0);const d=r.name("valid");e.subschema({schema:l,schemaPath:n.nil,errSchemaPath:`${a.errSchemaPath}/${s}`,topSchemaRef:u,compositeRule:!0},d),e.pass(d,(()=>e.error(!0)))},t.funcKeywordCode=function(e,t){var r;const{gen:l,keyword:u,schema:d,parentSchema:f,$data:h,it:p}=e;!function({schemaEnv:e},t){if(t.async&&!e.$async)throw new Error("async keyword in sync schema")}(p,t);const m=!h&&t.compile?t.compile.call(p.self,d,f,p):t.validate,y=c(l,u,m),g=l.let("valid");function v(r=(t.async?n._`await `:n.nil)){const i=p.opts.passContext?s.default.this:s.default.self,a=!("compile"in t&&!h||!1===t.schema);l.assign(g,n._`${r}${(0,o.callValidateCode)(e,y,i,a)}`,t.modifying)}function w(e){var r;l.if((0,n.not)(null!==(r=t.valid)&&void 0!==r?r:g),e)}e.block$data(g,(function(){if(!1===t.errors)v(),t.modifying&&a(e),w((()=>e.error()));else{const r=t.async?function(){const e=l.let("ruleErrs",null);return l.try((()=>v(n._`await `)),(t=>l.assign(g,!1).if(n._`${t} instanceof ${p.ValidationError}`,(()=>l.assign(e,n._`${t}.errors`)),(()=>l.throw(t))))),e}():function(){const e=n._`${y}.errors`;return l.assign(e,null),v(n.nil),e}();t.modifying&&a(e),w((()=>function(e,t){const{gen:r}=e;r.if(n._`Array.isArray(${t})`,(()=>{r.assign(s.default.vErrors,n._`${s.default.vErrors} === null ? ${t} : ${s.default.vErrors}.concat(${t})`).assign(s.default.errors,n._`${s.default.vErrors}.length`),(0,i.extendErrors)(e)}),(()=>e.error()))}(e,r)))}})),e.ok(null!==(r=t.valid)&&void 0!==r?r:g)},t.validSchemaType=function(e,t,r=!1){return!t.length||t.some((t=>"array"===t?Array.isArray(e):"object"===t?e&&"object"==typeof e&&!Array.isArray(e):typeof e==t||r&&void 0===e))},t.validateKeywordUsage=function({schema:e,opts:t,self:r,errSchemaPath:n},s,o){if(Array.isArray(s.keyword)?!s.keyword.includes(o):s.keyword!==o)throw new Error("ajv implementation error");const i=s.dependencies;if(null==i?void 0:i.some((t=>!Object.prototype.hasOwnProperty.call(e,t))))throw new Error(`parent schema must have dependencies of ${o}: ${i.join(",")}`);if(s.validateSchema&&!s.validateSchema(e[o])){const e=`keyword "${o}" value is invalid at path "${n}": `+r.errorsText(s.validateSchema.errors);if("log"!==t.validateSchema)throw new Error(e);r.logger.error(e)}}},9282:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendSubschemaMode=t.extendSubschemaData=t.getSubschema=void 0;const n=r(5899),s=r(320);t.getSubschema=function(e,{keyword:t,schemaProp:r,schema:o,schemaPath:i,errSchemaPath:a,topSchemaRef:c}){if(void 0!==t&&void 0!==o)throw new Error('both "keyword" and "schema" passed, only one allowed');if(void 0!==t){const o=e.schema[t];return void 0===r?{schema:o,schemaPath:n._`${e.schemaPath}${(0,n.getProperty)(t)}`,errSchemaPath:`${e.errSchemaPath}/${t}`}:{schema:o[r],schemaPath:n._`${e.schemaPath}${(0,n.getProperty)(t)}${(0,n.getProperty)(r)}`,errSchemaPath:`${e.errSchemaPath}/${t}/${(0,s.escapeFragment)(r)}`}}if(void 0!==o){if(void 0===i||void 0===a||void 0===c)throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"');return{schema:o,schemaPath:i,topSchemaRef:c,errSchemaPath:a}}throw new Error('either "keyword" or "schema" must be passed')},t.extendSubschemaData=function(e,t,{dataProp:r,dataPropType:o,data:i,dataTypes:a,propertyName:c}){if(void 0!==i&&void 0!==r)throw new Error('both "data" and "dataProp" passed, only one allowed');const{gen:l}=t;if(void 0!==r){const{errorPath:i,dataPathArr:a,opts:c}=t;u(l.let("data",n._`${t.data}${(0,n.getProperty)(r)}`,!0)),e.errorPath=n.str`${i}${(0,s.getErrorPath)(r,o,c.jsPropertySyntax)}`,e.parentDataProperty=n._`${r}`,e.dataPathArr=[...a,e.parentDataProperty]}function u(r){e.data=r,e.dataLevel=t.dataLevel+1,e.dataTypes=[],t.definedProperties=new Set,e.parentData=t.data,e.dataNames=[...t.dataNames,r]}void 0!==i&&(u(i instanceof n.Name?i:l.let("data",i,!0)),void 0!==c&&(e.propertyName=c)),a&&(e.dataTypes=a)},t.extendSubschemaMode=function(e,{jtdDiscriminator:t,jtdMetadata:r,compositeRule:n,createErrors:s,allErrors:o}){void 0!==n&&(e.compositeRule=n),void 0!==s&&(e.createErrors=s),void 0!==o&&(e.allErrors=o),e.jtdDiscriminator=t,e.jtdMetadata=r}},3579:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;var n=r(5032);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return n.KeywordCxt}});var s=r(5899);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return s._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return s.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return s.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return s.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return s.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return s.CodeGen}});const o=r(7173),i=r(433),a=r(6777),c=r(7760),l=r(5899),u=r(6885),d=r(2292),f=r(320),h=r(9756),p=r(2972),m=(e,t)=>new RegExp(e,t);m.code="new RegExp";const y=["removeAdditional","useDefaults","coerceTypes"],g=new Set(["validate","serialize","parse","wrapper","root","schema","keyword","pattern","formats","validate$data","func","obj","Error"]),v={errorDataPath:"",format:"`validateFormats: false` can be used instead.",nullable:'"nullable" keyword is supported by default.',jsonPointers:"Deprecated jsPropertySyntax can be used instead.",extendRefs:"Deprecated ignoreKeywordsWithRef can be used instead.",missingRefs:"Pass empty schema with $id that should be ignored to ajv.addSchema.",processCode:"Use option `code: {process: (code, schemaEnv: object) => string}`",sourceCode:"Use option `code: {source: true}`",strictDefaults:"It is default now, see option `strict`.",strictKeywords:"It is default now, see option `strict`.",uniqueItems:'"uniqueItems" keyword is always validated.',unknownFormats:"Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",cache:"Map is used as cache, schema object as key.",serialize:"Map is used as cache, schema object as key.",ajvErrors:"It is default now."},w={ignoreKeywordsWithRef:"",jsPropertySyntax:"",unicode:'"minLength"/"maxLength" account for unicode characters by default.'};function E(e){var t,r,n,s,o,i,a,c,l,u,d,f,h,y,g,v,w,E,$,_,b,S,O,P,x;const N=e.strict,I=null===(t=e.code)||void 0===t?void 0:t.optimize,j=!0===I||void 0===I?1:I||0,R=null!==(n=null===(r=e.code)||void 0===r?void 0:r.regExp)&&void 0!==n?n:m,A=null!==(s=e.uriResolver)&&void 0!==s?s:p.default;return{strictSchema:null===(i=null!==(o=e.strictSchema)&&void 0!==o?o:N)||void 0===i||i,strictNumbers:null===(c=null!==(a=e.strictNumbers)&&void 0!==a?a:N)||void 0===c||c,strictTypes:null!==(u=null!==(l=e.strictTypes)&&void 0!==l?l:N)&&void 0!==u?u:"log",strictTuples:null!==(f=null!==(d=e.strictTuples)&&void 0!==d?d:N)&&void 0!==f?f:"log",strictRequired:null!==(y=null!==(h=e.strictRequired)&&void 0!==h?h:N)&&void 0!==y&&y,code:e.code?{...e.code,optimize:j,regExp:R}:{optimize:j,regExp:R},loopRequired:null!==(g=e.loopRequired)&&void 0!==g?g:200,loopEnum:null!==(v=e.loopEnum)&&void 0!==v?v:200,meta:null===(w=e.meta)||void 0===w||w,messages:null===(E=e.messages)||void 0===E||E,inlineRefs:null===($=e.inlineRefs)||void 0===$||$,schemaId:null!==(_=e.schemaId)&&void 0!==_?_:"$id",addUsedSchema:null===(b=e.addUsedSchema)||void 0===b||b,validateSchema:null===(S=e.validateSchema)||void 0===S||S,validateFormats:null===(O=e.validateFormats)||void 0===O||O,unicodeRegExp:null===(P=e.unicodeRegExp)||void 0===P||P,int32range:null===(x=e.int32range)||void 0===x||x,uriResolver:A}}class ${constructor(e={}){this.schemas={},this.refs={},this.formats={},this._compilations=new Set,this._loading={},this._cache=new Map,e=this.opts={...e,...E(e)};const{es5:t,lines:r}=this.opts.code;this.scope=new l.ValueScope({scope:{},prefixes:g,es5:t,lines:r}),this.logger=function(e){if(!1===e)return N;if(void 0===e)return console;if(e.log&&e.warn&&e.error)return e;throw new Error("logger must implement log, warn and error methods")}(e.logger);const n=e.validateFormats;e.validateFormats=!1,this.RULES=(0,a.getRules)(),_.call(this,v,e,"NOT SUPPORTED"),_.call(this,w,e,"DEPRECATED","warn"),this._metaOpts=x.call(this),e.formats&&O.call(this),this._addVocabularies(),this._addDefaultMetaSchema(),e.keywords&&P.call(this,e.keywords),"object"==typeof e.meta&&this.addMetaSchema(e.meta),S.call(this),e.validateFormats=n}_addVocabularies(){this.addKeyword("$async")}_addDefaultMetaSchema(){const{$data:e,meta:t,schemaId:r}=this.opts;let n=h;"id"===r&&(n={...h},n.id=n.$id,delete n.$id),t&&e&&this.addMetaSchema(n,n[r],!1)}defaultMeta(){const{meta:e,schemaId:t}=this.opts;return this.opts.defaultMeta="object"==typeof e?e[t]||e:void 0}validate(e,t){let r;if("string"==typeof e){if(r=this.getSchema(e),!r)throw new Error(`no schema with key or ref "${e}"`)}else r=this.compile(e);const n=r(t);return"$async"in r||(this.errors=r.errors),n}compile(e,t){const r=this._addSchema(e,t);return r.validate||this._compileSchemaEnv(r)}compileAsync(e,t){if("function"!=typeof this.opts.loadSchema)throw new Error("options.loadSchema should be a function");const{loadSchema:r}=this.opts;return n.call(this,e,t);async function n(e,t){await s.call(this,e.$schema);const r=this._addSchema(e,t);return r.validate||o.call(this,r)}async function s(e){e&&!this.getSchema(e)&&await n.call(this,{$ref:e},!0)}async function o(e){try{return this._compileSchemaEnv(e)}catch(t){if(!(t instanceof i.default))throw t;return a.call(this,t),await c.call(this,t.missingSchema),o.call(this,e)}}function a({missingSchema:e,missingRef:t}){if(this.refs[e])throw new Error(`AnySchema ${e} is loaded but ${t} cannot be resolved`)}async function c(e){const r=await l.call(this,e);this.refs[e]||await s.call(this,r.$schema),this.refs[e]||this.addSchema(r,e,t)}async function l(e){const t=this._loading[e];if(t)return t;try{return await(this._loading[e]=r(e))}finally{delete this._loading[e]}}}addSchema(e,t,r,n=this.opts.validateSchema){if(Array.isArray(e)){for(const t of e)this.addSchema(t,void 0,r,n);return this}let s;if("object"==typeof e){const{schemaId:t}=this.opts;if(s=e[t],void 0!==s&&"string"!=typeof s)throw new Error(`schema ${t} must be string`)}return t=(0,u.normalizeId)(t||s),this._checkUnique(t),this.schemas[t]=this._addSchema(e,r,t,n,!0),this}addMetaSchema(e,t,r=this.opts.validateSchema){return this.addSchema(e,t,!0,r),this}validateSchema(e,t){if("boolean"==typeof e)return!0;let r;if(r=e.$schema,void 0!==r&&"string"!=typeof r)throw new Error("$schema must be a string");if(r=r||this.opts.defaultMeta||this.defaultMeta(),!r)return this.logger.warn("meta-schema not available"),this.errors=null,!0;const n=this.validate(r,e);if(!n&&t){const e="schema is invalid: "+this.errorsText();if("log"!==this.opts.validateSchema)throw new Error(e);this.logger.error(e)}return n}getSchema(e){let t;for(;"string"==typeof(t=b.call(this,e));)e=t;if(void 0===t){const{schemaId:r}=this.opts,n=new c.SchemaEnv({schema:{},schemaId:r});if(t=c.resolveSchema.call(this,n,e),!t)return;this.refs[e]=t}return t.validate||this._compileSchemaEnv(t)}removeSchema(e){if(e instanceof RegExp)return this._removeAllSchemas(this.schemas,e),this._removeAllSchemas(this.refs,e),this;switch(typeof e){case"undefined":return this._removeAllSchemas(this.schemas),this._removeAllSchemas(this.refs),this._cache.clear(),this;case"string":{const t=b.call(this,e);return"object"==typeof t&&this._cache.delete(t.schema),delete this.schemas[e],delete this.refs[e],this}case"object":{const t=e;this._cache.delete(t);let r=e[this.opts.schemaId];return r&&(r=(0,u.normalizeId)(r),delete this.schemas[r],delete this.refs[r]),this}default:throw new Error("ajv.removeSchema: invalid parameter")}}addVocabulary(e){for(const t of e)this.addKeyword(t);return this}addKeyword(e,t){let r;if("string"==typeof e)r=e,"object"==typeof t&&(this.logger.warn("these parameters are deprecated, see docs for addKeyword"),t.keyword=r);else{if("object"!=typeof e||void 0!==t)throw new Error("invalid addKeywords parameters");if(r=(t=e).keyword,Array.isArray(r)&&!r.length)throw new Error("addKeywords: keyword must be string or non-empty array")}if(j.call(this,r,t),!t)return(0,f.eachItem)(r,(e=>R.call(this,e))),this;T.call(this,t);const n={...t,type:(0,d.getJSONTypes)(t.type),schemaType:(0,d.getJSONTypes)(t.schemaType)};return(0,f.eachItem)(r,0===n.type.length?e=>R.call(this,e,n):e=>n.type.forEach((t=>R.call(this,e,n,t)))),this}getKeyword(e){const t=this.RULES.all[e];return"object"==typeof t?t.definition:!!t}removeKeyword(e){const{RULES:t}=this;delete t.keywords[e],delete t.all[e];for(const r of t.rules){const t=r.rules.findIndex((t=>t.keyword===e));t>=0&&r.rules.splice(t,1)}return this}addFormat(e,t){return"string"==typeof t&&(t=new RegExp(t)),this.formats[e]=t,this}errorsText(e=this.errors,{separator:t=", ",dataVar:r="data"}={}){return e&&0!==e.length?e.map((e=>`${r}${e.instancePath} ${e.message}`)).reduce(((e,r)=>e+t+r)):"No errors"}$dataMetaSchema(e,t){const r=this.RULES.all;e=JSON.parse(JSON.stringify(e));for(const n of t){const t=n.split("/").slice(1);let s=e;for(const e of t)s=s[e];for(const e in r){const t=r[e];if("object"!=typeof t)continue;const{$data:n}=t.definition,o=s[e];n&&o&&(s[e]=D(o))}}return e}_removeAllSchemas(e,t){for(const r in e){const n=e[r];t&&!t.test(r)||("string"==typeof n?delete e[r]:n&&!n.meta&&(this._cache.delete(n.schema),delete e[r]))}}_addSchema(e,t,r,n=this.opts.validateSchema,s=this.opts.addUsedSchema){let o;const{schemaId:i}=this.opts;if("object"==typeof e)o=e[i];else{if(this.opts.jtd)throw new Error("schema must be object");if("boolean"!=typeof e)throw new Error("schema must be object or boolean")}let a=this._cache.get(e);if(void 0!==a)return a;r=(0,u.normalizeId)(o||r);const l=u.getSchemaRefs.call(this,e,r);return a=new c.SchemaEnv({schema:e,schemaId:i,meta:t,baseId:r,localRefs:l}),this._cache.set(a.schema,a),s&&!r.startsWith("#")&&(r&&this._checkUnique(r),this.refs[r]=a),n&&this.validateSchema(e,!0),a}_checkUnique(e){if(this.schemas[e]||this.refs[e])throw new Error(`schema with key or id "${e}" already exists`)}_compileSchemaEnv(e){if(e.meta?this._compileMetaSchema(e):c.compileSchema.call(this,e),!e.validate)throw new Error("ajv implementation error");return e.validate}_compileMetaSchema(e){const t=this.opts;this.opts=this._metaOpts;try{c.compileSchema.call(this,e)}finally{this.opts=t}}}function _(e,t,r,n="error"){for(const s in e){const o=s;o in t&&this.logger[n](`${r}: option ${s}. ${e[o]}`)}}function b(e){return e=(0,u.normalizeId)(e),this.schemas[e]||this.refs[e]}function S(){const e=this.opts.schemas;if(e)if(Array.isArray(e))this.addSchema(e);else for(const t in e)this.addSchema(e[t],t)}function O(){for(const e in this.opts.formats){const t=this.opts.formats[e];t&&this.addFormat(e,t)}}function P(e){if(Array.isArray(e))this.addVocabulary(e);else{this.logger.warn("keywords option as map is deprecated, pass array");for(const t in e){const r=e[t];r.keyword||(r.keyword=t),this.addKeyword(r)}}}function x(){const e={...this.opts};for(const t of y)delete e[t];return e}t.default=$,$.ValidationError=o.default,$.MissingRefError=i.default;const N={log(){},warn(){},error(){}},I=/^[a-z_$][a-z0-9_$:-]*$/i;function j(e,t){const{RULES:r}=this;if((0,f.eachItem)(e,(e=>{if(r.keywords[e])throw new Error(`Keyword ${e} is already defined`);if(!I.test(e))throw new Error(`Keyword ${e} has invalid name`)})),t&&t.$data&&!("code"in t)&&!("validate"in t))throw new Error('$data keyword must have "code" or "validate" function')}function R(e,t,r){var n;const s=null==t?void 0:t.post;if(r&&s)throw new Error('keyword with "post" flag cannot have "type"');const{RULES:o}=this;let i=s?o.post:o.rules.find((({type:e})=>e===r));if(i||(i={type:r,rules:[]},o.rules.push(i)),o.keywords[e]=!0,!t)return;const a={keyword:e,definition:{...t,type:(0,d.getJSONTypes)(t.type),schemaType:(0,d.getJSONTypes)(t.schemaType)}};t.before?A.call(this,i,a,t.before):i.rules.push(a),o.all[e]=a,null===(n=t.implements)||void 0===n||n.forEach((e=>this.addKeyword(e)))}function A(e,t,r){const n=e.rules.findIndex((e=>e.keyword===r));n>=0?e.rules.splice(n,0,t):(e.rules.push(t),this.logger.warn(`rule ${r} is not defined`))}function T(e){let{metaSchema:t}=e;void 0!==t&&(e.$data&&this.opts.$data&&(t=D(t)),e.validateSchema=this.compile(t,!0))}const C={$ref:"https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#"};function D(e){return{anyOf:[e,C]}}},283:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(4063);n.code='require("ajv/dist/runtime/equal").default',t.default=n},6913:(e,t)=>{"use strict";function r(e){const t=e.length;let r,n=0,s=0;for(;s=55296&&r<=56319&&s{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(540);n.code='require("ajv/dist/runtime/uri").default',t.default=n},7173:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});class r extends Error{constructor(e){super("validation failed"),this.errors=e,this.ajv=this.validation=!0}}t.default=r},558:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateAdditionalItems=void 0;const n=r(5899),s=r(320),o={keyword:"additionalItems",type:"array",schemaType:["boolean","object"],before:"uniqueItems",error:{message:({params:{len:e}})=>n.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>n._`{limit: ${e}}`},code(e){const{parentSchema:t,it:r}=e,{items:n}=t;Array.isArray(n)?i(e,n):(0,s.checkStrictMode)(r,'"additionalItems" is ignored when "items" is not an array of schemas')}};function i(e,t){const{gen:r,schema:o,data:i,keyword:a,it:c}=e;c.items=!0;const l=r.const("len",n._`${i}.length`);if(!1===o)e.setParams({len:t.length}),e.pass(n._`${l} <= ${t.length}`);else if("object"==typeof o&&!(0,s.alwaysValidSchema)(c,o)){const o=r.var("valid",n._`${l} <= ${t.length}`);r.if((0,n.not)(o),(()=>function(o){r.forRange("i",t.length,l,(t=>{e.subschema({keyword:a,dataProp:t,dataPropType:s.Type.Num},o),c.allErrors||r.if((0,n.not)(o),(()=>r.break()))}))}(o))),e.ok(o)}}t.validateAdditionalItems=i,t.default=o},2038:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(7470),s=r(5899),o=r(6934),i=r(320),a={keyword:"additionalProperties",type:["object"],schemaType:["boolean","object"],allowUndefined:!0,trackErrors:!0,error:{message:"must NOT have additional properties",params:({params:e})=>s._`{additionalProperty: ${e.additionalProperty}}`},code(e){const{gen:t,schema:r,parentSchema:a,data:c,errsCount:l,it:u}=e;if(!l)throw new Error("ajv implementation error");const{allErrors:d,opts:f}=u;if(u.props=!0,"all"!==f.removeAdditional&&(0,i.alwaysValidSchema)(u,r))return;const h=(0,n.allSchemaProperties)(a.properties),p=(0,n.allSchemaProperties)(a.patternProperties);function m(e){t.code(s._`delete ${c}[${e}]`)}function y(n){if("all"===f.removeAdditional||f.removeAdditional&&!1===r)m(n);else{if(!1===r)return e.setParams({additionalProperty:n}),e.error(),void(d||t.break());if("object"==typeof r&&!(0,i.alwaysValidSchema)(u,r)){const r=t.name("valid");"failing"===f.removeAdditional?(g(n,r,!1),t.if((0,s.not)(r),(()=>{e.reset(),m(n)}))):(g(n,r),d||t.if((0,s.not)(r),(()=>t.break())))}}}function g(t,r,n){const s={keyword:"additionalProperties",dataProp:t,dataPropType:i.Type.Str};!1===n&&Object.assign(s,{compositeRule:!0,createErrors:!1,allErrors:!1}),e.subschema(s,r)}t.forIn("key",c,(r=>{h.length||p.length?t.if(function(r){let o;if(h.length>8){const e=(0,i.schemaRefOrVal)(u,a.properties,"properties");o=(0,n.isOwnProperty)(t,e,r)}else o=h.length?(0,s.or)(...h.map((e=>s._`${r} === ${e}`))):s.nil;return p.length&&(o=(0,s.or)(o,...p.map((t=>s._`${(0,n.usePattern)(e,t)}.test(${r})`)))),(0,s.not)(o)}(r),(()=>y(r))):y(r)})),e.ok(s._`${l} === ${o.default.errors}`)}};t.default=a},6386:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(320),s={keyword:"allOf",schemaType:"array",code(e){const{gen:t,schema:r,it:s}=e;if(!Array.isArray(r))throw new Error("ajv implementation error");const o=t.name("valid");r.forEach(((t,r)=>{if((0,n.alwaysValidSchema)(s,t))return;const i=e.subschema({keyword:"allOf",schemaProp:r},o);e.ok(o),e.mergeEvaluated(i)}))}};t.default=s},3235:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n={keyword:"anyOf",schemaType:"array",trackErrors:!0,code:r(7470).validateUnion,error:{message:"must match a schema in anyOf"}};t.default=n},6065:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o={keyword:"contains",type:"array",schemaType:["object","boolean"],before:"uniqueItems",trackErrors:!0,error:{message:({params:{min:e,max:t}})=>void 0===t?n.str`must contain at least ${e} valid item(s)`:n.str`must contain at least ${e} and no more than ${t} valid item(s)`,params:({params:{min:e,max:t}})=>void 0===t?n._`{minContains: ${e}}`:n._`{minContains: ${e}, maxContains: ${t}}`},code(e){const{gen:t,schema:r,parentSchema:o,data:i,it:a}=e;let c,l;const{minContains:u,maxContains:d}=o;a.opts.next?(c=void 0===u?1:u,l=d):c=1;const f=t.const("len",n._`${i}.length`);if(e.setParams({min:c,max:l}),void 0===l&&0===c)return void(0,s.checkStrictMode)(a,'"minContains" == 0 without "maxContains": "contains" keyword ignored');if(void 0!==l&&c>l)return(0,s.checkStrictMode)(a,'"minContains" > "maxContains" is always invalid'),void e.fail();if((0,s.alwaysValidSchema)(a,r)){let t=n._`${f} >= ${c}`;return void 0!==l&&(t=n._`${t} && ${f} <= ${l}`),void e.pass(t)}a.items=!0;const h=t.name("valid");function p(){const e=t.name("_valid"),r=t.let("count",0);m(e,(()=>t.if(e,(()=>function(e){t.code(n._`${e}++`),void 0===l?t.if(n._`${e} >= ${c}`,(()=>t.assign(h,!0).break())):(t.if(n._`${e} > ${l}`,(()=>t.assign(h,!1).break())),1===c?t.assign(h,!0):t.if(n._`${e} >= ${c}`,(()=>t.assign(h,!0))))}(r)))))}function m(r,n){t.forRange("i",0,f,(t=>{e.subschema({keyword:"contains",dataProp:t,dataPropType:s.Type.Num,compositeRule:!0},r),n()}))}void 0===l&&1===c?m(h,(()=>t.if(h,(()=>t.break())))):0===c?(t.let(h,!0),void 0!==l&&t.if(n._`${i}.length > 0`,p)):(t.let(h,!1),p()),e.result(h,(()=>e.reset()))}};t.default=o},4839:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateSchemaDeps=t.validatePropertyDeps=t.error=void 0;const n=r(5899),s=r(320),o=r(7470);t.error={message:({params:{property:e,depsCount:t,deps:r}})=>{const s=1===t?"property":"properties";return n.str`must have ${s} ${r} when property ${e} is present`},params:({params:{property:e,depsCount:t,deps:r,missingProperty:s}})=>n._`{property: ${e}, missingProperty: ${s}, depsCount: ${t}, deps: ${r}}`};const i={keyword:"dependencies",type:"object",schemaType:"object",error:t.error,code(e){const[t,r]=function({schema:e}){const t={},r={};for(const n in e)"__proto__"!==n&&((Array.isArray(e[n])?t:r)[n]=e[n]);return[t,r]}(e);a(e,t),c(e,r)}};function a(e,t=e.schema){const{gen:r,data:s,it:i}=e;if(0===Object.keys(t).length)return;const a=r.let("missing");for(const c in t){const l=t[c];if(0===l.length)continue;const u=(0,o.propertyInData)(r,s,c,i.opts.ownProperties);e.setParams({property:c,depsCount:l.length,deps:l.join(", ")}),i.allErrors?r.if(u,(()=>{for(const t of l)(0,o.checkReportMissingProp)(e,t)})):(r.if(n._`${u} && (${(0,o.checkMissingProp)(e,l,a)})`),(0,o.reportMissingProp)(e,a),r.else())}}function c(e,t=e.schema){const{gen:r,data:n,keyword:i,it:a}=e,c=r.name("valid");for(const l in t)(0,s.alwaysValidSchema)(a,t[l])||(r.if((0,o.propertyInData)(r,n,l,a.opts.ownProperties),(()=>{const t=e.subschema({keyword:i,schemaProp:l},c);e.mergeValidEvaluated(t,c)}),(()=>r.var(c,!0))),e.ok(c))}t.validatePropertyDeps=a,t.validateSchemaDeps=c,t.default=i},6100:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o={keyword:"if",schemaType:["object","boolean"],trackErrors:!0,error:{message:({params:e})=>n.str`must match "${e.ifClause}" schema`,params:({params:e})=>n._`{failingKeyword: ${e.ifClause}}`},code(e){const{gen:t,parentSchema:r,it:o}=e;void 0===r.then&&void 0===r.else&&(0,s.checkStrictMode)(o,'"if" without "then" and "else" is ignored');const a=i(o,"then"),c=i(o,"else");if(!a&&!c)return;const l=t.let("valid",!0),u=t.name("_valid");if(function(){const t=e.subschema({keyword:"if",compositeRule:!0,createErrors:!1,allErrors:!1},u);e.mergeEvaluated(t)}(),e.reset(),a&&c){const r=t.let("ifClause");e.setParams({ifClause:r}),t.if(u,d("then",r),d("else",r))}else a?t.if(u,d("then")):t.if((0,n.not)(u),d("else"));function d(r,s){return()=>{const o=e.subschema({keyword:r},u);t.assign(l,u),e.mergeValidEvaluated(o,l),s?t.assign(s,n._`${r}`):e.setParams({ifClause:r})}}e.pass(l,(()=>e.error(!0)))}};function i(e,t){const r=e.schema[t];return void 0!==r&&!(0,s.alwaysValidSchema)(e,r)}t.default=o},222:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(558),s=r(4531),o=r(4820),i=r(3785),a=r(6065),c=r(4839),l=r(7620),u=r(2038),d=r(3966),f=r(5975),h=r(2624),p=r(3235),m=r(7706),y=r(6386),g=r(6100),v=r(8957);t.default=function(e=!1){const t=[h.default,p.default,m.default,y.default,g.default,v.default,l.default,u.default,c.default,d.default,f.default];return e?t.push(s.default,i.default):t.push(n.default,o.default),t.push(a.default),t}},4820:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateTuple=void 0;const n=r(5899),s=r(320),o=r(7470),i={keyword:"items",type:"array",schemaType:["object","array","boolean"],before:"uniqueItems",code(e){const{schema:t,it:r}=e;if(Array.isArray(t))return a(e,"additionalItems",t);r.items=!0,(0,s.alwaysValidSchema)(r,t)||e.ok((0,o.validateArray)(e))}};function a(e,t,r=e.schema){const{gen:o,parentSchema:i,data:a,keyword:c,it:l}=e;!function(e){const{opts:n,errSchemaPath:o}=l,i=r.length,a=i===e.minItems&&(i===e.maxItems||!1===e[t]);if(n.strictTuples&&!a){const e=`"${c}" is ${i}-tuple, but minItems or maxItems/${t} are not specified or different at path "${o}"`;(0,s.checkStrictMode)(l,e,n.strictTuples)}}(i),l.opts.unevaluated&&r.length&&!0!==l.items&&(l.items=s.mergeEvaluated.items(o,r.length,l.items));const u=o.name("valid"),d=o.const("len",n._`${a}.length`);r.forEach(((t,r)=>{(0,s.alwaysValidSchema)(l,t)||(o.if(n._`${d} > ${r}`,(()=>e.subschema({keyword:c,schemaProp:r,dataProp:r},u))),e.ok(u))}))}t.validateTuple=a,t.default=i},3785:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o=r(7470),i=r(558),a={keyword:"items",type:"array",schemaType:["object","boolean"],before:"uniqueItems",error:{message:({params:{len:e}})=>n.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>n._`{limit: ${e}}`},code(e){const{schema:t,parentSchema:r,it:n}=e,{prefixItems:a}=r;n.items=!0,(0,s.alwaysValidSchema)(n,t)||(a?(0,i.validateAdditionalItems)(e,a):e.ok((0,o.validateArray)(e)))}};t.default=a},2624:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(320),s={keyword:"not",schemaType:["object","boolean"],trackErrors:!0,code(e){const{gen:t,schema:r,it:s}=e;if((0,n.alwaysValidSchema)(s,r))return void e.fail();const o=t.name("valid");e.subschema({keyword:"not",compositeRule:!0,createErrors:!1,allErrors:!1},o),e.failResult(o,(()=>e.reset()),(()=>e.error()))},error:{message:"must NOT be valid"}};t.default=s},7706:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o={keyword:"oneOf",schemaType:"array",trackErrors:!0,error:{message:"must match exactly one schema in oneOf",params:({params:e})=>n._`{passingSchemas: ${e.passing}}`},code(e){const{gen:t,schema:r,parentSchema:o,it:i}=e;if(!Array.isArray(r))throw new Error("ajv implementation error");if(i.opts.discriminator&&o.discriminator)return;const a=r,c=t.let("valid",!1),l=t.let("passing",null),u=t.name("_valid");e.setParams({passing:l}),t.block((function(){a.forEach(((r,o)=>{let a;(0,s.alwaysValidSchema)(i,r)?t.var(u,!0):a=e.subschema({keyword:"oneOf",schemaProp:o,compositeRule:!0},u),o>0&&t.if(n._`${u} && ${c}`).assign(c,!1).assign(l,n._`[${l}, ${o}]`).else(),t.if(u,(()=>{t.assign(c,!0),t.assign(l,o),a&&e.mergeEvaluated(a,n.Name)}))}))})),e.result(c,(()=>e.reset()),(()=>e.error(!0)))}};t.default=o},5975:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(7470),s=r(5899),o=r(320),i=r(320),a={keyword:"patternProperties",type:"object",schemaType:"object",code(e){const{gen:t,schema:r,data:a,parentSchema:c,it:l}=e,{opts:u}=l,d=(0,n.allSchemaProperties)(r),f=d.filter((e=>(0,o.alwaysValidSchema)(l,r[e])));if(0===d.length||f.length===d.length&&(!l.opts.unevaluated||!0===l.props))return;const h=u.strictSchema&&!u.allowMatchingProperties&&c.properties,p=t.name("valid");!0===l.props||l.props instanceof s.Name||(l.props=(0,i.evaluatedPropsToName)(t,l.props));const{props:m}=l;function y(e){for(const t in h)new RegExp(e).test(t)&&(0,o.checkStrictMode)(l,`property ${t} matches pattern ${e} (use allowMatchingProperties)`)}function g(r){t.forIn("key",a,(o=>{t.if(s._`${(0,n.usePattern)(e,r)}.test(${o})`,(()=>{const n=f.includes(r);n||e.subschema({keyword:"patternProperties",schemaProp:r,dataProp:o,dataPropType:i.Type.Str},p),l.opts.unevaluated&&!0!==m?t.assign(s._`${m}[${o}]`,!0):n||l.allErrors||t.if((0,s.not)(p),(()=>t.break()))}))}))}!function(){for(const e of d)h&&y(e),l.allErrors?g(e):(t.var(p,!0),g(e),t.if(p))}()}};t.default=a},4531:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(4820),s={keyword:"prefixItems",type:"array",schemaType:["array"],before:"uniqueItems",code:e=>(0,n.validateTuple)(e,"items")};t.default=s},3966:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5032),s=r(7470),o=r(320),i=r(2038),a={keyword:"properties",type:"object",schemaType:"object",code(e){const{gen:t,schema:r,parentSchema:a,data:c,it:l}=e;"all"===l.opts.removeAdditional&&void 0===a.additionalProperties&&i.default.code(new n.KeywordCxt(l,i.default,"additionalProperties"));const u=(0,s.allSchemaProperties)(r);for(const e of u)l.definedProperties.add(e);l.opts.unevaluated&&u.length&&!0!==l.props&&(l.props=o.mergeEvaluated.props(t,(0,o.toHash)(u),l.props));const d=u.filter((e=>!(0,o.alwaysValidSchema)(l,r[e])));if(0===d.length)return;const f=t.name("valid");for(const r of d)h(r)?p(r):(t.if((0,s.propertyInData)(t,c,r,l.opts.ownProperties)),p(r),l.allErrors||t.else().var(f,!0),t.endIf()),e.it.definedProperties.add(r),e.ok(f);function h(e){return l.opts.useDefaults&&!l.compositeRule&&void 0!==r[e].default}function p(t){e.subschema({keyword:"properties",schemaProp:t,dataProp:t},f)}}};t.default=a},7620:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o={keyword:"propertyNames",type:"object",schemaType:["object","boolean"],error:{message:"property name must be valid",params:({params:e})=>n._`{propertyName: ${e.propertyName}}`},code(e){const{gen:t,schema:r,data:o,it:i}=e;if((0,s.alwaysValidSchema)(i,r))return;const a=t.name("valid");t.forIn("key",o,(r=>{e.setParams({propertyName:r}),e.subschema({keyword:"propertyNames",data:r,dataTypes:["string"],propertyName:r,compositeRule:!0},a),t.if((0,n.not)(a),(()=>{e.error(!0),i.allErrors||t.break()}))})),e.ok(a)}};t.default=o},8957:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(320),s={keyword:["then","else"],schemaType:["object","boolean"],code({keyword:e,parentSchema:t,it:r}){void 0===t.if&&(0,n.checkStrictMode)(r,`"${e}" without "if" is ignored`)}};t.default=s},7470:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateUnion=t.validateArray=t.usePattern=t.callValidateCode=t.schemaProperties=t.allSchemaProperties=t.noPropertyInData=t.propertyInData=t.isOwnProperty=t.hasPropFunc=t.reportMissingProp=t.checkMissingProp=t.checkReportMissingProp=void 0;const n=r(5899),s=r(320),o=r(6934),i=r(320);function a(e){return e.scopeValue("func",{ref:Object.prototype.hasOwnProperty,code:n._`Object.prototype.hasOwnProperty`})}function c(e,t,r){return n._`${a(e)}.call(${t}, ${r})`}function l(e,t,r,s){const o=n._`${t}${(0,n.getProperty)(r)} === undefined`;return s?(0,n.or)(o,(0,n.not)(c(e,t,r))):o}function u(e){return e?Object.keys(e).filter((e=>"__proto__"!==e)):[]}t.checkReportMissingProp=function(e,t){const{gen:r,data:s,it:o}=e;r.if(l(r,s,t,o.opts.ownProperties),(()=>{e.setParams({missingProperty:n._`${t}`},!0),e.error()}))},t.checkMissingProp=function({gen:e,data:t,it:{opts:r}},s,o){return(0,n.or)(...s.map((s=>(0,n.and)(l(e,t,s,r.ownProperties),n._`${o} = ${s}`))))},t.reportMissingProp=function(e,t){e.setParams({missingProperty:t},!0),e.error()},t.hasPropFunc=a,t.isOwnProperty=c,t.propertyInData=function(e,t,r,s){const o=n._`${t}${(0,n.getProperty)(r)} !== undefined`;return s?n._`${o} && ${c(e,t,r)}`:o},t.noPropertyInData=l,t.allSchemaProperties=u,t.schemaProperties=function(e,t){return u(t).filter((r=>!(0,s.alwaysValidSchema)(e,t[r])))},t.callValidateCode=function({schemaCode:e,data:t,it:{gen:r,topSchemaRef:s,schemaPath:i,errorPath:a},it:c},l,u,d){const f=d?n._`${e}, ${t}, ${s}${i}`:t,h=[[o.default.instancePath,(0,n.strConcat)(o.default.instancePath,a)],[o.default.parentData,c.parentData],[o.default.parentDataProperty,c.parentDataProperty],[o.default.rootData,o.default.rootData]];c.opts.dynamicRef&&h.push([o.default.dynamicAnchors,o.default.dynamicAnchors]);const p=n._`${f}, ${r.object(...h)}`;return u!==n.nil?n._`${l}.call(${u}, ${p})`:n._`${l}(${p})`};const d=n._`new RegExp`;t.usePattern=function({gen:e,it:{opts:t}},r){const s=t.unicodeRegExp?"u":"",{regExp:o}=t.code,a=o(r,s);return e.scopeValue("pattern",{key:a.toString(),ref:a,code:n._`${"new RegExp"===o.code?d:(0,i.useFunc)(e,o)}(${r}, ${s})`})},t.validateArray=function(e){const{gen:t,data:r,keyword:o,it:i}=e,a=t.name("valid");if(i.allErrors){const e=t.let("valid",!0);return c((()=>t.assign(e,!1))),e}return t.var(a,!0),c((()=>t.break())),a;function c(i){const c=t.const("len",n._`${r}.length`);t.forRange("i",0,c,(r=>{e.subschema({keyword:o,dataProp:r,dataPropType:s.Type.Num},a),t.if((0,n.not)(a),i)}))}},t.validateUnion=function(e){const{gen:t,schema:r,keyword:o,it:i}=e;if(!Array.isArray(r))throw new Error("ajv implementation error");if(r.some((e=>(0,s.alwaysValidSchema)(i,e)))&&!i.opts.unevaluated)return;const a=t.let("valid",!1),c=t.name("_valid");t.block((()=>r.forEach(((r,s)=>{const i=e.subschema({keyword:o,schemaProp:s,compositeRule:!0},c);t.assign(a,n._`${a} || ${c}`),e.mergeValidEvaluated(i,c)||t.if((0,n.not)(a))})))),e.result(a,(()=>e.reset()),(()=>e.error(!0)))}},194:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r={keyword:"id",code(){throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID')}};t.default=r},4901:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(194),s=r(3898),o=["$schema","$id","$defs","$vocabulary",{keyword:"$comment"},"definitions",n.default,s.default];t.default=o},3898:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.callRef=t.getValidate=void 0;const n=r(433),s=r(7470),o=r(5899),i=r(6934),a=r(7760),c=r(320),l={keyword:"$ref",schemaType:"string",code(e){const{gen:t,schema:r,it:s}=e,{baseId:i,schemaEnv:c,validateName:l,opts:f,self:h}=s,{root:p}=c;if(("#"===r||"#/"===r)&&i===p.baseId)return function(){if(c===p)return d(e,l,c,c.$async);const r=t.scopeValue("root",{ref:p});return d(e,o._`${r}.validate`,p,p.$async)}();const m=a.resolveRef.call(h,p,i,r);if(void 0===m)throw new n.default(s.opts.uriResolver,i,r);return m instanceof a.SchemaEnv?function(t){const r=u(e,t);d(e,r,t,t.$async)}(m):function(n){const s=t.scopeValue("schema",!0===f.code.source?{ref:n,code:(0,o.stringify)(n)}:{ref:n}),i=t.name("valid"),a=e.subschema({schema:n,dataTypes:[],schemaPath:o.nil,topSchemaRef:s,errSchemaPath:r},i);e.mergeEvaluated(a),e.ok(i)}(m)}};function u(e,t){const{gen:r}=e;return t.validate?r.scopeValue("validate",{ref:t.validate}):o._`${r.scopeValue("wrapper",{ref:t})}.validate`}function d(e,t,r,n){const{gen:a,it:l}=e,{allErrors:u,schemaEnv:d,opts:f}=l,h=f.passContext?i.default.this:o.nil;function p(e){const t=o._`${e}.errors`;a.assign(i.default.vErrors,o._`${i.default.vErrors} === null ? ${t} : ${i.default.vErrors}.concat(${t})`),a.assign(i.default.errors,o._`${i.default.vErrors}.length`)}function m(e){var t;if(!l.opts.unevaluated)return;const n=null===(t=null==r?void 0:r.validate)||void 0===t?void 0:t.evaluated;if(!0!==l.props)if(n&&!n.dynamicProps)void 0!==n.props&&(l.props=c.mergeEvaluated.props(a,n.props,l.props));else{const t=a.var("props",o._`${e}.evaluated.props`);l.props=c.mergeEvaluated.props(a,t,l.props,o.Name)}if(!0!==l.items)if(n&&!n.dynamicItems)void 0!==n.items&&(l.items=c.mergeEvaluated.items(a,n.items,l.items));else{const t=a.var("items",o._`${e}.evaluated.items`);l.items=c.mergeEvaluated.items(a,t,l.items,o.Name)}}n?function(){if(!d.$async)throw new Error("async schema referenced by sync schema");const r=a.let("valid");a.try((()=>{a.code(o._`await ${(0,s.callValidateCode)(e,t,h)}`),m(t),u||a.assign(r,!0)}),(e=>{a.if(o._`!(${e} instanceof ${l.ValidationError})`,(()=>a.throw(e))),p(e),u||a.assign(r,!1)})),e.ok(r)}():e.result((0,s.callValidateCode)(e,t,h),(()=>m(t)),(()=>p(t)))}t.getValidate=u,t.callRef=d,t.default=l},8402:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(2872),o=r(7760),i=r(320),a={keyword:"discriminator",type:"object",schemaType:"object",error:{message:({params:{discrError:e,tagName:t}})=>e===s.DiscrError.Tag?`tag "${t}" must be string`:`value of tag "${t}" must be in oneOf`,params:({params:{discrError:e,tag:t,tagName:r}})=>n._`{error: ${e}, tag: ${r}, tagValue: ${t}}`},code(e){const{gen:t,data:r,schema:a,parentSchema:c,it:l}=e,{oneOf:u}=c;if(!l.opts.discriminator)throw new Error("discriminator: requires discriminator option");const d=a.propertyName;if("string"!=typeof d)throw new Error("discriminator: requires propertyName");if(a.mapping)throw new Error("discriminator: mapping is not supported");if(!u)throw new Error("discriminator: requires oneOf keyword");const f=t.let("valid",!1),h=t.const("tag",n._`${r}${(0,n.getProperty)(d)}`);function p(r){const s=t.name("valid"),o=e.subschema({keyword:"oneOf",schemaProp:r},s);return e.mergeEvaluated(o,n.Name),s}t.if(n._`typeof ${h} == "string"`,(()=>function(){const r=function(){var e;const t={},r=s(c);let n=!0;for(let t=0;te.error(!1,{discrError:s.DiscrError.Tag,tag:h,tagName:d}))),e.ok(f)}};t.default=a},2872:(e,t)=>{"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.DiscrError=void 0,(r=t.DiscrError||(t.DiscrError={})).Tag="tag",r.Mapping="mapping"},6568:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(4901),s=r(6823),o=r(222),i=r(4805),a=r(4110),c=[n.default,s.default,(0,o.default)(),i.default,a.metadataVocabulary,a.contentVocabulary];t.default=c},2970:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s={keyword:"format",type:["number","string"],schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>n.str`must match format "${e}"`,params:({schemaCode:e})=>n._`{format: ${e}}`},code(e,t){const{gen:r,data:s,$data:o,schema:i,schemaCode:a,it:c}=e,{opts:l,errSchemaPath:u,schemaEnv:d,self:f}=c;l.validateFormats&&(o?function(){const o=r.scopeValue("formats",{ref:f.formats,code:l.code.formats}),i=r.const("fDef",n._`${o}[${a}]`),c=r.let("fType"),u=r.let("format");r.if(n._`typeof ${i} == "object" && !(${i} instanceof RegExp)`,(()=>r.assign(c,n._`${i}.type || "string"`).assign(u,n._`${i}.validate`)),(()=>r.assign(c,n._`"string"`).assign(u,i))),e.fail$data((0,n.or)(!1===l.strictSchema?n.nil:n._`${a} && !${u}`,function(){const e=d.$async?n._`(${i}.async ? await ${u}(${s}) : ${u}(${s}))`:n._`${u}(${s})`,r=n._`(typeof ${u} == "function" ? ${e} : ${u}.test(${s}))`;return n._`${u} && ${u} !== true && ${c} === ${t} && !${r}`}()))}():function(){const o=f.formats[i];if(!o)return void function(){if(!1!==l.strictSchema)throw new Error(e());function e(){return`unknown format "${i}" ignored in schema at path "${u}"`}f.logger.warn(e())}();if(!0===o)return;const[a,c,h]=function(e){const t=e instanceof RegExp?(0,n.regexpCode)(e):l.code.formats?n._`${l.code.formats}${(0,n.getProperty)(i)}`:void 0,s=r.scopeValue("formats",{key:i,ref:e,code:t});return"object"!=typeof e||e instanceof RegExp?["string",e,s]:[e.type||"string",e.validate,n._`${s}.validate`]}(o);a===t&&e.pass(function(){if("object"==typeof o&&!(o instanceof RegExp)&&o.async){if(!d.$async)throw new Error("async format in sync schema");return n._`await ${h}(${s})`}return"function"==typeof c?n._`${h}(${s})`:n._`${h}.test(${s})`}())}())}};t.default=s},4805:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=[r(2970).default];t.default=n},4110:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.contentVocabulary=t.metadataVocabulary=void 0,t.metadataVocabulary=["title","description","default","deprecated","readOnly","writeOnly","examples"],t.contentVocabulary=["contentMediaType","contentEncoding","contentSchema"]},1475:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o=r(283),i={keyword:"const",$data:!0,error:{message:"must be equal to constant",params:({schemaCode:e})=>n._`{allowedValue: ${e}}`},code(e){const{gen:t,data:r,$data:i,schemaCode:a,schema:c}=e;i||c&&"object"==typeof c?e.fail$data(n._`!${(0,s.useFunc)(t,o.default)}(${r}, ${a})`):e.fail(n._`${c} !== ${r}`)}};t.default=i},975:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o=r(283),i={keyword:"enum",schemaType:"array",$data:!0,error:{message:"must be equal to one of the allowed values",params:({schemaCode:e})=>n._`{allowedValues: ${e}}`},code(e){const{gen:t,data:r,$data:i,schema:a,schemaCode:c,it:l}=e;if(!i&&0===a.length)throw new Error("enum must have non-empty array");const u=a.length>=l.opts.loopEnum;let d;const f=()=>null!=d?d:d=(0,s.useFunc)(t,o.default);let h;if(u||i)h=t.let("valid"),e.block$data(h,(function(){t.assign(h,!1),t.forOf("v",c,(e=>t.if(n._`${f()}(${r}, ${e})`,(()=>t.assign(h,!0).break()))))}));else{if(!Array.isArray(a))throw new Error("ajv implementation error");const e=t.const("vSchema",c);h=(0,n.or)(...a.map(((t,s)=>function(e,t){const s=a[t];return"object"==typeof s&&null!==s?n._`${f()}(${r}, ${e}[${t}])`:n._`${r} === ${s}`}(e,s))))}e.pass(h)}};t.default=i},6823:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(8276),s=r(7597),o=r(7398),i=r(3486),a=r(1448),c=r(8813),l=r(7511),u=r(9075),d=r(1475),f=r(975),h=[n.default,s.default,o.default,i.default,a.default,c.default,l.default,u.default,{keyword:"type",schemaType:["string","array"]},{keyword:"nullable",schemaType:"boolean"},d.default,f.default];t.default=h},7511:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s={keyword:["maxItems","minItems"],type:"array",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const r="maxItems"===e?"more":"fewer";return n.str`must NOT have ${r} than ${t} items`},params:({schemaCode:e})=>n._`{limit: ${e}}`},code(e){const{keyword:t,data:r,schemaCode:s}=e,o="maxItems"===t?n.operators.GT:n.operators.LT;e.fail$data(n._`${r}.length ${o} ${s}`)}};t.default=s},7398:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=r(320),o=r(6913),i={keyword:["maxLength","minLength"],type:"string",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const r="maxLength"===e?"more":"fewer";return n.str`must NOT have ${r} than ${t} characters`},params:({schemaCode:e})=>n._`{limit: ${e}}`},code(e){const{keyword:t,data:r,schemaCode:i,it:a}=e,c="maxLength"===t?n.operators.GT:n.operators.LT,l=!1===a.opts.unicode?n._`${r}.length`:n._`${(0,s.useFunc)(e.gen,o.default)}(${r})`;e.fail$data(n._`${l} ${c} ${i}`)}};t.default=i},8276:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s=n.operators,o={maximum:{okStr:"<=",ok:s.LTE,fail:s.GT},minimum:{okStr:">=",ok:s.GTE,fail:s.LT},exclusiveMaximum:{okStr:"<",ok:s.LT,fail:s.GTE},exclusiveMinimum:{okStr:">",ok:s.GT,fail:s.LTE}},i={message:({keyword:e,schemaCode:t})=>n.str`must be ${o[e].okStr} ${t}`,params:({keyword:e,schemaCode:t})=>n._`{comparison: ${o[e].okStr}, limit: ${t}}`},a={keyword:Object.keys(o),type:"number",schemaType:"number",$data:!0,error:i,code(e){const{keyword:t,data:r,schemaCode:s}=e;e.fail$data(n._`${r} ${o[t].fail} ${s} || isNaN(${r})`)}};t.default=a},1448:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s={keyword:["maxProperties","minProperties"],type:"object",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const r="maxProperties"===e?"more":"fewer";return n.str`must NOT have ${r} than ${t} properties`},params:({schemaCode:e})=>n._`{limit: ${e}}`},code(e){const{keyword:t,data:r,schemaCode:s}=e,o="maxProperties"===t?n.operators.GT:n.operators.LT;e.fail$data(n._`Object.keys(${r}).length ${o} ${s}`)}};t.default=s},7597:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(5899),s={keyword:"multipleOf",type:"number",schemaType:"number",$data:!0,error:{message:({schemaCode:e})=>n.str`must be multiple of ${e}`,params:({schemaCode:e})=>n._`{multipleOf: ${e}}`},code(e){const{gen:t,data:r,schemaCode:s,it:o}=e,i=o.opts.multipleOfPrecision,a=t.let("res"),c=i?n._`Math.abs(Math.round(${a}) - ${a}) > 1e-${i}`:n._`${a} !== parseInt(${a})`;e.fail$data(n._`(${s} === 0 || (${a} = ${r}/${s}, ${c}))`)}};t.default=s},3486:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(7470),s=r(5899),o={keyword:"pattern",type:"string",schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>s.str`must match pattern "${e}"`,params:({schemaCode:e})=>s._`{pattern: ${e}}`},code(e){const{data:t,$data:r,schema:o,schemaCode:i,it:a}=e,c=a.opts.unicodeRegExp?"u":"",l=r?s._`(new RegExp(${i}, ${c}))`:(0,n.usePattern)(e,o);e.fail$data(s._`!${l}.test(${t})`)}};t.default=o},8813:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(7470),s=r(5899),o=r(320),i={keyword:"required",type:"object",schemaType:"array",$data:!0,error:{message:({params:{missingProperty:e}})=>s.str`must have required property '${e}'`,params:({params:{missingProperty:e}})=>s._`{missingProperty: ${e}}`},code(e){const{gen:t,schema:r,schemaCode:i,data:a,$data:c,it:l}=e,{opts:u}=l;if(!c&&0===r.length)return;const d=r.length>=u.loopRequired;if(l.allErrors?function(){if(d||c)e.block$data(s.nil,f);else for(const t of r)(0,n.checkReportMissingProp)(e,t)}():function(){const o=t.let("missing");if(d||c){const r=t.let("valid",!0);e.block$data(r,(()=>function(r,o){e.setParams({missingProperty:r}),t.forOf(r,i,(()=>{t.assign(o,(0,n.propertyInData)(t,a,r,u.ownProperties)),t.if((0,s.not)(o),(()=>{e.error(),t.break()}))}),s.nil)}(o,r))),e.ok(r)}else t.if((0,n.checkMissingProp)(e,r,o)),(0,n.reportMissingProp)(e,o),t.else()}(),u.strictRequired){const t=e.parentSchema.properties,{definedProperties:n}=e.it;for(const e of r)if(void 0===(null==t?void 0:t[e])&&!n.has(e)){const t=`required property "${e}" is not defined at "${l.schemaEnv.baseId+l.errSchemaPath}" (strictRequired)`;(0,o.checkStrictMode)(l,t,l.opts.strictRequired)}}function f(){t.forOf("prop",i,(r=>{e.setParams({missingProperty:r}),t.if((0,n.noPropertyInData)(t,a,r,u.ownProperties),(()=>e.error()))}))}}};t.default=i},9075:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=r(2292),s=r(5899),o=r(320),i=r(283),a={keyword:"uniqueItems",type:"array",schemaType:"boolean",$data:!0,error:{message:({params:{i:e,j:t}})=>s.str`must NOT have duplicate items (items ## ${t} and ${e} are identical)`,params:({params:{i:e,j:t}})=>s._`{i: ${e}, j: ${t}}`},code(e){const{gen:t,data:r,$data:a,schema:c,parentSchema:l,schemaCode:u,it:d}=e;if(!a&&!c)return;const f=t.let("valid"),h=l.items?(0,n.getSchemaTypes)(l.items):[];function p(o,i){const a=t.name("item"),c=(0,n.checkDataTypes)(h,a,d.opts.strictNumbers,n.DataType.Wrong),l=t.const("indices",s._`{}`);t.for(s._`;${o}--;`,(()=>{t.let(a,s._`${r}[${o}]`),t.if(c,s._`continue`),h.length>1&&t.if(s._`typeof ${a} == "string"`,s._`${a} += "_"`),t.if(s._`typeof ${l}[${a}] == "number"`,(()=>{t.assign(i,s._`${l}[${a}]`),e.error(),t.assign(f,!1).break()})).code(s._`${l}[${a}] = ${o}`)}))}function m(n,a){const c=(0,o.useFunc)(t,i.default),l=t.name("outer");t.label(l).for(s._`;${n}--;`,(()=>t.for(s._`${a} = ${n}; ${a}--;`,(()=>t.if(s._`${c}(${r}[${n}], ${r}[${a}])`,(()=>{e.error(),t.assign(f,!1).break(l)}))))))}e.block$data(f,(function(){const n=t.let("i",s._`${r}.length`),o=t.let("j");e.setParams({i:n,j:o}),t.assign(f,!0),t.if(s._`${n} > 1`,(()=>(h.length>0&&!h.some((e=>"object"===e||"array"===e))?p:m)(n,o)))}),s._`${u} === false`),e.ok(f)}};t.default=a},5528:e=>{"use strict";var t=e.exports=function(e,t,n){"function"==typeof t&&(n=t,t={}),r(t,"function"==typeof(n=t.cb||n)?n:n.pre||function(){},n.post||function(){},e,"",e)};function r(e,n,s,o,i,a,c,l,u,d){if(o&&"object"==typeof o&&!Array.isArray(o)){for(var f in n(o,i,a,c,l,u,d),o){var h=o[f];if(Array.isArray(h)){if(f in t.arrayKeywords)for(var p=0;p{"use strict";const n=r(6022);e.exports=(e,t={})=>{if("function"!=typeof e)throw new TypeError(`Expected the first argument to be a function, got \`${typeof e}\``);const{wait:r=0,before:s=!1,after:o=!0}=t;if(!s&&!o)throw new Error("Both `before` and `after` are false, function wouldn't be called.");let i,a;const c=function(...t){const n=this,c=s&&!i;return clearTimeout(i),i=setTimeout((()=>{i=void 0,o&&(a=e.apply(n,t))}),r),c&&(a=e.apply(n,t)),a};return n(c,e),c.cancel=()=>{i&&(clearTimeout(i),i=void 0)},c}},6022:e=>{"use strict";const t=(e,t,n,s)=>{if("length"===n||"prototype"===n)return;if("arguments"===n||"caller"===n)return;const o=Object.getOwnPropertyDescriptor(e,n),i=Object.getOwnPropertyDescriptor(t,n);!r(o,i)&&s||Object.defineProperty(e,n,i)},r=function(e,t){return void 0===e||e.configurable||e.writable===t.writable&&e.enumerable===t.enumerable&&e.configurable===t.configurable&&(e.writable||e.value===t.value)},n=(e,t)=>`/* Wrapped ${e}*/\n${t}`,s=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),o=Object.getOwnPropertyDescriptor(Function.prototype.toString,"name");e.exports=(e,r,{ignoreNonConfigurable:i=!1}={})=>{const{name:a}=e;for(const n of Reflect.ownKeys(r))t(e,r,n,i);return((e,t)=>{const r=Object.getPrototypeOf(t);r!==Object.getPrototypeOf(e)&&Object.setPrototypeOf(e,r)})(e,r),((e,t,r)=>{const i=""===r?"":`with ${r.trim()}() `,a=n.bind(null,i,t.toString());Object.defineProperty(a,"name",o),Object.defineProperty(e,"toString",{...s,value:a})})(e,r,a),e}},3517:(e,t,r)=>{"use strict";const n=r(4290),s=new Set(["__proto__","prototype","constructor"]);function o(e){const t=e.split("."),r=[];for(let e=0;es.has(e)))?[]:r}e.exports={get(e,t,r){if(!n(e)||"string"!=typeof t)return void 0===r?e:r;const s=o(t);if(0!==s.length){for(let t=0;t{"use strict";const n=r(1787);class s{static instances={};errorHandler=null;eventLogger=null;functions={};hooks=[];isDev=!1;levels=null;logId=null;scope=null;transports={};variables={};constructor({allowUnknownLevel:e=!1,errorHandler:t,eventLogger:r,initializeFn:o,isDev:i=!1,levels:a=["error","warn","info","verbose","debug","silly"],logId:c,transportFactories:l={},variables:u}={}){this.addLevel=this.addLevel.bind(this),this.create=this.create.bind(this),this.logData=this.logData.bind(this),this.processMessage=this.processMessage.bind(this),this.allowUnknownLevel=e,this.initializeFn=o,this.isDev=i,this.levels=a,this.logId=c,this.transportFactories=l,this.variables=u||{},this.scope=n(this),this.addLevel("log",!1);for(const e of this.levels)this.addLevel(e,!1);this.errorHandler=t,t?.setOptions({logFn:this.error}),this.eventLogger=r,r?.setOptions({logger:this});for(const[e,t]of Object.entries(l))this.transports[e]=t(this);s.instances[c]=this}static getInstance({logId:e}){return this.instances[e]||this.instances.default}addLevel(e,t=this.levels.length){!1!==t&&this.levels.splice(t,0,e),this[e]=(...t)=>this.logData(t,{level:e}),this.functions[e]=this[e]}catchErrors(e){return this.processMessage({data:["log.catchErrors is deprecated. Use log.errorHandler instead"],level:"warn"},{transports:["console"]}),this.errorHandler.startCatching(e)}create(e){return"string"==typeof e&&(e={logId:e}),new s({...e,errorHandler:this.errorHandler,initializeFn:this.initializeFn,isDev:this.isDev,transportFactories:this.transportFactories,variables:{...this.variables}})}compareLevels(e,t,r=this.levels){const n=r.indexOf(e),s=r.indexOf(t);return-1===s||-1===n||s<=n}initialize(e={}){this.initializeFn({logger:this,...e})}logData(e,t={}){this.processMessage({data:e,...t})}processMessage(e,{transports:t=this.transports}={}){if("errorHandler"===e.cmd)return void this.errorHandler.handle(e.error,{errorName:e.errorName,processType:"renderer",showDialog:Boolean(e.showDialog)});let r=e.level;this.allowUnknownLevel||(r=this.levels.includes(e.level)?e.level:"info");const n={date:new Date,...e,level:r,variables:{...this.variables,...e.variables}};for(const[r,s]of this.transportEntries(t))if("function"==typeof s&&!1!==s.level&&this.compareLevels(s.level,e.level))try{const e=this.hooks.reduce(((e,t)=>e?t(e,s,r):e),n);e&&s({...e,data:[...e.data]})}catch(e){this.processInternalErrorFn(e)}}processInternalErrorFn(e){}transportEntries(e=this.transports){return(Array.isArray(e)?e:Object.entries(e)).map((e=>{switch(typeof e){case"string":return this.transports[e]?[e,this.transports[e]]:null;case"function":return[e.name,e];default:return Array.isArray(e)?e:null}})).filter(Boolean)}}e.exports=s},1787:e=>{"use strict";e.exports=function(e){return Object.defineProperties(t,{defaultLabel:{value:"",writable:!0},labelPadding:{value:!0,writable:!0},maxLabelLength:{value:0,writable:!0},labelLength:{get(){switch(typeof t.labelPadding){case"boolean":return t.labelPadding?t.maxLabelLength:0;case"number":return t.labelPadding;default:return 0}}}});function t(r){t.maxLabelLength=Math.max(t.maxLabelLength,r.length);const n={};for(const t of[...e.levels,"log"])n[t]=(...n)=>e.logData(n,{level:t,scope:r});return n}}},7377:(e,t,r)=>{"use strict";"undefined"==typeof process||"renderer"===process.type||"worker"===process.type?(r(9654),e.exports=r(9618)):e.exports=r(7631)},2641:(e,t,r)=>{"use strict";const n=r(2674);e.exports=class{isActive=!1;logFn=null;onError=null;showDialog=!0;constructor({logFn:e=null,onError:t=null,showDialog:r=!0}={}){this.createIssue=this.createIssue.bind(this),this.handleError=this.handleError.bind(this),this.handleRejection=this.handleRejection.bind(this),this.setOptions({logFn:e,onError:t,showDialog:r}),this.startCatching=this.startCatching.bind(this),this.stopCatching=this.stopCatching.bind(this)}handle(e,{logFn:t=this.logFn,onError:r=this.onError,processType:s="browser",showDialog:o=this.showDialog,errorName:i=""}={}){e=function(e){if(e instanceof Error)return e;if(e&&"object"==typeof e){if(e.message)return Object.assign(new Error(e.message),e);try{return new Error(JSON.stringify(e))}catch(t){return new Error(`Couldn't normalize error ${String(e)}: ${t}`)}}return new Error(`Can't normalize error ${String(e)}`)}(e);try{if("function"==typeof r){const t=n.getVersions();if(!1===r({createIssue:this.createIssue,error:e,errorName:i,processType:s,versions:t}))return}i?t(i,e):t(e),o&&!i.includes("rejection")&&n.showErrorBox(`A JavaScript error occurred in the ${s} process`,e.stack)}catch{console.error(e)}}setOptions({logFn:e,onError:t,showDialog:r}){"function"==typeof e&&(this.logFn=e),"function"==typeof t&&(this.onError=t),"boolean"==typeof r&&(this.showDialog=r)}startCatching({onError:e,showDialog:t}={}){this.isActive||(this.isActive=!0,this.setOptions({onError:e,showDialog:t}),process.on("uncaughtException",this.handleError),process.on("unhandledRejection",this.handleRejection))}stopCatching(){this.isActive=!1,process.removeListener("uncaughtException",this.handleError),process.removeListener("unhandledRejection",this.handleRejection)}createIssue(e,t){n.openUrl(`${e}?${new URLSearchParams(t).toString()}`)}handleError(e){this.handle(e,{errorName:"Unhandled"})}handleRejection(e){const t=e instanceof Error?e:new Error(JSON.stringify(e));this.handle(t,{errorName:"Unhandled rejection"})}}},6900:(e,t,r)=>{"use strict";const n=r(2674);e.exports=class{disposers=[];format="{eventSource}#{eventName}:";formatters={app:{"certificate-error":({args:e})=>this.arrayToObject(e.slice(1,4),["url","error","certificate"]),"child-process-gone":({args:e})=>1===e.length?e[0]:e,"render-process-gone":({args:[e,t]})=>t&&"object"==typeof t?{...t,...this.getWebContentsDetails(e)}:[]},webContents:{"console-message":({args:[e,t,r,n]})=>{if(!(e<3))return{message:t,source:`${n}:${r}`}},"did-fail-load":({args:e})=>this.arrayToObject(e,["errorCode","errorDescription","validatedURL","isMainFrame","frameProcessId","frameRoutingId"]),"did-fail-provisional-load":({args:e})=>this.arrayToObject(e,["errorCode","errorDescription","validatedURL","isMainFrame","frameProcessId","frameRoutingId"]),"plugin-crashed":({args:e})=>this.arrayToObject(e,["name","version"]),"preload-error":({args:e})=>this.arrayToObject(e,["preloadPath","error"])}};events={app:{"certificate-error":!0,"child-process-gone":!0,"render-process-gone":!0},webContents:{"did-fail-load":!0,"did-fail-provisional-load":!0,"plugin-crashed":!0,"preload-error":!0,unresponsive:!0}};level="error";scope="";constructor(e={}){this.setOptions(e)}setOptions({events:e,level:t,logger:r,format:n,formatters:s,scope:o}){"object"==typeof e&&(this.events=e),"string"==typeof t&&(this.level=t),"object"==typeof r&&(this.logger=r),"string"!=typeof n&&"function"!=typeof n||(this.format=n),"object"==typeof s&&(this.formatters=s),"string"==typeof o&&(this.scope=o)}startLogging(e={}){this.setOptions(e),this.disposeListeners();for(const e of this.getEventNames(this.events.app))this.disposers.push(n.onAppEvent(e,((...t)=>{this.handleEvent({eventSource:"app",eventName:e,handlerArgs:t})})));for(const e of this.getEventNames(this.events.webContents))this.disposers.push(n.onEveryWebContentsEvent(e,((...t)=>{this.handleEvent({eventSource:"webContents",eventName:e,handlerArgs:t})})))}stopLogging(){this.disposeListeners()}arrayToObject(e,t){const r={};return t.forEach(((t,n)=>{r[t]=e[n]})),e.length>t.length&&(r.unknownArgs=e.slice(t.length)),r}disposeListeners(){this.disposers.forEach((e=>e())),this.disposers=[]}formatEventLog({eventName:e,eventSource:t,handlerArgs:r}){const[n,...s]=r;if("function"==typeof this.format)return this.format({args:s,event:n,eventName:e,eventSource:t});const o=this.formatters[t]?.[e];let i=s;if("function"==typeof o&&(i=o({args:s,event:n,eventName:e,eventSource:t})),!i)return;const a={};return Array.isArray(i)?a.args=i:"object"==typeof i&&Object.assign(a,i),"webContents"===t&&Object.assign(a,this.getWebContentsDetails(n?.sender)),[this.format.replace("{eventSource}","app"===t?"App":"WebContents").replace("{eventName}",e),a]}getEventNames(e){return e&&"object"==typeof e?Object.entries(e).filter((([e,t])=>t)).map((([e])=>e)):[]}getWebContentsDetails(e){if(!e?.loadURL)return{};try{return{webContents:{id:e.id,url:e.getURL()}}}catch{return{}}}handleEvent({eventName:e,eventSource:t,handlerArgs:r}){const n=this.formatEventLog({eventName:e,eventSource:t,handlerArgs:r});if(n){const e=this.scope?this.logger.scope(this.scope):this.logger;e?.[this.level]?.(...n)}}}},2674:(e,t,r)=>{"use strict";const n=r(2037),s=r(1017);let o;try{o=r(2298)}catch{o=null}function i(){return c("app")}function a(){const e=i();return e?"name"in e?e.name:e.getName():null}function c(e){return o?.[e]||null}function l(){return"browser"===process.type&&o?.ipcMain?o.ipcMain:"renderer"===process.type&&o?.ipcRenderer?o.ipcRenderer:null}function u(){const e=i();return e?"version"in e?e.version:e.getVersion():null}function d(){let e=n.type().replace("_"," "),t=n.release();return"Darwin"===e&&(e="macOS",t=function(){const e=Number(n.release().split(".")[0]);return e<=19?"10."+(e-4):e-9}()),`${e} ${t}`}function f(e){const t=i();if(!t)return null;try{return t.getPath(e)}catch(e){return null}}e.exports={getAppUserDataPath:()=>f("userData"),getName:a,getPath:f,getVersion:u,getVersions:()=>({app:`${a()} ${u()}`,electron:`Electron ${process.versions.electron}`,os:d()}),isDev(){const e=i();return void 0!==e?.isPackaged?!e.isPackaged:"string"==typeof process.execPath?s.basename(process.execPath).toLowerCase().startsWith("electron"):"1"===process.env.ELECTRON_IS_DEV},isElectron:()=>Boolean(process.versions.electron),onAppEvent:(e,t)=>(o?.app?.on(e,t),()=>{o?.app?.off(e,t)}),onAppReady(e){o?.app?.isReady()?e():o?.app?.once?o?.app?.once("ready",e):e()},onEveryWebContentsEvent(e,t){return o?.webContents?.getAllWebContents().forEach((r=>{r.on(e,t)})),o?.app?.on("web-contents-created",r),()=>{o?.webContents?.getAllWebContents().forEach((r=>{r.off(e,t)})),o?.app?.off("web-contents-created",r)};function r(r,n){n.on(e,t)}},onIpc(e,t){l()?.on(e,t)},onIpcInvoke(e,t){l()?.handle?.(e,t)},openUrl(e,t=console.error){c("shell")?.openExternal(e).catch(t)},setPreloadFileForSessions({filePath:e,includeFutureSession:t=!0,getSessions:r=(()=>[o?.session?.defaultSession])}){for(const e of r().filter(Boolean))n(e);function n(t){t.setPreloads([...t.getPreloads(),e])}t&&o?.app?.on("session-created",(e=>{n(e)}))},sendIpc(e,t){"browser"===process.type?function(e,t){o?.BrowserWindow?.getAllWindows().forEach((r=>{!1===r.webContents?.isDestroyed()&&r.webContents.send(e,t)}))}(e,t):"renderer"===process.type&&function(e,t){l()?.send(e,t)}(e,t)},showErrorBox(e,t){const r=c("dialog");r&&r.showErrorBox(e,t)}}},7631:(e,t,r)=>{"use strict";const n=r(2674),{initialize:s}=r(5471),o=r(429),i=r(1688),a=r(7988),c=r(2666),l=r(2641),u=r(6900),d=new c({errorHandler:new l,eventLogger:new u,initializeFn:s,isDev:n.isDev(),logId:"default",transportFactories:{console:o,file:i,remote:a},variables:{processType:"main"}});function f(e){c.getInstance(e)?.processMessage(e)}d.processInternalErrorFn=e=>{d.transports.console.writeFn({message:{data:["Unhandled electron-log error",e],level:"error"}})},e.exports=d,e.exports.Logger=c,e.exports.default=e.exports,n.onIpc("__ELECTRON_LOG__",((e,t)=>{t.scope&&c.getInstance(t).scope(t.scope);const r=new Date(t.date);f({...t,date:r.getTime()?r:new Date})})),n.onIpcInvoke("__ELECTRON_LOG__",((e,{cmd:t="",logId:r})=>"getOptions"===t?{levels:c.getInstance({logId:r}).levels,logId:r}:(f({data:[`Unknown cmd '${t}'`],level:"error"}),{})))},5471:(e,t,r)=>{"use strict";const n=r(7147),s=r(2037),o=r(1017),i=r(2674),a=r(9654);e.exports={initialize({getSessions:e,includeFutureSession:t,logger:r,preload:c=!0,spyRendererConsole:l=!1}){i.onAppReady((()=>{try{c&&function({getSessions:e,includeFutureSession:t,preloadOption:r}){let c="string"==typeof r?r:o.resolve(__dirname,"../renderer/electron-log-preload.js");if(!n.existsSync(c)){c=o.join(i.getAppUserDataPath()||s.tmpdir(),"electron-log-preload.js");const e=`\n try {\n (${a.toString()})(require('electron'));\n } catch(e) {\n console.error(e);\n }\n `;n.writeFileSync(c,e,"utf8")}i.setPreloadFileForSessions({filePath:c,includeFutureSession:t,getSessions:e})}({getSessions:e,includeFutureSession:t,preloadOption:c}),l&&function({logger:e}){const t=["verbose","info","warning","error"];i.onEveryWebContentsEvent("console-message",((r,n,s)=>{e.processMessage({data:[s],level:t[n],variables:{processType:"renderer"}})}))}({logger:r})}catch(e){r.warn(e)}}))}}},1888:(e,t,r)=>{"use strict";const{transform:n}=r(6829);function s(e){const t=Math.abs(e);return`${e>=0?"-":"+"}${Math.floor(t/60).toString().padStart(2,"0")}:${(t%60).toString().padStart(2,"0")}`}function o({data:e,logger:t,message:r}){const{defaultLabel:n,labelLength:s}=t?.scope||{},o=e[0];let i,a=r.scope;return a||(a=n),i=""===a?s>0?"".padEnd(s+3):"":"string"==typeof a?` (${a})`.padEnd(s+3):"",e[0]=o.replace("{scope}",i),e}function i({data:e,message:t}){let r=e[0];if("string"!=typeof r)return e;r=r.replace("{level}]",`${t.level}]`.padEnd(6," "));const n=t.date||new Date;return e[0]=r.replace(/\{(\w+)}/g,((e,r)=>{switch(r){case"level":return t.level||"info";case"logId":return t.logId;case"y":return n.getFullYear().toString(10);case"m":return(n.getMonth()+1).toString(10).padStart(2,"0");case"d":return n.getDate().toString(10).padStart(2,"0");case"h":return n.getHours().toString(10).padStart(2,"0");case"i":return n.getMinutes().toString(10).padStart(2,"0");case"s":return n.getSeconds().toString(10).padStart(2,"0");case"ms":return n.getMilliseconds().toString(10).padStart(3,"0");case"z":return s(n.getTimezoneOffset());case"iso":return n.toISOString();default:return t.variables?.[r]||e}})).trim(),e}function a({data:e}){const t=e[0];if("string"!=typeof t)return e;if(t.lastIndexOf("{text}")===t.length-6)return e[0]=t.replace(/\s?{text}/,""),""===e[0]&&e.shift(),e;const r=t.split("{text}");let n=[];return""!==r[0]&&n.push(r[0]),n=n.concat(e.slice(1)),""!==r[1]&&n.push(r[1]),n}e.exports={concatFirstStringElements:function({data:e}){return"string"!=typeof e[0]||"string"!=typeof e[1]||e[0].match(/%[1cdfiOos]/)?e:[`${e[0]} ${e[1]}`,...e.slice(2)]},formatScope:o,formatText:a,formatVariables:i,timeZoneFromOffset:s,format({message:e,logger:t,transport:r,data:s=e?.data}){switch(typeof r.format){case"string":return n({message:e,logger:t,transforms:[i,o,a],transport:r,initialData:[r.format,...s]});case"function":return r.format({data:s,level:e?.level||"info",logger:t,message:e,transport:r});default:return s}}}},2189:(e,t,r)=>{"use strict";const n=r(3837);function s(e={}){const t=new WeakSet;return function(r,n){if("object"==typeof n&&null!==n){if(t.has(n))return;t.add(n)}return o(0,n,e)}}function o(e,t,r={}){const n=!1!==r?.serializeMapAndSet;return t instanceof Error?t.stack:t?"function"==typeof t?`[function] ${t.toString()}`:n&&t instanceof Map&&Object.fromEntries?Object.fromEntries(t):n&&t instanceof Set&&Array.from?Array.from(t):t:t}e.exports={serialize:o,maxDepth({data:t,transport:r,depth:n=r?.depth??6}){if(!t)return t;if(n<1)return Array.isArray(t)?"[array]":"object"==typeof t&&t?"[object]":t;if(Array.isArray(t))return t.map((t=>e.exports.maxDepth({data:t,depth:n-1})));if("object"!=typeof t)return t;if(t&&"function"==typeof t.toISOString)return t;if(null===t)return null;if(t instanceof Error)return t;const s={};for(const r in t)Object.prototype.hasOwnProperty.call(t,r)&&(s[r]=e.exports.maxDepth({data:t[r],depth:n-1}));return s},toJSON:({data:e})=>JSON.parse(JSON.stringify(e,s())),toString({data:e,transport:t}){const r=t?.inspectOptions||{},o=e.map((e=>{if(void 0!==e)try{const t=JSON.stringify(e,s()," ");return void 0===t?void 0:JSON.parse(t)}catch(t){return e}}));return n.formatWithOptions(r,...o)}}},2163:e=>{"use strict";e.exports={transformStyles:s,applyAnsiStyles:({data:e})=>s(e,r,n),removeStyles:({data:e})=>s(e,(()=>""))};const t={unset:"",black:"",red:"",green:"",yellow:"",blue:"",magenta:"",cyan:"",white:""};function r(e){const r=e.replace(/color:\s*(\w+).*/,"$1").toLowerCase();return t[r]||""}function n(e){return e+t.unset}function s(e,t,r){const n={};return e.reduce(((e,s,o,i)=>{if(n[o])return e;if("string"==typeof s){let e=o,a=!1;s=s.replace(/%[1cdfiOos]/g,(r=>{if(e+=1,"%c"!==r)return r;const o=i[e];return"string"==typeof o?(n[e]=!0,a=!0,t(o,s)):r})),a&&r&&(s=r(s))}return e.push(s),e}),[])}},6829:e=>{"use strict";e.exports={transform:function({logger:e,message:t,transport:r,initialData:n=t?.data||[],transforms:s=r?.transforms}){return s.reduce(((n,s)=>"function"==typeof s?s({data:n,logger:e,message:t,transport:r}):n),n)}}},429:(e,t,r)=>{"use strict";const{concatFirstStringElements:n,format:s}=r(1888),{maxDepth:o,toJSON:i}=r(2189),{applyAnsiStyles:a,removeStyles:c}=r(2163),{transform:l}=r(6829),u={error:console.error,warn:console.warn,info:console.info,verbose:console.info,debug:console.debug,silly:console.debug,log:console.log};e.exports=f;const d=`%c{h}:{i}:{s}.{ms}{scope}%c ${"win32"===process.platform?">":"›"} {text}`;function f(e){return Object.assign((function t(r){const n=l({logger:e,message:r,transport:t});t.writeFn({message:{...r,data:n}})}),{format:d,level:"silly",transforms:[h,s,p,n,o,i],useStyles:process.env.FORCE_STYLES,writeFn({message:e}){(u[e.level]||u.info)(...e.data)}})}function h({data:e,message:t,transport:r}){return r.format!==d?e:[`color:${m(t.level)}`,"color:unset",...e]}function p(e){const{message:t,transport:r}=e;return(function(e,t){if("boolean"==typeof e)return e;const r="error"===t||"warn"===t?process.stderr:process.stdout;return r&&r.isTTY}(r.useStyles,t.level)?a:c)(e)}function m(e){const t={error:"red",warn:"yellow",info:"cyan",default:"unset"};return t[e]||t.default}Object.assign(f,{DEFAULT_FORMAT:d})},9441:(e,t,r)=>{"use strict";const n=r(2361),s=r(7147),o=r(2037);e.exports=class extends n{asyncWriteQueue=[];bytesWritten=0;hasActiveAsyncWriting=!1;path=null;initialSize=void 0;writeOptions=null;writeAsync=!1;constructor({path:e,writeOptions:t={encoding:"utf8",flag:"a",mode:438},writeAsync:r=!1}){super(),this.path=e,this.writeOptions=t,this.writeAsync=r}get size(){return this.getSize()}clear(){try{return s.writeFileSync(this.path,"",{mode:this.writeOptions.mode,flag:"w"}),this.reset(),!0}catch(e){return"ENOENT"===e.code||(this.emit("error",e,this),!1)}}crop(e){try{const t=function(e,t){const r=Buffer.alloc(t),n=s.statSync(e),o=Math.min(n.size,t),i=Math.max(0,n.size-t),a=s.openSync(e,"r"),c=s.readSync(a,r,0,o,i);return s.closeSync(a),r.toString("utf8",0,c)}(this.path,e||4096);this.clear(),this.writeLine(`[log cropped]${o.EOL}${t}`)}catch(e){this.emit("error",new Error(`Couldn't crop file ${this.path}. ${e.message}`),this)}}getSize(){if(void 0===this.initialSize)try{const e=s.statSync(this.path);this.initialSize=e.size}catch(e){this.initialSize=0}return this.initialSize+this.bytesWritten}increaseBytesWrittenCounter(e){this.bytesWritten+=Buffer.byteLength(e,this.writeOptions.encoding)}isNull(){return!1}nextAsyncWrite(){const e=this;if(this.hasActiveAsyncWriting||0===this.asyncWriteQueue.length)return;const t=this.asyncWriteQueue.join("");this.asyncWriteQueue=[],this.hasActiveAsyncWriting=!0,s.writeFile(this.path,t,this.writeOptions,(r=>{e.hasActiveAsyncWriting=!1,r?e.emit("error",new Error(`Couldn't write to ${e.path}. ${r.message}`),this):e.increaseBytesWrittenCounter(t),e.nextAsyncWrite()}))}reset(){this.initialSize=void 0,this.bytesWritten=0}toString(){return this.path}writeLine(e){if(e+=o.EOL,this.writeAsync)return this.asyncWriteQueue.push(e),void this.nextAsyncWrite();try{s.writeFileSync(this.path,e,this.writeOptions),this.increaseBytesWrittenCounter(e)}catch(e){this.emit("error",new Error(`Couldn't write to ${this.path}. ${e.message}`),this)}}}},8126:(e,t,r)=>{"use strict";const n=r(2361),s=r(7147),o=r(1017),i=r(9441),a=r(8392);e.exports=class extends n{store={};constructor(){super(),this.emitError=this.emitError.bind(this)}provide({filePath:e,writeOptions:t,writeAsync:r=!1}){let n;try{if(e=o.resolve(e),this.store[e])return this.store[e];n=this.createFile({filePath:e,writeOptions:t,writeAsync:r})}catch(t){n=new a({path:e}),this.emitError(t,n)}return n.on("error",this.emitError),this.store[e]=n,n}createFile({filePath:e,writeOptions:t,writeAsync:r}){return this.testFileWriting(e),new i({path:e,writeOptions:t,writeAsync:r})}emitError(e,t){this.emit("error",e,t)}testFileWriting(e){s.mkdirSync(o.dirname(e),{recursive:!0}),s.writeFileSync(e,"",{flag:"a"})}}},8392:(e,t,r)=>{"use strict";const n=r(9441);e.exports=class extends n{clear(){}crop(){}getSize(){return 0}isNull(){return!0}writeLine(){}}},1688:(e,t,r)=>{"use strict";const n=r(7147),s=r(1017),o=r(2037),i=r(8126),a=r(1230),{transform:c}=r(6829),{removeStyles:l}=r(2163),{format:u}=r(1888),{toString:d}=r(2189);e.exports=function(e,t=f){let r;return t.listenerCount("error")<1&&t.on("error",((e,t)=>{p(`Can't write to ${t}`,e)})),Object.assign(i,{fileName:h(e.variables.processType),format:"[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}",getFile:m,inspectOptions:{depth:5},level:"silly",maxSize:1048576,readAllLogs:function({fileFilter:e=(e=>e.endsWith(".log"))}={}){const t=s.dirname(i.resolvePathFn(r));return n.readdirSync(t).map((e=>s.join(t,e))).filter(e).map((e=>{try{return{path:e,lines:n.readFileSync(e,"utf8").split(o.EOL)}}catch{return null}})).filter(Boolean)},sync:!0,transforms:[l,u,d],writeOptions:{flag:"a",mode:438,encoding:"utf8"},archiveLogFn(e){const t=e.toString(),r=s.parse(t);try{n.renameSync(t,s.join(r.dir,`${r.name}.old${r.ext}`))}catch(t){p("Could not rotate log",t);const r=Math.round(i.maxSize/4);e.crop(Math.min(r,262144))}},resolvePathFn:e=>s.join(e.libraryDefaultDir,e.fileName)});function i(t){const r=m(t);i.maxSize>0&&r.size>i.maxSize&&(i.archiveLogFn(r),r.reset());const n=c({logger:e,message:t,transport:i});r.writeLine(n)}function p(t,r=null,n="error"){const s=[`electron-log.transports.file: ${t}`];r&&s.push(r),e.transports.console({data:s,date:new Date,level:n})}function m(e){r||(r=Object.create(Object.prototype,{...Object.getOwnPropertyDescriptors(a.getPathVariables(process.platform)),fileName:{get:()=>i.fileName,enumerable:!0}}),"function"==typeof i.archiveLog&&(i.archiveLogFn=i.archiveLog,p("archiveLog is deprecated. Use archiveLogFn instead")),"function"==typeof i.resolvePath&&(i.resolvePathFn=i.resolvePath,p("resolvePath is deprecated. Use resolvePathFn instead")));const n=i.resolvePathFn(r,e);return t.provide({filePath:n,writeAsync:!i.sync,writeOptions:i.writeOptions})}};const f=new i;function h(e=process.type){switch(e){case"renderer":return"renderer.log";case"worker":return"worker.log";default:return"main.log"}}},2628:(e,t,r)=>{"use strict";const n=r(7147),s=r(1017);function o(...e){if(!e[0])return null;try{const t=function(e,t){let r=t;for(;;){const t=s.parse(r),o=t.root,i=t.dir;if(n.existsSync(s.join(r,e)))return s.resolve(s.join(r,e));if(r===o)return null;r=i}}("package.json",s.join(...e));if(!t)return null;const r=JSON.parse(n.readFileSync(t,"utf8")),o=r.productName||r.name;if(!o||"electron"===o.toLowerCase())return null;if(r.productName||r.name)return{name:o,version:r.version}}catch(e){return null}}e.exports={readPackageJson:function(){return o(r.c[r.s]&&r.c[r.s].filename)||o(function(){const e=process.argv.filter((e=>0===e.indexOf("--user-data-dir=")));return 0===e.length||"string"!=typeof e[0]?null:e[0].replace("--user-data-dir=","")}())||o(process.resourcesPath,"app.asar")||o(process.resourcesPath,"app")||o(process.cwd())||{name:null,version:null}},tryReadJsonAt:o}},1230:(e,t,r)=>{"use strict";const n=r(2037),s=r(1017),o=r(2674),i=r(2628);function a(e){const t=o.getPath("appData");if(t)return t;const r=c();switch(e){case"darwin":return s.join(r,"Library/Application Support");case"win32":return process.env.APPDATA||s.join(r,"AppData/Roaming");default:return process.env.XDG_CONFIG_HOME||s.join(r,".config")}}function c(){return n.homedir?n.homedir():process.env.HOME}function l(e,t){return"darwin"===e?s.join(c(),"Library/Logs",t):s.join(f(e,t),"logs")}function u(e){return"darwin"===e?s.join(c(),"Library/Logs","{appName}"):s.join(a(e),"{appName}","logs")}function d(){let e=o.getName()||"",t=o.getVersion();if("electron"===e.toLowerCase()&&(e="",t=""),e&&t)return{name:e,version:t};const r=i.readPackageJson();return e||(e=r.name),t||(t=r.version),e||(e="Electron"),{name:e,version:t}}function f(e,t){return o.getName()!==t?s.join(a(e),t):o.getPath("userData")||s.join(a(e),t)}e.exports={getAppData:a,getLibraryDefaultDir:l,getLibraryTemplate:u,getNameAndVersion:d,getPathVariables:function(e){const t=d(),r=t.name,s=t.version;return{appData:a(e),appName:r,appVersion:s,get electronDefaultDir(){return o.getPath("logs")},home:c(),libraryDefaultDir:l(e,r),libraryTemplate:u(e),temp:o.getPath("temp")||n.tmpdir(),userData:f(e,r)}},getUserData:f}},7988:(e,t,r)=>{"use strict";const n=r(3685),s=r(5687),{transform:o}=r(6829),{removeStyles:i}=r(2163),{toJSON:a,maxDepth:c}=r(2189);e.exports=function(e){return Object.assign(t,{client:{name:"electron-application"},depth:6,level:!1,requestOptions:{},transforms:[i,a,c],makeBodyFn:({message:e})=>JSON.stringify({client:t.client,data:e.data,date:e.date.getTime(),level:e.level,scope:e.scope,variables:e.variables}),processErrorFn({error:r}){e.processMessage({data:[`electron-log: can't POST ${t.url}`,r],level:"warn"},{transports:["console","file"]})},sendRequestFn({serverUrl:e,requestOptions:t,body:r}){const o=(e.startsWith("https:")?s:n).request(e,{method:"POST",...t,headers:{"Content-Type":"application/json","Content-Length":r.length,...t.headers}});return o.write(r),o.end(),o}});function t(r){if(!t.url)return;const n=t.makeBodyFn({logger:e,message:{...r,data:o({logger:e,message:r,transport:t})},transport:t}),s=t.sendRequestFn({serverUrl:t.url,requestOptions:t.requestOptions,body:Buffer.from(n,"utf8")});s.on("error",(n=>t.processErrorFn({error:n,logger:e,message:r,request:s,transport:t})))}}},9654:(e,t,r)=>{"use strict";let n={};try{n=r(2298)}catch(e){}function s({contextBridge:e,ipcRenderer:t}){if(!t)return;t.on("__ELECTRON_LOG_IPC__",((e,t)=>{window.postMessage({cmd:"message",...t})})),t.invoke("__ELECTRON_LOG__",{cmd:"getOptions"}).catch((e=>console.error(new Error(`electron-log isn't initialized in the main process. Please call log.initialize() before. ${e.message}`))));const r={sendToMain(e){try{t.send("__ELECTRON_LOG__",e)}catch(r){console.error("electronLog.sendToMain ",r,"data:",e),t.send("__ELECTRON_LOG__",{cmd:"errorHandler",error:{message:r?.message,stack:r?.stack},errorName:"sendToMain"})}},log(...e){r.sendToMain({data:e,level:"info"})}};for(const e of["error","warn","info","verbose","debug","silly"])r[e]=(...t)=>r.sendToMain({data:t,level:e});if(e&&process.contextIsolated)try{e.exposeInMainWorld("__electronLog",r)}catch{}"object"==typeof window?window.__electronLog=r:__electronLog=r}n.ipcRenderer&&s(n),e.exports=s},9618:(e,t,r)=>{"use strict";const n=r(2666),s=r(3893),o=r(6448),i=r(8671);e.exports=function(){const e=new n({allowUnknownLevel:!0,errorHandler:new s,initializeFn:()=>{},logId:"default",transportFactories:{console:o,ipc:i},variables:{processType:"renderer"}});return e.errorHandler.setOptions({logFn({error:t,errorName:r,showDialog:n}){e.transports.console({data:[r,t].filter(Boolean),level:"error"}),e.transports.ipc({cmd:"errorHandler",error:{cause:t?.cause,code:t?.code,name:t?.name,message:t?.message,stack:t?.stack},errorName:r,logId:e.logId,showDialog:n})}}),"object"==typeof window&&window.addEventListener("message",(e=>{const{cmd:t,logId:r,...s}=e.data||{},o=n.getInstance({logId:r});"message"===t&&o.processMessage(s,{transports:["console"]})})),new Proxy(e,{get:(t,r)=>void 0!==t[r]?t[r]:(...t)=>e.logData(t,{level:r})})}(),e.exports.Logger=n,e.exports.default=e.exports},3893:e=>{"use strict";const t=console.error;e.exports=class{logFn=null;onError=null;showDialog=!1;preventDefault=!0;constructor({logFn:e=null}={}){this.handleError=this.handleError.bind(this),this.handleRejection=this.handleRejection.bind(this),this.startCatching=this.startCatching.bind(this),this.logFn=e}handle(e,{logFn:r=this.logFn,errorName:n="",onError:s=this.onError,showDialog:o=this.showDialog}={}){try{!1!==s?.({error:e,errorName:n,processType:"renderer"})&&r({error:e,errorName:n,showDialog:o})}catch{t(e)}}setOptions({logFn:e,onError:t,preventDefault:r,showDialog:n}){"function"==typeof e&&(this.logFn=e),"function"==typeof t&&(this.onError=t),"boolean"==typeof r&&(this.preventDefault=r),"boolean"==typeof n&&(this.showDialog=n)}startCatching({onError:e,showDialog:t}={}){this.isActive||(this.isActive=!0,this.setOptions({onError:e,showDialog:t}),window.addEventListener("error",(e=>{this.preventDefault&&e.preventDefault?.(),this.handleError(e.error||e)})),window.addEventListener("unhandledrejection",(e=>{this.preventDefault&&e.preventDefault?.(),this.handleRejection(e.reason||e)})))}handleError(e){this.handle(e,{errorName:"Unhandled"})}handleRejection(e){const t=e instanceof Error?e:new Error(JSON.stringify(e));this.handle(t,{errorName:"Unhandled rejection"})}}},6448:e=>{"use strict";e.exports=function(e){return Object.assign(r,{format:"{h}:{i}:{s}.{ms}{scope} › {text}",formatDataFn:({data:t=[],date:n=new Date,format:s=r.format,logId:o=e.logId,scope:i=e.scopeName,...a})=>"function"==typeof s?s({...a,data:t,date:n,logId:o,scope:i}):("string"!=typeof s||(t.unshift(s),"string"==typeof t[1]&&t[1].match(/%[1cdfiOos]/)&&(t=[`${t[0]} ${t[1]}`,...t.slice(2)]),t[0]=t[0].replace(/\{(\w+)}/g,((e,t)=>{switch(t){case"level":return a.level;case"logId":return o;case"scope":return i?` (${i})`:"";case"text":return"";case"y":return n.getFullYear().toString(10);case"m":return(n.getMonth()+1).toString(10).padStart(2,"0");case"d":return n.getDate().toString(10).padStart(2,"0");case"h":return n.getHours().toString(10).padStart(2,"0");case"i":return n.getMinutes().toString(10).padStart(2,"0");case"s":return n.getSeconds().toString(10).padStart(2,"0");case"ms":return n.getMilliseconds().toString(10).padStart(3,"0");case"iso":return n.toISOString();default:return a.variables?.[t]||e}})).trim()),t),writeFn({message:{level:e,data:r}}){const n=t[e]||t.info;setTimeout((()=>n(...r)))}});function r(e){r.writeFn({message:{...e,data:r.formatDataFn(e)}})}};const t={error:console.error,warn:console.warn,info:console.info,verbose:console.info,debug:console.debug,silly:console.debug,log:console.log}},8671:e=>{"use strict";e.exports=function(e){return Object.assign(r,{depth:5,serializeFn:(e,{depth:n=5,seen:s=new WeakSet}={})=>n<1?`[${typeof e}]`:s.has(e)?e:["function","symbol"].includes(typeof e)?e.toString():Object(e)!==e?e:t.has(e.constructor)?`[${e.constructor.name}]`:Array.isArray(e)?e.map((e=>r.serializeFn(e,{depth:n-1,seen:s}))):e instanceof Error?e.stack:e instanceof Map?new Map(Array.from(e).map((([e,t])=>[r.serializeFn(e,{depth:n-1,seen:s}),r.serializeFn(t,{depth:n-1,seen:s})]))):e instanceof Set?new Set(Array.from(e).map((e=>r.serializeFn(e,{depth:n-1,seen:s})))):(s.add(e),Object.fromEntries(Object.entries(e).map((([e,t])=>[e,r.serializeFn(t,{depth:n-1,seen:s})]))))});function r(t){if(window.__electronLog)try{__electronLog.sendToMain(r.serializeFn(t,{depth:r.depth}))}catch(r){e.transports.console({data:["electronLog.transports.ipc",r,"data:",t.data],level:"error"})}else e.processMessage({data:["electron-log: logger isn't initialized in the main process"],level:"error"},{transports:["console"]})}};const t=new Set([Promise,WeakMap,WeakSet])},6143:(e,t,r)=>{"use strict";const n=r(1017),{app:s,ipcMain:o,ipcRenderer:i,shell:a}=r(2298),c=r(9658);let l=!1;const u=()=>{if(!o||!s)throw new Error("Electron Store: You need to call `.initRenderer()` from the main process.");const e={defaultCwd:s.getPath("userData"),appVersion:s.getVersion()};return l||(o.on("electron-store-get-data",(t=>{t.returnValue=e})),l=!0),e};e.exports=class extends c{constructor(e){let t,r;if(i){const e=i.sendSync("electron-store-get-data");if(!e)throw new Error("Electron Store: You need to call `.initRenderer()` from the main process.");({defaultCwd:t,appVersion:r}=e)}else o&&s&&({defaultCwd:t,appVersion:r}=u());(e={name:"config",...e}).projectVersion||(e.projectVersion=r),e.cwd?e.cwd=n.isAbsolute(e.cwd)?e.cwd:n.join(t,e.cwd):e.cwd=t,e.configName=e.name,delete e.name,super(e)}static initRenderer(){u()}openInEditor(){a.openPath(this.path)}}},1766:(e,t,r)=>{"use strict";const n=r(1017),s=r(2037),o=s.homedir(),i=s.tmpdir(),{env:a}=process,c=(e,t)=>{if("string"!=typeof e)throw new TypeError("Expected string, got "+typeof e);return(t=Object.assign({suffix:"nodejs"},t)).suffix&&(e+=`-${t.suffix}`),"darwin"===process.platform?(e=>{const t=n.join(o,"Library");return{data:n.join(t,"Application Support",e),config:n.join(t,"Preferences",e),cache:n.join(t,"Caches",e),log:n.join(t,"Logs",e),temp:n.join(i,e)}})(e):"win32"===process.platform?(e=>{const t=a.APPDATA||n.join(o,"AppData","Roaming"),r=a.LOCALAPPDATA||n.join(o,"AppData","Local");return{data:n.join(r,e,"Data"),config:n.join(t,e,"Config"),cache:n.join(r,e,"Cache"),log:n.join(r,e,"Log"),temp:n.join(i,e)}})(e):(e=>{const t=n.basename(o);return{data:n.join(a.XDG_DATA_HOME||n.join(o,".local","share"),e),config:n.join(a.XDG_CONFIG_HOME||n.join(o,".config"),e),cache:n.join(a.XDG_CACHE_HOME||n.join(o,".cache"),e),log:n.join(a.XDG_STATE_HOME||n.join(o,".local","state"),e),temp:n.join(i,t,e)}})(e)};e.exports=c,e.exports.default=c},4063:e=>{"use strict";e.exports=function e(t,r){if(t===r)return!0;if(t&&r&&"object"==typeof t&&"object"==typeof r){if(t.constructor!==r.constructor)return!1;var n,s,o;if(Array.isArray(t)){if((n=t.length)!=r.length)return!1;for(s=n;0!=s--;)if(!e(t[s],r[s]))return!1;return!0}if(t.constructor===RegExp)return t.source===r.source&&t.flags===r.flags;if(t.valueOf!==Object.prototype.valueOf)return t.valueOf()===r.valueOf();if(t.toString!==Object.prototype.toString)return t.toString()===r.toString();if((n=(o=Object.keys(t)).length)!==Object.keys(r).length)return!1;for(s=n;0!=s--;)if(!Object.prototype.hasOwnProperty.call(r,o[s]))return!1;for(s=n;0!=s--;){var i=o[s];if(!e(t[i],r[i]))return!1}return!0}return t!=t&&r!=r}},9516:(e,t,r)=>{"use strict";const n=r(1017),s=r(6401);e.exports=(e,t={})=>{const r=n.resolve(t.cwd||""),{root:o}=n.parse(r),i=[].concat(e);return new Promise((e=>{!function t(r){s(i,{cwd:r}).then((s=>{s?e(n.join(r,s)):r===o?e(null):t(n.dirname(r))}))}(r)}))},e.exports.sync=(e,t={})=>{let r=n.resolve(t.cwd||"");const{root:o}=n.parse(r),i=[].concat(e);for(;;){const e=s.sync(i,{cwd:r});if(e)return n.join(r,e);if(r===o)return null;r=n.dirname(r)}}},4290:e=>{"use strict";e.exports=e=>{const t=typeof e;return null!==e&&("object"===t||"function"===t)}},6401:(e,t,r)=>{"use strict";const n=r(1017),s=r(6789),o=r(1885);e.exports=(e,t)=>(t=Object.assign({cwd:process.cwd()},t),o(e,(e=>s(n.resolve(t.cwd,e))),t)),e.exports.sync=(e,t)=>{t=Object.assign({cwd:process.cwd()},t);for(const r of e)if(s.sync(n.resolve(t.cwd,r)))return r}},6789:(e,t,r)=>{"use strict";const n=r(7147);e.exports=e=>new Promise((t=>{n.access(e,(e=>{t(!e)}))})),e.exports.sync=e=>{try{return n.accessSync(e),!0}catch(e){return!1}}},9593:(e,t,r)=>{"use strict";const n=r(4411),s=Symbol("max"),o=Symbol("length"),i=Symbol("lengthCalculator"),a=Symbol("allowStale"),c=Symbol("maxAge"),l=Symbol("dispose"),u=Symbol("noDisposeOnSet"),d=Symbol("lruList"),f=Symbol("cache"),h=Symbol("updateAgeOnGet"),p=()=>1,m=(e,t,r)=>{const n=e[f].get(t);if(n){const t=n.value;if(y(e,t)){if(v(e,n),!e[a])return}else r&&(e[h]&&(n.value.now=Date.now()),e[d].unshiftNode(n));return t.value}},y=(e,t)=>{if(!t||!t.maxAge&&!e[c])return!1;const r=Date.now()-t.now;return t.maxAge?r>t.maxAge:e[c]&&r>e[c]},g=e=>{if(e[o]>e[s])for(let t=e[d].tail;e[o]>e[s]&&null!==t;){const r=t.prev;v(e,t),t=r}},v=(e,t)=>{if(t){const r=t.value;e[l]&&e[l](r.key,r.value),e[o]-=r.length,e[f].delete(r.key),e[d].removeNode(t)}};class w{constructor(e,t,r,n,s){this.key=e,this.value=t,this.length=r,this.now=n,this.maxAge=s||0}}const E=(e,t,r,n)=>{let s=r.value;y(e,s)&&(v(e,r),e[a]||(s=void 0)),s&&t.call(n,s.value,s.key,e)};e.exports=class{constructor(e){if("number"==typeof e&&(e={max:e}),e||(e={}),e.max&&("number"!=typeof e.max||e.max<0))throw new TypeError("max must be a non-negative number");this[s]=e.max||1/0;const t=e.length||p;if(this[i]="function"!=typeof t?p:t,this[a]=e.stale||!1,e.maxAge&&"number"!=typeof e.maxAge)throw new TypeError("maxAge must be a number");this[c]=e.maxAge||0,this[l]=e.dispose,this[u]=e.noDisposeOnSet||!1,this[h]=e.updateAgeOnGet||!1,this.reset()}set max(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[s]=e||1/0,g(this)}get max(){return this[s]}set allowStale(e){this[a]=!!e}get allowStale(){return this[a]}set maxAge(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[c]=e,g(this)}get maxAge(){return this[c]}set lengthCalculator(e){"function"!=typeof e&&(e=p),e!==this[i]&&(this[i]=e,this[o]=0,this[d].forEach((e=>{e.length=this[i](e.value,e.key),this[o]+=e.length}))),g(this)}get lengthCalculator(){return this[i]}get length(){return this[o]}get itemCount(){return this[d].length}rforEach(e,t){t=t||this;for(let r=this[d].tail;null!==r;){const n=r.prev;E(this,e,r,t),r=n}}forEach(e,t){t=t||this;for(let r=this[d].head;null!==r;){const n=r.next;E(this,e,r,t),r=n}}keys(){return this[d].toArray().map((e=>e.key))}values(){return this[d].toArray().map((e=>e.value))}reset(){this[l]&&this[d]&&this[d].length&&this[d].forEach((e=>this[l](e.key,e.value))),this[f]=new Map,this[d]=new n,this[o]=0}dump(){return this[d].map((e=>!y(this,e)&&{k:e.key,v:e.value,e:e.now+(e.maxAge||0)})).toArray().filter((e=>e))}dumpLru(){return this[d]}set(e,t,r){if((r=r||this[c])&&"number"!=typeof r)throw new TypeError("maxAge must be a number");const n=r?Date.now():0,a=this[i](t,e);if(this[f].has(e)){if(a>this[s])return v(this,this[f].get(e)),!1;const i=this[f].get(e).value;return this[l]&&(this[u]||this[l](e,i.value)),i.now=n,i.maxAge=r,i.value=t,this[o]+=a-i.length,i.length=a,this.get(e),g(this),!0}const h=new w(e,t,a,n,r);return h.length>this[s]?(this[l]&&this[l](e,t),!1):(this[o]+=h.length,this[d].unshift(h),this[f].set(e,this[d].head),g(this),!0)}has(e){if(!this[f].has(e))return!1;const t=this[f].get(e).value;return!y(this,t)}get(e){return m(this,e,!0)}peek(e){return m(this,e,!1)}pop(){const e=this[d].tail;return e?(v(this,e),e.value):null}del(e){v(this,this[f].get(e))}load(e){this.reset();const t=Date.now();for(let r=e.length-1;r>=0;r--){const n=e[r],s=n.e||0;if(0===s)this.set(n.k,n.v);else{const e=s-t;e>0&&this.set(n.k,n.v,e)}}}prune(){this[f].forEach(((e,t)=>m(this,t,!1)))}}},4341:e=>{"use strict";const t=(e,t)=>{for(const r of Reflect.ownKeys(t))Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(t,r));return e};e.exports=t,e.exports.default=t},1572:function(e,t,r){e.exports=(r(2081),r(6113),function(e){function t(n){if(r[n])return r[n].exports;var s=r[n]={exports:{},id:n,loaded:!1};return e[n].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){e.exports=r(34)},function(e,t,r){var n=r(29)("wks"),s=r(33),o=r(2).Symbol,i="function"==typeof o;(e.exports=function(e){return n[e]||(n[e]=i&&o[e]||(i?o:s)("Symbol."+e))}).store=n},function(e,t){var r=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(e,t,r){var n=r(9);e.exports=function(e){if(!n(e))throw TypeError(e+" is not an object!");return e}},function(e,t,r){e.exports=!r(24)((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},function(e,t,r){var n=r(12),s=r(17);e.exports=r(4)?function(e,t,r){return n.f(e,t,s(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t){var r=e.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(e,t,r){var n=r(14);e.exports=function(e,t,r){if(n(e),void 0===t)return e;switch(r){case 1:return function(r){return e.call(t,r)};case 2:return function(r,n){return e.call(t,r,n)};case 3:return function(r,n,s){return e.call(t,r,n,s)}}return function(){return e.apply(t,arguments)}}},function(e,t){var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){e.exports={}},function(e,t){var r={}.toString;e.exports=function(e){return r.call(e).slice(8,-1)}},function(e,t,r){var n=r(3),s=r(26),o=r(32),i=Object.defineProperty;t.f=r(4)?Object.defineProperty:function(e,t,r){if(n(e),t=o(t,!0),n(r),s)try{return i(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(e[t]=r.value),e}},function(e,t,r){var n=r(42),s=r(15);e.exports=function(e){return n(s(e))}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,r){var n=r(9),s=r(2).document,o=n(s)&&n(s.createElement);e.exports=function(e){return o?s.createElement(e):{}}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,r){var n=r(12).f,s=r(8),o=r(1)("toStringTag");e.exports=function(e,t,r){e&&!s(e=r?e:e.prototype,o)&&n(e,o,{configurable:!0,value:t})}},function(e,t,r){var n=r(29)("keys"),s=r(33);e.exports=function(e){return n[e]||(n[e]=s(e))}},function(e,t){var r=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:r)(e)}},function(e,t,r){var n=r(11),s=r(1)("toStringTag"),o="Arguments"==n(function(){return arguments}());e.exports=function(e){var t,r,i;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),s))?r:o?n(t):"Object"==(i=n(t))&&"function"==typeof t.callee?"Arguments":i}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,r){var n=r(2),s=r(6),o=r(7),i=r(5),a="prototype",c=function(e,t,r){var l,u,d,f=e&c.F,h=e&c.G,p=e&c.S,m=e&c.P,y=e&c.B,g=e&c.W,v=h?s:s[t]||(s[t]={}),w=v[a],E=h?n:p?n[t]:(n[t]||{})[a];for(l in h&&(r=t),r)(u=!f&&E&&void 0!==E[l])&&l in v||(d=u?E[l]:r[l],v[l]=h&&"function"!=typeof E[l]?r[l]:y&&u?o(d,n):g&&E[l]==d?function(e){var t=function(t,r,n){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,r)}return new e(t,r,n)}return e.apply(this,arguments)};return t[a]=e[a],t}(d):m&&"function"==typeof d?o(Function.call,d):d,m&&((v.virtual||(v.virtual={}))[l]=d,e&c.R&&w&&!w[l]&&i(w,l,d)))};c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,e.exports=c},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,r){e.exports=r(2).document&&document.documentElement},function(e,t,r){e.exports=!r(4)&&!r(24)((function(){return 7!=Object.defineProperty(r(16)("div"),"a",{get:function(){return 7}}).a}))},function(e,t,r){"use strict";var n=r(28),s=r(23),o=r(57),i=r(5),a=r(8),c=r(10),l=r(45),u=r(18),d=r(52),f=r(1)("iterator"),h=!([].keys&&"next"in[].keys()),p="keys",m="values",y=function(){return this};e.exports=function(e,t,r,g,v,w,E){l(r,t,g);var $,_,b,S=function(e){if(!h&&e in N)return N[e];switch(e){case p:case m:return function(){return new r(this,e)}}return function(){return new r(this,e)}},O=t+" Iterator",P=v==m,x=!1,N=e.prototype,I=N[f]||N["@@iterator"]||v&&N[v],j=I||S(v),R=v?P?S("entries"):j:void 0,A="Array"==t&&N.entries||I;if(A&&(b=d(A.call(new e)))!==Object.prototype&&(u(b,O,!0),n||a(b,f)||i(b,f,y)),P&&I&&I.name!==m&&(x=!0,j=function(){return I.call(this)}),n&&!E||!h&&!x&&N[f]||i(N,f,j),c[t]=j,c[O]=y,v)if($={values:P?j:S(m),keys:w?j:S(p),entries:R},E)for(_ in $)_ in N||o(N,_,$[_]);else s(s.P+s.F*(h||x),t,$);return $}},function(e,t){e.exports=!0},function(e,t,r){var n=r(2),s="__core-js_shared__",o=n[s]||(n[s]={});e.exports=function(e){return o[e]||(o[e]={})}},function(e,t,r){var n,s,o,i=r(7),a=r(41),c=r(25),l=r(16),u=r(2),d=u.process,f=u.setImmediate,h=u.clearImmediate,p=u.MessageChannel,m=0,y={},g="onreadystatechange",v=function(){var e=+this;if(y.hasOwnProperty(e)){var t=y[e];delete y[e],t()}},w=function(e){v.call(e.data)};f&&h||(f=function(e){for(var t=[],r=1;arguments.length>r;)t.push(arguments[r++]);return y[++m]=function(){a("function"==typeof e?e:Function(e),t)},n(m),m},h=function(e){delete y[e]},"process"==r(11)(d)?n=function(e){d.nextTick(i(v,e,1))}:p?(o=(s=new p).port2,s.port1.onmessage=w,n=i(o.postMessage,o,1)):u.addEventListener&&"function"==typeof postMessage&&!u.importScripts?(n=function(e){u.postMessage(e+"","*")},u.addEventListener("message",w,!1)):n=g in l("script")?function(e){c.appendChild(l("script"))[g]=function(){c.removeChild(this),v.call(e)}}:function(e){setTimeout(i(v,e,1),0)}),e.exports={set:f,clear:h}},function(e,t,r){var n=r(20),s=Math.min;e.exports=function(e){return e>0?s(n(e),9007199254740991):0}},function(e,t,r){var n=r(9);e.exports=function(e,t){if(!n(e))return e;var r,s;if(t&&"function"==typeof(r=e.toString)&&!n(s=r.call(e)))return s;if("function"==typeof(r=e.valueOf)&&!n(s=r.call(e)))return s;if(!t&&"function"==typeof(r=e.toString)&&!n(s=r.call(e)))return s;throw TypeError("Can't convert object to primitive value")}},function(e,t){var r=0,n=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++r+n).toString(36))}},function(e,t,r){"use strict";function n(e){return(0,a.createHash)("sha256").update(e).digest("hex")}function s(e){switch(c){case"darwin":return e.split("IOPlatformUUID")[1].split("\n")[0].replace(/\=|\s+|\"/gi,"").toLowerCase();case"win32":return e.toString().split("REG_SZ")[1].replace(/\r+|\n+|\s+/gi,"").toLowerCase();case"linux":case"freebsd":return e.toString().replace(/\r+|\n+|\s+/gi,"").toLowerCase();default:throw new Error("Unsupported platform: "+process.platform)}}Object.defineProperty(t,"__esModule",{value:!0});var o=function(e){return e&&e.__esModule?e:{default:e}}(r(35));t.machineIdSync=function(e){var t=s((0,i.execSync)(l[c]).toString());return e?t:n(t)},t.machineId=function(e){return new o.default((function(t,r){return(0,i.exec)(l[c],{},(function(o,i,a){if(o)return r(new Error("Error while obtaining machine id: "+o.stack));var c=s(i.toString());return t(e?c:n(c))}))}))};var i=r(70),a=r(71),c=process.platform,l={darwin:"ioreg -rd1 -c IOPlatformExpertDevice",win32:{native:"%windir%\\System32",mixed:"%windir%\\sysnative\\cmd.exe /c %windir%\\System32"}["win32"!==process.platform?"":"ia32"===process.arch&&process.env.hasOwnProperty("PROCESSOR_ARCHITEW6432")?"mixed":"native"]+"\\REG.exe QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid",linux:"( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :",freebsd:"kenv -q smbios.system.uuid || sysctl -n kern.hostuuid"}},function(e,t,r){e.exports={default:r(36),__esModule:!0}},function(e,t,r){r(66),r(68),r(69),r(67),e.exports=r(6).Promise},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t,r,n){if(!(e instanceof t)||void 0!==n&&n in e)throw TypeError(r+": incorrect invocation!");return e}},function(e,t,r){var n=r(13),s=r(31),o=r(62);e.exports=function(e){return function(t,r,i){var a,c=n(t),l=s(c.length),u=o(i,l);if(e&&r!=r){for(;l>u;)if((a=c[u++])!=a)return!0}else for(;l>u;u++)if((e||u in c)&&c[u]===r)return e||u||0;return!e&&-1}}},function(e,t,r){var n=r(7),s=r(44),o=r(43),i=r(3),a=r(31),c=r(64),l={},u={};t=e.exports=function(e,t,r,d,f){var h,p,m,y,g=f?function(){return e}:c(e),v=n(r,d,t?2:1),w=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(o(g)){for(h=a(e.length);h>w;w++)if((y=t?v(i(p=e[w])[0],p[1]):v(e[w]))===l||y===u)return y}else for(m=g.call(e);!(p=m.next()).done;)if((y=s(m,v,p.value,t))===l||y===u)return y},t.BREAK=l,t.RETURN=u},function(e,t){e.exports=function(e,t,r){var n=void 0===r;switch(t.length){case 0:return n?e():e.call(r);case 1:return n?e(t[0]):e.call(r,t[0]);case 2:return n?e(t[0],t[1]):e.call(r,t[0],t[1]);case 3:return n?e(t[0],t[1],t[2]):e.call(r,t[0],t[1],t[2]);case 4:return n?e(t[0],t[1],t[2],t[3]):e.call(r,t[0],t[1],t[2],t[3])}return e.apply(r,t)}},function(e,t,r){var n=r(11);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t,r){var n=r(10),s=r(1)("iterator"),o=Array.prototype;e.exports=function(e){return void 0!==e&&(n.Array===e||o[s]===e)}},function(e,t,r){var n=r(3);e.exports=function(e,t,r,s){try{return s?t(n(r)[0],r[1]):t(r)}catch(t){var o=e.return;throw void 0!==o&&n(o.call(e)),t}}},function(e,t,r){"use strict";var n=r(49),s=r(17),o=r(18),i={};r(5)(i,r(1)("iterator"),(function(){return this})),e.exports=function(e,t,r){e.prototype=n(i,{next:s(1,r)}),o(e,t+" Iterator")}},function(e,t,r){var n=r(1)("iterator"),s=!1;try{var o=[7][n]();o.return=function(){s=!0},Array.from(o,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!s)return!1;var r=!1;try{var o=[7],i=o[n]();i.next=function(){return{done:r=!0}},o[n]=function(){return i},e(o)}catch(e){}return r}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,r){var n=r(2),s=r(30).set,o=n.MutationObserver||n.WebKitMutationObserver,i=n.process,a=n.Promise,c="process"==r(11)(i);e.exports=function(){var e,t,r,l=function(){var n,s;for(c&&(n=i.domain)&&n.exit();e;){s=e.fn,e=e.next;try{s()}catch(n){throw e?r():t=void 0,n}}t=void 0,n&&n.enter()};if(c)r=function(){i.nextTick(l)};else if(o){var u=!0,d=document.createTextNode("");new o(l).observe(d,{characterData:!0}),r=function(){d.data=u=!u}}else if(a&&a.resolve){var f=a.resolve();r=function(){f.then(l)}}else r=function(){s.call(n,l)};return function(n){var s={fn:n,next:void 0};t&&(t.next=s),e||(e=s,r()),t=s}}},function(e,t,r){var n=r(3),s=r(50),o=r(22),i=r(19)("IE_PROTO"),a=function(){},c="prototype",l=function(){var e,t=r(16)("iframe"),n=o.length;for(t.style.display="none",r(25).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write("
================================================ FILE: chat2db-client/src/pages/login/index.less ================================================ .loginPage { width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; background-image: url('../../assets/img/login-bg.svg'); background-size: cover; background-position: center center; background-repeat: no-repeat; } .logo { position: fixed; top: 32px; left: 32px; // app-region: 'no-drag'; display: flex; align-items: center; .logoImage { width: 36px; height: 36px; margin-right: 12px; } .logoText { color: var(--color-text); font-weight: bold; font-size: 20px; } } .loginPlane { width: 360px; border: 1px solid var(--color-border); box-shadow: rgb(22 14 45 / 2%) 0px 0px 40px, rgb(22 14 45 / 6%) 0px 0px 104px; border-radius: 8px; padding: 48px; color: var(--color-text); background-color: var(--color-bg-container); margin-bottom: 48px; .loginWelcome { font-size: 24px; text-align: center; } .whyLogin { margin: 8px auto 0px; text-align: center; font-size: 12px; line-height: 24px; opacity: 0.4; cursor: pointer; max-width: fit-content; &:hover { opacity: 0.6; font-size: 14px; text-decoration: underline; } } } .defaultPasswordTips{ opacity: 0.3; text-align: center; } .loginForm { margin-top: 36px; .loginFormSubmit { margin-top: 24px; width: 100%; } } .setting { position: fixed; bottom: 32px; left: 32px; height: auto; width: auto; .settingBtn { padding: 0px 12px; font-size: 14px; color: var(--color-text); opacity: 0.8; &:hover { opacity: 1; } } } ================================================ FILE: chat2db-client/src/pages/login/index.tsx ================================================ import React, { useEffect } from 'react'; import { Button, Form, Input, Tooltip } from 'antd'; import { userLogin } from '@/service/user'; import LogoImg from '@/assets/logo/logo.png'; import styles from './index.less'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import i18n from '@/i18n'; // import { useNavigate } from 'react-router-dom'; import { logoutClearSomeLocalStorage, navigate } from '@/utils'; import { queryCurUser } from '@/store/user'; interface IFormData { userName: string; password: string; } const Login: React.FC = () => { useEffect(() => { logoutClearSomeLocalStorage(); }, []); const handleLogin = async (formData: IFormData) => { const token = await userLogin(formData); const res = await queryCurUser(); if (token && res) { navigate('/'); } }; return (
Chat2DB
{i18n('login.text.welcome')}
{i18n('login.text.tips')}
} >
{i18n('login.text.tips.title')}
{i18n('login.tips.defaultPassword')}
} className={styles.settingBtn} > {i18n('login.text.setting')} } />
); }; export default Login; ================================================ FILE: chat2db-client/src/pages/main/connection/index.less ================================================ @import '../../../styles/var.less'; .box { display: flex; background-color: var(--color-bg); width: 100%; height: 100%; } .layoutLeft { flex-shrink: 0; display: flex; flex-direction: column; width: 220px; overflow: hidden; background-color: var(--color-bg-subtle); border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; } .pageTitle { font-size: 20px; line-height: 24px; font-weight: 500; margin: 20px 0px 10px; padding-left: 20px; } .menuBox { flex: 1; overflow-y: auto; padding: 0px 8px; .menuItem { display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 8px; margin-bottom: 4px; height: 20px; border-radius: 8px; user-select: none; .menuItemsTitle { flex: 1; width: 0; display: flex; align-items: center; .name { .f-single-line(); } .envTag { flex-shrink: 0; width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-primary); margin-right: 8px; } .databaseTypeIcon { margin-right: 6px; } } .moreButton { flex-shrink: 0; display: none; transform: rotate(90deg); } &:hover { background-color: var(--color-hover-bg); .moreButton { display: block; } } } .menuItemIcon { color: var(--color-primary) !important; } .menuItemActive { color: var(--color-primary); // background-color: var(--color-primary-bg); background-color: var(--color-hover-bg); } :global { .ant-menu-inline { border-inline-end: none !important; } .ant-menu-item { padding-left: 14px !important; } } } .addConnection { margin: 0 20px 10px; } .layoutRight { flex: 1; overflow: auto; display: flex; justify-content: center; align-items: center; position: relative; } .dataBaseList { // display: flex; // justify-content: space-between; // flex-wrap: wrap; // max-width: 800px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } @media (max-width: 1000px) { .dataBaseList { grid-template-columns: repeat(2, 1fr); } } .databaseItem { min-width: 220px; border-radius: 4px; height: 50px; margin: 10px 20px; padding: 0px 16px; border-radius: 8px; overflow: hidden; box-sizing: border-box; border: 1px solid var(--color-border); .databaseItemMain { display: flex; justify-content: space-between; align-items: center; height: 50px; border-radius: 8px; } .databaseItemLeft { display: flex; align-items: center; } .databaseItemRight { display: none; i { font-size: 16px; } } &:hover { background-color: var(--color-bg-medium); color: var(--color-primary); border: 1px solid var(--color-primary); cursor: pointer; .databaseItemRight { display: block; i { color: var(--color-primary); } } } .logoBox { display: flex; justify-content: center; align-items: center; height: 28px; width: 28px; border-radius: 8px; margin-right: 16px; i { font-size: 16px; } } } .databaseItemSpacer { flex-grow: 1; width: 210px; margin: 0px 20px; padding: 0px 16px; box-sizing: border-box; } .createConnections { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1; background-color: var(--color-bg); overflow-y: auto; transform: scale(0.2); transition: 0.1s ease-in-out; } .showCreateConnections { z-index: 1; transform: scale(1); transition: transform 0.3s ease-in-out; } .envLabel { display: flex; justify-content: space-between; line-height: 30px; padding: 4px; font-size: 14px; } .envRefreshBox { &:hover { cursor: pointer; color: var(--color-primary); } } ================================================ FILE: chat2db-client/src/pages/main/connection/index.tsx ================================================ import React, { useRef, useState, Fragment, useEffect } from 'react'; import { Button, Dropdown } from 'antd'; import classnames from 'classnames'; import i18n from '@/i18n'; // import RefreshLoadingButton from '@/components/RefreshLoadingButton'; // ----- services ----- import connectionService from '@/service/connection'; // ----- constants/typings ----- import { databaseMap } from '@/constants'; import { IConnectionDetails, IConnectionListItem } from '@/typings'; // ----- components ----- import CreateConnection from '@/blocks/CreateConnection'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import MenuLabel from '@/components/MenuLabel'; // ----- hooks ----- import useClickAndDoubleClick from '@/hooks/useClickAndDoubleClick'; // ----- store ----- import { useConnectionStore, getConnectionList } from '@/pages/main/store/connection'; import { setMainPageActiveTab } from '@/pages/main/store/main'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; import { getOpenConsoleList } from '@/pages/main/workspace/store/console'; import styles from './index.less'; const ConnectionsPage = () => { const { connectionList } = useConnectionStore((state) => { return { connectionList: state.connectionList, }; }); const volatileRef = useRef(); const [connectionActiveId, setConnectionActiveId] = useState(null); const [connectionDetail, setConnectionDetail] = useState(null); // 处理列表单击事件 const handleMenuItemSingleClick = (t: IConnectionListItem) => { if (connectionActiveId !== t.id) { setConnectionActiveId(t.id); } }; // 处理列表双击事件 const handleMenuItemDoubleClick = (t: IConnectionListItem) => { setCurrentConnectionDetails(t); setMainPageActiveTab('workspace'); }; // 处理列表单击和双击事件 const handleClickConnectionMenu = useClickAndDoubleClick(handleMenuItemSingleClick, handleMenuItemDoubleClick); // 切换连接的详情 useEffect(() => { if (!connectionActiveId) { return; } setConnectionDetail(undefined); connectionService .getDetails({ id: connectionActiveId }) .then((res) => { setConnectionDetail(res); }) .catch(() => { setConnectionActiveId(null); }); }, [connectionActiveId]); // const createDropdownItems = (t) => { const handelDelete = (e) => { // 禁止冒泡到menuItem e.domEvent?.stopPropagation?.(); connectionService.remove({ id: t.id }).then(() => { getConnectionList().then(() => { // 连接删除后需要更新下 consoleList getOpenConsoleList(); }); if (connectionActiveId === t.id) { setConnectionActiveId(null); setConnectionDetail(null); } }); }; const enterWorkSpace = (e) => { e.domEvent?.stopPropagation?.(); handleMenuItemDoubleClick(t); }; const copyConnection = (e) => { e.domEvent?.stopPropagation?.(); connectionService.clone({ id: t.id }).then((res) => { getConnectionList(); setConnectionActiveId(res); }); } return [ { key: 'enterWorkSpace', label: , onClick: enterWorkSpace, }, { key: 'copyConnection', label: , onClick: copyConnection, }, { key: 'delete', label: , onClick: handelDelete, }, ]; }; const renderConnectionMenuList = () => { return connectionList?.map((t) => { return (
{ handleClickConnectionMenu(t); }} >
{} {t.alias} {/* {t.environment.shortName} */}
); }); }; const onSubmit = (data) => { return connectionService .save({ ...data, }) .then((res) => { getConnectionList(); setConnectionActiveId(res); }); }; return ( <>
{i18n('connection.title.connections')}
{renderConnectionMenuList()}
{connectionActiveId && ( )}
); }; export default ConnectionsPage; ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/bar/index.less ================================================ ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx ================================================ import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import * as charts from 'echarts'; import ReactEcharts from 'echarts-for-react'; import './index.less'; type EChartsOption = charts.EChartsOption; interface IProps { data?: { xAxis: string[]; yAxis: any[]; }; } const BarChart = (props: IProps, ref) => { const barRef = useRef(null); const option: EChartsOption = useMemo( () => ({ xAxis: { type: 'category', data: props?.data?.xAxis ?? [], }, yAxis: { type: 'value', }, series: [ { data: props?.data?.yAxis ?? [], type: 'bar', }, ], }), [props.data], ); useImperativeHandle(ref, () => ({ getEchartsInstance: () => barRef.current.getEchartsInstance(), })); return ; }; export default forwardRef(BarChart); ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/line/index.less ================================================ ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/line/index.tsx ================================================ import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import * as charts from 'echarts'; import ReactEcharts from 'echarts-for-react'; import './index.less'; type EChartsOption = charts.EChartsOption; interface IProps { data: { xAxis: string[]; yAxis: any[]; }; } const LineChart = (props: IProps, ref) => { const lineRef = useRef(null); const option: EChartsOption = useMemo( () => ({ xAxis: { type: 'category', data: props?.data?.xAxis ?? [], }, yAxis: { type: 'value', }, series: [ { data: props?.data?.yAxis ?? [], type: 'line', }, ], tooltip: { trigger: 'axis', }, }), [props.data], ); useImperativeHandle(ref, () => ({ getEchartsInstance: () => lineRef.current.getEchartsInstance(), })); return ; }; export default forwardRef(LineChart); ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/pie/index.less ================================================ ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx ================================================ import React, { ForwardedRef, forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import * as charts from 'echarts'; import ReactEcharts from 'echarts-for-react'; import './index.less'; type EChartsOption = charts.EChartsOption; interface IProps { data: Array<{ value: number; name: string }>; } const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Function }>) => { const pieRef = useRef(null); const option: EChartsOption = useMemo( () => ({ tooltip: { trigger: 'item', }, legend: { orient: 'horizontal', align: 'auto', type: 'scroll', //分页类型 }, series: [ { type: 'pie', radius: ['40%', '70%'], data: props.data, // label: { // show: false, // position: 'center' // }, emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold', }, }, }, ], }), [props.data], ); useImperativeHandle(ref, () => ({ getEchartsInstance: () => pieRef.current.getEchartsInstance(), })); return ; }; export default forwardRef(PieChart); ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart-item/index.less ================================================ .container { flex: 1; position: relative; border: 1px solid var(--color-border); border-radius: var(--border-radius-l-g); padding: 16px; background-color: var(--color-gray-200); margin-bottom: 20px; } .titleBar { display: flex; justify-content: space-between; line-height: 20px; font-size: 14px; } .edit { cursor: pointer; } .left_overlay_add { position: absolute; top: 0; left: -8px; width: 16px; height: 100%; cursor: pointer; } .right_overlay_add { position: absolute; top: 0; right: -8px; width: 16px; height: 100%; cursor: pointer; } .top_overlay_add { position: absolute; left: 0; top: -8px; height: 16px; width: 100%; cursor: pointer; } .bottom_overlay_add { position: absolute; left: 0; bottom: -8px; height: 16px; width: 100%; cursor: pointer; } .left_overlay_add:hover .add_chart_icon, .right_overlay_add:hover .add_chart_icon, .top_overlay_add:hover .add_chart_icon, .bottom_overlay_add:hover .add_chart_icon { opacity: 1; } .add_chart_icon { opacity: 0; width: 16px; height: 16px; background-color: var(--color-primary-400); position: absolute; left: 0; top: 50%; transform: translateY(-50%); transition: opacity 0.3s; border-radius: 999px; } .add_chart_icon_y { left: 50%; transform: translate(-50%, -50%); } .add_chart_plus_icon { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 16px; height: 16px; filter: var(--filter-color-gray-100); } .emptyChartBlock { width: 100%; padding: 20px 0; display: flex; flex-direction: column; justify-content: center; align-items: center; } .emptyDataImage { font-size: 56px; color: #666666; margin-bottom: 16px; } .emptyDataText { font-size: 12px; color: var(--color-gray-300); line-height: 14px; margin-bottom: 40px; } .editBlock { border-radius: 4px; // background-color: var(--color-bg-subtle); max-height: 1000px; } .editorBlock { display: flex; // flex-direction: column; flex-wrap: wrap; } .editor { flex: 2; min-width: 320px; min-height: 320px; display: flex; flex-direction: column; position: relative; border: 1px solid var(--color-border-secondary); overflow: hidden; } .dataSourceSelect { position: absolute; bottom: 8px; right: 24px; } .chartParamsForm { border: 1px solid var(--color-border); border-left: 1px solid var(--color-border); color: var(--color-text); flex: 1; min-width: 120px; display: flex; flex-direction: column; justify-content: start; align-items: start; padding: 24px; } .chartParamsFormTitle { font-size: 14px; font-weight: bold; line-height: 14px; margin-bottom: 24px; } .editorOptionBlock { display: flex; justify-content: flex-end; padding: 20px 0px 0px 0px; } ================================================ FILE: chat2db-client/src/pages/main/dashboard/chart-item/index.tsx ================================================ import { IChartItem, IChartType, IConnectionDetails } from '@/typings'; import React, { useEffect, useRef, useState, useMemo } from 'react'; import styles from './index.less'; import addImage from '@/assets/img/add.svg'; import cs from 'classnames'; import Line from '../chart/line'; import Pie from '../chart/pie'; import Bar from '../chart/bar'; import { MoreOutlined } from '@ant-design/icons'; import { Button, Dropdown, Form, message, Select, Spin } from 'antd'; import { deleteChart, getChartById, updateChart } from '@/service/dashboard'; import ConsoleEditor from '@/components/ConsoleEditor'; import Iconfont from '@/components/Iconfont'; import sqlService, { IExecuteSqlParams } from '@/service/sql'; import { Option } from '@/typings/common'; import { handleDatabaseAndSchema } from '@/utils/database'; import i18n from '@/i18n'; import { isValid } from '@/utils/check'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; const mockData = headerList?.reduce((acc, cur, index) => { acc[cur.name] = { ...cur, data: dataList?.map((i: any) => i[index]), }; return acc; }, {}); return mockData; }; function countArrayElements(arr: T[]): { name: T; value: number }[] { const counts = new Map(); // 统计每个元素出现的次数 arr.forEach((item) => { if (counts.has(item)) { counts.set(item, counts.get(item)! + 1); } else { counts.set(item, 1); } }); // 转换为数组形式 const result: { name: T; value: number }[] = []; for (const [key, value] of counts.entries()) { result.push({ name: key, value }); } return result; } interface IChartItemProps { id: number; isEditing?: boolean; canAddRowItem: boolean; connectionList: any[]; remainingUse: any; onDelete?: (id: number) => void; addChartTop?: () => void; addChartBottom?: () => void; addChartLeft?: () => void; addChartRight?: () => void; } function ChartItem(props: IChartItemProps) { const { connectionList, id } = props; const [cascaderOption, setCascaderOption] = useState([]); const [curConnection, setCurConnection] = useState(); const [chartData, setChartData] = useState({}); const [chartMetaData, setChartMetaData] = useState(); const [, setCascaderValue] = useState<(string | number)[]>([]); const [isEditing, setIsEditing] = useState(props.isEditing ?? false); const [isLoading, setIsLoading] = useState(false); const [initDDL, setInitDDL] = useState(''); const [form] = Form.useForm(); // 创建一个表单实例 const chartRef = useRef(); useEffect(() => { if (id !== undefined) { queryChartData(); } }, [id]); useEffect(() => { if (connectionList && connectionList.length > 0) { setCurConnection(connectionList[0]); setCascaderOption( (connectionList || []).map((c) => ({ value: c.id, label: c.alias, isLeaf: false, })), ); } }, [connectionList]); useEffect(() => { if (!curConnection) { return; } setChartData({ ...chartData, dataSourceId: curConnection.id, type: curConnection.type, }); queryDatabaseAndSchemaList(curConnection.id); }, [curConnection]); useEffect(() => { handleChartConfigChange(); }, [chartData.sqlData]); const queryDatabaseAndSchemaList = async (dataSourceId: number) => { const res = await sqlService.getDatabaseSchemaList({ dataSourceId }); const dataSource = (cascaderOption || []).find((c) => c.value === dataSourceId); if (!dataSource) return; dataSource.children = handleDatabaseAndSchema(res); setCascaderOption([...cascaderOption]); }; const handleExecuteSQL = async (sql: string, _chartData: IChartItem) => { const { dataSourceId, databaseName, schemaName } = _chartData; if (!isValid(dataSourceId)) { message.success(i18n('dashboard.editor.execute.noDataSource')); return; } setIsLoading(true); try { const executeSQLParams: IExecuteSqlParams = { sql, dataSourceId, databaseName, schemaName }; // 获取当前SQL的查询结果 const sqlResult = await sqlService.executeSql(executeSQLParams); let sqlData; if (sqlResult && sqlResult[0]) { sqlData = handleSQLResult2ChartData(sqlResult[0]); } setChartData({ ..._chartData, ddl: sql, sqlData, }); message.success(i18n('dashboard.editor.execute.success')); } finally { setIsLoading(false); } }; /** 根据id请求Chart数据 */ const queryChartData = async () => { setIsLoading(true); const res = await getChartById({ id: props.id }); if (!res.dataSourceId) { res.connectable = undefined; } setChartData(res); // 设置级联value const cascaderKey = ['dataSourceId', 'databaseName', 'schemaName']; const _cascaderValue = cascaderKey.map((k: string) => res[k]).filter((i) => !!i); setCascaderValue(_cascaderValue); // 设置Chart参数,eg ChartType、xAxis、yAxis const formValue = JSON.parse(res.schema || '{}'); form.setFieldsValue(formValue); if (res.ddl && res.connectable) { setInitDDL(res.ddl); handleExecuteSQL(res.ddl, res); // let p: IExecuteSqlParams = { // sql: res?.ddl ?? '', // dataSourceId, // databaseName, // }; // sqlService.executeSql(p).then((result) => { // let sqlData; // if (result && result[0]) { // sqlData = handleSQLResult2ChartData(result[0]); // } // setChartData({ // ...res, // ...chartData, // sqlData, // }); // }); } setIsLoading(false); }; const onExport2Image = () => { const echartInstance = chartRef.current.getEchartsInstance(); const img = new Image(); img.src = echartInstance.getDataURL({ type: 'png', devicePixelRatio: 4, backgroundColor: '#FFF', }); img.onload = function () { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0); const dataURL = canvas.toDataURL('image/png'); const a = document.createElement('a'); const event = new MouseEvent('click'); a.download = 'image.png'; a.href = dataURL; a.dispatchEvent(event); }; }; const onDeleteChart = () => { deleteChart({ id: props.id }); props.onDelete && props.onDelete(props.id); }; const handleSaveChart = async () => { const params: IChartItem = { id: props.id, ...chartData, schema: JSON.stringify(form.getFieldsValue(true)), }; await updateChart(params); setIsEditing(false); message.success(i18n('common.tips.saveSuccessfully')); }; const handleChartConfigChange = () => { const { sqlData = {} } = chartData || {}; const { chartType, xAxis, yAxis } = form.getFieldsValue(true); // let xAxisOptions: Array<{ label: string; value: string }> = []; // let yAxisOptions: Array<{ label: string; value: string }> = []; if (chartType === IChartType.Pie) { const dimension = sqlData[xAxis]; const { data = [] } = dimension || {}; const finallyData = countArrayElements(data); setChartMetaData(finallyData); } else if (chartType === IChartType.Line) { const dimensionX = sqlData[xAxis]?.data; const dimensionY = sqlData[yAxis]?.data; setChartMetaData({ xAxis: dimensionX, yAxis: dimensionY, }); } else if (chartType === IChartType.Column) { const dimensionX = sqlData[xAxis]?.data; const dimensionY = sqlData[yAxis]?.data; setChartMetaData({ xAxis: dimensionX, yAxis: dimensionY, }); } }; const renderPlusIcon = () => { return ( <> {props.canAddRowItem && (
Add chart
)} {props.canAddRowItem && (
Add chart
)}
Add chart
Add chart
); }; const renderChart = () => { // const { chartType } = chartData || {}; // const { chartType } = JSON.parse(schema) || {}; const { chartType } = form.getFieldsValue(true); switch (chartType) { case IChartType.Pie: return ; case IChartType.Line: return ; case IChartType.Column: return ; default: return (
); } }; const renderEmptyBlock = () => { return (
No data selected
); }; const initDDLMemo = useMemo(() => { return { text: initDDL, range: 'front', }; }, [initDDL]); const setBoundInfo = (boundInfo) => { setChartData({ ...chartData, ...boundInfo, }); }; const renderEditorBlock = () => { const { sqlData = {} } = chartData || {}; const options = Object.keys(sqlData).map((i) => ({ label: i, value: i })); return (
handleExecuteSQL(sql, chartData)} editorOptions={{ lineNumbers: 'off', }} isActive={true} /> {/* { const p: any = { dataSourceId: '', }; //包含了dataSourceId、databaseName、schemaName (selectedOptions || []).forEach((o: any) => { if (o.type) { p[`${o.type}Name`] = o.value; } else { p.dataSourceId = o.value; } }); setCascaderValue(value); setChartData({ ...chartData, ...p, }); }} className={styles.dataSourceSelect} placeholder={i18n('dashboard.editor.cascader.placeholder')} // style={{ width: '100%' }} /> */}
Charts:
); } export default Chart; ================================================ FILE: chat2db-client/src/pages/main/dashboard/left-block/index.less ================================================ ================================================ FILE: chat2db-client/src/pages/main/dashboard/left-block/index.tsx ================================================ ================================================ FILE: chat2db-client/src/pages/main/functions/getConnection.ts ================================================ import connectionService from '@/service/connection'; import { setConnectionEnvList } from '@/pages/main/store/connection'; const getConnectionEnvList = () => { connectionService.getEnvList().then((res) => { setConnectionEnvList(res); }); }; export default getConnectionEnvList; ================================================ FILE: chat2db-client/src/pages/main/index.less ================================================ .page { display: flex; position: absolute; top: 0; right: 0; left: 0; bottom: 0; } .layoutLeft { position: relative; flex-shrink: 0; display: flex; flex-direction: column; justify-content: space-between; align-items: center; width: 52px; padding-top: 10px; background-color: var(--color-bg-subtle); border-right: 1px solid var(--color-border-secondary); user-select: none; overflow: hidden; } .dargBox { position: absolute; top: 0; left: 0; right: 0; bottom: 0; -webkit-app-region: drag; //set electron this region Can drag z-index: -10; pointer-events: none; } .brandLogo { flex-shrink: 0; overflow: hidden; margin-bottom: 20px; margin-top: 10px; cursor: pointer; } .navList { flex: 1; width: 100%; display: flex; flex-direction: column; align-items: center; li { display: flex; justify-content: center; align-items: center; align-items: center; margin-bottom: 16px; width: 40px; height: 40px; border-radius: 8px; font-size: 12px; color: var(--color-text); cursor: pointer; .icon { color: var(--custom-color-icon); } &:last-of-type { margin-bottom: 0px; } &:hover { .icon { color: var(--color-primary); } background-color: var(--color-hover-bg); } } .activeNav { .icon { color: var(--color-primary); } background-color: var(--color-hover-bg); } } .footer { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; margin-bottom: 20px; .userBox { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; margin-bottom: 6px; .questionIcon { font-size: 20px; cursor: pointer; transform: translateX(1px); color: var(--custom-color-icon); } &:hover .questionIcon { color: var(--color-primary); } } .rocketIcon{ font-size: 20px; cursor: pointer; color: var(--custom-color-icon); cursor: pointer; margin-bottom: 12px; &:hover { color: var(--color-primary); } } } .userDropdown { display: flex; align-items: center; i { margin-right: 6px; } } .layoutRight { flex: 1; overflow: hidden; } .main { position: relative; min-height: 100vh; } .componentBox { position: relative; width: 100%; height: 100%; } ================================================ FILE: chat2db-client/src/pages/main/index.tsx ================================================ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button, Dropdown, Tooltip } from 'antd'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; import i18n from '@/i18n'; import { userLogout } from '@/service/user'; import { INavItem } from '@/typings/main'; import { IRole } from '@/typings/user'; // ----- hooks ----- import getConnectionEnvList from './functions/getConnection'; // ----- store ----- import { useMainStore, setMainPageActiveTab } from '@/pages/main/store/main'; import { getConnectionList } from '@/pages/main/store/connection'; import { useUserStore, setCurUser } from '@/store/user'; import { setAppTitleBarRightComponent } from '@/store/common/appTitleBarConfig'; // ----- component ----- import CustomLayout from '@/components/CustomLayout'; // ----- block ----- import Workspace from './workspace'; import Dashboard from './dashboard'; import Connection from './connection'; import Team from './team'; import Setting from '@/blocks/Setting'; import styles from './index.less'; import { useUpdateEffect } from '@/hooks'; import { getLinkBasedOnTimezone } from '@/utils/timezone'; import { RocketIcon } from 'lucide-react'; const initNavConfig: INavItem[] = [ { key: 'workspace', icon: '\ue616', iconFontSize: 16, isLoad: false, component: , name: i18n('workspace.title'), }, { key: 'dashboard', icon: '\ue629', iconFontSize: 24, isLoad: false, component: , name: i18n('dashboard.title'), }, { key: 'connections', icon: '\ue622', iconFontSize: 20, isLoad: false, component: , name: i18n('connection.title'), }, { key: 'github', icon: '\ue885', iconFontSize: 26, isLoad: false, openBrowser: 'https://github.com/chat2db/Chat2DB/', name: 'Github', }, ]; function MainPage() { const navigate = useNavigate(); const { userInfo } = useUserStore((state) => { return { userInfo: state.curUser, }; }); const [navConfig, setNavConfig] = useState(initNavConfig); const mainPageActiveTab = useMainStore((state) => state.mainPageActiveTab); const [activeNavKey, setActiveNavKey] = useState( __ENV__ === 'desktop' ? mainPageActiveTab : window.location.pathname.split('/')[1] || mainPageActiveTab, ); const isMac = useMemo(() => { return window.electronApi?.getPlatform().isMac; }, []); // 当页面在workspace时,显示自定义布局 useEffect(() => { if (mainPageActiveTab === 'workspace') { setAppTitleBarRightComponent(); } else { setAppTitleBarRightComponent(false); } return () => { setAppTitleBarRightComponent(false); }; }, [mainPageActiveTab]); useEffect(() => { handleInitPage(); getConnectionList(); getConnectionEnvList(); }, []); useUpdateEffect(() => { switchingNav(mainPageActiveTab); }, [mainPageActiveTab]); // 切换tab useEffect(() => { // 获取当前地址栏的tab const activeIndex = navConfig.findIndex((t) => `${t.key}` === activeNavKey); if (activeIndex > -1) { navConfig[activeIndex].isLoad = true; setNavConfig([...navConfig]); if (__ENV__ !== 'desktop') { const href = window.location.origin + '/' + activeNavKey; window.history.pushState({}, '', href); } } }, [activeNavKey]); const handleInitPage = async () => { const cloneNavConfig = [...navConfig]; if (userInfo) { const hasTeamIcon = cloneNavConfig.find((i) => i.key === 'team'); if (userInfo.admin && !hasTeamIcon) { cloneNavConfig.splice(3, 0, { key: 'team', icon: '\ue64b', iconFontSize: 24, isLoad: activeNavKey === 'team', // 如果当前是team,直接加载 component: , name: i18n('team.title'), }); } if (!userInfo.admin && hasTeamIcon) { cloneNavConfig.splice(3, 1); } } setNavConfig([...cloneNavConfig]); }; const switchingNav = (key: string) => { if (key === 'github') { window.open('https://github.com/chat2db/Chat2DB/', '_blank'); } else { setActiveNavKey(key); setMainPageActiveTab(key); } }; const handleLogout = () => { userLogout().then(() => { setCurUser(undefined); navigate('/login'); }); }; const renderUser = () => { return ( {i18n('login.text.logout')}
), }, ], }} placement="bottomRight" trigger={['click']} >
); }; return (
{isMac === void 0 && }
    {navConfig.map((item) => { return (
  • switchingNav(item.key)} >
  • ); })}
{ const link = getLinkBasedOnTimezone(); window.open(link, '_blank'); }} /> {/* {userInfo?.roleCode !== IRole.DESKTOP ? renderUser() : null} */}
{navConfig.map((item) => { return ( ); })}
); } export default MainPage; ================================================ FILE: chat2db-client/src/pages/main/store/connection/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; import connectionService from '@/service/connection'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; export interface IConnectionStore { connectionList: IConnectionListItem[] | null; connectionEnvList: IConnectionEnv[] | null; } export const initConnectionStore = { connectionList: null, connectionEnvList: null, }; export const useConnectionStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => initConnectionStore), shallow, ); export const setConnectionList = (connectionList: IConnectionListItem[]) => { return useConnectionStore.setState({ connectionList }); }; export const setConnectionEnvList = (connectionEnvList: IConnectionEnv[]) => { return useConnectionStore.setState({ connectionEnvList }); }; export const getConnectionList: () => Promise = () => { return new Promise((resolve, reject) => { const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; connectionService .getList({ pageNo: 1, pageSize: 1000, refresh: true, }) .then((res) => { const connectionList = res?.data || []; useConnectionStore.setState({ connectionList }); resolve(connectionList); // 如果连接列表为空,则设置当前连接为空 if (connectionList.length === 0) { setCurrentConnectionDetails(null); return; } // 如果当前连接不存在,则设置当前连接为第一个连接 if (!currentConnectionDetails?.id) { setCurrentConnectionDetails(connectionList[0]); return; } // 如果存在但是不在列表中,则设置当前连接为第一个连接 const currentConnection = connectionList.find((item) => item.id === currentConnectionDetails?.id); if (!currentConnection) { setCurrentConnectionDetails(connectionList[0]); } }) .catch(() => { useConnectionStore.setState({ connectionList: [] }); reject([]); }); }); }; ================================================ FILE: chat2db-client/src/pages/main/store/main/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools, persist } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; export interface IMainStore { mainPageActiveTab: string; } const initMainStore = { mainPageActiveTab: 'connections', }; export const useMainStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( persist(() => initMainStore, { name: 'main-page-store', getStorage: () => localStorage, // 工作区的状态只保存 layout布局信息 partialize: (state: IMainStore) => ({ mainPageActiveTab: state.mainPageActiveTab, }), }), ), shallow, ); export const setMainPageActiveTab = (mainPageActiveTab: string) => { return useMainStore.setState({ mainPageActiveTab, }); }; ================================================ FILE: chat2db-client/src/pages/main/store/monaco/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; // 表信息 export interface tableItem { columnList: { columnName?: string; columnType?: string; }[]; } // schema信息 export interface schemaItem { tableList?: tableItem[]; } // 数据库信息 export type databaseItem = { schemaList?: schemaItem[]; } | { databaseName: string; tableList: tableItem[]; } // monaco store export interface IMonacoStore { registerProvider: { // 数据源id [key: number]: databaseItem[] } } const initMonacoStore = { registerProvider: {} } export const useMonacoStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => (initMonacoStore)), shallow ); export const setRegisterProvider = (id: number, data: databaseItem[]) => { useMonacoStore.getState().registerProvider[id] = data; } ================================================ FILE: chat2db-client/src/pages/main/team/datasource-management/index.less ================================================ .tableTop { display: flex; justify-content: space-between; align-items: center; margin: 16px 0; } ================================================ FILE: chat2db-client/src/pages/main/team/datasource-management/index.tsx ================================================ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Button, Input, Table, Popconfirm, message, Drawer } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import ConnectionServer from '@/service/connection'; import { createDataSource, deleteDataSource, getDataSourceList, updateDataSource } from '@/service/team'; import { IConnectionDetails } from '@/typings'; import { AffiliationType, IDataSourceVO } from '@/typings/team'; import i18n from '@/i18n'; import { isValid } from '@/utils/check'; import CreateConnection from '@/blocks/CreateConnection'; import UniversalDrawer from '../universal-drawer'; import { isNumber } from 'lodash'; import styles from './index.less'; function DataSourceManagement() { const [dataSource, setDataSource] = useState([]); const [pagination, setPagination] = useState({ searchKey: '', current: 1, pageSize: 10, total: 0, showSizeChanger: true, showQuickJumper: true, // pageSizeOptions: ['10', '20', '30', '40'], }); const [showCreateConnection, setShowCreateConnection] = useState(false); const connectionInfo = useRef(null); const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: AffiliationType; id?: number }>({ open: false, type: AffiliationType['DATASOURCE_USER/TEAM'], }); const columns = useMemo( () => [ { title: i18n('team.datasource.alias'), dataIndex: 'alias', key: 'alias', }, { title: i18n('team.datasource.url'), dataIndex: 'url', key: 'url', }, { title: i18n('common.text.action'), key: 'action', width: 300, render: (_: any, record: IDataSourceVO) => ( <> handleDelete(record.id)} okText={i18n('common.button.affirm')} cancelText={i18n('common.button.cancel')} > e.preventDefault()}> {i18n('common.button.delete')} ), }, ], [], ); useEffect(() => { queryDataSourceList(); }, [pagination.current, pagination.pageSize, pagination.searchKey]); const queryDataSourceList = async () => { const { searchKey, current: pageNo, pageSize } = pagination; const res = await getDataSourceList({ searchKey, pageNo, pageSize }); if (res) { setDataSource(res?.data ?? []); setPagination({ ...pagination, total: res?.total ?? 0, } as any); } }; const handleSearch = (searchKey: string) => { setPagination({ ...pagination, searchKey, }); }; const handleTableChange = (p: any) => { setPagination({ ...pagination, ...p, }); }; const handleAddDataSource = () => { connectionInfo.current = null; setShowCreateConnection(true); }; const handleEdit = async (record: IDataSourceVO) => { const { id } = record; if (!id) { return; } const detail = await ConnectionServer.getDetails({ id }); connectionInfo.current = detail; setShowCreateConnection(true); }; const handleDelete = async (id?: number) => { if (isNumber(id)) { await deleteDataSource({ id }); message.success(i18n('common.text.successfullyDelete')); queryDataSourceList(); } }; const handleConfirmConnection = (data: IConnectionDetails) => { if (JSON.stringify(connectionInfo.current) === '{}') { return new Promise((resolve) => { resolve(true); }); } connectionInfo.current = data; const isUpdate = isValid(connectionInfo?.current?.id); const requestApi = isUpdate ? updateDataSource : createDataSource; return requestApi({ ...connectionInfo.current }).then(() => { message.success(isUpdate ? i18n('common.tips.updateSuccess') : i18n('common.tips.createSuccess')); setShowCreateConnection(false); queryDataSourceList(); }); }; return (
} />
setShowCreateConnection(false)} > { setDrawerInfo({ ...drawerInfo, open: false, }); }} /> ); } export default DataSourceManagement; ================================================ FILE: chat2db-client/src/pages/main/team/index.less ================================================ .teamWrapper { height: 100vh; padding: 14px 16px; box-sizing: border-box; } .teamTabsBox{ height: 100%; } ================================================ FILE: chat2db-client/src/pages/main/team/index.tsx ================================================ import React, { useMemo, useState } from 'react'; import { ApiOutlined, UserOutlined, TeamOutlined } from '@ant-design/icons'; import { Tabs } from 'antd'; import DataSourceManagement from './datasource-management'; import UserManagement from './user-management'; import TeamManagement from './team-management'; import i18n from '@/i18n'; import styles from './index.less'; const Team = () => { const [activeKey, setActiveKey] = useState('0'); const tabList = useMemo( () => [ { label: i18n('team.tab.datasource'), icon: , children: , }, { label: i18n('team.tab.user'), icon: , children: , }, { label: i18n('team.tab.team'), icon: , children: , }, ], [], ); return (
setActiveKey(_activeKey)} items={tabList.map((tab, index) => { return { key: String(index), label: ( {tab.icon} {tab.label} ), children: tab.children, }; })} />
); }; export default Team; ================================================ FILE: chat2db-client/src/pages/main/team/team-management/index.less ================================================ .tableTop { display: flex; justify-content: space-between; align-items: center; margin: 16px 0; } ================================================ FILE: chat2db-client/src/pages/main/team/team-management/index.tsx ================================================ import React, { useEffect, useMemo, useState } from 'react'; import { createTeam, deleteTeam, getTeamManagementList, updateTeam } from '@/service/team'; import { Button, Form, Input, Modal, Popconfirm, Radio, Table, Tag, message } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import UniversalDrawer from '../universal-drawer'; import { AffiliationType, ITeamVO, StatusType } from '@/typings/team'; import i18n from '@/i18n'; import styles from './index.less'; const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 }, colon: false, }; const requireRule = { required: true, message: i18n('common.form.error.required') }; function TeamManagement() { const [form] = Form.useForm(); const [loadding, setLoading] = useState(false); const [dataSource, setDataSource] = useState([]); const [pagination, setPagination] = useState({ searchKey: '', current: 1, pageSize: 10, total: 0, showSizeChanger: true, showQuickJumper: true, // pageSizeOptions: ['10', '20', '30', '40'], }); const [isModalVisible, setIsModalVisible] = useState(false); const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type?: AffiliationType; teamId?: number }>({ open: false, }); const columns = useMemo( () => [ { title: i18n('team.team.addForm.code'), dataIndex: 'code', key: 'code', }, { title: i18n('team.team.addForm.name'), dataIndex: 'name', key: 'name', }, { title: i18n('team.team.addForm.status'), dataIndex: 'status', key: 'status', render: (status: StatusType) => {status}, }, { title: i18n('common.text.action'), key: 'action', width: 260, render: (_: any, record: ITeamVO) => ( <> handleDelete(record.id)} okText={i18n('common.button.affirm')} cancelText={i18n('common.button.cancel')} > e.preventDefault()}> {i18n('common.button.delete')} ), }, ], [], ); useEffect(() => { queryTeamList(); }, [pagination.current, pagination.pageSize, pagination.searchKey]); const queryTeamList = async () => { setLoading(true); try { const { searchKey, current: pageNo, pageSize } = pagination; let res = await getTeamManagementList({ searchKey, pageNo, pageSize }); if (res) { setDataSource(res?.data ?? []); setPagination({ ...pagination, total: res?.total ?? 0, } as any); } } catch (error) { } finally { setLoading(false); } }; const handleTableChange = (p: any) => { setPagination({ ...pagination, ...p, }); }; const handleSearch = (searchKey: string) => { setPagination({ ...pagination, searchKey, }); }; const handleCreateOrUpdateTeam = async (teamInfo: ITeamVO) => { const requestApi = teamInfo.id ? updateTeam : createTeam; let res = await requestApi(teamInfo); if (res) { queryTeamList(); } }; const handleEdit = (record: ITeamVO) => { form.setFieldsValue(record); setIsModalVisible(true); }; const handleDelete = async (id?: number) => { if (id !== undefined) { await deleteTeam({ id }); message.success(i18n('common.text.successfullyDelete')); queryTeamList(); } }; return (
} />
{ form .validateFields() .then((values) => { const formValues = form.getFieldsValue(true); handleCreateOrUpdateTeam(formValues); setIsModalVisible(false); form.resetFields(); }) .catch((errorInfo) => { form.scrollToField(errorInfo.errorFields[0].name); form.setFields(errorInfo.errorFields); }); }} onCancel={() => { form.resetFields(); setIsModalVisible(false); }} >
{i18n('team.team.addForm.status.valid')} {i18n('team.team.addForm.status.invalid')}
{ setDrawerInfo({ ...drawerInfo, open: false, }); }} /> ); } export default TeamManagement; ================================================ FILE: chat2db-client/src/pages/main/team/universal-add-modal/index.less ================================================ ================================================ FILE: chat2db-client/src/pages/main/team/universal-add-modal/index.tsx ================================================ import { getCommonDataSourceList, getCommonTeamList, getCommonUserAndTeamList, getCommonUserList, } from '@/service/team'; import { IDataSourceVO, ITeamAndUserVO, ITeamVO, IUserVO, ManagementType, SearchType } from '@/typings/team'; import { Modal, Select, Spin } from 'antd'; import debounce from 'lodash/debounce'; import React, { useEffect, useMemo, useState } from 'react'; import styles from './index.less'; import i18n from '@/i18n'; interface IProps { open: boolean; type?: SearchType; onConfirm: (values: Object) => void; onClose: () => void; } interface ValueType { id?: number; key: number; label: React.ReactNode; value: number; } const addAuthMap = { [SearchType['USER/TEAM']]: { title: i18n('team.action.addUserAndTeam'), loadRequest: getCommonUserAndTeamList, searchLabel: (data: ITeamAndUserVO) => data.name, searchValue: (data: ITeamAndUserVO) => JSON.stringify({ id: data.id, type: data.type }), searchListKey: 'accessObjectList', placeholder: i18n('team.action.addUserAndTeam.placeholder'), }, [SearchType.TEAM]: { title: i18n('team.action.addTeam'), loadRequest: getCommonTeamList, searchLabel: (data: ITeamVO) => data.name, searchValue: (data: ITeamVO) => data.id, searchListKey: 'teamIdList', placeholder: i18n('team.action.addTeam.placeholder'), }, [SearchType.USER]: { title: i18n('team.action.addUser'), loadRequest: getCommonUserList, searchLabel: (data: IUserVO) => data.userName, searchValue: (data: IUserVO) => data.id, searchListKey: 'userIdList', placeholder: i18n('team.action.addUser.placeholder'), }, [SearchType.DATASOURCE]: { title: i18n('team.action.addDatasource'), loadRequest: getCommonDataSourceList, searchLabel: (data: IDataSourceVO) => data.alias, searchValue: (data: IDataSourceVO) => data.id, searchListKey: 'dataSourceIdList', placeholder: i18n('team.action.addDatasource.placeholder'), }, }; function UniversalAddModal(props: IProps) { const { open, type } = props; const [fetching, setFetching] = useState(false); const [options, setOptions] = useState([]); const [selectedValues, setSelectedValues] = useState([]); const authData = useMemo(() => { if (type) { return addAuthMap[type]; } }, [type]); useEffect(() => { loadOptions(''); }, []); const loadOptions = (value: string) => { setOptions([]); setFetching(true); authData?.loadRequest({ searchKey: value }).then((res) => { const newOptions = (res || []).map((i) => ({ ...i, label: authData.searchLabel(i), value: authData.searchValue(i), key: i.id, })); setOptions(newOptions); setFetching(false); }); }; const handleOk = () => { if (!props.onConfirm || !authData) { return; } const realValue = { [authData.searchListKey]: type !== SearchType['USER/TEAM'] ? selectedValues : selectedValues.map((i) => JSON.parse(i)), }; props.onConfirm(realValue); props.onClose && props.onClose(); setSelectedValues([]); setOptions([]); }; return ( { props.onClose && props.onClose(); }} title={authData?.title} >
{ managementDataByType.updateListApi({ [managementDataByType.byIdKey]: props.byId, ...values }).then((res) => { message.success(i18n('common.tips.updateSuccess')); queryTableList(); }); }} onClose={() => { setModalInfo({ ...modalInfo, open: false, }); }} /> ); } export default UniversalDrawer; ================================================ FILE: chat2db-client/src/pages/main/team/user-management/index.less ================================================ .tableTop { display: flex; justify-content: space-between; align-items: center; margin: 16px 0; } ================================================ FILE: chat2db-client/src/pages/main/team/user-management/index.tsx ================================================ import { createUser, deleteUser, getUserManagementList, updateUser } from '@/service/team'; import { Button, Form, Input, Modal, Popconfirm, Radio, Table, Tag, message } from 'antd'; import React, { useEffect, useMemo, useState } from 'react'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import styles from './index.less'; import { AffiliationType, IUserVO, RoleType, StatusType } from '@/typings/team'; import i18n from '@/i18n'; import UniversalDrawer from '../universal-drawer'; const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 }, colon: false, }; const requireRule = { required: true, message: i18n('common.form.error.required') }; function UserManagement() { const [form] = Form.useForm(); const [dataSource, setDataSource] = useState([]); const [pagination, setPagination] = useState({ searchKey: '', current: 1, pageSize: 10, total: 0, showSizeChanger: true, showQuickJumper: true, pageSizeOptions: ['10', '20', '30', '40'], }); const [isModalVisible, setIsModalVisible] = useState(false); const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type?: AffiliationType; id?: number }>({ open: false, }); const columns = useMemo( () => [ { title: i18n('team.user.userName'), dataIndex: 'userName', key: 'userName', }, { title: i18n('team.user.nickName'), dataIndex: 'nickName', key: 'nickName', }, { title: i18n('team.user.status'), dataIndex: 'status', key: 'status', render: (status: StatusType) => {status}, }, { title: i18n('common.text.action'), key: 'action', width: 260, render: (_: any, record: IUserVO) => ( <> handleDelete(record.id)} okText={i18n('common.button.affirm')} cancelText={i18n('common.button.cancel')} > e.preventDefault()}> {i18n('common.button.delete')} ), }, ], [], ); useEffect(() => { queryUserList(); }, [pagination.current, pagination.pageSize, pagination.searchKey]); const queryUserList = async () => { const { searchKey, current: pageNo, pageSize } = pagination; let res = await getUserManagementList({ searchKey, pageNo, pageSize }); if (res) { setDataSource(res?.data ?? []); setPagination({ ...pagination, total: res?.total ?? 0, } as any); } }; const handleTableChange = (p: any) => { setPagination({ ...pagination, ...p, }); }; const handleSearch = (searchKey: string) => { setPagination({ ...pagination, searchKey, }); }; const handleCreateOrUpdateUser = async (userInfo: IUserVO) => { const requestApi = userInfo?.id ? updateUser : createUser; let res = await requestApi(userInfo); if (res) { queryUserList(); } }; const handleEdit = (record: IUserVO) => { form.setFieldsValue(record); setIsModalVisible(true); }; const handleDelete = async (id: number) => { await deleteUser({ id }); message.success(i18n('common.text.successfullyDelete')); queryUserList(); }; const isEditing = useMemo(() => { return form.getFieldValue('id') !== undefined; }, [form.getFieldValue('id')]); return (
} />
{ form .validateFields() .then((values) => { const formValues = form.getFieldsValue(true); handleCreateOrUpdateUser(formValues); setIsModalVisible(false); form.resetFields(); }) .catch((errorInfo) => { form.scrollToField(errorInfo.errorFields[0].name); form.setFields(errorInfo.errorFields); }) .finally(() => { form.resetFields(); }); }} onCancel={() => { form.resetFields(); setIsModalVisible(false); }} >
{i18n('team.user.addForm.roleCode.admin')} {i18n('team.user.addForm.roleCode.user')} {i18n('team.user.addForm.status.valid')} {i18n('team.user.addForm.status.invalid')}
{ setDrawerInfo({ ...drawerInfo, open: false, }); }} /> ); } export default UserManagement; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/OperationLine/index.less ================================================ .operationLine{ flex-shrink: 0; border-top: 1px solid var(--color-border-secondary); height: 30px; display: flex; align-items: center; justify-content: space-between; padding: 0px 4px; margin: 0px -2px; .operationLineLeft{ display: flex; align-items: center; >div{ margin: 0px 2px; } } } .searchBox{ flex-shrink: 0; padding: 4px; border-top: 1px solid var(--color-border-secondary); } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx ================================================ import React, { memo, useMemo, useState } from 'react'; import i18n from '@/i18n'; import styles from './index.less'; import { Input } from 'antd'; // ----- constants ----- import { DatabaseTypeCode } from '@/constants'; // ----- components ----- import Iconfont from '@/components/Iconfont'; // ----- store ----- import { useWorkspaceStore } from '@/pages/main/workspace/store'; interface IProps { searchValue: string; setSearchValue: (value: string) => void; getTreeData: (refresh?: boolean) => void; } // 不支持创建数据库的数据库类型 const notSupportCreateDatabaseType = [DatabaseTypeCode.H2]; // 不支持创建schema的数据库类型 const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE]; const OperationLine = (props: IProps) => { const [searchIng, setSearchIng] = useState(false); const { searchValue, setSearchValue, getTreeData } = props; const { currentConnectionDetails, openCreateDatabaseModal } = useWorkspaceStore((state) => { return { currentConnectionDetails: state.currentConnectionDetails, openCreateDatabaseModal: state.openCreateDatabaseModal, }; }); const handelOpenCreateDatabaseModal = () => { const type = currentConnectionDetails?.supportDatabase ? 'database' : 'schema'; openCreateDatabaseModal?.({ type, relyOnParams: { databaseType: currentConnectionDetails!.type!, dataSourceId: currentConnectionDetails!.id!, }, executedCallback: () => { getTreeData(true); }, }); }; const showCreate = useMemo(() => { if (currentConnectionDetails?.supportDatabase) { return !notSupportCreateDatabaseType.includes(currentConnectionDetails!.type!); } if (currentConnectionDetails?.supportSchema) { return !notSupportCreateSchemaType.includes(currentConnectionDetails!.type!); } }, [currentConnectionDetails]); return ( <>
{showCreate && ( )} { getTreeData(true); }} code="" box boxSize={20} size={14} /> {/* {searchIng ? ( { setSearchIng(false); setSearchValue(''); }} box boxSize={20} code="" /> ) : ( { setSearchIng(true); }} code="" box boxSize={20} size={14} /> )} */}
{/*
1
*/}
{/* {searchIng && ( */}
} value={searchValue} onChange={(e) => setSearchValue(e.target.value)} allowClear placeholder={i18n('workspace.tree.search.placeholder')} />
{/* )} */} ); }; export default memo(OperationLine); ================================================ FILE: chat2db-client/src/pages/main/workspace/components/SQLExecute/index.less ================================================ @import '../../../../../styles/var.less'; .sqlExecute { height: 100%; } .boxRightCenter { height: 100%; } .boxRightConsole { height: 40vh; overflow: hidden; } .boxRightResult { border-top: 1px solid var(--color-border); height: 0px; flex: 1; position: relative; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx ================================================ import React, { memo, useEffect, useRef, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import ConsoleEditor, { IConsoleRef } from '@/components/ConsoleEditor'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { IBoundInfo } from '@/typings'; interface IProps { boundInfo: IBoundInfo; initDDL: string; // 异步加载sql loadSQL: () => Promise; } const SQLExecute = memo((props) => { const { boundInfo: _boundInfo, initDDL, loadSQL } = props; const draggableRef = useRef(); const searchResultRef = useRef(null); const consoleRef = useRef(null); const [boundInfo, setBoundInfo] = useState(_boundInfo); const activeConsoleId = useWorkspaceStore((state) => state.activeConsoleId); useEffect(() => { if (loadSQL) { loadSQL().then((sql) => { consoleRef.current?.editorRef?.setValue(sql, 'cover'); }); } }, []); return (
{ searchResultRef.current?.handleExecuteSQL(sql); }} />
); }); export default SQLExecute; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/SaveList/index.less ================================================ @import '../../../../../styles/var.less'; .saveModule { flex-shrink: 0; height: 100%; display: flex; flex-direction: column; } .leftModuleTitle { flex-shrink: 0; margin-bottom: 4px; padding: 0px 10px; height: 32px; display: flex; align-items: center; border-bottom: 1px solid var(--color-border); .leftModuleTitleText { width: 100%; display: flex; justify-content: space-between; align-items: center; height: 26px; .modelName { font-weight: bold; line-height: 100%; } .iconBox { display: flex; align-items: center; height: 100%; } .refreshIcon { margin-right: 10px; } .refreshIcon, .searchIcon { cursor: pointer; height: 100%; display: flex; align-items: center; &:hover { color: var(--color-primary); } } } .leftModuleTitleSearch { height: 26px; } } .loadingContent { height: auto; padding-bottom: 4px; } .saveBoxList { flex: 1; height: 0px; padding: 0px 4px; overflow-y: hidden; &:hover { overflow-y: auto; } } .leftModuleTitleShadow { // 地步加一点模糊 box-shadow: 0px 1px 2px 0px var(--color-border); } .saveItem { display: flex; justify-content: space-between; align-items: center; padding: 0px 6px; height: 26px; line-height: 26px; border-radius: 4px; user-select: none; cursor: pointer; .saveItemText { width: 0px; flex: 1; .f-single-line(); display: flex; align-items: center; .iconBox { width: 20px; flex-shrink: 0; } .itemName { flex: 1; .f-single-line(); } } &:hover { background-color: var(--color-hover-bg); color: var(--color-primary); } } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx ================================================ import React, { useState, useEffect, useRef } from 'react'; import i18n from '@/i18n'; import { Input, Dropdown, Modal } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import historyServer from '@/service/history'; import { ConsoleOpenedStatus, workspaceTabConfig } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; import { addWorkspaceTab, getSavedConsoleList } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import MenuLabel from '@/components/MenuLabel'; const SaveList = () => { const [searching, setSearching] = useState(false); const inputRef = useRef(); const [searchedList, setSearchedList] = useState(); const leftModuleTitleRef = useRef(null); const saveBoxListRef = useRef(null); const consoleList = useWorkspaceStore((state) => state.savedConsoleList); const [editData, setEditData] = useState(null); useEffect(() => { getSavedConsoleList(); }, []); useEffect(() => { if (searching) { inputRef.current!.focus({ cursor: 'start', }); } }, [searching]); function openSearch() { setSearching(true); } function onBlur() { if (!inputRef.current.input.value) { setSearching(false); setSearchedList(undefined); } } function onChange(value: string) { if (consoleList) { setSearchedList(approximateList(consoleList as any, value)); } } function openConsole(item: IConsole) { const params: any = { id: item.id, tabOpened: ConsoleOpenedStatus.IS_OPEN, }; historyServer.updateSavedConsole(params).then(() => { addWorkspaceTab({ id: item.id, type: item.operationType, title: item.name, uniqueData: { dataSourceId: item.dataSourceId, dataSourceName: item.dataSourceName, databaseType: item.type, databaseName: item.databaseName, schemaName: item.schemaName, status: item.status, ddl: item.ddl, connectable: item.connectable, }, }); }); } function deleteSaved(data: IConsole) { const params: any = { id: data.id, }; historyServer.deleteSavedConsole(params).then(() => { getSavedConsoleList(); }); } const editSaved = (data: IConsole) => { setEditData(data); }; return ( <>
{searching ? (
} onBlur={onBlur} onChange={(e) => onChange(e.target.value)} allowClear />
) : (
{i18n('workspace.title.savedConsole')}
{/*
refreshTableList()}>
*/}
openSearch()}>
)}
{(searchedList || consoleList)?.map((t) => { return ( , onClick: () => { openConsole(t); }, }, { key: 'edit', label: , onClick: () => { editSaved(t); }, }, { key: 'delete', label: , onClick: () => { deleteSaved(t); }, }, ], }} >
{ openConsole(t); }} className={styles.saveItem} >
); })}
{ const params: any = { id: editData.id, name: editData.name, }; historyServer.updateSavedConsole(params).then(() => { getSavedConsoleList(); setEditData(null); }); }} onCancel={() => setEditData(null)} > { setEditData({ ...editData, name: e.target.value, }); }} /> ); }; export default SaveList; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/TableList/index.less ================================================ @import '../../../../../styles/var.less'; .treeContainer{ flex: 1; height: 0px; display: flex; flex-direction: column; } .treeBox{ flex: 1; height: 0px; } .leftModuleTitleShadow{ border-bottom: 1px solid var(--color-border-secondary); } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/TableList/index.tsx ================================================ import React, { memo, useEffect, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; // ----- components ----- import OperationLine from '../OperationLine'; import Tree from '@/blocks/Tree'; import { treeConfig } from '@/blocks/Tree/treeConfig'; import { ITreeNode } from '@/typings'; import { TreeNodeType } from '@/constants'; interface IProps { className?: string; } export default memo((props) => { const { className } = props; const [treeData, setTreeData] = useState(null); const [searchValue, setSearchValue] = useState(''); const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); const getTreeData = (refresh = false) => { if (!currentConnectionDetails?.id) { setTreeData([]); return; } const treeNodeType = currentConnectionDetails.supportDatabase ? TreeNodeType.DATA_SOURCE : TreeNodeType.DATABASE; setTreeData(null); treeConfig[treeNodeType] .getChildren?.({ dataSourceId: currentConnectionDetails.id, dataSourceName: currentConnectionDetails.alias, refresh: refresh, extraParams: { dataSourceId: currentConnectionDetails.id, dataSourceName: currentConnectionDetails.alias, databaseType: currentConnectionDetails.type, }, }) .then((res) => { setTreeData(res); }) .catch(() => { setTreeData([]); }); }; useEffect(() => { getTreeData(); }, [currentConnectionDetails]); return (
); }); ================================================ FILE: chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.less ================================================ @import '../../../../../styles/var.less'; .allTable { height: 100%; display: flex; flex-direction: column; } .contentCenter{ flex: 1; display: flex; width: 100%; .tableBox{ flex: 1; } .viewDDLBox{ border-left: 1px solid var(--color-border); flex-shrink: 0; width: 200px; display: flex; flex-direction: column; } .viewDDLHeader{ height: 25px; border-bottom: 1px solid var(--color-border); box-sizing: border-box; display: flex; align-items: center; justify-content: center; font-weight: bold; // background-color: var(--color-bg-subtle); } .viewDDL{ flex: 1; } } .headerBox { height: 30px; display: flex; align-items: center; justify-content: space-between; padding: 0px 4px; border-bottom: 1px solid var(--color-border); .headerBoxLeft { display: flex; align-items: center; } } .pagingBox { height: 40px; padding: 4px 4px; box-sizing: border-box; display: flex; align-items: center; justify-content: flex-end; border-top: 1px solid var(--color-border); } .tableCell { padding: 2px 6px; height: 24px; box-sizing: border-box; cursor: pointer; } .activeTableCell { background-color: var(--color-primary-hover); color: var(--color-bg-base); } :global { .ant-table-wrapper .ant-table-cell { padding: 0px; } .ant-table-wrapper .ant-table-thead > tr > th { padding: 2px 6px; height: 24px; } } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/ViewAllTable/index.tsx ================================================ import React, { memo, useEffect } from 'react'; import i18n from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; import { Table, Dropdown, Input, Pagination } from 'antd'; import { DatabaseTypeCode, TreeNodeType, OperationColumn, WorkspaceTabType } from '@/constants'; import sqlServer from '@/service/sql'; import type { ColumnsType } from 'antd/es/table'; import { IPageParams } from '@/typings'; import { v4 as uuid } from 'uuid'; // ----- components ----- import Iconfont from '@/components/Iconfont'; import { getRightClickMenu } from '@/blocks/Tree/hooks/useGetRightClickMenu'; import MenuLabel from '@/components/MenuLabel'; import { setCurrentWorkspaceGlobalExtend } from '@/pages/main/workspace/store/common'; // ----- store ----- import { addWorkspaceTab } from '@/pages/main/workspace/store/console'; const { Search } = Input; interface IProps { className?: string; uniqueData: { dataSourceId: string; dataSourceName: string; databaseType: DatabaseTypeCode; databaseName?: string; schemaName?: string; }; } export default memo((props) => { const { className, uniqueData } = props; const [tableData, setTableData] = React.useState(null); const [tableLoading, setTableLoading] = React.useState(false); const tableBoxRef = React.useRef(null); const [allTableWidth, setAllTableWidth] = React.useState(0); const [allTableHeight, setAllTableHeight] = React.useState(0); // 选中表 const [activeId, setActiveId] = React.useState(''); const [tableDataTotal, setTableDataTotal] = React.useState(0); const [currentPageNo, setCurrentPageNo] = React.useState(1); const [openDropdown, setOpenDropdown] = React.useState(undefined); const [dropdownItems, setDropdownItems] = React.useState([]); useEffect(() => { getTable({ pageNo: 1, pageSize: 1000, }); }, []); useEffect(() => { if (openDropdown === false) { setOpenDropdown(undefined); } }, [openDropdown]); const getTable = (params: IPageParams) => { setCurrentPageNo(params.pageNo); setTableLoading(true); sqlServer .getTableList({ ...props.uniqueData, ...(params || {}), } as any) .then((res) => { setTableDataTotal(res.total); const data = res.data.map((t) => { const key = uuid(); return { uuid: key, name: t.name, treeNodeType: TreeNodeType.TABLE, key: t.name, pinned: t.pinned, comment: t.comment, extraParams: { ...uniqueData, tableName: t.name, }, }; }); setTableData(data); }) .finally(() => { setTableLoading(false); }); }; const paginationChange = (pageNo: number) => { getTable({ pageNo, pageSize: 1000, }); }; const createTable = () => { addWorkspaceTab({ id: uuid(), title: i18n('editTable.button.createTable'), type: WorkspaceTabType.CreateTable, uniqueData: { ...props.uniqueData, }, }); }; const getDropdownsItems = (record) => { const rightClickMenu = getRightClickMenu({ treeNodeData: record, loadData: () => {}, }); const dropdownsItems: any = rightClickMenu.map((item) => { return { key: item.key, type: item.type, onClick: () => { setOpenDropdown(false); item.onClick(record); }, label: , }; }); const excludeList = [ OperationColumn.OpenTable, OperationColumn.CreateConsole, // OperationColumn.Pin, OperationColumn.ViewDDL, OperationColumn.EditTable, OperationColumn.CopyName, ]; return dropdownsItems.filter((item) => excludeList.includes(item.type)); }; const renderCell = (text, record) => { return (
{text}
); }; const columns: ColumnsType = [ { title: 'Table name', dataIndex: 'name', key: 'name', render: renderCell, }, { title: 'Comment', dataIndex: 'comment', key: 'comment', render: renderCell, }, ]; // 监听allTable的高度的变化 useEffect(() => { const resizeObserver = new ResizeObserver((entries) => { const { width, height } = entries[0].contentRect; setAllTableWidth(width); setAllTableHeight(height); }); resizeObserver.observe(tableBoxRef.current!); }, []); // 监听allTable的宽度的变化 useEffect(() => { const resizeObserver = new ResizeObserver((entries) => { const { width, height } = entries[0].contentRect; setAllTableWidth(width); setAllTableHeight(height); }); resizeObserver.observe(tableBoxRef.current!); }, []); // useEffect(() => { // const record = tableData?.find((t) => t.key === activeId); // if (record) { // sqlServer // .exportCreateTableSql({ // ...uniqueData, // tableName: record.name, // } as any) // .then((res) => { // setViewDDLSql(res); // }); // } // }, [activeId]); const onSearch = (value: string) => { getTable({ pageNo: 1, pageSize: 1000, searchKey: value, }); }; return ( //
{ getTable({ pageNo: 1, pageSize: 1000, refresh: true, }); }} code="" box boxSize={24} />
{ setOpenDropdown(_open); }} >
{ return { onClick: () => { setActiveId(row.key); setCurrentWorkspaceGlobalExtend({ code: 'viewDDL', uniqueData: { ...uniqueData, tableName: row.name, } }); }, onContextMenu: (event) => { event.preventDefault(); setActiveId(row.key); setOpenDropdown(true); setDropdownItems(getDropdownsItems(tableData?.find((t) => t.key === row.key))); }, }; }} virtual scroll={{ x: allTableWidth - 10, y: allTableHeight - 25 }} columns={columns} pagination={false} dataSource={tableData || []} /> {/* {tableDataTotal > 1000 && ( )} */}
); }); ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/GlobalExtendComponents/index.less ================================================ .viewDDLBox { height: 100%; display: flex; flex-direction: column; .viewDDLHeader { flex-shrink: 0; line-height: 32px; padding: 0px 10px; border-bottom: 1px solid var(--color-border); font-weight: bold; } .viewDDL { flex: 1; } } .noInformation{ height: 100%; display: flex; align-items: center; justify-content: center; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/GlobalExtendComponents/index.tsx ================================================ import React from 'react'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { GlobalComponents } from '../config'; import ViewDDL from '@/components/ViewDDL'; import styles from './index.less'; const GlobalExtendComponents = () => { const { currentWorkspaceGlobalExtend } = useWorkspaceStore((state) => { return { currentWorkspaceGlobalExtend: state.currentWorkspaceGlobalExtend, }; }); switch (currentWorkspaceGlobalExtend?.code) { case GlobalComponents.view_ddl: return
{`${currentWorkspaceGlobalExtend.uniqueData.tableName}-DDL`}
;
default: return
No information
; } }; export default GlobalExtendComponents; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/WorkspaceExtendBody/index.less ================================================ .WorkspaceExtendBody{ // flex: 1; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/WorkspaceExtendBody/index.tsx ================================================ import React, { useMemo } from 'react'; import {extendConfig} from '../config'; import {useWorkspaceStore} from '@/pages/main/workspace/store'; export default () => { const {currentWorkspaceExtend} = useWorkspaceStore((state) => { return { currentWorkspaceExtend: state.currentWorkspaceExtend, } }); const Component = useMemo(() => { return extendConfig.find((item) => item.code === currentWorkspaceExtend)?.components }, [currentWorkspaceExtend]); return Component ? : false }; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/WorkspaceExtendNav/index.less ================================================ @import '../../../../../../styles/var.less'; // .workspaceExtend { // display: none; // background-color: var(--color-bg-subtle); // .workspaceExtendMain { // flex: 1; // width: 0px; // border-right: 1px solid var(--color-border); // } // } .workspaceExtendNav { flex-shrink: 0; width: 38px; display: flex; flex-direction: column; align-items: center; padding: 5px 0px; } .rightBarFront { margin: 2px 0px; } .workspaceExtendActive { display: flex; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/WorkspaceExtendNav/index.tsx ================================================ import React from 'react'; import styles from './index.less'; import classnames from 'classnames'; // import i18n from '@/i18n'; import { Popover } from 'antd'; import Iconfont from '@/components/Iconfont'; import {extendConfig} from '../config'; import { setCurrentWorkspaceExtend } from '@/pages/main/workspace/store/common'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; interface IToolbar { code: string; title: string; icon: string; components: any; } interface IProps { className?: any; } export default (props:IProps) => { const { className } = props; const { currentWorkspaceExtend } = useWorkspaceStore((state) => { return { currentWorkspaceExtend: state.currentWorkspaceExtend, }; }); const changeExtend = (item: IToolbar) => { if (currentWorkspaceExtend === item.code) { setCurrentWorkspaceExtend(null); return; } setCurrentWorkspaceExtend(item.code); }; return (
{extendConfig.map((item, index) => { return (
); })}
); }; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/config.tsx ================================================ import i18n from '@/i18n'; import Output from '@/components/Output'; import GlobalExtendComponents from './GlobalExtendComponents'; import SaveList from '../SaveList'; import ViewDDL from '@/components/ViewDDL'; interface IToolbar { code: string; title: string; icon: string; components: any; } export enum GlobalComponents { view_ddl = 'viewDDL', executive_log = 'executiveLog', save_list = 'saveList' } export const globalComponents: { [key in GlobalComponents]: any; } = { [GlobalComponents.view_ddl]: ViewDDL, [GlobalComponents.executive_log]: Output, [GlobalComponents.save_list]: SaveList } export const extendConfig: IToolbar[] = [ { code: 'info', title: i18n('common.title.info'), icon: '\ue8e8', components: GlobalExtendComponents, }, { code: 'executiveLog', title: i18n('common.title.executiveLogging'), icon: '\ue8ad', components: globalComponents.executiveLog, }, { code: 'saveList', title: i18n('workspace.title.savedConsole'), icon: '\ue619', components: globalComponents.saveList, }, ]; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less ================================================ @import '../../../../../styles/var.less'; .workspaceExtend { display: none; background-color: var(--color-bg-subtle); .workspaceExtendBar { flex-shrink: 0; width: 38px; display: flex; flex-direction: column; align-items: center; padding: 5px 0px; } .workspaceExtendMain { flex: 1; width: 0px; border-right: 1px solid var(--color-border); } } .rightBarFront { margin: 2px 0px; } .workspaceExtendActive { display: flex; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx ================================================ import React, { useState } from 'react'; import styles from './index.less'; // import classnames from 'classnames'; import { Popover } from 'antd'; import Iconfont from '@/components/Iconfont'; import Output from '@/components/Output'; import SaveList from '../SaveList'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import i18n from '@/i18n'; interface IToolbar { code: string; title: string; icon: string; components: any; } export const useWorkspaceExtend = () => { const [activeExtend, setActiveExtend] = useState(null); const { panelRight } = useWorkspaceStore((state) => state.layout); const toolbarConfig: IToolbar[] = [ // { // code: 'ai', // title: 'AI', // icon: '\ue8ad', // components:
ai
, // }, { code: 'executiveLog', title: i18n('common.title.executiveLogging'), icon: '\ue8ad', components: , }, { code: 'saveList', title: i18n('workspace.title.savedConsole'), icon: '\ue619', components: , }, ]; const changeExtend = (item: IToolbar) => { if (activeExtend?.code === item.code) { setActiveExtend(null); return; } setActiveExtend(item); }; // return ( //
//
// ); const extendBody = ( <>{activeExtend &&
{activeExtend?.components}
} ); const extendNav = (
{toolbarConfig.map((item, index) => { return (
); })}
); return [extendBody, extendNav]; }; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less ================================================ @import '../../../../../styles/var.less'; .workspaceLeft { display: flex; flex-direction: column; height: 100%; background-color: var(--color-bg-subtle); box-sizing: border-box; min-width: 200px; } .noConnectionList { height: 100%; margin-top: 30vh; text-align: center; font-size: 14px; .noConnectionListIcon { font-size: 60px; color: var(--color-primary); } .noConnectionListTips { margin: 10px 0px; } .create { color: var(--color-primary); text-decoration: underline; cursor: pointer; margin-right: 4px; &:hover { color: var(--color-primary-hover); } } } .createButtonBox { padding: 10px 0px 10px; } .divider { margin: 0px; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx ================================================ import React, { memo } from 'react'; import i18n from '@/i18n'; import classnames from 'classnames'; import styles from './index.less'; import TableList from '../TableList'; import WorkspaceLeftHeader from '../WorkspaceLeftHeader'; import CreateDatabase from '@/components/CreateDatabase'; import Iconfont from '@/components/Iconfont'; import { useConnectionStore } from '@/pages/main/store/connection'; import { setMainPageActiveTab } from '@/pages/main/store/main'; const WorkspaceLeft = memo(() => { const { connectionList } = useConnectionStore((state) => { return { connectionList: state.connectionList, }; }); const jumpPage = () => { setMainPageActiveTab('connections'); }; return ( <>
{connectionList?.length ? ( <> ) : (
{i18n('workspace.tips.noConnection')}
{i18n('common.title.create')} {i18n('connection.title.connections')}
)}
); }); export default WorkspaceLeft; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less ================================================ @import '../../../../../styles/var.less'; .splitViewLeft { } .selectConnection { padding: 6px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; box-sizing: border-box; height: 32px; .menuLabel { flex: 1; width: 0px; } .dropDownArrow { transform: rotate(90deg); color: var(--color-text-secondary); } &:hover { background: var(--color-hover-bg); } } .dropdownOverlay { max-width: 500px; :global { .ant-dropdown-menu-item { padding: 4px 6px !important; } } } .menuLabel { display: flex; align-items: center; .menuLabelIconBox { width: 22px; display: flex; align-items: center; } .menuLabelIcon { color: var(--color-primary); } .menuLabelTag { margin-right: 10px; } .menuLabelTitle { flex: 1; width: 0px; .f-single-line(); } .envTag { flex-shrink: 0; width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-primary); margin-right: 8px; } } .databaseTypeIcon { margin-right: 10px; font-weight: 400; color: var(--color-primary); } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx ================================================ import React, { memo, useMemo } from 'react'; import { Dropdown } from 'antd'; import classnames from 'classnames'; import styles from './index.less'; // ---- store ---- import { useConnectionStore } from '@/pages/main/store/connection'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; // ----- components ----- import Iconfont from '@/components/Iconfont'; // ----- constants/typings ----- import { databaseMap } from '@/constants'; import { IConnectionListItem } from '@/typings/connection'; export default memo(() => { const { connectionList } = useConnectionStore((state) => { return { connectionList: state.connectionList, }; }); const { currentConnectionDetails } = useWorkspaceStore((state) => { return { currentConnectionDetails: state.currentConnectionDetails, }; }); const renderConnectionLabel = (item: IConnectionListItem) => { return (
{/* {item.environment.shortName} */}
{item.alias}
); }; const connectionItems = useMemo(() => { return ( connectionList?.map((item) => { return { key: item.id, label: renderConnectionLabel(item), onClick: () => { setCurrentConnectionDetails(item); }, }; }) || [] ); }, [connectionList, currentConnectionDetails]); return (
{currentConnectionDetails && renderConnectionLabel(currentConnectionDetails)}
); }); ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less ================================================ @import '../../../../../styles/var.less'; .workspaceRight { position: relative; display: flex; flex: 1; width: 0px; } .workspaceExtendNav { border-left: 1px solid var(--color-border); } .draggableContainer { flex: 1; width: 0px; overflow: hidden; } .workspaceExtendBody { border-left: 1px solid var(--color-border); overflow: hidden; } .workspaceExtend { height: 100%; flex-shrink: 0; border-left: 1px solid var(--color-border); display: flex; } .consoleTabsContainer { height: 50%; } .searchResultContainer { border-top: 1px solid var(--color-border-secondary); flex: 1; } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx ================================================ import React, { memo, useRef, useCallback } from 'react'; import styles from './index.less'; // import classnames from 'classnames'; import WorkspaceExtendBody from '../WorkspaceExtend/WorkspaceExtendBody'; import WorkspaceExtendNav from '../WorkspaceExtend/WorkspaceExtendNav'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { setPanelRightWidth } from '@/pages/main/workspace/store/config'; // ----- components ----- import WorkspaceTabs from '../WorkspaceTabs'; import DraggableContainer from '@/components/DraggableContainer'; const WorkspaceRight = memo(() => { const draggableRef = useRef(); const { currentWorkspaceExtend, panelRight, panelRightWidth } = useWorkspaceStore((state) => { return { currentWorkspaceExtend: state.currentWorkspaceExtend, panelRight: state.layout.panelRight, panelRightWidth: state.layout.panelRightWidth, }; }); const draggableContainerResize = useCallback((data: number) => { setPanelRightWidth(data); }, []); return (
{panelRight && }
); }); export default WorkspaceRight; ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less ================================================ @import '../../../../../styles/var.less'; .tabBox { height: 100%; flex: 1; width: 0px; } .ears { flex: 1; width: 0px; display: flex; justify-content: center; align-items: center; overflow: hidden; button { display: flex; align-items: center; } } ================================================ FILE: chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx ================================================ import React, { memo, useEffect, useMemo, Fragment } from 'react'; import styles from './index.less'; import i18n from '@/i18n'; import { Button } from 'antd'; // ----- constants ----- import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; import { IWorkspaceTab } from '@/typings'; // ----- components ----- import Tabs, { ITabItem } from '@/components/Tabs'; import SearchResult from '@/components/SearchResult'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; import SequenceEditor from '@/blocks/SequenceEditor'; import SQLExecute from '../SQLExecute'; import ViewAllTable from '../ViewAllTable'; import Iconfont from '@/components/Iconfont'; import ShortcutKey from '@/components/ShortcutKey'; // ---- store ----- import { getOpenConsoleList, setActiveConsoleId, setWorkspaceTabList, createConsole, } from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { useTreeStore } from '@/blocks/Tree/treeStore'; // ----- services ----- import historyService from '@/service/history'; import indexedDB from '@/indexedDB'; const WorkspaceTabs = memo(() => { const { activeConsoleId, consoleList, workspaceTabList } = useWorkspaceStore((state) => { return { consoleList: state.consoleList, activeConsoleId: state.activeConsoleId, workspaceTabList: state.workspaceTabList, }; }); const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); // 获取console useEffect(() => { getOpenConsoleList(); }, []); // consoleList 先转换为通用的 workspaceTabList useEffect(() => { const _workspaceTabItems = consoleList?.map((item) => { return { id: item.id, type: item.operationType, title: item.name, uniqueData: { dataSourceId: item.dataSourceId, dataSourceName: item.dataSourceName, databaseType: item.type, databaseName: item.databaseName, schemaName: item.schemaName, status: item.status, ddl: item.ddl, connectable: item.connectable, }, }; }) || []; setWorkspaceTabList(_workspaceTabItems); }, [consoleList]); // 关闭tab const closeWindowTab = (key: number) => { const p: any = { id: key, tabOpened: 'n', }; historyService.updateSavedConsole(p).then(() => { indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', key); }); }; const createNewConsole = () => { const { databaseName, schemaName } = useTreeStore.getState().focusTreeNode || {}; if (currentConnectionDetails) { createConsole({ dataSourceId: currentConnectionDetails.id, dataSourceName: currentConnectionDetails.alias, databaseType: currentConnectionDetails.type, databaseName, schemaName }); } }; // 删除 新增tab const handelTabsEdit = (action: 'add' | 'remove', data: ITabItem[]) => { if (action === 'remove') { setWorkspaceTabList( workspaceTabList?.filter((t) => { return data.findIndex((item) => item.key === t.id) === -1; }) || [], ); data.forEach((item) => { const editData = workspaceTabList?.find((t) => t.id === item.key); if ( editData?.type === WorkspaceTabType.CONSOLE || editData?.type === WorkspaceTabType.FUNCTION || editData?.type === WorkspaceTabType.PROCEDURE || editData?.type === WorkspaceTabType.TRIGGER || editData?.type === WorkspaceTabType.VIEW || editData?.type === WorkspaceTabType.SEQUENCE || // table 和 !editData?.type 为了兼容老数据 editData?.type === ('table' as any) || !editData?.type ) { closeWindowTab(item.key as number); } }); } if (action === 'add') { createNewConsole(); } }; // 切换tab const onTabChange = (key: string | null) => { setActiveConsoleId(key); }; // 编辑名称 const editableNameOnBlur = (t: ITabItem) => { const _params: any = { id: t.key, name: t.label, }; historyService.updateSavedConsole(_params); const _workspaceTabList: any = workspaceTabList?.map((item) => { if (item.id === t.key) { return { ...item, title: t.label, }; } return item; }) || []; setWorkspaceTabList(_workspaceTabList); }; // 修改tab详情 const changeTabDetails = (data: IWorkspaceTab) => { const list = workspaceTabList?.map((t) => { if (t.id === data.id) { return data; } return t; }) || []; setWorkspaceTabList(list); }; // 渲染sql执行器 const renderSQLExecute = (item: IWorkspaceTab) => { const { uniqueData } = item; return ( ); }; // 渲染序列编辑器 const renderSequenceEditor = (item: IWorkspaceTab) => { const { uniqueData } = item; return ( ); }; // 渲染表格编辑器 const renderTableEditor = (item: IWorkspaceTab) => { const { uniqueData } = item; return ( ); }; // 渲染搜索结果 const renderSearchResult = (item: IWorkspaceTab) => { const { uniqueData } = item; return ( ); }; // 渲染所有表 const renderViewAllTable = (item: IWorkspaceTab) => { const { uniqueData } = item; return ; }; // 根据不同的tab类型渲染不同的内容 const workspaceTabConnectionMap = (item: IWorkspaceTab) => { switch (item.type) { case 'table' as any: // 为了兼容老数据 case null as any: // 为了兼容老数据 case WorkspaceTabType.CONSOLE: case WorkspaceTabType.FUNCTION: case WorkspaceTabType.SEQUENCE: case WorkspaceTabType.PROCEDURE: case WorkspaceTabType.TRIGGER: case WorkspaceTabType.VIEW: return renderSQLExecute(item); case WorkspaceTabType.EditTable: case WorkspaceTabType.CreateTable: return renderTableEditor(item); case WorkspaceTabType.EditTableData: return renderSearchResult(item); case WorkspaceTabType.ViewAllTable: return renderViewAllTable(item); case WorkspaceTabType.CreateSequence: case WorkspaceTabType.EditSequence: return renderSequenceEditor(item); default: return
Unknown
; } }; // tab列表 const workspaceTabItems = useMemo(() => { return workspaceTabList?.map((item) => { return { prefixIcon: workspaceTabConfig[item.type]?.icon, label: item.title, key: item.id, editableName: item.type === WorkspaceTabType.CONSOLE, children: {workspaceTabConnectionMap(item)}, }; }); }, [workspaceTabList, activeConsoleId]); function renderCreateConsoleButton() { return (
); } return workspaceTabItems?.length ? ( ) : (
); }); export default WorkspaceTabs; ================================================ FILE: chat2db-client/src/pages/main/workspace/functions/shortcutKeyCreateConsole.ts ================================================ import { createConsole} from '../store/console' import { useWorkspaceStore } from '../store'; export const handelCreateConsole = () => { const params = useWorkspaceStore.getState().currentConnectionDetails; if (params) { createConsole({ dataSourceId: params.id, dataSourceName: params.alias, databaseType: params.type, }); } } const shortcutKeyCreateConsole = () => { // 注册快捷键监听cmd+shift+l或ctrl+shift+l新建一个console const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey && e.shiftKey && e.code === 'KeyL') || (e.ctrlKey && e.shiftKey && e.key === 'KeyL')) { handelCreateConsole() } }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; } export default shortcutKeyCreateConsole; ================================================ FILE: chat2db-client/src/pages/main/workspace/index.less ================================================ @import '../../../styles/var.less'; .workspace { width: 100%; height: 100%; display: flex; flex-direction: column; } .workspaceMain { height: 100%; } .loadingContent { flex: 1; height: 0px !important; } .boxLeft { width: var(--panel-left-width); height: 100%; overflow: hidden; border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; } .boxRight { position: relative; z-index: 1; //为了覆盖左侧 Cascader flex: 1; width: 0px; background-color: var(--color-bg-base); } .box_right_center { height: 100%; } .box_right_console { height: 300px; overflow: hidden; } .box_right_result { height: 0px; flex: 1; } .select_database_box { display: flex; align-items: center; width: 100%; .current_database { flex: 1; width: 0px; display: flex; align-items: center; cursor: pointer; i { flex-shrink: 0; margin-left: 10px; } .name { max-width: 90%; .f-single-line(); } } .other_operations { flex-shrink: 0; } .icon_box { width: 30px; height: 30px; display: flex; justify-content: center; align-items: center; } } .cascade_popup { :global { .ant-cascader-menu-item-content { font-weight: 400 !important; } } } .hiddenPanelLeft { display: none; } ================================================ FILE: chat2db-client/src/pages/main/workspace/index.tsx ================================================ import React, { memo, useCallback, useEffect, useRef } from 'react'; import classnames from 'classnames'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import { setPanelLeftWidth } from '@/pages/main/workspace/store/config'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; import useMonacoTheme from '@/components/MonacoEditor/useMonacoTheme'; import shortcutKeyCreateConsole from './functions/shortcutKeyCreateConsole'; import styles from './index.less'; const workspacePage = memo(() => { const draggableRef = useRef(); const { panelLeft, panelLeftWidth } = useWorkspaceStore((state) => { return { panelLeft: state.layout.panelLeft, panelLeftWidth: state.layout.panelLeftWidth, }; }); // 编辑器的主题 useMonacoTheme(); // 快捷键 useEffect(() => { shortcutKeyCreateConsole(); }, []); const draggableContainerResize = useCallback((data: number) => { setPanelLeftWidth(data); }, []); return (
); }); export default workspacePage; ================================================ FILE: chat2db-client/src/pages/main/workspace/store/common.ts ================================================ import { IConnectionListItem } from '@/typings/connection'; import { useWorkspaceStore } from './index'; export interface ICommonStore { currentConnectionDetails: IConnectionListItem | null; currentWorkspaceExtend: string | null; currentWorkspaceGlobalExtend: { code: string, uniqueData: any, } | null; } export const initCommonStore: ICommonStore = { currentConnectionDetails: null, currentWorkspaceExtend: null, currentWorkspaceGlobalExtend: null, } export const setCurrentConnectionDetails = (connectionDetails: ICommonStore['currentConnectionDetails']) => { return useWorkspaceStore.setState({ currentConnectionDetails: connectionDetails }); } export const setCurrentWorkspaceExtend = (workspaceExtend: ICommonStore['currentWorkspaceExtend']) => { return useWorkspaceStore.setState({ currentWorkspaceExtend: workspaceExtend }); } export const setCurrentWorkspaceGlobalExtend = (workspaceGlobalExtend: ICommonStore['currentWorkspaceGlobalExtend']) => { return useWorkspaceStore.setState({ currentWorkspaceGlobalExtend: workspaceGlobalExtend }); } ================================================ FILE: chat2db-client/src/pages/main/workspace/store/config.ts ================================================ import {useWorkspaceStore} from './index' export interface IConfigStore { layout: { panelLeft: boolean; panelLeftWidth: number; panelRight: boolean; panelRightWidth: number; }; } export const initConfigStore: IConfigStore = { layout: { panelLeft: true, panelRight: true, panelLeftWidth: 220, panelRightWidth: 300, }, } export const togglePanelRight = () => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelRight: !state.layout.panelRight, }, })) } export const togglePanelLeft = () => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelLeft: !state.layout.panelLeft, }, })) } export const setPanelLeftWidth = (width: number) => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelLeftWidth: width, }, })) } export const setPanelRightWidth = (width: number) => { return useWorkspaceStore.setState((state) => ({ layout: { ...state.layout, panelRightWidth: width, }, })) } ================================================ FILE: chat2db-client/src/pages/main/workspace/store/console.ts ================================================ import { useWorkspaceStore } from './index'; import { IConsole, ICreateConsoleParams } from '@/typings'; import { IWorkspaceTab } from '@/typings/workspace'; import historyService from '@/service/history'; import { ConsoleStatus, WorkspaceTabType } from '@/constants'; import { message } from 'antd'; import i18n from '@/i18n'; export interface IConsoleStore { consoleList: IConsole[] | null; savedConsoleList: IConsole[] | null; activeConsoleId: string | number | null; workspaceTabList: IWorkspaceTab[] | null; createConsoleLoading: boolean } export const initConsoleStore = { consoleList: null, savedConsoleList: null, activeConsoleId: null, workspaceTabList: null, createConsoleLoading: false, }; export const getOpenConsoleList = () => { historyService .getConsoleList({ tabOpened: 'y', pageNo: 1, pageSize: 20, }) .then((res) => { useWorkspaceStore.setState({ consoleList: res?.data }); }); }; export const getSavedConsoleList = () => { historyService .getConsoleList({ pageNo: 1, pageSize: 100, status: ConsoleStatus.RELEASE, }) .then((res) => { useWorkspaceStore.setState({ savedConsoleList: res?.data }); }); } export const setActiveConsoleId = (id: IConsoleStore['activeConsoleId']) => { useWorkspaceStore.setState({ activeConsoleId: id }); }; export const setWorkspaceTabList = (items: IConsoleStore['workspaceTabList']) => { useWorkspaceStore.setState({ workspaceTabList: items }); }; export const createConsole = (params: ICreateConsoleParams) => { const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; const newConsole = { ...params, name: params.name || `untitled-${params.databaseName || params.schemaName} (${params.dataSourceName})`, ddl: params.ddl || '', status: ConsoleStatus.DRAFT, operationType: params.operationType || WorkspaceTabType.CONSOLE, type: params.databaseType, supportDatabase: currentConnectionDetails?.supportDatabase, supportSchema: currentConnectionDetails?.supportSchema, }; return new Promise((resolve) => { if ((workspaceTabList?.length || 0) >= 20) { message.warning(i18n('workspace.tips.maxConsole')); return; } useWorkspaceStore.setState({ createConsoleLoading: true }); historyService.createConsole(newConsole).then((res) => { const newList = [ ...(workspaceTabList || []), { id: res, title: newConsole.name, type: newConsole.operationType, uniqueData: newConsole, }, ]; setWorkspaceTabList(newList); setActiveConsoleId(res); resolve(res); }) .finally(() => { useWorkspaceStore.setState({ createConsoleLoading: false }); }); }); }; export const addWorkspaceTab = (params: IWorkspaceTab) => { const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; if (workspaceTabList?.findIndex((item) => item?.id === params?.id) !== -1) { setActiveConsoleId(params.id); return; } const newList = [...(workspaceTabList || []), params]; setWorkspaceTabList(newList); setActiveConsoleId(params.id); }; ================================================ FILE: chat2db-client/src/pages/main/workspace/store/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools, persist } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; import { initConfigStore, IConfigStore } from './config'; import { initConsoleStore, IConsoleStore } from './console'; import { initCommonStore, ICommonStore } from './common'; import { initModalStore, IModalStore } from './modal'; export type IStore = IConfigStore & IConsoleStore & ICommonStore & IModalStore; export const useWorkspaceStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( persist( () => ({ ...initConsoleStore, ...initConfigStore, ...initCommonStore, ...initModalStore, }), // persist config { name: 'workspace-store', getStorage: () => localStorage, // 工作区的状态只保存 layout布局信息 partialize: (state: IStore) => ({ layout: state.layout, currentConnectionDetails: state.currentConnectionDetails, }), }, ), { name: 'workspaceStore', }, ), shallow, ); ================================================ FILE: chat2db-client/src/pages/main/workspace/store/modal.ts ================================================ import { useWorkspaceStore } from './index'; import { DatabaseTypeCode } from '@/constants'; import { CreateType } from '@/components/CreateDatabase'; export interface IModalStore { openCreateDatabaseModal: ((params: { type: CreateType; relyOnParams: { databaseType: DatabaseTypeCode; dataSourceId: number; databaseName?: string; }; executedCallback?: (status: true) => void; }) => void) | null; } export const initModalStore: IModalStore = { openCreateDatabaseModal: null, }; export const setOpenCreateDatabaseModal = (fn: any) => { useWorkspaceStore.setState({ openCreateDatabaseModal: fn }); }; ================================================ FILE: chat2db-client/src/pages/test/index.less ================================================ .container{ width: 100vw; height: 100vh; } ================================================ FILE: chat2db-client/src/pages/test/index.tsx ================================================ import MonacoEditor from '@/components/MonacoEditor'; import { Button } from 'antd'; import React, { useState } from 'react'; import styles from './index.less'; function Test() { const [value, setValue] = useState('select * from '); return ( <> { setValue(v); }} /> ); } export default Test; ================================================ FILE: chat2db-client/src/service/ai.ts ================================================ import createRequest from './base'; import { IInviteQrCode, ILoginAndQrCode, IRemainingUse } from '@/typings/ai'; const getRemainingUse = createRequest('/api/ai/config/remaininguses', { errorLevel: false, }); const getLoginQrCode = createRequest<{ token?: string }, ILoginAndQrCode>('/api/ai/config/getLoginQrCode'); const getLoginStatus = createRequest<{ token?: string }, ILoginAndQrCode>('/api/ai/config/getLoginStatus', { errorLevel: false, }); const getInviteQrCode = createRequest('/api/ai/config/getInviteQrCode'); export default { getRemainingUse, getLoginQrCode, getLoginStatus, getInviteQrCode }; ================================================ FILE: chat2db-client/src/service/base.ts ================================================ import { extend, ResponseError, type RequestOptionsInit } from 'umi-request'; import { message } from 'antd'; import { navigate } from '@/utils'; export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; export interface IOptions { method?: 'get' | 'post' | 'put' | 'delete'; mock?: boolean; errorLevel?: 'toast' | 'prompt' | 'critical' | false; delayTime?: number | true; outside?: boolean; isFullPath?: boolean; dynamicUrl?: boolean; } // TODO: const codeMessage: { [errorCode: number]: string } = { 200: '服务器成功返回请求的数据。', 201: '新建或修改数据成功。', 202: '一个请求已经进入后台排队(异步任务)。', 204: '删除数据成功。', 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 401: '用户没有权限(令牌、用户名、密码错误)。', 403: '用户得到授权,但是访问是被禁止的。', 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 406: '请求的格式不可得。', 410: '请求的资源被永久删除,且不会再得到的。', 422: '当创建一个对象时,发生一个验证错误。', 500: '服务器发生错误,请检查服务器。', 502: '网关错误。', 503: '服务不可用,服务器暂时过载或维护。', 504: '网关超时。', }; enum ErrorCode { /** 需要登录 */ NEED_LOGGED_IN = 'common.needLoggedIn', } const noNeedToastErrorCode = [ErrorCode.NEED_LOGGED_IN]; // yapi mock地址 const mockUrl = 'https://yapi.com/mock/1000160'; // 桌面端的服务器地址 const desktopServiceUrl = `http://127.0.0.1:${__APP_PORT__ || '10824'}`; // 非桌面端的服务器地址 const prodServiceUrl = location.origin; // 是否自定义了 _BaseURL || 是否为桌面端地址 const baseURL = localStorage.getItem('_BaseURL') || (location.href.indexOf('dist/index.html') > -1 ? desktopServiceUrl : prodServiceUrl); window._BaseURL = baseURL; // window._BaseURL = 'http://127.0.0.1:8000'; const appGatewayParams = localStorage.getItem('app-gateway-params'); // appGateway 的基本信息 if (appGatewayParams) { window._appGatewayParams = JSON.parse(appGatewayParams); } else { window._appGatewayParams = {}; } const outsideUrlPrefix = window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway'; const errorHandler = (error: ResponseError, errorLevel: IErrorLevel) => { const { response } = error; if (!response) return; const errorText = codeMessage[response.status] || response.statusText; const { status } = response; if (errorLevel === 'toast') { // notification.open({ // type: 'error', // message: status, // description: errorText, // placement: 'topRight', // }); message.error(`${status}: ${errorText}`); } }; const request = extend({ // prefix: '/api', credentials: 'include', // 默认请求是否带上cookie headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }); request.interceptors.request.use((url, options) => { const myOptions: any = { ...options, headers: { ...options.headers, }, }; if (localStorage.getItem('Chat2db')) { myOptions.headers.Chat2db = localStorage.getItem('Chat2db'); } return { options: myOptions, }; }); request.interceptors.response.use(async (response) => { const res = await response.clone().json(); if (__ENV__ === 'desktop') { const Chat2db = response.headers.get('Chat2db') || ''; if (Chat2db) { localStorage.setItem('Chat2db', Chat2db); } } const { errorCode } = res; if (errorCode === ErrorCode.NEED_LOGGED_IN) { navigate('/login'); // const callback = window.location.hash.substr(1).split('?')[0]; // window.location.href = '#/login' + (callback === '/login' ? '' : `?callback=${callback}`); } return response; }); export default function createRequest

(url: string, options?: IOptions) { // 路由跳转 const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside, isFullPath, dynamicUrl, } = options || {}; return function (params: P, restParams?: RequestOptionsInit) { // 是否需要mock const _baseURL = (mock ? mockUrl : baseURL) || ''; // if (url === '/api/rdb/ddl/list') { // debugger; // } // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; const _url = url.replace(/:(.+?)\b/, (_, name: string) => { const value = params[name]; paramsInUrl.push(name); return `${value}`; }); if (paramsInUrl.length) { paramsInUrl.forEach((name) => { delete params[name]; }); } return new Promise((resolve, reject) => { let dataName = ''; switch (method) { case 'get': dataName = 'params'; break; case 'delete': dataName = 'params'; break; case 'post': dataName = 'data'; break; case 'put': dataName = 'data'; break; default: dataName = 'params'; break; } let eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; eventualUrl = isFullPath ? url : eventualUrl; // 动态的url if (dynamicUrl) { eventualUrl = params as string; } request[method](eventualUrl, { [dataName]: params, ...restParams }) .then((res) => { if (!res) return; const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { window._notificationApi({ requestUrl: eventualUrl, requestParams: JSON.stringify(params), errorCode, errorMessage, errorDetail, solutionLink, }); // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; } // 有些loading效果添加强制延时效果可能会更好看, 可行性待商榷 delayTimeFn(() => { resolve(data); }, delayTime); }) .catch((error) => { delayTimeFn(() => { errorHandler(error, errorLevel); reject(error); }, delayTime); }); }); }; } // 简单的延时函数 function delayTimeFn(callback: () => void, time: number | true | undefined) { if (time) { const timer = setTimeout(() => { callback(); clearInterval(timer); }, time && 500); } else { callback(); } } ================================================ FILE: chat2db-client/src/service/config.ts ================================================ import { IAiConfig } from '@/typings'; import createRequest from './base'; export interface ILatestVersion { /** * 桌面 */ desktop: boolean; /** * 新版本 */ version: string; /** * 热更新包地址,可用来判断是否热更新 */ hotUpgradeUrl: null | string; /** * 用户选择的是手动更新还是自动更新 */ type: 'manual' | 'auto'; /** * 是否需要更新 */ needUpdate?: boolean; /** * 下载地址 */ downloadLink?: null | string; /** * 更新日志 */ updateLog?: null | string; /** * 白名单,用于测试 */ whiteList?: null | string; } const getSystemConfig = createRequest<{ code: string }, { code: string; content: string }>( '/api/config/system_config/:code', { errorLevel: false }, ); const setSystemConfig = createRequest<{ code: string; content: string }, void>('/api/config/system_config', { errorLevel: 'toast', method: 'post', }); const getAiSystemConfig = createRequest<{ aiSqlSource?: string }, IAiConfig>('/api/config/system_config/ai', { errorLevel: false, }); const setAiSystemConfig = createRequest('/api/config/system_config/ai', { errorLevel: 'toast', method: 'post', }); const getAiWhiteAccess = createRequest<{ apiKey: string }, boolean>('/api/ai/embedding/white/check', { method: 'get', }); // 检测仪更新获取最新的版本信息,如果返回结果为null,说明没有更新 const getLatestVersion = createRequest<{ currentVersion: string }, ILatestVersion>('/api/system/get_latest_version', { method: 'get', }); // 检测最新的包后端是否下载成功 const isUpdateSuccess = createRequest<{ version: string }, boolean>('/api/system/is_update_success', { method: 'get', }); // 告诉后端下载最新的包 const updateDesktopVersion = createRequest('/api/system/update_desktop_version', { method: 'post', }); // 告诉后端下载最新的包 const setAppUpdateType = createRequest('/api/system/set_update_type', { method: 'post', }); export default { getSystemConfig, setSystemConfig, getAiSystemConfig, setAiSystemConfig, getAiWhiteAccess, getLatestVersion, isUpdateSuccess, updateDesktopVersion, setAppUpdateType }; ================================================ FILE: chat2db-client/src/service/connection.ts ================================================ import { IPageResponse, IConnectionDetails, ICreateConnectionDetails, IConnectionEnv, IPageParams, IConnectionListItem } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; export interface IDriverResponse { driverConfigList: { jdbcDriver: string; jdbcDriverClass: string; }[]; defaultDriverConfig: { jdbcDriverClass: string; }; } interface IDriverParams { dbType: DatabaseTypeCode; } interface IUploadDriver { multipartFiles: any; jdbcDriverClass: string; dbType: string; } /** * 查询连接列表 */ const getList = createRequest>( '/api/connection/datasource/list', {}, ); const getDetails = createRequest<{ id: number }, IConnectionDetails>('/api/connection/datasource/:id', {}); const save = createRequest('/api/connection/datasource/create', { method: 'post', delayTime: true, }); const close = createRequest('/api/connection/datasource/close', { method: 'post' }); const test = createRequest('/api/connection/datasource/pre_connect', { method: 'post', delayTime: true, }); const testSSH = createRequest('/api/connection/ssh/pre_connect', { method: 'post', delayTime: true, }); const update = createRequest('/api/connection/datasource/update', { method: 'post' }); const remove = createRequest<{ id: number }, void>('/api/connection/datasource/:id', { method: 'delete' }); const clone = createRequest<{ id: number }, number>('/api/connection/datasource/clone', { method: 'post' }); const getDatabaseList = createRequest<{ dataSourceId: number; refresh?: boolean }, any>('/api/rdb/database/list', { method: 'get', }); const getSchemaList = createRequest<{ dataSourceId: number; databaseName?: string; refresh?: boolean }, any>( '/api/rdb/schema/list', { method: 'get' }, ); const getDriverList = createRequest('/api/jdbc/driver/list', { errorLevel: false, method: 'get', }); const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver/download', { method: 'get', }); const saveDriver = createRequest('/api/jdbc/driver/save', { method: 'post' }); const getEnvList = createRequest('/api/common/environment/list_all', { errorLevel: false }); /** 导入Navicat链接 */ // const importNavicatConnection = createRequest< // { // formData: FormData; // }, // void // >('/api/converter/ncx/upload', { // method: 'post', // }); export default { getEnvList, getList, getDetails, save, test, update, remove, clone, getDatabaseList, getSchemaList, close, testSSH, getDriverList, downloadDriver, saveDriver, // importNavicatConnection, }; ================================================ FILE: chat2db-client/src/service/dashboard.ts ================================================ import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import createRequest from './base'; /** 获取报表列表 */ const getDashboardList = createRequest<{}, IPageResponse>('/api/dashboard/list', { method: 'get' }); const getDashboardById = createRequest<{ id: number }, IDashboardItem>('/api/dashboard/:id', { method: 'get' }); /** 创建报表 */ const createDashboard = createRequest<{ name: string; description: string; schema?: string; chartId?: number[] }, void>( '/api/dashboard/create', { method: 'post' }, ); /** 更新报表 */ const updateDashboard = createRequest('/api/dashboard/update', { method: 'post' }); /** 删除报表 */ const deleteDashboard = createRequest<{ id: number }, string>('/api/dashboard/:id', { method: 'delete' }); /** 根据id 查询图表详情 */ const getChartById = createRequest<{ id: number }, IChartItem>('/api/chart/:id', { method: 'get' }); /** 创建图表 */ const createChart = createRequest('/api/chart/create', { method: 'post' }); /** 更新图表 */ const updateChart = createRequest('/api/chart/update', { method: 'post' }); /** 删除图表 */ const deleteChart = createRequest<{ id: number }, string>('/api/chart/:id', { method: 'delete' }); export { getDashboardList, getDashboardById, createDashboard, updateDashboard, deleteDashboard, getChartById, createChart, updateChart, deleteChart, }; ================================================ FILE: chat2db-client/src/service/history.ts ================================================ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants' import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings'; export interface IGetSavedListParams extends IPageParams { tabOpened?: 'y' | 'n'; status?: ConsoleStatus } export interface IGetHistoryListParams extends IPageParams { dataSourceId?: number; databaseName?: string; } export interface ISaveBasicInfo { name: string; type: DatabaseTypeCode; ddl: string; dataSourceId: number; databaseName: string; } export interface IUpdateConsoleParams { id: number; } export interface IHistoryRecord { /** * 是否可连接 */ connectable?: boolean | null; /** * DB名称 */ databaseName?: null | string; /** * 数据源id */ dataSourceId?: number | null; /** * 数据源名称 */ dataSourceName?: null | string; /** * ddl内容 */ ddl?: null | string; /** * 扩展信息 */ extendInfo?: null | string; /** * 主键 */ id?: number | null; /** * 文件别名 */ name?: null | string; /** * 操作行数 */ operationRows?: number | null; /** * schema名称 */ schemaName?: null | string; /** * 状态 */ status?: null | string; /** * ddl语言类型 */ type?: DatabaseTypeCode | null; /** * 使用时长 */ useTime?: number | null; /** * 创建时间 */ gmtCreate: string; } const createConsole = createRequest('/api/operation/saved/create', { method: 'post' }); // orderByDesc true 降序 const getWindowTab = createRequest<{ id: number, orderByDesc: boolean }, number>('/api/operation/saved/:id', { method: 'get' }); const updateSavedConsole = createRequest & {id: number}, number>('/api/operation/saved/update', { method: 'post' }); const getConsoleList = createRequest>('/api/operation/saved/list', {}); const deleteSavedConsole = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); const createHistory = createRequest('/api/operation/log/create', { method: 'post' }); const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { getConsoleList, updateSavedConsole, getHistoryList, createConsole, deleteSavedConsole, createHistory, getWindowTab } ================================================ FILE: chat2db-client/src/service/misc.tsx ================================================ import createRequest from './base'; const testService = createRequest('/api/system', { errorLevel: false }); const systemStop = createRequest('/api/system/stop', { errorLevel: false, method: 'post' }); const testApiSmooth = createRequest('/api/system/get-version-a', { errorLevel: false, method: 'get' }); export default { testService, systemStop, testApiSmooth, }; ================================================ FILE: chat2db-client/src/service/outside.ts ================================================ import createRequest from './base'; const dynamicUrl = createRequest('', { dynamicUrl: true, }); export default { dynamicUrl, }; ================================================ FILE: chat2db-client/src/service/sql.ts ================================================ import createRequest from './base'; import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseSupportField, IEditTableInfo, ITable, ISequenceInfo, } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; export interface IGetTableListParams extends IPageParams { dataSourceId: number; databaseName: string; schemaName?: string; databaseType?: DatabaseTypeCode; } export interface IExecuteSqlParams { sql?: string; consoleId?: number; dataSourceId?: number; databaseName?: string; schemaName?: string | null; tableName?: string; pageNo?: number; pageSize?: number; } export interface IExecuteSqlResponse { sql: string; description: string; message: string; success: boolean; headerList: any[]; dataList: any[]; } export interface IConnectConsoleParams { consoleId: number; dataSourceId: number; databaseName: string; } const getTableList = createRequest>('/api/rdb/table/list', { method: 'get' }); const executeSql = createRequest('/api/rdb/dml/execute', { method: 'post', delayTime: 10 }); const viewTable = createRequest('/api/rdb/dml/execute_table', { method: 'post', delayTime: 10 }); const connectConsole = createRequest('/api/connection/console/connect', { method: 'get' }); //表操作 export interface ITableParams { Name: string; dataSourceId: number; databaseName: string; schemaName?: string; } export interface IExecuteTableParams { sql: string; consoleId: number; dataSourceId: number; databaseName: string; } export interface IColumn { name: string; dataType: string; columnType: string; // 列的类型 比如 varchar(100) ,double(10,6) nullable: boolean; primaryKey: boolean; defaultValue: string; autoIncrement: boolean; numericPrecision: number; numericScale: number; characterMaximumLength: number; comment: string; } export interface ISchemaParams { dataSourceId: number; databaseName: string; } export interface ISchemaResponse { name: string; } export interface MetaSchemaVO { databases?: Database[]; schemas?: Schema[]; } export interface Database { name: string; schemas?: Schema[]; } export interface Schema { name: string; } const deleteTable = createRequest('/api/rdb/ddl/delete', { method: 'post' }); const deleteSequence = createRequest('/api/rdb/sequence/delete', { method: 'post' }); const createTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/create/example', { method: 'get', }); const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/update/example', { method: 'get', }); const exportCreateTableSql = createRequest('/api/rdb/ddl/export', { method: 'get' }); const executeTable = createRequest('/api/rdb/ddl/execute', { method: 'post' }); const getColumnList = createRequest('/api/rdb/ddl/column_list', { method: 'get', delayTime: 200, }); const getIndexList = createRequest('/api/rdb/ddl/index_list', { method: 'get', delayTime: 200, }); const getSequenceList = createRequest('/api/rdb/sequence/list', { method: 'get', delayTime: 200, }); const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get', delayTime: 200 }); const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get', delayTime: 200, }); const getDatabaseSchemaList = createRequest<{ dataSourceId: number }, MetaSchemaVO>( '/api/rdb/ddl/database_schema_list', { method: 'get' }, ); const addTablePin = createRequest('/api/pin/table/add', { method: 'post' }); const deleteTablePin = createRequest('/api/pin/table/delete', { method: 'post' }); /** 获取当前执行SQL 所有行 */ const getDMLCount = createRequest('/api/rdb/dml/count', { method: 'post' }); export interface IExportParams extends IExecuteSqlParams { originalSql: string; exportType: ExportTypeEnum; exportSize: ExportSizeEnum; } /** * 导出-表格 */ // const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); const exportCreateSequenceSql = createRequest('/api/rdb/sequence/export', { method: 'get' }); /** 获取视图列表 */ const getViewList = createRequest>('/api/rdb/view/list', { method: 'get', }); /** 获取函数列表 */ const getFunctionList = createRequest>('/api/rdb/function/list', { method: 'get', }); /** 获取触发器列表 */ const getTriggerList = createRequest>('/api/rdb/trigger/list', { method: 'get', }); /** 获取过程列表 */ const getProcedureList = createRequest>('/api/rdb/procedure/list', { method: 'get', }); /** 获取视图列列表 */ const getViewColumnList = createRequest>('/api/rdb/view/column_list', { method: 'get', }); /** 获取视图详情 */ const getViewDetail = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string; tableName: string; }, { ddl: string } >('/api/rdb/view/detail', { method: 'get' }); /** 获取触发器详情 */ const getTriggerDetail = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string; triggerName: string; }, { triggerBody: string } >('/api/rdb/trigger/detail', { method: 'get' }); /** 获取函数详情 */ const getFunctionDetail = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string; functionName: string; }, { functionBody: string } >('/api/rdb/function/detail', { method: 'get' }); /** 获取过程详情 */ const getProcedureDetail = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string; procedureName: string; }, { procedureBody: string } >('/api/rdb/procedure/detail', { method: 'get' }); /** 格式化sql */ const sqlFormat = createRequest< { sql: string; dbType: DatabaseTypeCode; }, string >('/api/sql/format', { method: 'get' }); /** 数据库支持的数据类型 */ const getDatabaseFieldTypeList = createRequest< { dataSourceId: number; databaseName: string; }, IDatabaseSupportField >('/api/rdb/table/table_meta', { method: 'get' }); /** 获取表的详情 */ const getTableDetails = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName: string; refresh: boolean; }, IEditTableInfo >('/api/rdb/table/query', { method: 'get' }); /** 获取库的所有表 */ const getAllTableList = createRequest< { dataSourceId: number; databaseName?: string | null; schemaName?: string | null }, Array<{ name: string; comment: string }> >('/api/rdb/table/table_list', { method: 'get' }); /** 获取表的所有字段 */ const getAllFieldByTable = createRequest< { dataSourceId: number; databaseName?: string; schemaName?: string | null; tableName: string }, Array<{ name: string; tableName: string }> >('/api/rdb/table/column_list', { method: 'get' }); export interface IModifyTableSqlParams { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName?: string; oldTable?: IEditTableInfo; newTable: IEditTableInfo; refresh: boolean; } /** 获取修改表的sql */ const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post', }); export interface IModifySequenceSqlParams { dataSourceId: number; databaseName: string; schemaName?: string | null; sequenceName?: string; oldSequence?: ISequenceInfo; newSequence: ISequenceInfo; refresh: boolean; } /** 获取序列的详情 */ const getSequenceDetails = createRequest< { dataSourceId: number; databaseName: string; schemaName?: string | null; sequenceName: string; refresh: boolean; }, ISequenceInfo >('/api/rdb/sequence/query', { method: 'get' }); /** * 获取修改序列的sql */ const getModifySequenceSql = createRequest('/api/rdb/sequence/modify/sql', { method: 'post', }); /** 执行编辑表的sql, 专为编辑表而生 */ const executeDDL = createRequest( '/api/rdb/dml/execute_ddl', { method: 'post' }, ); // 执行修改表数据的sql const executeUpdateDataSql = createRequest( '/api/rdb/dml/execute_update', { method: 'post' }, ); /** 获取修改表数据的接口 */ const getExecuteUpdateSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); /** 创建数据库 */ const getCreateDatabaseSql = createRequest<{ dataSourceId: number; databaseName: string; }, { sql: string }>('/api/rdb/database/create_database_sql', { method: 'post' }); /** 创建schema */ const getCreateSchemaSql = createRequest<{ dataSourceId: number; databaseName?: string; schemaName?: string; }, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' }); /** 查询数据库用户名列表 */ const getDatabaseUserNameList = createRequest<{ dataSourceId: number; databaseName: string; schemaName?: string | null; refresh: boolean; },{sql:[]}>('/api/rdb/database/database_username_list', { method: 'get' }); export default { getCreateSchemaSql, getCreateDatabaseSql, executeUpdateDataSql, executeDDL, getExecuteUpdateSql, getModifyTableSql, getTableDetails, getDatabaseFieldTypeList, sqlFormat, getTriggerDetail, getProcedureDetail, getFunctionDetail, getViewDetail, getViewColumnList, getProcedureList, getTriggerList, getFunctionList, getViewList, getTableList, executeSql, executeTable, connectConsole, deleteTable, createTableExample, updateTableExample, exportCreateTableSql, viewTable, getColumnList, getIndexList, getKeyList, getSchemaList, getDatabaseSchemaList, addTablePin, deleteTablePin, getDMLCount, // exportResultTable getAllTableList, getAllFieldByTable, getSequenceList, exportCreateSequenceSql, getModifySequenceSql, getSequenceDetails, deleteSequence, getDatabaseUserNameList, }; ================================================ FILE: chat2db-client/src/service/team.ts ================================================ import createRequest from './base'; import { IConnectionDetails, IPageParams, IPageResponse } from '@/typings'; import { IDataSourceAccessObjectVO, IDataSourceVO, ITeamAndUserVO, ITeamVO, ITeamWithDataSourceVO, ITeamWithUserVO, IUserVO, IUserWithDataSourceVO, IUserWithTeamVO, RoleType } from '@/typings/team'; // =============================== DataSource ============================ /** * 链接-获取共享链接列表 */ const getDataSourceList = createRequest>('/api/admin/data_source/page', { method: 'get', }); /** * 链接-创建链接 */ const createDataSource = createRequest('/api/admin/data_source/create', { method: 'post', }); /** * 链接-更新链接 */ const updateDataSource = createRequest('/api/admin/data_source/update', { method: 'post', }); /** * 链接-删除链接 */ const deleteDataSource = createRequest<{ id: number }, boolean>('/api/admin/data_source/:id', { method: 'delete', }); /** * 链接-获取链接包含的团队/用户列表 */ const getUserAndTeamListFromDataSource = createRequest< IPageParams & { dataSourceId: number }, IPageResponse >('/api/admin/data_source/access/page', { method: 'get', }); /** * 链接-添加团队/人员权限到共享链接 */ const updateUserAndTeamListFromDataSource = createRequest< { dataSourceId: number; accessObjectList: Array<{ id: number; type: RoleType }> }, number >('/api/admin/data_source/access/batch_create', { method: 'post', }); /** * 链接-删除团队/人员权限到共享链接 */ const deleteUserOrTeamFromDataSource = createRequest<{ id: number }, boolean>('/api/admin/data_source/access/:id', { method: 'delete', }); // ====================== User ====================== /** 用户-用户管理列表查询 */ const getUserManagementList = createRequest>('/api/admin/user/page', { method: 'get', }); /** 创建用户 */ const createUser = createRequest('/api/admin/user/create', { method: 'post', }); /** 更新用户 */ const updateUser = createRequest('/api/admin/user/update', { method: 'post', }); /** 删除用户 */ const deleteUser = createRequest<{ id: number }, boolean>('/api/admin/user/:id', { method: 'delete', }); /** 用户-用户管理中获取所属团队列表 */ const getTeamListFromUser = createRequest>( '/api/admin/user/team/page', { method: 'get', }, ); /** 用户-用户管理中更新所属团队 */ const updateTeamListFromUser = createRequest<{ userId: number; teamIdList: number[] }, number>( '/api/admin/user/team/batch_create', { method: 'post', } ); /** 用户-用户管理中删除所属团队 */ const deleteTeamListFromUser = createRequest<{ id: number }, boolean>('/api/admin/user/team/:id', { method: 'delete', }); /** 用户-用户管理中添加链接 */ const getDataSourceListFromUser = createRequest< IPageParams & { userId: number }, IPageResponse >('/api/admin/user/data_source/page', { method: 'get', }); /** 用户-用户管理中更新链接 */ const updateDataSourceListFromUser = createRequest< { userId: number; dataSourceIdList: number[] }, number>( '/api/admin/user/data_source/batch_create', { method: 'post', }); /** 用户-用户管理中删除链接 */ const deleteDataSourceFromUser = createRequest<{ id: number }, boolean>('/api/admin/user/data_source/:id', { method: 'delete', }); // ======================== 团队 ====================== /** 团队-团队管理列表查询 */ const getTeamManagementList = createRequest>('/api/admin/team/page', { method: 'get', }); /** 团队-创建团队 */ const createTeam = createRequest('/api/admin/team/create', { method: 'post', }); /** 团队-更新团队 */ const updateTeam = createRequest('/api/admin/team/update', { method: 'post', }); /** 团队-删除团队 */ const deleteTeam = createRequest<{ id: number }, boolean>('/api/admin/team/:id', { method: 'delete', }); /** 团队-团队管理中获取包含用户列表 */ const getUserListFromTeam = createRequest>( '/api/admin/team/user/page', { method: 'get', }, ); /** 团队-团队管理中更新包含用户列表 */ const updateUserListFromTeam = createRequest<{ teamId: number; userIdList: number[] }, number>( '/api/admin/team/user/batch_create', { method: 'post', }, ); /** 团队-团队管理中删除包含用户列表 */ const deleteUserFromTeam = createRequest<{ id: number }, boolean>('/api/admin/team/user/:id', { method: 'delete', }); /** 用户-用户管理中添加归属链接 */ const getDataSourceListFromTeam = createRequest< IPageParams & { userId: number }, IPageResponse >('/api/admin/team/data_source/page', { method: 'get', }); /** 用户-用户管理中更新归属链接 */ const updateDataSourceListFromTeam = createRequest< { userId: number; dataSourceIdList: number[] }, number >('/api/admin/team/data_source/batch_create', { method: 'post', }); /** 用户-用户管理中删除所属团队 */ const deleteDataSourceFromTeam = createRequest<{ id: number }, boolean>('/api/admin/team/data_source/:id', { method: 'delete', }); // ======================= 通用列表 ===================== /** 通用-获取user列表 */ const getCommonUserList = createRequest<{ searchKey: string }, IUserVO[]>('/api/admin/common/user/list', { method: 'get', }); /** 通用-获取team列表 */ const getCommonTeamList = createRequest<{ searchKey: string }, ITeamVO[]>('/api/admin/common/team/list', { method: 'get', }); /** 通用-获取DataSource列表 */ const getCommonDataSourceList = createRequest<{ searchKey: string }, IDataSourceVO[]>( '/api/admin/common/data_source/list', { method: 'get', }, ); /** 通用-获取user和team列表 */ const getCommonUserAndTeamList = createRequest<{ searchKey: string }, ITeamAndUserVO[]>( '/api/admin/common/team_user/list', { method: 'get', }, ); export { // dataSource getDataSourceList, createDataSource, updateDataSource, deleteDataSource, getUserAndTeamListFromDataSource, updateUserAndTeamListFromDataSource, deleteUserOrTeamFromDataSource, // user getUserManagementList, createUser, updateUser, deleteUser, getTeamListFromUser, updateTeamListFromUser, deleteTeamListFromUser, getDataSourceListFromUser, updateDataSourceListFromUser, deleteDataSourceFromUser, // team getTeamManagementList, createTeam, updateTeam, deleteTeam, getUserListFromTeam, updateUserListFromTeam, deleteUserFromTeam, getDataSourceListFromTeam, updateDataSourceListFromTeam, deleteDataSourceFromTeam, // common getCommonUserList, getCommonTeamList, getCommonDataSourceList, getCommonUserAndTeamList, }; ================================================ FILE: chat2db-client/src/service/user.ts ================================================ import createRequest from './base'; import { IPageParams, IPageResponse } from '@/typings'; import { IUserVO, IUser } from '@/typings/user'; /** 用户登录接口 */ const userLogin = createRequest<{ userName: string; password: string }, boolean>('/api/oauth/login_a', { method: 'post', }); /** 用户登出 */ const userLogout = createRequest('/api/oauth/logout_a', { method: 'post', }); /** 获取用户信息 */ const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); /** 获取用户列表信息 */ const getUserList = createRequest>('/api/user/list', { method: 'get', }); /** 创建新用户 */ const createUser = createRequest('/api/user/create', { method: 'post', }); /** 更新用户信息 */ const updateUser = createRequest('/api/user/update', { method: 'post', }); /** 查询用户 */ const queryUserById = createRequest<{ id: number }>('/api/user/:id', { method: 'get', }); /** 删除用户 */ const deleteUser = createRequest<{ id: number }>('/api/user/:id', { method: 'delete', }); export { createUser, updateUser, queryUserById, deleteUser, getUserList, userLogin, userLogout, getUser }; ================================================ FILE: chat2db-client/src/store/common/appTitleBarConfig.ts ================================================ import React from 'react'; import { useCommonStore } from './index'; export interface IAppTitleBarConfig { appTitleBarRightComponent: React.ReactNode | null; } export const initAppTitleBarConfig = { appTitleBarRightComponent: null, }; export const setAppTitleBarRightComponent: (appTitleBarRightComponent: React.ReactNode | null) => void = ( appTitleBarRightComponent, ) => { return useCommonStore.setState({ appTitleBarRightComponent }); }; ================================================ FILE: chat2db-client/src/store/common/components.ts ================================================ import { useCommonStore } from './index'; import { IModalData } from '@/components/Modal/BaseModal'; export interface IComponentsContent { openModal: ((params: IModalData) => void) | null; } export const initComponentsContent = { openModal: null, }; export const injectOpenModal = (openModal: IComponentsContent['openModal']) => { return useCommonStore.setState({ openModal }); }; export const openModal = (modal: IModalData) => { return useCommonStore.getState().openModal?.(modal); }; ================================================ FILE: chat2db-client/src/store/common/copyFocusedContent.ts ================================================ import {useCommonStore} from './index' export interface ICopyFocusedContent { focusedContent: any[][]| any[] | string | null; } export const initCopyFocusedContent = { focusedContent: null, } export const setFocusedContent: (content: any[][] | any[] | string | null) => void = (focusedContent) => { return useCommonStore.setState({focusedContent}) } ================================================ FILE: chat2db-client/src/store/common/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; import { initCopyFocusedContent, ICopyFocusedContent } from './copyFocusedContent'; import { initComponentsContent, IComponentsContent } from './components'; import { initAppTitleBarConfig, IAppTitleBarConfig } from './appTitleBarConfig'; export type IStore = ICopyFocusedContent & IComponentsContent & IAppTitleBarConfig; export const useCommonStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( () => ({ ...initCopyFocusedContent, ...initComponentsContent, ...initAppTitleBarConfig }), ), shallow ); ================================================ FILE: chat2db-client/src/store/config/index.ts ================================================ import { StoreApi } from 'zustand'; import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; export interface IConfigStore { curRoute: string; } const initConfigStore: IConfigStore = { curRoute: '/', }; /** * 配置 store */ export const useConfigStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => initConfigStore), shallow, ); /** * * @param curRoute 设置当前路由 */ export const setCurRoute = (curRoute: string) => { useConfigStore.setState({ curRoute }); } ================================================ FILE: chat2db-client/src/store/monaco/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; // 表信息 export interface tableItem { columnList: { columnName?: string; columnType?: string; }[]; } // schema信息 export interface schemaItem { tableList?: tableItem[]; } // 数据库信息 export type databaseItem = { schemaList?: schemaItem[]; } | { databaseName: string; tableList: tableItem[]; } // monaco store export interface IMonacoStore { registerProvider: { // 数据源id [key: number]: databaseItem[] } } const initMonacoStore = { registerProvider: {} } export const useMonacoStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => (initMonacoStore)), shallow ); export const setRegisterProvider = (id: number, data: databaseItem[]) => { useMonacoStore.getState().registerProvider[id] = data; } ================================================ FILE: chat2db-client/src/store/setting/index.ts ================================================ import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools, persist } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; import { message } from 'antd'; import i18n from '@/i18n'; import { IAiConfig } from '@/typings/setting'; import { IRemainingUse, AIType } from '@/typings/ai'; import configService from '@/service/config'; import aiService from '@/service/ai'; export interface ISettingState { aiConfig: IAiConfig; remainingUse?: IRemainingUse; hasWhite: boolean; holdingService: boolean; } const initSetting = { remainingUse: undefined, aiConfig: { aiSqlSource: AIType.CHAT2DBAI, }, hasWhite: false, holdingService: false, } export const useSettingStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( persist( () => (initSetting), { name: 'global-setting', getStorage: () => localStorage, // 工作区的状态只保存 layout布局信息 partialize: (state: ISettingState) => ({ holdingService: state.holdingService, }), }, ), ), shallow ); export const setAiConfig = (aiConfig: IAiConfig) => { useSettingStore.setState({ aiConfig }); } export const setRemainUse = (remainingUse?: IRemainingUse) => { useSettingStore.setState({ remainingUse }); } export const setAiWithWhite = (hasWhite: boolean) => { useSettingStore.setState({ hasWhite }); } export const updateAiWithWhite = (apiKey: string) => { configService.getAiWhiteAccess({ apiKey: apiKey ?? '' }).then((res) => { setAiWithWhite(res); }); } export const getAiSystemConfig = () => { configService.getAiSystemConfig({}).then((res) => { setAiConfig(res); if (res?.aiSqlSource === AIType.CHAT2DBAI && res.apiKey) { updateAiWithWhite(res.apiKey); } }); } export const setAiSystemConfig = (aiConfig) => { configService.setAiSystemConfig(aiConfig).then(() => { message.success(i18n('common.text.submittedSuccessfully')); setAiConfig(aiConfig); }) if (aiConfig?.aiSqlSource === AIType.CHAT2DBAI) { updateAiWithWhite(aiConfig?.apiKey); } else { setAiWithWhite(false); } } export const fetchRemainingUse = (apiKey)=>{ const currentState = useSettingStore.getState(); if (!apiKey || currentState.aiConfig.aiSqlSource !== AIType.CHAT2DBAI) { setRemainUse(undefined); return; } aiService.getRemainingUse().then((res) => { setRemainUse(res); }) } export const setHoldingService = (holdingService: boolean) => { useSettingStore.setState({ holdingService }); } ================================================ FILE: chat2db-client/src/store/user/index.ts ================================================ import { StoreApi } from 'zustand'; import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; import { shallow } from 'zustand/shallow'; import { IUserVO } from '@/typings/user'; import { getUser } from '@/service/user'; export interface IUserStore { curUser?: IUserVO | null; } const initUserStore: IUserStore = { curUser: null, }; /** * 用户 store */ export const useUserStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => initUserStore), shallow, ); /** * * @param curUser 设置当前用户 */ export const setCurUser = (curUser?: IUserVO) => { useUserStore.setState({ curUser }); }; /** * 获取当前用户 */ export const queryCurUser = async () => { // null 表示在padding,返回 void 0(undefined)表示未登录 const curUser = await getUser() || void 0; useUserStore.setState({ curUser }); // 向cookie中写入当前用户id const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.USER_ID=${curUser?.id};Expires=${date}`; return curUser }; ================================================ FILE: chat2db-client/src/styles/antd.less ================================================ :root { :global { // 覆盖antd 的一些样式 button { box-shadow: none !important; } // There is some animation when switching the theme color causing a delay in switching background .ant-input, .ant-input-password { transition: all 0.2s, background-color 0s; } .ant-btn { transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s; } .ant-select-single:not(.ant-select-customize-input) .ant-select-selector { transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s; } .ant-modal-header { border-bottom: 0px; } // .ant-modal-content { // background-color: var(--color-bg-elevated) !important; // .ant-modal-confirm-title { // color: var(--color-text) !important; // } // .ant-modal-confirm-content { // color: var(--color-text) !important; // } // } .ant-modal-footer { border-top: 0px; padding: 8px; padding-top: 0px; } // .ant-notification-notice { // background-color: var(--color-bg-elevated) !important; // padding: 8px 8px 8px 16px !important; // width: 280px !important; // .ant-notification-notice-icon { // font-size: 16px !important; // top: 12px !important; // } // .ant-notification-notice-message { // font-size: 14px !important; // color: var(--color-text) !important; // } // .ant-notification-notice-close { // font-size: 12px !important; // color: var(--color-text) !important; // width: 16px !important; // height: 16px !important; // top: 10px !important; // } // } .ant-dropdown{ .ant-dropdown-menu { height: auto; max-height: 50vh; // overflow: hidden ; // &:hover { // } overflow: auto; } } .ant-btn-link { color: var(--color-primary); } .ant-btn-link:not(:disabled):not(.ant-btn-disabled):hover { color: var(--color-primary-hover); } } } ================================================ FILE: chat2db-client/src/styles/common.less ================================================ ================================================ FILE: chat2db-client/src/styles/global.less ================================================ @import '../theme/custom/dark.less'; @import '../theme/custom/light.less'; @import './antd.less'; @font-face { font-family: 'HarmonyOS_Sans'; src: url('../assets/font/HarmonyOS_Sans_Regular.woff2') format('woff2'); } html, body { height: 100%; color: var(--color-text); font-size: var(--font-size); background-color: var(--color-bg-base); font-family: 'HarmonyOS_Sans', 'Segoe UI', 'SF Pro Display', -apple-system, BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, 'HarmonyOS Sans SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft Yahei UI', 'Microsoft Yahei', 'Source Han Sans CN', sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol', 'Apple Color Emoji', 'Twemoji Mozilla', 'Noto Color Emoji', 'Android Emoji'; } // 修改账号密码自动回填后input背景变色问题 input:-webkit-autofill { -webkit-text-fill-color: var(--color-text) !important; // 填充状态下的字体颜色 transition: background-color 0s 9999999999s !important; caret-color: var(--color-text) !important; // 光标的颜色 } * { ::-webkit-scrollbar { cursor: pointer; width: 4px; height: 4px; background-color: transparent; } ::-webkit-scrollbar-thumb { cursor: pointer; background-color: transparent; border-radius: 2px; // transition: background-color 500ms ${token.motionEaseOut}; &:hover { background-color: var(--color-text); } } ::-webkit-scrollbar-corner { display: none; width: 0; height: 0; } &:hover { ::-webkit-scrollbar-thumb { background-color: var(--color-fill); } } } html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; } ul, ol, li { list-style: none; } ================================================ FILE: chat2db-client/src/styles/var.less ================================================ .f-single-line() { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .f-lines(@lines) { word-break: break-word; display: -webkit-box; -webkit-box-orient: vertical; box-orient: vertical; -webkit-line-clamp: @lines; line-clamp: @lines; overflow: hidden; text-overflow: ellipsis; } .f-icon-button { display: flex; justify-content: center; align-items: center; height: 32px; width: 32px; border-radius: 4px; cursor: pointer; color: var(--color-text-45); &:hover { background-color: var(--color-bg-hover); } i { font-size: 22px; } } .f-fill-absolute { position: absolute; top: 0; right: 0; left: 0; bottom: 0; } .f-button { display: flex !important; align-items: center; span { font-size: 12px !important; font-weight: 400 !important; } } // 文档英文强制换行 .f-doc-en-break { word-break: break-all; } @keyframes loading-animation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ================================================ FILE: chat2db-client/src/theme/abandon/demo/dark.less ================================================ html[theme='dark'], html[primary-color='polar-blue'] { --blue: #1677ff; --purple: #722ed1; --cyan: #13c2c2; --green: #52c41a; --magenta: #eb2f96; --pink: #eb2f96; --red: #f5222d; --orange: #fa8c16; --yellow: #fadb14; --volcano: #fa541c; --geekblue: #2f54eb; --gold: #faad14; --lime: #a0d911; --color-primary: #1668dc; --color-success: #49aa19; --color-warning: #d89614; --color-error: #dc4446; --color-info: #1668dc; --color-text-base: #fff; --color-bg-base: #0a0b0c; --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-family-code: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; --font-size: 12; --line-width: 1; --line-type: solid; --motion-unit: 0.1; --motion-base: 0; --motion-ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); --motion-ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); --motion-ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); --motion-ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); --motion-ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); --motion-ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); --motion-ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); --motion-ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); --border-radius: 4; --size-unit: 4; --size-step: 4; --size-popup-arrow: 16; --control-height: 28; --z-index-base: 0; --z-index-popup-base: 1000; --opacity-image: 1; --wireframe: true; --motion: true; --blue-1: #111a2c; --blue1: #111a2c; --blue-2: #112545; --blue2: #112545; --blue-3: #15325b; --blue3: #15325b; --blue-4: #15417e; --blue4: #15417e; --blue-5: #1554ad; --blue5: #1554ad; --blue-6: #1668dc; --blue6: #1668dc; --blue-7: #3c89e8; --blue7: #3c89e8; --blue-8: #65a9f3; --blue8: #65a9f3; --blue-9: #8dc5f8; --blue9: #8dc5f8; --blue-10: #b7dcfa; --blue10: #b7dcfa; --purple-1: #1a1325; --purple1: #1a1325; --purple-2: #24163a; --purple2: #24163a; --purple-3: #301c4d; --purple3: #301c4d; --purple-4: #3e2069; --purple4: #3e2069; --purple-5: #51258f; --purple5: #51258f; --purple-6: #642ab5; --purple6: #642ab5; --purple-7: #854eca; --purple7: #854eca; --purple-8: #ab7ae0; --purple8: #ab7ae0; --purple-9: #cda8f0; --purple9: #cda8f0; --purple-10: #ebd7fa; --purple10: #ebd7fa; --cyan-1: #112123; --cyan1: #112123; --cyan-2: #113536; --cyan2: #113536; --cyan-3: #144848; --cyan3: #144848; --cyan-4: #146262; --cyan4: #146262; --cyan-5: #138585; --cyan5: #138585; --cyan-6: #13a8a8; --cyan6: #13a8a8; --cyan-7: #33bcb7; --cyan7: #33bcb7; --cyan-8: #58d1c9; --cyan8: #58d1c9; --cyan-9: #84e2d8; --cyan9: #84e2d8; --cyan-10: #b2f1e8; --cyan10: #b2f1e8; --green-1: #162312; --green1: #162312; --green-2: #1d3712; --green2: #1d3712; --green-3: #274916; --green3: #274916; --green-4: #306317; --green4: #306317; --green-5: #3c8618; --green5: #3c8618; --green-6: #49aa19; --green6: #49aa19; --green-7: #6abe39; --green7: #6abe39; --green-8: #8fd460; --green8: #8fd460; --green-9: #b2e58b; --green9: #b2e58b; --green-10: #d5f2bb; --green10: #d5f2bb; --magenta-1: #291321; --magenta1: #291321; --magenta-2: #40162f; --magenta2: #40162f; --magenta-3: #551c3b; --magenta3: #551c3b; --magenta-4: #75204f; --magenta4: #75204f; --magenta-5: #a02669; --magenta5: #a02669; --magenta-6: #cb2b83; --magenta6: #cb2b83; --magenta-7: #e0529c; --magenta7: #e0529c; --magenta-8: #f37fb7; --magenta8: #f37fb7; --magenta-9: #f8a8cc; --magenta9: #f8a8cc; --magenta-10: #fad2e3; --magenta10: #fad2e3; --pink-1: #291321; --pink1: #291321; --pink-2: #40162f; --pink2: #40162f; --pink-3: #551c3b; --pink3: #551c3b; --pink-4: #75204f; --pink4: #75204f; --pink-5: #a02669; --pink5: #a02669; --pink-6: #cb2b83; --pink6: #cb2b83; --pink-7: #e0529c; --pink7: #e0529c; --pink-8: #f37fb7; --pink8: #f37fb7; --pink-9: #f8a8cc; --pink9: #f8a8cc; --pink-10: #fad2e3; --pink10: #fad2e3; --red-1: #2a1215; --red1: #2a1215; --red-2: #431418; --red2: #431418; --red-3: #58181c; --red3: #58181c; --red-4: #791a1f; --red4: #791a1f; --red-5: #a61d24; --red5: #a61d24; --red-6: #d32029; --red6: #d32029; --red-7: #e84749; --red7: #e84749; --red-8: #f37370; --red8: #f37370; --red-9: #f89f9a; --red9: #f89f9a; --red-10: #fac8c3; --red10: #fac8c3; --orange-1: #2b1d11; --orange1: #2b1d11; --orange-2: #442a11; --orange2: #442a11; --orange-3: #593815; --orange3: #593815; --orange-4: #7c4a15; --orange4: #7c4a15; --orange-5: #aa6215; --orange5: #aa6215; --orange-6: #d87a16; --orange6: #d87a16; --orange-7: #e89a3c; --orange7: #e89a3c; --orange-8: #f3b765; --orange8: #f3b765; --orange-9: #f8cf8d; --orange9: #f8cf8d; --orange-10: #fae3b7; --orange10: #fae3b7; --yellow-1: #2b2611; --yellow1: #2b2611; --yellow-2: #443b11; --yellow2: #443b11; --yellow-3: #595014; --yellow3: #595014; --yellow-4: #7c6e14; --yellow4: #7c6e14; --yellow-5: #aa9514; --yellow5: #aa9514; --yellow-6: #d8bd14; --yellow6: #d8bd14; --yellow-7: #e8d639; --yellow7: #e8d639; --yellow-8: #f3ea62; --yellow8: #f3ea62; --yellow-9: #f8f48b; --yellow9: #f8f48b; --yellow-10: #fafab5; --yellow10: #fafab5; --volcano-1: #2b1611; --volcano1: #2b1611; --volcano-2: #441d12; --volcano2: #441d12; --volcano-3: #592716; --volcano3: #592716; --volcano-4: #7c3118; --volcano4: #7c3118; --volcano-5: #aa3e19; --volcano5: #aa3e19; --volcano-6: #d84a1b; --volcano6: #d84a1b; --volcano-7: #e87040; --volcano7: #e87040; --volcano-8: #f3956a; --volcano8: #f3956a; --volcano-9: #f8b692; --volcano9: #f8b692; --volcano-10: #fad4bc; --volcano10: #fad4bc; --geekblue-1: #131629; --geekblue1: #131629; --geekblue-2: #161d40; --geekblue2: #161d40; --geekblue-3: #1c2755; --geekblue3: #1c2755; --geekblue-4: #203175; --geekblue4: #203175; --geekblue-5: #263ea0; --geekblue5: #263ea0; --geekblue-6: #2b4acb; --geekblue6: #2b4acb; --geekblue-7: #5273e0; --geekblue7: #5273e0; --geekblue-8: #7f9ef3; --geekblue8: #7f9ef3; --geekblue-9: #a8c1f8; --geekblue9: #a8c1f8; --geekblue-10: #d2e0fa; --geekblue10: #d2e0fa; --gold-1: #2b2111; --gold1: #2b2111; --gold-2: #443111; --gold2: #443111; --gold-3: #594214; --gold3: #594214; --gold-4: #7c5914; --gold4: #7c5914; --gold-5: #aa7714; --gold5: #aa7714; --gold-6: #d89614; --gold6: #d89614; --gold-7: #e8b339; --gold7: #e8b339; --gold-8: #f3cc62; --gold8: #f3cc62; --gold-9: #f8df8b; --gold9: #f8df8b; --gold-10: #faedb5; --gold10: #faedb5; --lime-1: #1f2611; --lime1: #1f2611; --lime-2: #2e3c10; --lime2: #2e3c10; --lime-3: #3e4f13; --lime3: #3e4f13; --lime-4: #536d13; --lime4: #536d13; --lime-5: #6f9412; --lime5: #6f9412; --lime-6: #8bbb11; --lime6: #8bbb11; --lime-7: #a9d134; --lime7: #a9d134; --lime-8: #c9e75d; --lime8: #c9e75d; --lime-9: #e4f88b; --lime9: #e4f88b; --lime-10: #f0fab5; --lime10: #1c2128; --color-text: rgba(255, 255, 255, 0.85); --color-text-secondary: rgba(255, 255, 255, 0.65); --color-text-tertiary: rgba(255, 255, 255, 0.45); --color-text-quaternary: rgba(255, 255, 255, 0.25); --color-fill: rgba(255, 255, 255, 0.18); --color-fill-secondary: rgba(255, 255, 255, 0.12); --color-fill-tertiary: rgba(255, 255, 255, 0.08); --color-fill-quaternary: rgba(255, 255, 255, 0.04); --color-bg-layout: #0a0b0c; --color-bg-container: #1d1f22; --color-bg-elevated: #262a2d; --color-bg-spotlight: #464d54; --color-border: #464d54; --color-border-secondary: #363b41; --color-primary-bg: #111a2c; --color-primary-bg-hover: #112545; --color-primary-border: #15325b; --color-primary-border-hover: #15417e; --color-primary-hover: #3c89e8; --color-primary-active: #1554ad; --color-primary-text-hover: #3c89e8; --color-primary-text: #1668dc; --color-primary-text-active: #1554ad; --color-success-bg: #162312; --color-success-bg-hover: #1d3712; --color-success-border: #274916; --color-success-border-hover: #306317; --color-success-hover: #306317; --color-success-active: #3c8618; --color-success-text-hover: #6abe39; --color-success-text: #49aa19; --color-success-text-active: #3c8618; --color-error-bg: #2c1618; --color-error-bg-hover: #451d1f; --color-error-border: #5b2526; --color-error-border-hover: #7e2e2f; --color-error-hover: #e86e6b; --color-error-active: #ad393a; --color-error-text-hover: #e86e6b; --color-error-text: #dc4446; --color-error-text-active: #ad393a; --color-warning-bg: #2b2111; --color-warning-bg-hover: #443111; --color-warning-border: #594214; --color-warning-border-hover: #7c5914; --color-warning-hover: #7c5914; --color-warning-active: #aa7714; --color-warning-text-hover: #e8b339; --color-warning-text: #d89614; --color-warning-text-active: #aa7714; --color-info-bg: #111a2c; --color-info-bg-hover: #112545; --color-info-border: #15325b; --color-info-border-hover: #15417e; --color-info-hover: #15417e; --color-info-active: #1554ad; --color-info-text-hover: #3c89e8; --color-info-text: #1668dc; --color-info-text-active: #1554ad; --color-bg-mask: rgba(0, 0, 0, 0.45); --color-white: #fff; --font-size-s-m: 10; --font-size-l-g: 14; --font-size-x-l: 16; --font-size-heading1: 32; --font-size-heading2: 26; --font-size-heading3: 20; --font-size-heading4: 16; --font-size-heading5: 14; --line-height: 1.6666666666666667; --line-height-l-g: 1.5714285714285714; --line-height-s-m: 1.8; --line-height-heading1: 1.25; --line-height-heading2: 1.3076923076923077; --line-height-heading3: 1.4; --line-height-heading4: 1.5; --line-height-heading5: 1.5714285714285714; --size-x-x-l: 48; --size-x-l: 32; --size-l-g: 16; --size-m-d: 16; --size-m-s: 12; --size: 8; --size-s-m: 8; --size-x-s: 4; --size-x-x-s: 4; --control-height-s-m: 21; --control-height-x-s: 14; --control-height-l-g: 35; --motion-duration-fast: 0.1s; --motion-duration-mid: 0.2s; --motion-duration-slow: 0.3s; --line-width-bold: 2; --border-radius-x-s: 1; --border-radius-s-m: 4; --border-radius-l-g: 4; --border-radius-outer: 4; --color-link: #1668dc; --color-link-hover: #15417e; --color-link-active: #1554ad; --color-fill-content: rgba(255, 255, 255, 0.12); --color-fill-content-hover: rgba(255, 255, 255, 0.18); --color-fill-alter: rgba(255, 255, 255, 0.04); --color-bg-container-disabled: rgba(255, 255, 255, 0.08); --color-border-bg: #1d1f22; --color-split: rgba(208, 231, 255, 0.14); --color-text-placeholder: rgba(255, 255, 255, 0.25); --color-text-disabled: rgba(255, 255, 255, 0.25); --color-text-heading: rgba(255, 255, 255, 0.85); --color-text-label: rgba(255, 255, 255, 0.65); --color-text-description: rgba(255, 255, 255, 0.45); --color-text-light-solid: #fff; --color-highlight: #dc4446; --color-bg-text-hover: rgba(255, 255, 255, 0.12); --color-bg-text-active: rgba(255, 255, 255, 0.18); --color-icon: rgba(255, 255, 255, 0.45); --color-icon-hover: rgba(255, 255, 255, 0.85); --color-error-outline: rgba(81, 0, 0, 0.29); --color-warning-outline: rgba(57, 35, 0, 0.5); --font-size-icon: 10; --line-width-focus: 4; --control-outline-width: 2; --control-interactive-size: 14; --control-item-bg-hover: rgba(255, 255, 255, 0.08); --control-item-bg-active: #111a2c; --control-item-bg-active-hover: #112545; --control-item-bg-active-disabled: rgba(255, 255, 255, 0.18); --control-tmp-outline: rgba(255, 255, 255, 0.04); --control-outline: rgba(0, 19, 58, 0.41); --font-weight-strong: 600; --opacity-loading: 0.65; --link-decoration: none; --link-hover-decoration: none; --link-focus-decoration: none; --control-padding-horizontal: 12; --control-padding-horizontal-s-m: 8; --padding-x-x-s: 4; --padding-x-s: 4; --padding-s-m: 8; --padding: 8; --padding-m-d: 16; --padding-l-g: 16; --padding-x-l: 32; --padding-content-horizontal-l-g: 16; --padding-content-vertical-l-g: 12; --padding-content-horizontal: 12; --padding-content-vertical: 8; --padding-content-horizontal-s-m: 8; --padding-content-vertical-s-m: 4; --margin-x-x-s: 4; --margin-x-s: 4; --margin-s-m: 8; --margin: 8; --margin-m-d: 16; --margin-l-g: 16; --margin-x-l: 32; --margin-x-x-l: 48; --box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-secondary: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-tertiary: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px 0 rgba(0, 0, 0, 0.02); --screen-x-s: 480; --screen-x-s-min: 480; --screen-x-s-max: 575; --screen-s-m: 576; --screen-s-m-min: 576; --screen-s-m-max: 767; --screen-m-d: 768; --screen-m-d-min: 768; --screen-m-d-max: 991; --screen-l-g: 992; --screen-l-g-min: 992; --screen-l-g-max: 1199; --screen-x-l: 1200; --screen-x-l-min: 1200; --screen-x-l-max: 1599; --screen-x-x-l: 1600; --screen-x-x-l-min: 1600; --box-shadow-popover-arrow: 2px 2px 5px rgba(0, 0, 0, 0.05); --box-shadow-card: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09); --box-shadow-drawer-right: -6px 0 16px 0 rgba(0, 0, 0, 0.08), -3px 0 6px -4px rgba(0, 0, 0, 0.12), -9px 0 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-drawer-left: 6px 0 16px 0 rgba(0, 0, 0, 0.08), 3px 0 6px -4px rgba(0, 0, 0, 0.12), 9px 0 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-drawer-up: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-drawer-down: 0 -6px 16px 0 rgba(0, 0, 0, 0.08), 0 -3px 6px -4px rgba(0, 0, 0, 0.12), 0 -9px 28px 8px rgba(0, 0, 0, 0.05); --box-shadow-tabs-overflow-left: inset 10px 0 8px -8px rgba(0, 0, 0, 0.08); --box-shadow-tabs-overflow-right: inset -10px 0 8px -8px rgba(0, 0, 0, 0.08); --box-shadow-tabs-overflow-top: inset 0 10px 8px -8px rgba(0, 0, 0, 0.08); --box-shadow-tabs-overflow-bottom: inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08); --_token-key: nhv2lr; --_hash-id: css-dev-only-do-not-override-aany79; } ================================================ FILE: chat2db-client/src/theme/abandon/demo/light.less ================================================ { "blue": "#1677ff", "purple": "#722ED1", "cyan": "#13C2C2", "green": "#52C41A", "magenta": "#EB2F96", "pink": "#eb2f96", "red": "#F5222D", "orange": "#FA8C16", "yellow": "#FADB14", "volcano": "#FA541C", "geekblue": "#2F54EB", "gold": "#FAAD14", "lime": "#A0D911", "colorPrimary": "#1677ff", "colorSuccess": "#52c41a", "colorWarning": "#faad14", "colorError": "#ff4d4f", "colorInfo": "#1677ff", "colorTextBase": "#000", "colorBgBase": "#fff", "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,\n'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n'Noto Color Emoji'", "fontFamilyCode": "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace", "fontSize": null, "lineWidth": 1, "lineType": "solid", "motionUnit": 0.1, "motionBase": 0, "motionEaseOutCirc": "cubic-bezier(0.08, 0.82, 0.17, 1)", "motionEaseInOutCirc": "cubic-bezier(0.78, 0.14, 0.15, 0.86)", "motionEaseOut": "cubic-bezier(0.215, 0.61, 0.355, 1)", "motionEaseInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)", "motionEaseOutBack": "cubic-bezier(0.12, 0.4, 0.29, 1.46)", "motionEaseInBack": "cubic-bezier(0.71, -0.46, 0.88, 0.6)", "motionEaseInQuint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)", "motionEaseOutQuint": "cubic-bezier(0.23, 1, 0.32, 1)", "borderRadius": "4px", "sizeUnit": 4, "sizeStep": 4, "sizePopupArrow": 16, "controlHeight": 28, "zIndexBase": 0, "zIndexPopupBase": 1000, "opacityImage": 1, "wireframe": true, "motion": true, "borderRadiusLG": "8px", "colorHoverBg": "#eee", "colorBgContainer": "#fff", "colorBgElevated": "#F8F9FA", "colorBorder": "#d3d3d4", "blue-1": "#e6f4ff", "blue1": "#e6f4ff", "blue-2": "#bae0ff", "blue2": "#bae0ff", "blue-3": "#91caff", "blue3": "#91caff", "blue-4": "#69b1ff", "blue4": "#69b1ff", "blue-5": "#4096ff", "blue5": "#4096ff", "blue-6": "#1677ff", "blue6": "#1677ff", "blue-7": "#0958d9", "blue7": "#0958d9", "blue-8": "#003eb3", "blue8": "#003eb3", "blue-9": "#002c8c", "blue9": "#002c8c", "blue-10": "#001d66", "blue10": "#001d66", "purple-1": "#f9f0ff", "purple1": "#f9f0ff", "purple-2": "#efdbff", "purple2": "#efdbff", "purple-3": "#d3adf7", "purple3": "#d3adf7", "purple-4": "#b37feb", "purple4": "#b37feb", "purple-5": "#9254de", "purple5": "#9254de", "purple-6": "#722ed1", "purple6": "#722ed1", "purple-7": "#531dab", "purple7": "#531dab", "purple-8": "#391085", "purple8": "#391085", "purple-9": "#22075e", "purple9": "#22075e", "purple-10": "#120338", "purple10": "#120338", "cyan-1": "#e6fffb", "cyan1": "#e6fffb", "cyan-2": "#b5f5ec", "cyan2": "#b5f5ec", "cyan-3": "#87e8de", "cyan3": "#87e8de", "cyan-4": "#5cdbd3", "cyan4": "#5cdbd3", "cyan-5": "#36cfc9", "cyan5": "#36cfc9", "cyan-6": "#13c2c2", "cyan6": "#13c2c2", "cyan-7": "#08979c", "cyan7": "#08979c", "cyan-8": "#006d75", "cyan8": "#006d75", "cyan-9": "#00474f", "cyan9": "#00474f", "cyan-10": "#002329", "cyan10": "#002329", "green-1": "#f6ffed", "green1": "#f6ffed", "green-2": "#d9f7be", "green2": "#d9f7be", "green-3": "#b7eb8f", "green3": "#b7eb8f", "green-4": "#95de64", "green4": "#95de64", "green-5": "#73d13d", "green5": "#73d13d", "green-6": "#52c41a", "green6": "#52c41a", "green-7": "#389e0d", "green7": "#389e0d", "green-8": "#237804", "green8": "#237804", "green-9": "#135200", "green9": "#135200", "green-10": "#092b00", "green10": "#092b00", "magenta-1": "#fff0f6", "magenta1": "#fff0f6", "magenta-2": "#ffd6e7", "magenta2": "#ffd6e7", "magenta-3": "#ffadd2", "magenta3": "#ffadd2", "magenta-4": "#ff85c0", "magenta4": "#ff85c0", "magenta-5": "#f759ab", "magenta5": "#f759ab", "magenta-6": "#eb2f96", "magenta6": "#eb2f96", "magenta-7": "#c41d7f", "magenta7": "#c41d7f", "magenta-8": "#9e1068", "magenta8": "#9e1068", "magenta-9": "#780650", "magenta9": "#780650", "magenta-10": "#520339", "magenta10": "#520339", "pink-1": "#fff0f6", "pink1": "#fff0f6", "pink-2": "#ffd6e7", "pink2": "#ffd6e7", "pink-3": "#ffadd2", "pink3": "#ffadd2", "pink-4": "#ff85c0", "pink4": "#ff85c0", "pink-5": "#f759ab", "pink5": "#f759ab", "pink-6": "#eb2f96", "pink6": "#eb2f96", "pink-7": "#c41d7f", "pink7": "#c41d7f", "pink-8": "#9e1068", "pink8": "#9e1068", "pink-9": "#780650", "pink9": "#780650", "pink-10": "#520339", "pink10": "#520339", "red-1": "#fff1f0", "red1": "#fff1f0", "red-2": "#ffccc7", "red2": "#ffccc7", "red-3": "#ffa39e", "red3": "#ffa39e", "red-4": "#ff7875", "red4": "#ff7875", "red-5": "#ff4d4f", "red5": "#ff4d4f", "red-6": "#f5222d", "red6": "#f5222d", "red-7": "#cf1322", "red7": "#cf1322", "red-8": "#a8071a", "red8": "#a8071a", "red-9": "#820014", "red9": "#820014", "red-10": "#5c0011", "red10": "#5c0011", "orange-1": "#fff7e6", "orange1": "#fff7e6", "orange-2": "#ffe7ba", "orange2": "#ffe7ba", "orange-3": "#ffd591", "orange3": "#ffd591", "orange-4": "#ffc069", "orange4": "#ffc069", "orange-5": "#ffa940", "orange5": "#ffa940", "orange-6": "#fa8c16", "orange6": "#fa8c16", "orange-7": "#d46b08", "orange7": "#d46b08", "orange-8": "#ad4e00", "orange8": "#ad4e00", "orange-9": "#873800", "orange9": "#873800", "orange-10": "#612500", "orange10": "#612500", "yellow-1": "#feffe6", "yellow1": "#feffe6", "yellow-2": "#ffffb8", "yellow2": "#ffffb8", "yellow-3": "#fffb8f", "yellow3": "#fffb8f", "yellow-4": "#fff566", "yellow4": "#fff566", "yellow-5": "#ffec3d", "yellow5": "#ffec3d", "yellow-6": "#fadb14", "yellow6": "#fadb14", "yellow-7": "#d4b106", "yellow7": "#d4b106", "yellow-8": "#ad8b00", "yellow8": "#ad8b00", "yellow-9": "#876800", "yellow9": "#876800", "yellow-10": "#614700", "yellow10": "#614700", "volcano-1": "#fff2e8", "volcano1": "#fff2e8", "volcano-2": "#ffd8bf", "volcano2": "#ffd8bf", "volcano-3": "#ffbb96", "volcano3": "#ffbb96", "volcano-4": "#ff9c6e", "volcano4": "#ff9c6e", "volcano-5": "#ff7a45", "volcano5": "#ff7a45", "volcano-6": "#fa541c", "volcano6": "#fa541c", "volcano-7": "#d4380d", "volcano7": "#d4380d", "volcano-8": "#ad2102", "volcano8": "#ad2102", "volcano-9": "#871400", "volcano9": "#871400", "volcano-10": "#610b00", "volcano10": "#610b00", "geekblue-1": "#f0f5ff", "geekblue1": "#f0f5ff", "geekblue-2": "#d6e4ff", "geekblue2": "#d6e4ff", "geekblue-3": "#adc6ff", "geekblue3": "#adc6ff", "geekblue-4": "#85a5ff", "geekblue4": "#85a5ff", "geekblue-5": "#597ef7", "geekblue5": "#597ef7", "geekblue-6": "#2f54eb", "geekblue6": "#2f54eb", "geekblue-7": "#1d39c4", "geekblue7": "#1d39c4", "geekblue-8": "#10239e", "geekblue8": "#10239e", "geekblue-9": "#061178", "geekblue9": "#061178", "geekblue-10": "#030852", "geekblue10": "#030852", "gold-1": "#fffbe6", "gold1": "#fffbe6", "gold-2": "#fff1b8", "gold2": "#fff1b8", "gold-3": "#ffe58f", "gold3": "#ffe58f", "gold-4": "#ffd666", "gold4": "#ffd666", "gold-5": "#ffc53d", "gold5": "#ffc53d", "gold-6": "#faad14", "gold6": "#faad14", "gold-7": "#d48806", "gold7": "#d48806", "gold-8": "#ad6800", "gold8": "#ad6800", "gold-9": "#874d00", "gold9": "#874d00", "gold-10": "#613400", "gold10": "#613400", "lime-1": "#fcffe6", "lime1": "#fcffe6", "lime-2": "#f4ffb8", "lime2": "#f4ffb8", "lime-3": "#eaff8f", "lime3": "#eaff8f", "lime-4": "#d3f261", "lime4": "#d3f261", "lime-5": "#bae637", "lime5": "#bae637", "lime-6": "#a0d911", "lime6": "#a0d911", "lime-7": "#7cb305", "lime7": "#7cb305", "lime-8": "#5b8c00", "lime8": "#5b8c00", "lime-9": "#3f6600", "lime9": "#3f6600", "lime-10": "#254000", "lime10": "#254000", "colorText": "rgba(0, 0, 0, 0.88)", "colorTextSecondary": "rgba(0, 0, 0, 0.65)", "colorTextTertiary": "rgba(0, 0, 0, 0.45)", "colorTextQuaternary": "rgba(0, 0, 0, 0.25)", "colorFill": "rgba(0, 0, 0, 0.15)", "colorFillSecondary": "rgba(0, 0, 0, 0.06)", "colorFillTertiary": "rgba(0, 0, 0, 0.04)", "colorFillQuaternary": "rgba(0, 0, 0, 0.02)", "colorBgLayout": "#f5f5f5", "colorBgSpotlight": "rgba(0, 0, 0, 0.85)", "colorBorderSecondary": "#f0f0f0", "colorPrimaryBg": "#e6f4ff", "colorPrimaryBgHover": "#bae0ff", "colorPrimaryBorder": "#91caff", "colorPrimaryBorderHover": "#69b1ff", "colorPrimaryHover": "#4096ff", "colorPrimaryActive": "#0958d9", "colorPrimaryTextHover": "#4096ff", "colorPrimaryText": "#1677ff", "colorPrimaryTextActive": "#0958d9", "colorSuccessBg": "#f6ffed", "colorSuccessBgHover": "#d9f7be", "colorSuccessBorder": "#b7eb8f", "colorSuccessBorderHover": "#95de64", "colorSuccessHover": "#95de64", "colorSuccessActive": "#389e0d", "colorSuccessTextHover": "#73d13d", "colorSuccessText": "#52c41a", "colorSuccessTextActive": "#389e0d", "colorErrorBg": "#fff2f0", "colorErrorBgHover": "#fff1f0", "colorErrorBorder": "#ffccc7", "colorErrorBorderHover": "#ffa39e", "colorErrorHover": "#ff7875", "colorErrorActive": "#d9363e", "colorErrorTextHover": "#ff7875", "colorErrorText": "#ff4d4f", "colorErrorTextActive": "#d9363e", "colorWarningBg": "#fffbe6", "colorWarningBgHover": "#fff1b8", "colorWarningBorder": "#ffe58f", "colorWarningBorderHover": "#ffd666", "colorWarningHover": "#ffd666", "colorWarningActive": "#d48806", "colorWarningTextHover": "#ffc53d", "colorWarningText": "#faad14", "colorWarningTextActive": "#d48806", "colorInfoBg": "#e6f4ff", "colorInfoBgHover": "#bae0ff", "colorInfoBorder": "#91caff", "colorInfoBorderHover": "#69b1ff", "colorInfoHover": "#69b1ff", "colorInfoActive": "#0958d9", "colorInfoTextHover": "#4096ff", "colorInfoText": "#1677ff", "colorInfoTextActive": "#0958d9", "colorBgMask": "rgba(0, 0, 0, 0.45)", "colorWhite": "#fff", "fontSizeSM": null, "fontSizeLG": null, "fontSizeXL": null, "fontSizeHeading1": null, "fontSizeHeading2": null, "fontSizeHeading3": null, "fontSizeHeading4": null, "fontSizeHeading5": null, "lineHeight": null, "lineHeightLG": null, "lineHeightSM": null, "lineHeightHeading1": null, "lineHeightHeading2": null, "lineHeightHeading3": null, "lineHeightHeading4": null, "lineHeightHeading5": null, "sizeXXL": 48, "sizeXL": 32, "sizeLG": 16, "sizeMD": 16, "sizeMS": 12, "size": 8, "sizeSM": 8, "sizeXS": 4, "sizeXXS": 4, "controlHeightSM": 21, "controlHeightXS": 14, "controlHeightLG": 35, "motionDurationFast": "0.1s", "motionDurationMid": "0.2s", "motionDurationSlow": "0.3s", "lineWidthBold": 2, "borderRadiusXS": "4px", "borderRadiusSM": "4px", "borderRadiusOuter": "4px", "colorLink": "#1677ff", "colorLinkHover": "#69b1ff", "colorLinkActive": "#0958d9", "colorFillContent": "rgba(0, 0, 0, 0.06)", "colorFillContentHover": "rgba(0, 0, 0, 0.15)", "colorFillAlter": "rgba(0, 0, 0, 0.02)", "colorBgContainerDisabled": "rgba(0, 0, 0, 0.04)", "colorBorderBg": "#fff", "colorSplit": "rgba(5, 5, 5, 0.06)", "colorTextPlaceholder": "rgba(0, 0, 0, 0.25)", "colorTextDisabled": "rgba(0, 0, 0, 0.25)", "colorTextHeading": "rgba(0, 0, 0, 0.88)", "colorTextLabel": "rgba(0, 0, 0, 0.65)", "colorTextDescription": "rgba(0, 0, 0, 0.45)", "colorTextLightSolid": "#fff", "colorHighlight": "#ff4d4f", "colorBgTextHover": "rgba(0, 0, 0, 0.06)", "colorBgTextActive": "rgba(0, 0, 0, 0.15)", "colorIcon": "rgba(0, 0, 0, 0.45)", "colorIconHover": "rgba(0, 0, 0, 0.88)", "colorErrorOutline": "rgba(255, 38, 5, 0.06)", "colorWarningOutline": "rgba(255, 215, 5, 0.1)", "fontSizeIcon": null, "lineWidthFocus": 4, "controlOutlineWidth": 2, "controlInteractiveSize": 14, "controlItemBgHover": "rgba(0, 0, 0, 0.04)", "controlItemBgActive": "#e6f4ff", "controlItemBgActiveHover": "#bae0ff", "controlItemBgActiveDisabled": "rgba(0, 0, 0, 0.15)", "controlTmpOutline": "rgba(0, 0, 0, 0.02)", "controlOutline": "rgba(5, 145, 255, 0.1)", "fontWeightStrong": 600, "opacityLoading": 0.65, "linkDecoration": "none", "linkHoverDecoration": "none", "linkFocusDecoration": "none", "controlPaddingHorizontal": 12, "controlPaddingHorizontalSM": 8, "paddingXXS": 4, "paddingXS": 4, "paddingSM": 8, "padding": 8, "paddingMD": 16, "paddingLG": 16, "paddingXL": 32, "paddingContentHorizontalLG": 16, "paddingContentVerticalLG": 12, "paddingContentHorizontal": 12, "paddingContentVertical": 8, "paddingContentHorizontalSM": 8, "paddingContentVerticalSM": 4, "marginXXS": 4, "marginXS": 4, "marginSM": 8, "margin": 8, "marginMD": 16, "marginLG": 16, "marginXL": 32, "marginXXL": 48, "boxShadow": "\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowSecondary": "\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowTertiary": "\n 0 1px 2px 0 rgba(0, 0, 0, 0.03),\n 0 1px 6px -1px rgba(0, 0, 0, 0.02),\n 0 2px 4px 0 rgba(0, 0, 0, 0.02)\n ", "screenXS": 480, "screenXSMin": 480, "screenXSMax": 575, "screenSM": 576, "screenSMMin": 576, "screenSMMax": 767, "screenMD": 768, "screenMDMin": 768, "screenMDMax": 991, "screenLG": 992, "screenLGMin": 992, "screenLGMax": 1199, "screenXL": 1200, "screenXLMin": 1200, "screenXLMax": 1599, "screenXXL": 1600, "screenXXLMin": 1600, "boxShadowPopoverArrow": "2px 2px 5px rgba(0, 0, 0, 0.05)", "boxShadowCard": "\n 0 1px 2px -2px rgba(0, 0, 0, 0.16),\n 0 3px 6px 0 rgba(0, 0, 0, 0.12),\n 0 5px 12px 4px rgba(0, 0, 0, 0.09)\n ", "boxShadowDrawerRight": "\n -6px 0 16px 0 rgba(0, 0, 0, 0.08),\n -3px 0 6px -4px rgba(0, 0, 0, 0.12),\n -9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowDrawerLeft": "\n 6px 0 16px 0 rgba(0, 0, 0, 0.08),\n 3px 0 6px -4px rgba(0, 0, 0, 0.12),\n 9px 0 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowDrawerUp": "\n 0 6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 9px 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowDrawerDown": "\n 0 -6px 16px 0 rgba(0, 0, 0, 0.08),\n 0 -3px 6px -4px rgba(0, 0, 0, 0.12),\n 0 -9px 28px 8px rgba(0, 0, 0, 0.05)\n ", "boxShadowTabsOverflowLeft": "inset 10px 0 8px -8px rgba(0, 0, 0, 0.08)", "boxShadowTabsOverflowRight": "inset -10px 0 8px -8px rgba(0, 0, 0, 0.08)", "boxShadowTabsOverflowTop": "inset 0 10px 8px -8px rgba(0, 0, 0, 0.08)", "boxShadowTabsOverflowBottom": "inset 0 -10px 8px -8px rgba(0, 0, 0, 0.08)", "_tokenKey": "1xy3sb6", "_hashId": "css-dev-only-do-not-override-mqdxtd" } ================================================ FILE: chat2db-client/src/theme/abandon/primaryColor.less ================================================ html[primary-color='polar-blue'] { //品牌色 --color-primary: #1677ff; // 品牌主色 --color-primary-bg: #e6f4ff; // 选中背景色,如下拉框选中的颜色 --color-primary-hover: rgba(27, 28, 33, 0.18); // 主色悬浮色 --color-primary-active: #0958d9; // 主色激活态 } html[primary-color='polar-green'] { //品牌色 --color-primary: #3c8618; // 品牌主色 --color-primary-bg: #e6f4ff; // 选中背景色,如下拉框选中的颜色 --color-primary-hover: rgba(27, 28, 33, 0.18); // 主色悬浮色 --color-primary-active: #26610d; // 主色激活态 } html[primary-color='golden-purple'] { //品牌色 --color-primary: #51258f; // 品牌主色 --color-primary-bg: #e6f4ff; // 选中背景色,如下拉框选中的颜色 --color-primary-hover: rgba(27, 28, 33, 0.18); // 主色悬浮色 --color-primary-active: #361669; // 主色激活态 } ================================================ FILE: chat2db-client/src/theme/background/dark.ts ================================================ import { theme } from 'antd'; import { PrimaryColorType } from '@/constants'; import { commonToken } from '../common'; type IAntdPrimaryColor = { [key in PrimaryColorType]: any; }; // 主题色 const antdPrimaryColor: IAntdPrimaryColor = { [PrimaryColorType.Polar_Green]: { colorPrimary: '#3c8618', }, [PrimaryColorType.Golden_Purple]: { colorPrimary: '#7688c9', }, [PrimaryColorType.Polar_Blue]: { colorPrimary: '#1677ff', }, [PrimaryColorType.Silver]: { colorPrimary: '#c3b7a4', }, [PrimaryColorType.Red]: { colorPrimary: '#fd6874', }, [PrimaryColorType.Orange]: { colorPrimary: '#ffa940', }, [PrimaryColorType.Blue2]: { colorPrimary: '#009cc7', }, [PrimaryColorType.Gold]: { colorPrimary: '#b59a6d', }, }; const antDarkTheme = { algorithm: [theme.darkAlgorithm, theme.compactAlgorithm], customName: 'dark', antdPrimaryColor, token: { ...commonToken, colorTextBase: '#f1f1f4', colorBgBase: '#0a0b0c', colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#0a0b0c', colorBgSubtle: '#131418', colorBgElevated: '#0a0b0c', colorBorder: '#36373a66', colorBorderSecondary: '#36373a66', }, }; export default antDarkTheme; ================================================ FILE: chat2db-client/src/theme/background/darkDimmed.ts ================================================ import { theme } from 'antd'; import { PrimaryColorType } from '@/constants'; import { commonToken } from '../common'; type IAntdPrimaryColor = { [key in PrimaryColorType]: any; }; // 主题色 const antdPrimaryColor: IAntdPrimaryColor = { [PrimaryColorType.Polar_Green]: { colorPrimary: '#3c8618', }, [PrimaryColorType.Golden_Purple]: { colorPrimary: '#8276c9', }, [PrimaryColorType.Polar_Blue]: { colorPrimary: '#1677ff', }, [PrimaryColorType.Silver]: { colorPrimary: '#c3b7a4', }, [PrimaryColorType.Red]: { colorPrimary: '#fd6874', }, [PrimaryColorType.Orange]: { colorPrimary: '#ffa940', }, [PrimaryColorType.Blue2]: { colorPrimary: '#009cc7', }, [PrimaryColorType.Gold]: { colorPrimary: '#b59a6d', }, }; const antdLightTheme = { algorithm: [theme.darkAlgorithm, theme.compactAlgorithm], customName: 'dark-dimmed', antdPrimaryColor, token: { ...commonToken, colorTextBase: '#f1f1f4', colorBgBase: '#1c2128', colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#1c2128', colorBgSubtle: '#22272e', colorBgElevated: '#1c2128', colorBorder: '#373e4766', colorBorderSecondary: '#373e4766', controlItemBgActive: '#f1f1f414', }, }; export default antdLightTheme; ================================================ FILE: chat2db-client/src/theme/background/light.ts ================================================ import { theme } from 'antd'; import { PrimaryColorType } from '@/constants'; import { commonToken } from '../common'; type IAntdPrimaryColor = { [key in PrimaryColorType]: any; }; // 主题色 const antdPrimaryColor: IAntdPrimaryColor = { [PrimaryColorType.Polar_Green]: { colorPrimary: '#039e74', }, [PrimaryColorType.Golden_Purple]: { colorPrimary: '#9373ee', }, [PrimaryColorType.Polar_Blue]: { colorPrimary: '#587df1', }, [PrimaryColorType.Silver]: { colorPrimary: '#8e8374', }, [PrimaryColorType.Red]: { colorPrimary: '#fd6874', }, [PrimaryColorType.Orange]: { colorPrimary: '#fa8c16', }, [PrimaryColorType.Blue2]: { colorPrimary: '#00c3ee', }, [PrimaryColorType.Gold]: { colorPrimary: '#9a7d56', }, }; const antdLightTheme = { algorithm: [theme.defaultAlgorithm, theme.compactAlgorithm], customName: 'light', antdPrimaryColor, token: { ...commonToken, // colorText: "#232429", colorTextBase: '#232429', colorBgBase: '#ffffff', colorHoverBg: 'rgba(0, 0, 0, 0.03)', colorBgContainer: '#ffffff', colorBgSubtle: '#f6f8fa', colorBgElevated: '#ffffff', colorBorder: 'rgba(211, 211, 212, 0.4)', colorBorderSecondary: 'rgba(211, 211, 212, 0.4)', }, }; export default antdLightTheme; ================================================ FILE: chat2db-client/src/theme/common.ts ================================================ export const commonToken = { fontSize1: 14, wireframe: true, borderRadius: 4, borderRadiusLG: 8, }; ================================================ FILE: chat2db-client/src/theme/custom/dark.less ================================================ :root [theme='dark'] { --custom-color-icon: #5C5D5E; } ================================================ FILE: chat2db-client/src/theme/custom/darkDimmed.less ================================================ :root [theme='darkDimmed'] { --custom-color-icon: #5C5D5E; } ================================================ FILE: chat2db-client/src/theme/custom/light.less ================================================ html[theme='light'] { --custom-color-icon: #383d4b; } ================================================ FILE: chat2db-client/src/theme/index.ts ================================================ import antdDarkTheme from './background/dark'; import antdDarkDimmedTheme from './background/darkDimmed'; import antdLightTheme from './background/light'; import { ThemeType, PrimaryColorType } from '@/constants'; import { ITheme } from '@/typings/theme'; import lodash from 'lodash'; import { theme } from 'antd'; const antdThemeConfigs = { [ThemeType.Dark]: antdDarkTheme, [ThemeType.Light]: antdLightTheme, [ThemeType.DarkDimmed]: antdDarkDimmedTheme, }; export function getAntdThemeConfig(_theme: ITheme) { const antdThemeConfig = lodash.cloneDeep(antdThemeConfigs[_theme.backgroundColor]); antdThemeConfig.token = { ...antdThemeConfig.token, ...(antdThemeConfig.antdPrimaryColor[_theme.primaryColor as PrimaryColorType] || {}), }; const token = theme.getDesignToken(antdThemeConfig); injectThemeVar(token as any, _theme.backgroundColor, _theme.primaryColor); return antdThemeConfig; } // TODO: 只插入一次 export function injectThemeVar(token: { [key in string]: string }, _theme: ThemeType, primaryColor: PrimaryColorType) { let css = ''; Object.keys(token).map((t) => { const attributeName = camelToDash(t); let value = token[t]; // 将需要px的数字带上px const joinPxArr = ['fontSize', 'borderRadius', 'borderRadiusLG']; if (joinPxArr.includes(t)) { value = value + 'px'; } css = css + `--${attributeName}: ${value};\n`; }); const container = `html[theme='${_theme}'],html[primary-color='${primaryColor}']{ ${css} }`; const style = document.createElement('style'); // 创建style标签 style.type = 'text/css'; style.appendChild(document.createTextNode(container)); document.head.appendChild(style); // 将style标签插入到head标签中 window._AppThemePack = token; } function camelToDash(str: string) { return str.replace(/([A-Z])/g, '-$1').toLowerCase(); } ================================================ FILE: chat2db-client/src/typings/ai.ts ================================================ export enum AIType { CHAT2DBAI = 'CHAT2DBAI', ZHIPUAI = 'ZHIPUAI', BAICHUANAI='BAICHUANAI', WENXINAI='WENXINAI', TONGYIQIANWENAI='TONGYIQIANWENAI', OPENAI = 'OPENAI', AZUREAI = 'AZUREAI', RESTAI = 'RESTAI', } export interface IRemainingUse { key: string; wechatMpUrl: string; expiry: number; remainingUses: number; } export interface ILoginAndQrCode { token: string; wechatQrCodeUrl: string; apiKey: string; tip: string; } export interface IInviteQrCode { wechatQrCodeUrl: string; tip: string; } ================================================ FILE: chat2db-client/src/typings/common.ts ================================================ export type NonNullable = T extends null | undefined ? never : T; export interface IPageResponse { data: T[]; pageNo: number; pageSize: number; total: number; hasNextPage?: boolean; } export interface IPageParams { searchKey?: string; pageNo: number; pageSize: number; refresh?: boolean; } export interface IPagingData { hasNextPage?: boolean; pageNo: number; pageSize: number; total: number; } export interface Option { value: number | string; label: string; isLeaf?: boolean; children?: Option[]; } export interface IUniversalTableParams { dataSourceId: string; databaseName?: string; schemaName?: string; tableName?: string; } /** * 版本返回 * VersionResponse */ export interface IVersionResponse { /** * 基础链接 * 类似于:http://test.sqlgpt.cn/gateway */ baseUrl?: string; /** * 下载链接 */ downloadLink?: string; /** * 版本 */ version?: string; /** * 微信公众号名字 */ wechatMpName?: string; } ================================================ FILE: chat2db-client/src/typings/connection.ts ================================================ import { DatabaseTypeCode } from '@/constants'; // 连接 高级配置列表的信息 export interface IConnectionExtendInfoItem { key: string; value: string; } // 连接的环境信息 export interface IConnectionEnv { id: number; name: string; shortName: string; color: string; } // 连接列表的信息 export interface IConnectionListItem { id: number; alias: string; environment: IConnectionEnv; type: DatabaseTypeCode; supportDatabase: boolean; supportSchema: boolean; user: string; } export interface IConnectionDetails { id: number; alias: string; environment: IConnectionEnv; type: DatabaseTypeCode; isAdmin: boolean; url: string; user: string; password: string; ConsoleOpenedStatus: 'y' | 'n'; extendInfo: IConnectionExtendInfoItem[]; environmentId: number; ssh: any; driverConfig: { jdbcDriver: string; jdbcDriverClass: string; }; [key: string]: any; } export interface IConnectionListItem { id: number; alias: string; environment: IConnectionEnv; type: DatabaseTypeCode; supportDatabase: boolean; supportSchema: boolean; user: string; } export type ICreateConnectionDetails = Omit ================================================ FILE: chat2db-client/src/typings/console.ts ================================================ import { ConsoleStatus, DatabaseTypeCode, WorkspaceTabType, ConsoleOpenedStatus } from '@/constants'; export interface ICreateConsoleParams { name?: string; ddl?: string; dataSourceId: number; dataSourceName: string; databaseType: DatabaseTypeCode; databaseName?: string; schemaName?: string; operationType?: WorkspaceTabType; loadSQL?: () => Promise; } // 控制台详情 export interface IConsole { id: number; // consoleId name: string; // 控制台名称 ddl: string; // 控制台内的sql dataSourceId?: number; // 数据源id dataSourceName?: string; // 数据源名称 type?: DatabaseTypeCode; // 数据库类型 databaseName?: string; // 数据库名称 schemaName?: string; // schema名称 status: ConsoleStatus; // 控制台状态 connectable: boolean; // 是否可连接 tabOpened?: ConsoleOpenedStatus; // 控制台tab是否打开 operationType: WorkspaceTabType; // 操作类型 } export type ICreateConsole = Omit; ================================================ FILE: chat2db-client/src/typings/dashboard.ts ================================================ // export type IChartType = 'Pie' | 'Column' | 'Line'; export enum IChartType { 'Pie' = 'Pie', 'Column' = 'Column', 'Line' = 'Line', } // export interface IDashboardItem { // name: string; // data: Array; // } export interface IDashboardItem { id: number; name?: string; description?: string; /** 保存图表布局 二维数据 number[][] */ schema?: string; chartIds?: number[]; gmtModified?: number; gmtCreate?: number; } // export interface IChartDataItem { // /** sql内容 */ // sqlContext: string; // /** sql返回数据 */ // sqlData: any; // /** 图表类型 */ // chartType: IChartType; // /** 图表参数 */ // chartParam: any; // } export interface IChartItem { id?: number; /** 图表名称 */ name?: string; /** 图表描述 */ description?: string; /** 图表参数 */ schema?: string; /** 图表类型 */ chartType?: IChartType; /** 数据源连接ID */ dataSourceId?: number; /** 数据库类型 */ type?: string; /** db名称 */ databaseName?: string; /** schema名称 */ schemaName?: string; /** ddl内容 */ ddl?: string; /** 是否链接 */ connectable?: boolean; /** sql返回数据 */ sqlData?: any; } ================================================ FILE: chat2db-client/src/typings/database.ts ================================================ import { DatabaseTypeCode, TableDataType } from '@/constants'; export interface IDatabase { name: string; code: DatabaseTypeCode; img: string; icon: string; } export interface ITableHeaderItem { dataType: TableDataType; name: string; autoIncrement: boolean | null; // 是否自增 columnSize: number | null; // 字段长度 comment: string | null; // 字段注释 decimalDigits: number | null; // 小数位 defaultValue: string | null; // 默认值 nullable: boolean | null; // 是否为空 primaryKey: boolean | null; // 是否为主键 } export interface IManageResultData { dataList: string[][]; headerList: ITableHeaderItem[]; description: string; message: string; sql: string; originalSql: string; success: boolean; uuid?: string; duration: number; fuzzyTotal: string; hasNextPage: boolean; sqlType: 'SELECT' | 'UNKNOWN'; updateCount?: number; // 如果是修改的话。后端会返回修改的条数 canEdit?: boolean; // 返回的数据是否可以编辑 tableName?: string; // 如果可以编辑的话。后端会返回表名称。修改需要给后端传递表名 } /** 查询结果 配置属性 */ export interface IResultConfig { pageNo: number; pageSize: number; total: number | string; hasNextPage: boolean; } /** 不同数据库支持的列字段类型 以及字符集 排列规则列表*/ export interface IDatabaseSupportField { columnTypes: IColumnTypes[]; charsets: ICharset[]; collations: ICollation[]; indexTypes: IIndexTypes[]; defaultValues: IDefaultValue[]; } /** 字段所对应的 字符集*/ export interface ICharset { charsetName: string; // 字符集名称 defaultCollationName: string; // 字符集默认的排序规则 } /** 排列规则*/ export interface ICollation { collationName: string; } /** 索引的类型*/ export interface IIndexTypes { typeName: string; } /** 不同数据库支持的列字段类型 以及支持调整的选项*/ export interface IColumnTypes { typeName: string; supportAutoIncrement: boolean; // 是否支持自增 supportCharset: boolean; // 是否支持字符集 supportCollation: boolean; // 是否支持排序规则 supportComments: boolean; // 是否支持注释 supportDefaultValue: boolean; // 是否支持默认值 supportExtent: boolean; // 是否支持扩展 supportLength: boolean; // 是否支持长度 supportNullable: boolean; // 是否支持为空 supportScale: boolean; // 是否支持小数位 supportValue: boolean; // 是否支持值 supportUnit: boolean; // 是否支持单位 } /** 不同数据库支持不同的默认值 */ export interface IDefaultValue { defaultValue: string; // 默认值 } ================================================ FILE: chat2db-client/src/typings/editSequence.ts ================================================ import { EditColumnOperationType, NullableType } from '@/constants'; export interface ISequenceInfo { nspname: String, relname: string; comment?: string | null; typname?: string | null; seqcache?: number | null; rolname?: string | null; seqstart?: number | null; seqincrement?: string | null; seqmax?: number | null; seqmin?: number | null; seqcycle?: Boolean | null; } ================================================ FILE: chat2db-client/src/typings/editTable.ts ================================================ import { EditColumnOperationType, NullableType } from '@/constants'; // 编辑表时表的基础数据 export interface IBaseInfo { name: string; comment?: string | null; charset: string | null; // 字符集 engine: string | null; // 引擎 incrementValue: string | null; // 自增值 } export interface IColumnItemNew { editStatus: EditColumnOperationType | null; // 操作类型 key?: string; oldName: string | null; // 老的列名 name: string | null; // 列名 databaseName: string | null; // 数据库名 schemaName: string | null; // 模式名 tableName: string | null; // 表名 columnType: string | null; // 列的类型 比如 varchar(100) ,double(10,6) dataType: number | null; // 数据类型 defaultValue: string | null; // 默认值 autoIncrement: string | null; // 是否自增 comment: string | null; // 注释 primaryKey: boolean | null; // 是否主键 primaryKeyOrder: number | null; // 主键顺序 typeName: string | null; // 类型名 columnSize: number | null; // 列的长度 bufferLength: number | null; // 缓冲区长度 decimalDigits: string | null; // 小数位数 numPrecRadix: number| null; // 数字精度 sqlDataType: string| null; // sql数据类型 sqlDatetimeSub: string| null; // sql日期时间子类型 charOctetLength:string| null; // 字符串最大长度 ordinalPosition: number| null; // 位置 nullable: NullableType | null; //是否为空 generatedColumn: string | null; // 是否生成列 charSetName: string | null; // 字符集名 collationName: string | null; // 排序规则名 value: string | null; // 值 } // export interface IIndexIncludeColumnItem { key?: string; // 前端添加的唯一标识 ascOrDesc: string | null; // 升序还是降序 cardinality: number | null; // 基数 collation: string | null; // 排序规则 columnName: string | null; // 列名 comment: string | null; // 注释 databaseName: string | null; // 数据库名 filterCondition: string | null; // 过滤条件 indexName: string | null; // 索引名 indexQualifier: string | null; // 索引限定符 nonUnique: boolean | null; // 是否唯一 ordinalPosition: number | null; // 位置 schemaName: string | null; // 模式名 tableName: string | null; // 表名 type: string | null; // 类型 pages: number | null; // 页数 } // 编辑表时索引的数据结构 export interface IIndexItem { key?: string; name: string | null; comment?: string | null; type: any | null; columnList: IIndexIncludeColumnItem[]; editStatus: EditColumnOperationType | null; // 操作类型 } // 编辑表时整体的数据结构 export interface IEditTableInfo extends IBaseInfo { columnList: IColumnItemNew[]; indexList: IIndexItem[]; } ================================================ FILE: chat2db-client/src/typings/index.ts ================================================ export * from './common'; export * from './connection'; export * from './dashboard'; export * from './database'; export * from './main'; export * from './theme'; export * from './tree'; export * from './setting'; export * from './team'; export * from './workspace'; export * from './editTable'; export * from './console'; export * from './editSequence'; ================================================ FILE: chat2db-client/src/typings/main.ts ================================================ import React from 'react'; export interface INavItem { key: string; icon: string; component?: React.ReactNode; openBrowser?: string; iconFontSize?: number; isLoad: boolean; name: string; } ================================================ FILE: chat2db-client/src/typings/resultTable.ts ================================================ import { IExecuteSqlParams } from '@/service/sql'; export enum ExportTypeEnum { CSV = 'CSV', INSERT = 'INSERT', WORD = 'WORD', EXCEL = 'EXCEL', HTML = 'HTML', MARKDOWN = 'MARKDOWN', PDF = 'PDF' } export enum ExportSizeEnum { CURRENT_PAGE = 'CURRENT_PAGE', ALL = 'ALL', } ================================================ FILE: chat2db-client/src/typings/setting.ts ================================================ import { AIType } from './ai'; export interface IAiConfig { aiSqlSource: AIType; apiKey?: string; apiHost?: string; httpProxyHost?: string; httpProxyPort?: string; stream?: boolean; secretKey?:string; model?: string; } ================================================ FILE: chat2db-client/src/typings/team.ts ================================================ // ===================== Common ================== export enum ManagementType { DATASOURCE = 'DATASOURCE', TEAM = 'TEAM', USER = 'USER', } export enum AffiliationType { 'USER_TEAM' = 'USER_TEAM', 'USER_DATASOURCE' = 'USER_DATASOURCE', 'TEAM_USER' = 'TEAM_USER', 'TEAM_DATASOURCE' = 'TEAM_DATASOURCE', 'DATASOURCE_USER/TEAM' = 'DATASOURCE_USER/TEAM' } export enum SearchType { DATASOURCE = 'DATASOURCE', TEAM = 'TEAM', USER = 'USER', 'USER/TEAM' = 'USER/TEAM' } export enum StatusType { INVALID = 'INVALID', VALID = 'VALID', } export enum RoleType { ADMIN = 'ADMIN', USER = 'USER', } export enum MemberType { TEAM = 'TEAM', USER = 'USER' } // ===================== DataSource ================== export interface IDataSourceVO { /** * 连接别名 */ alias?: string; /** * 环境 */ environment?: IEnvironmentVO; /** * 环境id */ environmentId?: number; /** * 主键id */ id?: number; /** * 连接地址 */ url?: string; } export interface IEnvironmentVO { /** * 主键 */ id?: number; /** * 环境名称 */ name?: string; /** * 环境缩写 */ shortName?: string; /** * 样式类型 */ style?: string; } export interface IDataSourceAccessVO { /** * 授权对象 */ accessObject: IDataSourceAccessObjectVO; /** * 授权id,根据类型区分是用户还是团队 */ accessObjectId: number; /** * 授权类型 */ accessObjectType: RoleType; /** * 主键 */ id: number; } export interface IDataSourceAccessObjectVO { /** * The name of the code that belongs to the authorization type, such as user account, team * code */ code?: string; /** * 授权id,根据类型区分是用户还是团队 */ id?: number; /** * Code that belongs to the authorization type, such as user name, team name */ name?: string; /** * 授权类型 */ type?: RoleType; } // ===================== User ====================== export interface IUserVO { /** * 主键 */ id: number; /** * 邮箱 */ email: string; /** * 昵称 */ nickName: string; /** * 密码 */ password: string; /** * 角色编码 */ roleCode: RoleType; /** * 用户状态 */ status: StatusType; /** * 用户名 */ userName: string; } export interface IUserWithTeamVO { /** * 主键 */ id?: number; /** * 团队 */ team?: ITeamVO; /** * user id */ userId?: number; } export interface IUserWithDataSourceVO { id?: number; /** * Data Source */ dataSource?: IDataSourceVO; /** * user id */ userId?: number; } // ===================== Team ===================== export interface ITeamVO { id?: number; /** * 团队编码 */ code: string; /** * 团队描述 */ description?: string; /** * 团队名称 */ name: string; /** * 团队状态 */ status: StatusType; } export interface ITeamWithUserVO { id: number; teamId: number; user: IUserVO; } export interface ITeamWithDataSourceVO { /** * Data Source */ dataSource?: IDataSourceVO; /** * 主键 */ id: number; /** * team id */ teamId?: number; } // ===================== USER/TEAM ===================== export interface ITeamAndUserVO { code?: string; id?: number; name?: string; type?: MemberType } ================================================ FILE: chat2db-client/src/typings/theme.ts ================================================ import { ThemeType, PrimaryColorType } from '@/constants'; export interface ITheme { backgroundColor: ThemeType; primaryColor: PrimaryColorType; } ================================================ FILE: chat2db-client/src/typings/tree.ts ================================================ import { TreeNodeType, DatabaseTypeCode } from '@/constants'; export interface IExtraParams { dataSourceId: number; databaseType: DatabaseTypeCode; dataSourceName: string; databaseName?: string; schemaName?: string; tableName?: string; functionName?: string; procedureName?: string; triggerName?: string; } export interface ITreeNode { uuid: string; key: string | number; name: string; // 用展示的name // displayName: string; treeNodeType: TreeNodeType; // 节点的类型 表、列、文件等等 pretendNodeType?: TreeNodeType; // 伪装的节点类型,当树不连续时,需要用到 isLeaf?: boolean; // 是否为叶子节点 children?: ITreeNode[] | null; columnType?: string; // 列的类型 extraParams?: IExtraParams; pinned?: boolean; // 是否置顶 comment?: string; // 表列的注释 loadData?: (params:{refresh: boolean}) => void; // 加载数据的方法 // 父元素 parentNode?: ITreeNode; level?: number; // 层级 // 是否展开 expanded?: boolean; parentId?: string; // 分页 page?: number; pageSize?: number; total?: number; } // 视图 函数 触发器 过程 通用的返回结果 export interface IRoutines { name: string; // 名称 comment: string; // 描述 pinned: boolean; // 是否置顶 } export interface ITable { /** * 表描述 */ comment?: string | null; /** * 表名称 */ name: string | null; /** * 是否已经被固定 */ pinned?: boolean; } ================================================ FILE: chat2db-client/src/typings/user.ts ================================================ export enum IRole { 'ADMIN' = 'ADMIN', 'USER' = 'USER', 'DESKTOP' = 'DESKTOP', } export interface IUser { id?: number; userName: string; nickName: string; password?: string; password2?: string; email: string; role?: IRole; } export type IUserVO = { admin: boolean; id : number; nickName: string; roleCode: string; token: string; } | null ================================================ FILE: chat2db-client/src/typings/workspace.ts ================================================ import { CreateTabIntroType, WorkspaceTabType, DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { ITreeNode } from '@/typings'; export interface ICreateTabIntro { type: CreateTabIntroType; workspaceTabType: WorkspaceTabType; treeNodeData: ITreeNode; } export interface IWorkspaceTab { id: number | string; // Tab的id type: WorkspaceTabType; // 工作区tab的类型 title: string; // 工作区tab的名称 uniqueData?: any; } export interface IBoundInfo { consoleId?: number; dataSourceId: number; dataSourceName: string; databaseType: DatabaseTypeCode; databaseName?: string; schemaName?: string; status: ConsoleStatus; connectable: boolean; supportDatabase: boolean; supportSchema: boolean; } ================================================ FILE: chat2db-client/src/utils/IntelliSense/database.ts ================================================ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import i18n from '@/i18n'; export const resetSenseDatabase = () => { intelliSenseDatabase.dispose(); } let intelliSenseDatabase = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; }, }); const checkTableContext = (text) => { const normalizedText = text.trim().toUpperCase(); const tableKeywords = ['FROM', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'UPDATE']; for (const keyword of tableKeywords) { if (normalizedText.endsWith(keyword)) { return true; } } return false; }; const registerIntelliSenseDatabase = (databaseName: Array<{ name: string; dataSourceName: string }>) => { resetSenseDatabase(); intelliSenseDatabase = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '.'], provideCompletionItems: (model, position) => { const lineContentUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); const isTableContext = checkTableContext(lineContentUntilPosition); return { suggestions: (databaseName || []).map(({ name, dataSourceName }) => ({ label: { label: name, detail: dataSourceName ? `(${dataSourceName})` : null, description: i18n('sqlEditor.text.databaseName'), }, insertText: name, sortText: isTableContext ? '01' : '08', kind: monaco.languages.CompletionItemKind.Property, })), }; }, }); }; export { intelliSenseDatabase, registerIntelliSenseDatabase }; ================================================ FILE: chat2db-client/src/utils/IntelliSense/field.ts ================================================ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import sqlService from '@/service/sql'; import i18n from '@/i18n'; let fieldList: Record> = {}; /** 当前库下的表 */ let intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [], }; }, }); export const resetSenseField = () => { intelliSenseField.dispose(); } const addIntelliSenseField = async (props: { tableName: string; dataSourceId: number; databaseName: string; schemaName?: string; }) => { const { tableName, dataSourceId, databaseName, schemaName } = props; if (!fieldList[tableName]) { const data = await sqlService.getAllFieldByTable({ dataSourceId, databaseName, schemaName, tableName, }); fieldList[tableName] = data; } }; function checkFieldContext(text) { const normalizedText = text.trim().toUpperCase(); const columnKeywords = ['SELECT', 'WHERE', 'AND', 'OR', 'GROUP BY', 'ORDER BY', 'SET']; for (const keyword of columnKeywords) { if (normalizedText.endsWith(keyword)) { return true; } } return false; } const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseName, schemaName) => { resetSenseField(); fieldList = {}; intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', ',', '.', '('], provideCompletionItems: async (model, position) => { // 获取到当前行文本 const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); const isFieldContext = checkFieldContext(textUntilPosition); const match = textUntilPosition.match(/(\b\w+\b)[^\w]*$/); let word; if (match) { word = match[1]; } if (!word) { return; // 如果没有匹配到,直接返回 } if (word && tableList.includes(word) && !fieldList[word]) { const data = await sqlService.getAllFieldByTable({ dataSourceId, databaseName, schemaName, tableName: word, }); fieldList[word] = data; } const suggestions: monaco.languages.CompletionItem[] = Object.keys(fieldList).reduce((acc, cur) => { const arr = fieldList[cur].map((fieldObj) => ({ label: { label: fieldObj.name, detail: `(${fieldObj.tableName})`, description: i18n('sqlEditor.text.fieldName'), }, kind: monaco.languages.CompletionItemKind.Field, insertText: fieldObj.name, sortText: isFieldContext ? '01' : '08', })); return [...acc, ...arr]; }, []); return { suggestions, }; }, }); }; export { intelliSenseField, registerIntelliSenseField, addIntelliSenseField }; ================================================ FILE: chat2db-client/src/utils/IntelliSense/index.ts ================================================ import { intelliSenseKeyword, registerIntelliSenseKeyword, resetSenseKeyword } from './keyword'; import { intelliSenseDatabase, registerIntelliSenseDatabase, resetSenseDatabase } from './database'; import { intelliSenseTable, registerIntelliSenseTable, resetSenseTable } from './table'; import { intelliSenseField, registerIntelliSenseField, resetSenseField } from './field'; import { intelliSenseView, registerIntelliSenseView, resetSenseView } from './view'; export { intelliSenseKeyword, registerIntelliSenseKeyword, resetSenseKeyword, intelliSenseDatabase, registerIntelliSenseDatabase, resetSenseDatabase, intelliSenseTable, registerIntelliSenseTable, resetSenseTable, intelliSenseField, registerIntelliSenseField, resetSenseField, intelliSenseView, registerIntelliSenseView, resetSenseView, }; ================================================ FILE: chat2db-client/src/utils/IntelliSense/keyword.ts ================================================ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { DatabaseTypeCode } from '@/constants'; import intelliSense from '@/constants/IntelliSense'; import i18n from '@/i18n'; /** 关键词 */ const getSQLKeywords = (keywords: string[]) => { return keywords.map((key: any) => ({ label: { label: key, detail: '', description: i18n('sqlEditor.text.keyword'), }, kind: monaco.languages.CompletionItemKind.Text, insertText: key, sortText: '08', })); }; /** 函数 */ const getSQLFunctions = (functions: string[]) => { return functions.map((key: any) => ({ label: { label: key, detail: '', description: i18n('sqlEditor.text.function'), sortText: '09', }, // kind: monaco.languages.CompletionItemKind.Method, kind: monaco.languages.CompletionItemKind.Function, insertText: key, })); }; export const resetSenseKeyword = () => { intelliSenseKeyword.dispose(); } let intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; }, }); const registerIntelliSenseKeyword = (databaseCode?: DatabaseTypeCode) => { resetSenseKeyword(); intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '('], // 触发提示的字符 provideCompletionItems: (model, position) => { // 获取所有的编辑器实例 const commonIntelliSense = Object.values(intelliSense).find((v) => v.type === databaseCode); if (commonIntelliSense) { return { suggestions: [ ...getSQLKeywords(commonIntelliSense?.keywords), ...getSQLFunctions(commonIntelliSense?.functions), ], }; } else { const intelliSenseMySQL = Object.values(intelliSense).find((v) => v.type === DatabaseTypeCode.MYSQL); if (!intelliSenseMySQL) return { suggestions: [] }; return { suggestions: [ ...getSQLKeywords(intelliSenseMySQL?.keywords), ...getSQLFunctions(intelliSenseMySQL?.functions), ], }; } }, }); }; export { intelliSenseKeyword, registerIntelliSenseKeyword }; ================================================ FILE: chat2db-client/src/utils/IntelliSense/table.ts ================================================ import { DatabaseTypeCode } from '@/constants'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { addIntelliSenseField } from './field'; import i18n from '@/i18n'; import { compatibleDataBaseName } from '../database'; export const resetSenseTable = () => { intelliSenseTable.dispose(); }; /** 当前库下的表 */ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; }, }); const checkTableContext = (text) => { const normalizedText = text.trim().toUpperCase(); const tableKeywords = ['FROM', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'UPDATE']; for (const keyword of tableKeywords) { if (normalizedText.endsWith(keyword)) { return true; } } return false; }; const handleInsertText = (keyword: string, tableName: string, databaseCode: DatabaseTypeCode) => { if (/^[\"\`\[]/.test(keyword)) { return tableName; } return compatibleDataBaseName(tableName, databaseCode); }; const registerIntelliSenseTable = ( tableList: Array<{ name: string; comment: string }>, databaseCode: DatabaseTypeCode, dataSourceId?: number, databaseName?: string | null, schemaName?: string | null, ) => { monaco.editor.registerCommand('addFieldList', (_: any, ...args: any[]) => { addIntelliSenseField(args[0]); return; }); resetSenseTable(); intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '.'], provideCompletionItems: (model, position) => { const lineContentUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); const isTableContext = checkTableContext(lineContentUntilPosition); // 获取触发提示的字符 const match = lineContentUntilPosition.match(/\S+$/); const word = match ? match[0] : ''; return { suggestions: (tableList || []).map((tableName) => ({ label: { label: tableName.name, detail: databaseName ? `(${databaseName})` : null, description: i18n('sqlEditor.text.tableName'), }, kind: monaco.languages.CompletionItemKind.Folder, insertText: handleInsertText(word, tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), // documentation: tableName.comment, sortText: isTableContext ? '01' : '08', command: { id: 'addFieldList', title: 'addFieldList', arguments: [ { tableName: tableName.name, dataSourceId, databaseName, schemaName, }, ], }, })), }; }, }); }; export { intelliSenseTable, registerIntelliSenseTable }; ================================================ FILE: chat2db-client/src/utils/IntelliSense/view.ts ================================================ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import i18n from '@/i18n'; export const resetSenseView = () => { intelliSenseView.dispose(); } /** 当前库下的表 */ let intelliSenseView = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: () => { return { suggestions: [] }; }, }); const checkViewContext = (text) => { const normalizedText = text.trim().toUpperCase(); const tableKeywords = ['FROM', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'UPDATE']; for (const keyword of tableKeywords) { if (normalizedText.endsWith(keyword)) { return true; } } return false; }; const registerIntelliSenseView = ( viewList: string[], databaseName?: string | null, ) => { resetSenseView(); intelliSenseView = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' '], provideCompletionItems: (model, position) => { const lineContentUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); const isViewContext = checkViewContext(lineContentUntilPosition); return { suggestions: (viewList || []).map((viewName) => { return { label: { label: viewName, detail: databaseName ? `(${databaseName})` : null, description: i18n('sqlEditor.text.viewName'), }, kind: monaco.languages.CompletionItemKind.Unit, insertText: viewName, // range: monaco.Range.fromPositions(position), // documentation: tableName.comment, sortText: isViewContext ? '01' : '08' }; }), }; }, }); }; export { intelliSenseView, registerIntelliSenseView }; ================================================ FILE: chat2db-client/src/utils/check.ts ================================================ // type NonNullable = T extends null | undefined ? never : T; const toString = Object.prototype.toString; const isType = (type: string | string[]) => (obj: unknown): obj is T => getType(obj) === `[object ${type}]`; export const getType = (obj: any) => toString.call(obj); export const isArr = Array.isArray; export const isValid = (val: any) => val !== null && val !== undefined; export const isFn = (val: any): val is Function => typeof val === 'function'; export const isPlainObj = isType('Object'); export const isStr = isType('String'); export const isBool = isType('Boolean'); export const isNum = isType('Number'); export const isMap = (val: any): val is Map => val && val instanceof Map; export const isSet = (val: any): val is Set => val && val instanceof Set; export const isWeakMap = (val: any): val is WeakMap => val && val instanceof WeakMap; export const isWeakSet = (val: any): val is WeakSet => val && val instanceof WeakSet; export const isNumberLike = (index: any): index is number => isNum(index) || /^\d+$/.test(index); export const isObj = (val: unknown): val is object => typeof val === 'object'; export const isRegExp = isType('RegExp'); export const isReactElement = (obj: any): boolean => obj && obj['$$typeof'] && obj['_owner']; export const isHTMLElement = (target: any): target is EventTarget => { return Object.prototype.toString.call(target).indexOf('HTML') > -1; }; ================================================ FILE: chat2db-client/src/utils/database.ts ================================================ import { DatabaseTypeCode } from '@/constants/common'; import { IWorkspaceModelType } from '@/models/workspace'; import { Option } from '@/typings/common'; export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { let newCascaderOptions: Option[] = []; if (databaseAndSchema.databases) { newCascaderOptions = (databaseAndSchema?.databases || []).map((t) => { let schemasList: Option[] = []; if (t.schemas) { schemasList = t.schemas.map((t) => { return { value: t.name, label: t.name, type: 'schema', isLeaf: true, }; }); } return { value: t.name, label: t.name, type: 'database', children: schemasList, isLeaf: schemasList.length === 0, }; }); } else if (databaseAndSchema?.schemas) { newCascaderOptions = (databaseAndSchema?.schemas || []).map((t) => { return { value: t.name, label: t.name, type: 'schema', isLeaf: true, }; }); } return newCascaderOptions; } /** * 兼容处理数据库名称 * @param databaseName * @param databaseType * @returns */ export function compatibleDataBaseName(databaseName: string, databaseType: DatabaseTypeCode) { //"" oracele sqlite postgrsql h2 dm // ` MYSQL clickhouse MariaDB // [ sqlserver if ( [ DatabaseTypeCode.ORACLE, DatabaseTypeCode.SQLITE, DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.H2, DatabaseTypeCode.DB2, DatabaseTypeCode.KINGBASE, DatabaseTypeCode.DM, ].includes(databaseType) ) { return `"${databaseName}"`; } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseType)) { return `[${databaseName}]`; } else if ( [DatabaseTypeCode.MYSQL, DatabaseTypeCode.CLICKHOUSE, DatabaseTypeCode.TIMEPLUS, DatabaseTypeCode.MARIADB].includes( databaseType, ) ) { return `\`${databaseName}\``; } else { return `${databaseName}`; } } ================================================ FILE: chat2db-client/src/utils/date.ts ================================================ export function formatDate(date: any, fmt = 'yyyy-MM-dd') { if (!date) { return ''; } if (typeof date == 'number' || typeof date == 'string') { date = new Date(date); } if (!(date instanceof Date) || isNaN(date.getTime())) { return ''; } var o: any = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), S: date.getMilliseconds(), }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; } // 带有时区的时间戳转换为0时区时间戳 export function transitionTimezoneTimestamp(timestamp: number) { const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 return timestamp + timezoneOffset } // 通过0时区的时间戳计算出用户的时间戳 export function getUserTimezoneTimestamp(timestamp: number | string) { const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 return +timestamp - timezoneOffset } ================================================ FILE: chat2db-client/src/utils/eventSource.ts ================================================ import { EventSourcePolyfill } from 'event-source-polyfill'; const connectToEventSource = (params: { url: string; uid: string; onOpen: Function; onMessage: Function; onError: Function; }) => { const { url, uid, onOpen, onMessage, onError } = params; if (!url || !onMessage || !onError) { throw new Error('url, onMessage, and onError are required'); } const DBHUB = localStorage.getItem('DBHUB'); const p = { headers: { uid, DBHUB, }, }; const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, p); eventSource.onopen = () => { onOpen(); }; eventSource.onmessage = (event) => { onMessage(event.data); }; eventSource.onerror = (error) => { onError(error); console.error('EventSourcePolyfill error:', error); }; // 返回一个关闭 eventSource 的函数,以便在需要时调用它 return () => { eventSource.close(); }; }; export default connectToEventSource; ================================================ FILE: chat2db-client/src/utils/file.ts ================================================ /** * 文件下载 * @param url * @param params */ export function downloadFile(url: string, params: any) { // 创建POST请求 fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', // 或者根据服务端的要求设置其他的内容类型 }, body: JSON.stringify(params), // 将参数转换为JSON字符串 }) .then((response) => { // 从content-disposition头中获取文件名 const contentDisposition = response.headers.get('content-disposition'); const filename = contentDisposition ? decodeURIComponent(contentDisposition.split("''")[1]) : 'file.txt'; // 获取返回的Blob数据 return response.blob().then((blob) => ({ blob, filename })); }) .then(({ blob, filename }) => { // 创建一个代表Blob对象的URL const blobUrl = URL.createObjectURL(blob); // 创建一个隐藏的 标签,并设置其 href 属性 const a = document.createElement('a'); a.style.display = 'none'; a.href = blobUrl; // 使用从响应头解析的文件名 a.download = filename; // 将 标签附加到 DOM,并触发点击事件 document.body.appendChild(a); a.click(); // 清理:从 DOM 中移除 标签,并释放Blob URL document.body.removeChild(a); URL.revokeObjectURL(blobUrl); }) .catch((error) => { console.error('下载文件失败:', error); }); } ================================================ FILE: chat2db-client/src/utils/getTree.ts ================================================ export const getTreeConfig = { } ================================================ FILE: chat2db-client/src/utils/index.ts ================================================ import { ThemeType } from '@/constants'; import { ITreeNode } from '@/typings'; import lodash from 'lodash'; export function getOsTheme() { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeType.Dark : ThemeType.Light; } export function deepClone(target: any) { const map = new WeakMap(); function isObject(_target: any) { return (typeof _target === 'object' && _target) || typeof _target === 'function'; } function clone(data: any) { if (!isObject(data)) { return data; } if ([Date, RegExp].includes(data.constructor)) { return new data.constructor(data); } if (typeof data === 'function') { return new Function('return ' + data.toString())(); } const exist = map.get(data); if (exist) { return exist; } if (data instanceof Map) { const result = new Map(); map.set(data, result); data.forEach((val, key) => { if (isObject(val)) { result.set(key, clone(val)); } else { result.set(key, val); } }); return result; } if (data instanceof Set) { const result = new Set(); map.set(data, result); data.forEach((val) => { if (isObject(val)) { result.add(clone(val)); } else { result.add(val); } }); return result; } const keys = Reflect.ownKeys(data); const allDesc = Object.getOwnPropertyDescriptors(data); const result = Object.create(Object.getPrototypeOf(data), allDesc); map.set(data, result); keys.forEach((key) => { const val = data[key]; if (isObject(val)) { result[key] = clone(val); } else { result[key] = val; } }); return result; } return clone(target); } // 模糊匹配树并且高亮 export function approximateTreeNode(treeData: ITreeNode[], target: string = '', isDelete = true) { if (target) { const newTree: ITreeNode[] = lodash.cloneDeep(treeData || []); newTree.map((item, index) => { // 暂时不递归,只搜索datasource // if(item.children?.length){ // item.children = approximateTreeNode(item.children, target,false); // } if (item.name?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && isDelete) { delete newTree[index]; } else { item.name = item.name?.replace(target, `${target}`); } }); return newTree.filter((i) => i); } else { return treeData; } } // 模糊匹配树并且高亮 export function approximateList( data: T[], target: string, // @ts-ignore' keyName: K = 'name', isDelete = true, ) { if (target) { const newData: T[] = lodash.cloneDeep(data || []); newData.map((item, index) => { // 暂时不递归,只搜索datasource // if(item.children?.length){ // item.children = approximateTreeNode(item.children, target,false); // } // @ts-ignore' if (item[keyName]?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && isDelete) { delete newData[index]; } else { // @ts-ignore' item[keyName] = item[keyName]?.replace(target, `${target}`); } }); return newData.filter((i) => i); } else { return data; } } // 获取var变量的值 export const callVar = (css: string) => { return getComputedStyle(document.documentElement).getPropertyValue(css).trim(); }; // 给我一个 obj[], 和 obj的 key 和 value,给你返index export function findObjListValue(list: T[], key: K, value: any) { let flag = -1; list.forEach((t: T, index) => { Object.keys(t).forEach((j: K) => { if (j === key && t[j] === value) { flag = index; } }); }); return flag; } // 清理就版本不兼容的LocalStorage export function clearOlderLocalStorage() { if (localStorage.getItem('app-local-storage-versions') !== 'v4') { localStorage.clear(); localStorage.setItem('app-local-storage-versions', 'v4'); } } // 退出登录清理一些记录位置的localStorage export function logoutClearSomeLocalStorage() { localStorage.removeItem('current-workspace-database'); localStorage.removeItem('cur-connection'); localStorage.removeItem('active-console-id'); localStorage.removeItem('curPage'); } // 判断是否需要更新版本 export function isVersionHigher(version: string, currentVersion: string): boolean { // 按照 . 分割版本号 const versionParts = version.split('.'); const currentVersionParts = currentVersion.split('.'); // 按照从左到右的顺序比较每一位的大小 for (let i = 0; i < versionParts.length; i++) { const part = parseInt(versionParts[i]); const currentPart = parseInt(currentVersionParts[i] || '0'); if (part > currentPart) { return true; } else if (part < currentPart) { return false; } } // 如果两个版本号完全相等,则返回false return false; } // 获取应用的一些基本信息 export function getApplicationMessage() { const env = __ENV__; const versions = __APP_VERSION__; const buildTime = __BUILD_TIME__; const userAgent = navigator.userAgent; return { env, versions, buildTime, userAgent, }; } // os is mac or windows export function osNow(): { isMac: boolean; isWin: boolean; } { const agent = navigator.userAgent.toLowerCase(); const isMac = /macintosh|mac os x/i.test(navigator.userAgent); const isWin = agent.indexOf('win32') >= 0 || agent.indexOf('wow32') >= 0 || agent.indexOf('win64') >= 0 || agent.indexOf('wow64') >= 0; return { isMac, isWin, }; } // 桌面端用hash模式,web端用history模式,路由跳转 export function navigate(path: string) { if (__ENV__ === 'desktop') { window.location.replace(`#${path}`); } else { window.location.replace(path); } } // 获取cookie export function getCookie(name: string) { const arr = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)(;|$)')); if (arr != null) { return decodeURIComponent(arr[2]); } return null; } // 判断两个版本的大小 export function compareVersion(version1: string, version2: string) { const v1 = version1.split('.'); const v2 = version2.split('.'); const len = Math.max(v1.length, v2.length); while (v1.length < len) { v1.push('0'); } while (v2.length < len) { v2.push('0'); } for (let i = 0; i < len; i++) { const num1 = parseInt(v1[i]); const num2 = parseInt(v2[i]); if (num1 > num2) { return 1; } else if (num1 < num2) { return -1; } } return 0; } // 把剪切板的内容转成二维数组 export function clipboardToArray(text: string): Array> { if (!text) { return [[]]; } try { const rows = text.split('\n'); const array2D = rows.map((row) => row.split('\t')); return array2D; } catch { console.log('copy error'); return [[]]; } } // Copy export function copy(message: string) { // clipboardCopy(message); navigator.clipboard.writeText(message); } // 二维数组复制 export function tableCopy(array2D: Array>) { try { const text = array2D.map((row) => row.join('\t')).join('\n'); navigator.clipboard.writeText(text); } catch { console.log('copy error'); } } ================================================ FILE: chat2db-client/src/utils/localStorage.ts ================================================ import { ThemeType, PrimaryColorType, LangType } from '@/constants'; import { ICurWorkspaceParams } from '@/models/workspace'; import { getCookie } from '@/utils'; export function getLang(): LangType { return (localStorage.getItem('lang') as LangType) || 'en-us'; } export function setLang(lang: LangType) { return localStorage.setItem('lang', lang); } export function getTheme(): ThemeType { const themeColor: any = localStorage.getItem('theme') as ThemeType if (themeColor) { return themeColor } localStorage.setItem('theme', ThemeType.Light) // 默认主题色 return ThemeType.Light } export function setTheme(theme: ThemeType) { return localStorage.setItem('theme', theme); } export function getPrimaryColor(): PrimaryColorType { const primaryColor = localStorage.getItem('primary-color') as PrimaryColorType if (primaryColor) { return primaryColor } localStorage.setItem('primary-color', PrimaryColorType.Golden_Purple) // 默认主题色 return PrimaryColorType.Golden_Purple } export function setPrimaryColor(primaryColor: PrimaryColorType) { return localStorage.setItem('primary-color', primaryColor); } export function setCurrentWorkspaceDatabase(value: ICurWorkspaceParams) { return localStorage.setItem(`current-workspace-database`, JSON.stringify(value)); } export function getCurrentWorkspaceDatabase(): ICurWorkspaceParams { const curWorkspaceParams = localStorage.getItem(`current-workspace-database`); if (curWorkspaceParams) { return JSON.parse(curWorkspaceParams); } return {} as ICurWorkspaceParams; } export function getCurConnection() { const curConnection = localStorage.getItem(`cur-connection`); if (curConnection) { return JSON.parse(curConnection || '{}'); } return undefined; } ================================================ FILE: chat2db-client/src/utils/lodash.ts ================================================ ================================================ FILE: chat2db-client/src/utils/sort.ts ================================================ /** * 比较两个字符串数字的大小,包含大数情况 * @param a * @param b * @returns */ export function compareStrings(a: string, b: string) { if (!a ) { return -1; } if (!b) { return 1; } // 比较字符串长度 if (a.length !== b.length) { return a.length - b.length; } // 逐位比较字符的ASCII码值 for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) { return a.charCodeAt(i) - b.charCodeAt(i); } } // 如果两个字符串完全相等,则返回0 return 0; } ================================================ FILE: chat2db-client/src/utils/sql.ts ================================================ import { DatabaseTypeCode } from '@/constants'; import sqlServer from '@/service/sql'; import { format } from 'sql-formatter'; /** * 格式化sql */ export function formatSql(sql: string, dbType: DatabaseTypeCode) { const arr = [ 'bigquery', 'db2', 'hive', 'mariadb', 'mysql', 'n1ql', 'plsql', 'postgresql', 'redshift', 'spark', 'sqlite', 'sql', 'trino', 'transactsql', 'singlestoredb', 'snowflake', ]; const language = arr.includes(dbType.toLowerCase()) ? dbType.toLowerCase() : 'sql'; return new Promise((r: (sql: string) => void) => { let formatRes = ''; // debugger; try { formatRes = format(sql || '', { language }); // formatRes = ''; } catch (e) { console.log('Frontend format sql error', e); } // // 如果格式化失败,直接返回原始sql if (!formatRes) { sqlServer .sqlFormat({ sql, dbType, }) .then((res) => { formatRes = res; r(formatRes); }); } else { r(formatRes); } }); } ================================================ FILE: chat2db-client/src/utils/timezone.ts ================================================ export function getLinkBasedOnTimezone(): string { // 获取当前时区 const timezone = new Intl.DateTimeFormat().resolvedOptions().timeZone; // 定义中国时区的链接和非中国时区的链接 const chinaLink = "https://chat2db-ai.com"; const nonChinaLink = "https://chat2db.ai"; // 判断时区是否为中国的时区,这里简化为检查是否为"Asia/Shanghai"或"Asia/Chongqing" // 你也可以根据需要检查时区偏移是否为UTC+8 if (timezone === "Asia/Shanghai" || timezone === "Asia/Chongqing") { return chinaLink; } else { return nonChinaLink; } } ================================================ FILE: chat2db-client/src/utils/url.ts ================================================ /** * 获取url参数 * @param paramName * @returns */ export function getUrlParam(paramName) { // 获取当前URL const currentUrl = window.location.href; // 获取URL中的查询字符串 const queryString = currentUrl.split('?')[1]; // 如果没有查询字符串,直接返回 if (queryString === undefined) { return; } // 将查询字符串分割成数组 const paramList = queryString.split('&'); // 遍历每个参数 for (let i = 0; i < paramList.length; i++) { // 检查每个参数的名称是否匹配 const param = paramList[i].split('=')[0]; if (param === paramName) { // 返回参数值 return paramList[i].split('=')[1]; } } // 如果没有找到参数,则返回null return null; } /** * 更新URL的参数 * @param key * @param value * @returns */ export function updateQueryStringParameter(key, value) { const uri = window.location.href; if (!value) { return uri; } const re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i'); const separator = uri.indexOf('?') !== -1 ? '&' : '?'; if (uri.match(re)) { return uri.replace(re, '$1' + key + '=' + value + '$2'); } else { return uri + separator + key + '=' + value; } } /** * 格式化参数 * @param obj * @returns */ export function formatParams(obj: { [key: string]: any }) { const params = new URLSearchParams(); Object.entries(obj).forEach(([key, value]) => { if (value === undefined || value === null) { return; } if (Array.isArray(value)) { value.forEach((item) => { params.append(key, item); }); } else { params.append(key, value); } }); return params.toString(); } /** * 生成url * @param key * @returns */ export function generateUrl(key: string) { if (__ENV__ === 'desktop') { return window.location.href.split('/#/')[0] + '/#/' + key; } return window.location.origin + '/' + key; } ================================================ FILE: chat2db-client/src/utils/webpack.ts ================================================ // .umirc里无法识别到@/xxxx 所以单独开了webpack.ts,这个文件只可以写函数,不可以引入其他组件 // 分割出yarn启动命令里添加的参数 export function extractYarnConfig(argv: string[]){ const newArgv = argv.slice(2) const yarn_config:{[k in string]: string} = {} newArgv.forEach(t=>{ if(t && t.startsWith("--")){ const regex = /--(.+?)=(.+)/; const matches = t.match(regex); if (matches) { const key = matches[1]; const value = matches[2]; yarn_config[key] = value } } }) return yarn_config } export function formatDate(date: any, fmt = 'yyyy-MM-dd') { if (!date) { return ''; } if (typeof date == 'number' || typeof date == 'string') { date = new Date(date); } if (!(date instanceof Date) || isNaN(date.getTime())) { return ''; } var o: any = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), S: date.getMilliseconds(), }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; } // 带有时区的时间戳转换为0时区时间戳 export function transitionTimezoneTimestamp(timestamp: number) { const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 return timestamp + timezoneOffset } ================================================ FILE: chat2db-client/tsconfig.json ================================================ { "extends": "./src/.umi/tsconfig.json", "compilerOptions": { "moduleResolution": "node", "jsx": "react-jsx", "noImplicitAny": false } } ================================================ FILE: chat2db-client/typings.d.ts ================================================ import 'umi/typings'; import { IVersionResponse } from '@/typings'; declare module 'monaco-editor/esm/vs/basic-languages/sql/sql'; declare module 'monaco-editor/esm/vs/language/typescript/ts.worker.js'; declare module 'monaco-editor/esm/vs/editor/editor.worker.js'; declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: 'development' | 'production' readonly UMI_ENV: string readonly __ENV: string; } } declare global { interface Window { _Lang: string; _APP_PORT: string; _BUILD_TIME: string; _BaseURL: string; _AppThemePack: { [key in string]: string }; _appGatewayParams: IVersionResponse; _notificationApi: any; _indexedDB: any; electronApi?: { startServerForSpawn: () => void; quitApp: () => void; setBaseURL: (baseUrl: string) => void; registerAppMenu: (data: any) => void; setForceQuitCode: (code: boolean) => void; setMaximize: () => void; getPlatform: () => { isLinux: boolean, isWin: boolean, isMac: boolean, }; minimizeWindow: () => void; closeWindow: () => void; isMaximized: () => boolean; }; } const __APP_VERSION__: string; const __BUILD_TIME__: string; const __ENV__: string; const __APP_PORT__: string; } ================================================ FILE: chat2db-server/.apifox-helper.properties ================================================ # Configuration generated by easy-yapi plug-in # Convert date to long json.rule.convert[java.util.Date]=java.lang.Long json.rule.convert[java.sql.Timestamp]=java.lang.Long json.rule.convert[java.time.LocalDateTime]=java.lang.Long json.rule.convert[java.time.LocalDate]=java.lang.Long # Use version to modify tags api.tag=#version # ignore serialVersionUID constant.field.ignore=groovy:it.name()=="serialVersionUID" # sprnSupport for Jackson annotations json.rule.field.ignore=@com.fasterxml.jackson.annotation.JsonIgnore#value field.demo=#demo field.default.value=#default #apifox mock field.mock=#mock ================================================ FILE: chat2db-server/.easy.api.config ================================================ # Configuration generated by easy-yapi plug-in # Convert date to long json.rule.convert[java.util.Date]=java.lang.Long json.rule.convert[java.sql.Timestamp]=java.lang.Long json.rule.convert[java.time.LocalDateTime]=java.lang.Long json.rule.convert[java.time.LocalDate]=java.lang.Long # Use version to modify tags api.tag=#version # ignore serialVersionUID constant.field.ignore=groovy:it.name()=="serialVersionUID" # sprnSupport for Jackson annotations json.rule.field.ignore=@com.fasterxml.jackson.annotation.JsonIgnore#value ================================================ FILE: chat2db-server/.gitignore ================================================ target/ rebel.xml rebel-remote.xml .gradle .flattened-pom.xml ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ ## The code generated by front-end packaging does not need to be submitted to github /chat2db-server-start/src/main/resources/static/front/ /chat2db-server-start/src/main/resources/thymeleaf/ ================================================ FILE: chat2db-server/README.md ================================================ # 内部协作规范 ## 接口规范 不会使用的参照:https://yuque.antfin-inc.com/docs/share/8a5ff21a-6367-4c77-9e3c-1d5ae9570060?# 《yapi》 ## 国际化处理方案 * 在`messages.properties` 文件下新增code * 规范是 `作用域.描述` ,比如 `dataSource.sqlAnalysisError` * 方案1:在需要提示用户的地方抛出业务异常 ```java // 框架会将 dataSource.sqlAnalysisError 翻译成对应的异常,并返回给前端 throw new BusinessException("dataSource.sqlAnalysisError"); ``` * 方案2:不用异常直接获取国际化 ```java // 直接可以获取国际化翻译的文案 I18nUtils.getMessage("dataSource.sqlAnalysisError") ``` ### 国际化中文乱码 Editor -> File Encodeings -> Defualt encoding for properties files: 改成 utf-8 ### 编辑国际化文件 建议安装插件 `Resource Bundle Editor`去编辑,,点击`messages.properties`,下方有个`Resource Bundle` 就可以编辑了。 ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-clickhouse src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java ================================================ package ai.chat2db.plugin.clickhouse; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import org.apache.commons.lang3.StringUtils; import java.sql.*; import java.util.Objects; public class ClickHouseDBManage extends DefaultDBManage implements DBManage { @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTablesOrViewsOrDictionaries(connection, databaseName, schemaName,asyncContext); exportFunctions(connection, asyncContext); } private void exportFunctions(Connection connection, AsyncContext asyncContext) throws SQLException { String sql ="SELECT name,create_query from system.functions where origin='SQLUserDefined'"; try(ResultSet resultSet=connection.createStatement().executeQuery(sql)){ while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP FUNCTION IF EXISTS ").append(resultSet.getString("name")).append(";") .append("\n") .append(resultSet.getString("create_query")).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTablesOrViewsOrDictionaries(Connection connection,String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql =String.format("SELECT create_table_query, has_own_data,engine,name from system.`tables` WHERE `database`='%s'", databaseName); try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { String ddl = resultSet.getString("create_table_query"); boolean dataFlag = resultSet.getInt("has_own_data") == 1; String tableType = resultSet.getString("engine"); String tableOrViewName = resultSet.getString("name"); if (Objects.equals("View", tableType)) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP VIEW IF EXISTS ").append(databaseName).append(".").append(tableOrViewName) .append(";").append("\n").append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } else if (Objects.equals("Dictionary", tableType)) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP DICTIONARY IF EXISTS ").append(databaseName).append(".").append(tableOrViewName) .append(";").append("\n").append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } else { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP TABLE IF EXISTS ").append(databaseName).append(".").append(tableOrViewName) .append(";").append("\n").append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData() && dataFlag) { exportTableData(connection, databaseName,schemaName, tableOrViewName, asyncContext); } } } } } @Override public Connection getConnection(ConnectInfo connectInfo) { String url = setDatabaseInJdbcUrl(connectInfo); connectInfo.setUrl(url); return super.getConnection(connectInfo); } private String setDatabaseInJdbcUrl(ConnectInfo connectInfo) { String databaseName; String url = connectInfo.getUrl(); if (StringUtils.isBlank((databaseName = connectInfo.getDatabaseName())) && StringUtils.isBlank((databaseName = connectInfo.getSchemaName()))) { return url; } String connectAddress = connectInfo.getHost() + ":" + connectInfo.getPort(); String[] addressSplit = url.split(connectAddress); String connectParams = addressSplit.length == 2 ? addressSplit[1] : ""; if (connectParams.startsWith("/")) { // Remove / from connection parameters connectParams = connectParams.substring(1); if (connectParams.startsWith(databaseName)) { // 删除连接参数中的数据库名 connectParams = connectParams.substring(databaseName.length()); } else { // 是否有连接参数 int beginIndex = connectParams.indexOf("?"); // 无连接参数直接设置 "" if (beginIndex == -1) { connectParams = ""; } else { // 删除连接参数前的数据库名 connectParams = connectParams.substring(beginIndex); } } } // Add database name return addressSplit[0] + connectAddress + "/" + databaseName + connectParams; } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE IF EXISTS " + databaseName + "." + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException { String sql = "CREATE TABLE " + newTableName + " AS " + tableName + ""; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); if(copyData){ sql = "INSERT INTO " + newTableName + " SELECT * FROM " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseMetaData.java ================================================ package ai.chat2db.plugin.clickhouse; import ai.chat2db.plugin.clickhouse.builder.ClickHouseSqlBuilder; import ai.chat2db.plugin.clickhouse.type.ClickHouseColumnTypeEnum; import ai.chat2db.plugin.clickhouse.type.ClickHouseEngineTypeEnum; import ai.chat2db.plugin.clickhouse.type.ClickHouseIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.stream.Collectors; import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class ClickHouseMetaData extends DefaultMetaService implements MetaData { private static String ROUTINES_SQL = "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + "routine_name = '%s';"; private static String TRIGGER_SQL = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; private static String TRIGGER_SQL_LIST = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; private static String SELECT_TABLE_COLUMNS = "select * from `system`.columns where table ='%s' and database='%s';"; private static String VIEW_SQL = "SELECT create_table_query from system.`tables` WHERE `database`='%s' and name='%s'"; private List systemDatabases = Arrays.asList("information_schema", "system"); public static final String FUNCTION_SQL = "SELECT name,create_query as ddl from system.functions where origin='SQLUserDefined'"; public static String format(String tableName) { return "`" + tableName + "`"; } @Override public List functions(Connection connection, String databaseName, String schemaName) { return SQLExecutor.getInstance().execute(connection, FUNCTION_SQL, resultSet -> { List functions = new ArrayList<>(); while (resultSet.next()) { Function function = new Function(); function.setFunctionName(resultSet.getString("name")); functions.add(function); } return functions; }); } @Override public List databases(Connection connection) { List list = SQLExecutor.getInstance().execute(connection, "SELECT name FROM system.databases;", resultSet -> { List databases = new ArrayList<>(); try { while (resultSet.next()) { String dbName = resultSet.getString("name"); Database database = new Database(); database.setName(dbName); databases.add(database); } } catch (SQLException e) { throw new RuntimeException(e); } return databases; }); return sortDatabase(list, systemDatabases, connection); } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(schemaName) + "." + format(tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { return resultSet.getString("Create Table"); } return null; }); } @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { return SQLExecutor.getInstance().execute(connection, FUNCTION_SQL, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { /* function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); function.setRemarks(resultSet.getString("ROUTINE_COMMENT"));*/ function.setFunctionBody(resultSet.getString("ddl")); } return function; }); } @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); String sql = String.format(TRIGGER_SQL_LIST, schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, databaseName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); } return trigger; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "PROCEDURE", schemaName, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); } return procedure; }); } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TABLE_COLUMNS, tableName, databaseName); List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn column = new TableColumn(); column.setDatabaseName(databaseName); column.setTableName(tableName); column.setOldName(resultSet.getString("name")); column.setName(resultSet.getString("name")); String dataType = resultSet.getString("type"); if (dataType.startsWith("Nullable(")) { dataType = dataType.substring(9, dataType.length() - 1); column.setNullable(1); } column.setColumnType(dataType); column.setDefaultValue(resultSet.getString("default_expression")); // column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); column.setComment(resultSet.getString("comment")); column.setOrdinalPosition(resultSet.getInt("position")); column.setDecimalDigits(resultSet.getInt("numeric_scale")); /*column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); column.setCollationName(resultSet.getString("COLLATION_NAME"));*/ setColumnSize(column, dataType); tableColumns.add(column); } return tableColumns; }); } private void setColumnSize(TableColumn column, String columnType) { try { if (columnType.contains("(")) { String size = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")")); if ("SET".equalsIgnoreCase(column.getColumnType()) || "ENUM".equalsIgnoreCase(column.getColumnType())) { column.setValue(size); } else { if (size.contains(",")) { String[] sizes = size.split(","); if (StringUtils.isNotBlank(sizes[0])) { column.setColumnSize(Integer.parseInt(sizes[0])); } if (StringUtils.isNotBlank(sizes[1])) { column.setDecimalDigits(Integer.parseInt(sizes[1])); } } else { column.setColumnSize(Integer.parseInt(size)); } } } } catch (Exception e) { } } @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, databaseName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("create_table_query")); } return table; }); } @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM "); queryBuf.append("`").append(schemaName).append("`"); queryBuf.append(" FROM "); queryBuf.append("`").append(databaseName).append("`"); return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique(!resultSet.getBoolean("Non_unique")); index.setType(resultSet.getString("Index_type")); // index.setComment(resultSet.getString("Index_comment")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.addAll(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); if ("PRIMARY".equalsIgnoreCase(keyName)) { index.setType(ClickHouseIndexTypeEnum.PRIMARY.getName()); } map.put(keyName, index); } return map.values().stream().collect(Collectors.toList()); }); } private List getTableIndexColumn(ResultSet resultSet) throws SQLException { List tableIndexColumns = new ArrayList<>(); String name = StringUtils.isBlank(resultSet.getString("column_name")) ? resultSet.getString("expression") : resultSet.getString("column_name"); if (StringUtils.isNotBlank(name)) { String[] split = name.split(","); for (String columName : split) { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(columName); tableIndexColumn.setOrdinalPosition(resultSet.getShort("seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("collation")); tableIndexColumn.setCardinality(resultSet.getLong("cardinality")); tableIndexColumn.setSubPart(resultSet.getLong("sub_part")); tableIndexColumns.add(tableIndexColumn); } } return tableIndexColumns; } @Override public SqlBuilder getSqlBuilder() { return new ClickHouseSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(ClickHouseColumnTypeEnum.getTypes()) .engineTypes(ClickHouseEngineTypeEnum.getTypes()) .indexTypes(ClickHouseIndexTypeEnum.getIndexTypes()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); } @Override public List getSystemDatabases() { return systemDatabases; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHousePlugin.java ================================================ package ai.chat2db.plugin.clickhouse; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class ClickHousePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"clickhouse.json", DBConfig.class); } @Override public MetaData getMetaData() { return new ClickHouseMetaData(); } @Override public DBManage getDBManage() { return new ClickHouseDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/ClickHouseSqlBuilder.java ================================================ package ai.chat2db.plugin.clickhouse.builder; import ai.chat2db.plugin.clickhouse.type.ClickHouseColumnTypeEnum; import ai.chat2db.plugin.clickhouse.type.ClickHouseIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; import java.util.List; public class ClickHouseSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { // Initialize StringBuilder to build the SQL script StringBuilder script = new StringBuilder("CREATE TABLE "); // Append the database name, if present appendDatabaseName(script, table.getDatabaseName()); // Append the table name script.append("`").append(table.getName()).append("`").append(" (").append("\n"); // append column appendColumns(script, table.getColumnList()); // append index appendIndexes(script, table.getIndexList()); // Remove the last comma script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); // Append the engine, if present appendEngine(script, table.getEngine()); // append primary key appendPrimaryKey(script, table.getIndexList()); // Append the comment, if present appendComment(script, table.getComment()); // Append a semicolon to complete the SQL statement script.append(";"); // Return the complete SQL script return script.toString(); } // Method to append the database name to the SQL script private void appendDatabaseName(StringBuilder script, String databaseName) { if (StringUtils.isNotBlank(databaseName)) { script.append("`").append(databaseName).append("`."); } } // Method to append columns to the SQL script private void appendColumns(StringBuilder script, List columns) { for (TableColumn column : columns) { // Check if column name and type are not blank if (StringUtils.isNotBlank(column.getName()) && StringUtils.isNotBlank(column.getColumnType())) { // Get the column type enum and append the column SQL to the script ClickHouseColumnTypeEnum typeEnum = ClickHouseColumnTypeEnum.getByType(column.getColumnType()); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } } } // Method to append indexes to the SQL script private void appendIndexes(StringBuilder script, List indexes) { for (TableIndex index : indexes) { // Check if index name and type are not blank if (StringUtils.isNotBlank(index.getName()) && StringUtils.isNotBlank(index.getType())) { // Get the index type enum and append the index script to the script ClickHouseIndexTypeEnum indexTypeEnum = ClickHouseIndexTypeEnum.getByType(index.getType()); if (!ClickHouseIndexTypeEnum.PRIMARY.equals(indexTypeEnum)) { script.append("\t").append(indexTypeEnum.buildIndexScript(index)).append(",\n"); } } } } // Method to append the engine to the SQL script private void appendEngine(StringBuilder script, String engine) { if (StringUtils.isNotBlank(engine)) { script.append(" ENGINE=").append(engine).append("\n"); } } // Method to append the primary key to the SQL script private void appendPrimaryKey(StringBuilder script, List indexes) { for (TableIndex index : indexes) { // Check if index name and type are not blank if (StringUtils.isNotBlank(index.getName()) && StringUtils.isNotBlank(index.getType())) { // Get the index type enum and append the index script to the script ClickHouseIndexTypeEnum indexTypeEnum = ClickHouseIndexTypeEnum.getByType(index.getType()); if (ClickHouseIndexTypeEnum.PRIMARY.equals(indexTypeEnum)) { script.append("\t").append(indexTypeEnum.buildIndexScript(index)).append("\n"); } } } } // Method to append the comment to the SQL script private void appendComment(StringBuilder script, String comment) { if (StringUtils.isNotBlank(comment)) { script.append(" COMMENT '").append(comment).append("'"); } } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE "); if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { script.append("`").append(oldTable.getDatabaseName()).append("`").append("."); } script.append("`").append(oldTable.getName()).append("`").append("\n"); if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { script.append("\t").append("MODIFY COMMENT").append("'").append(newTable.getComment()).append("'").append(",\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { ClickHouseColumnTypeEnum typeEnum = ClickHouseColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { ClickHouseIndexTypeEnum clickHouseIndexTypeEnum = ClickHouseIndexTypeEnum .getByType(tableIndex.getType()); if(clickHouseIndexTypeEnum == null){ continue; } script.append("\t").append(clickHouseIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); } } if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if (offset == 0) { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(pageSize); } else { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(offset); sqlBuilder.append(","); sqlBuilder.append(pageSize); } return sqlBuilder.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`"); if(StringUtils.isNotBlank(database.getComment())){ sqlBuilder.append(";ALTER DATABASE ").append(database.getName()).append(" COMMENT '").append(database.getComment()).append("';"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json ================================================ { "dbType": "CLICKHOUSE", "supportDatabase": false, "supportSchema":true, "driverConfigList": [ { "url": "jdbc:clickhouse://localhost:8123/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar" ], "jdbcDriver": "clickhouse-jdbc-0.3.2-patch8-http.jar", "jdbcDriverClass": "com.clickhouse.jdbc.ClickHouseDriver" } ], "name": "ClickHouse" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/type/ClickHouseColumnTypeEnum.java ================================================ package ai.chat2db.plugin.clickhouse.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum ClickHouseColumnTypeEnum implements ColumnBuilder { String("String", false, false, true, false, false, false, true, true, false, false), Int8("Int8", false, false, true, false, false, false, true, true, false, false), Int16("Int16", false, false, true, false, false, false, true, true, false, false), Int32("Int32", false, false, true, false, false, false, true, true, false, false), Int64("Int64", false, false, true, false, false, false, true, true, false, false), Int128("Int128", false, false, true, false, false, false, true, true, false, false), Int256("Int256", false, false, true, false, false, false, true, true, false, false), UInt8("UInt8", false, false, true, false, false, false, true, true, false, false), UInt16("UInt16", false, false, true, false, false, false, true, true, false, false), UInt32("UInt32", false, false, true, false, false, false, true, true, false, false), UInt64("UInt64", false, false, true, false, false, false, true, true, false, false), UInt128("UInt128", false, false, true, false, false, false, true, true, false, false), UInt256("UInt256", false, false, true, false, false, false, true, true, false, false), Float32("Float32", false, false, true, false, false, false, true, true, false, false), Float64("Float64", false, false, true, false, false, false, true, true, false, false), Decimal("Decimal", true, true, true, false, false, false, true, true, false, false), Boolean("Boolean", false, false, true, false, false, false, true, true, false, false), FixedString("FixedString", false, false, true, false, false, false, true, true, false, false), UUID("UUID", false, false, true, false, false, false, true, true, false, false), Date("Date", false, false, true, false, false, false, true, true, false, false), DATE32("DATE32", false, false, true, false, false, false, true, true, false, false), DateTime("DateTime", false, false, true, false, false, false, true, true, false, false), DateTime64("DateTime64", false, false, true, false, false, false, true, true, false, false), Enum8("Enum8", false, false, true, false, false, false, true, true, false, false), Enum16("Enum16", false, false, true, false, false, false, true, true, false, false), Array("Array", false, false, false, false, false, false, true, true, false, false), JSON("JSON", false, false, true, false, false, false, true, true, false, false), Nested("Nested", false, false, true, false, false, false, true, true, false, false), Map("Map", true, true, true, false, false, false, true, true, false, false), IPv4("IPv4", false, false, true, false, false, false, true, true, false, false), IPv6("IPv6", false, false, true, false, false, false, true, true, false, false), Point("Point", false, false, true, false, false, false, true, true, false, false), Ring("Ring", false, false, true, false, false, false, true, true, false, false), Polygon("Polygon", false, false, true, false, false, false, true, true, false, false), MultiPolygon("MultiPolygon", false, false, true, false, false, false, true, true, false, false), AggregateFunction("AggregateFunction", true, true, true, false, false, false, true, true, false, false), SimpleAggregateFunction("SimpleAggregateFunction", true, true, true, false, false, false, true, true, false, false), ; private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (ClickHouseColumnTypeEnum value : ClickHouseColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } private ColumnType columnType; ClickHouseColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); } public static ClickHouseColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public static List getTypes() { return Arrays.stream(ClickHouseColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } public ColumnType getColumnType() { return columnType; } @Override public String buildCreateColumnSql(TableColumn column) { ClickHouseColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildNullableAndDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildComment(column, type)).append(" "); return script.toString(); } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { String modifyColumn = ""; if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { modifyColumn = StringUtils.join("RENAME COLUMN `", tableColumn.getOldName(), "` TO `", tableColumn.getName(), "`, ", buildCreateColumnSql(tableColumn)); } return StringUtils.join(modifyColumn, "MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); } return ""; } private String buildComment(TableColumn column, ClickHouseColumnTypeEnum type) { if (!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())) { return ""; } return StringUtils.join("COMMENT '", column.getComment(), "'"); } private String buildDefaultValue(TableColumn column, ClickHouseColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } if (Arrays.asList(Enum8,Enum16).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(Date).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(DateTime).contains(type)) { if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ", column.getDefaultValue()); } return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildNullableAndDataType(TableColumn column, ClickHouseColumnTypeEnum type) { StringBuilder script = new StringBuilder(); script.append(buildDataType(column, type)); if (!type.getColumnType().isSupportNullable()) { return script.toString(); } if (column.getNullable() != null && 1 == column.getNullable()) { return "Nullable("+script.append(")").toString(); } else { return script.toString(); } } private String buildDataType(TableColumn column, ClickHouseColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(FixedString).contains(type)) { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(Decimal).contains(type)) { if (column.getColumnSize() == null || column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } if (column.getColumnSize() != null && column.getDecimalDigits() != null) { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } return columnType; } public String buildColumn(TableColumn column) { ClickHouseColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); if (StringUtils.isNoneBlank(column.getComment())) { script.append("COMMENT").append(" ").append("'").append(column.getComment()).append("'").append(" "); } return script.toString(); } private String unsignedDataType(String dataTypeName, String middle) { String[] split = dataTypeName.split(" "); if (split.length == 2) { return StringUtils.join(split[0], middle, split[1]); } return StringUtils.join(dataTypeName, middle); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/type/ClickHouseEngineTypeEnum.java ================================================ package ai.chat2db.plugin.clickhouse.type; import ai.chat2db.spi.model.EngineType; import com.google.common.collect.Maps; import java.util.Arrays; import java.util.List; import java.util.Map; public enum ClickHouseEngineTypeEnum { AzureBlobStorage("AzureBlobStorage",false,true,false,false,true,false,false,false ), KeeperMap("KeeperMap",false,true,false,false,false,true,false,false ), SQLite("SQLite",false,false,false,false,false,false,false,false ), ExternalDistributed("ExternalDistributed",false,false,false,false,false,false,false,false ), PostgreSQL("PostgreSQL",false,false,false,false,false,false,false,false ), NATS("NATS",false,false,false,false,true,false,false,false ), RabbitMQ("RabbitMQ",false,false,false,false,true,false,false,false ), Kafka("Kafka",false,false,false,false,true,false,false,false ), MongoDB("MongoDB",false,false,false,false,false,false,false,false ), FileLog("FileLog",false,false,false,false,true,false,false,false ), Dictionary("Dictionary",false,false,false,false,false,false,false,false ), MySQL("MySQL",false,false,false,false,true,false,false,false ), S3Queue("S3Queue",false,false,false,false,true,false,false,false ), HDFS("HDFS",false,true,false,false,false,false,false,false ), MaterializedPostgreSQL("MaterializedPostgreSQL",false,true,false,false,true,false,false,false ), S3("S3",false,true,false,false,true,false,false,false ), FuzzJSON("FuzzJSON",false,false,false,false,false,false,false,false ), OSS("OSS",false,true,false,false,true,false,false,false ), WindowView("WindowView",false,false,false,false,false,false,false,false ), Distributed("Distributed",false,false,false,false,true,true,false,false ), ReplicatedSummingMergeTree("ReplicatedSummingMergeTree",true,true,true,true,true,true,true,true ), ExecutablePool("ExecutablePool",false,false,false,false,true,false,false,false ), COSN("COSN",false,true,false,false,true,false,false,false ), Iceberg("Iceberg",false,false,false,false,false,false,false,false ), MaterializedView("MaterializedView",false,false,false,false,false,false,false,false ), View("View",false,false,false,false,false,false,false,false ), JDBC("JDBC",false,false,false,false,false,false,false,false ), Join("Join",false,false,false,false,true,false,false,false ), Executable("Executable",false,false,false,false,true,false,false,false ), Set("Set",false,false,false,false,true,false,false,false ), Redis("Redis",false,true,false,false,false,true,false,false ), GenerateRandom("GenerateRandom",false,false,false,false,false,false,false,false ), LiveView("LiveView",false,false,false,false,false,false,false,false ), MergeTree("MergeTree",true,true,true,false,true,true,true,false ), ReplicatedReplacingMergeTree("ReplicatedReplacingMergeTree",true,true,true,true,true,true,true,true ), Memory("Memory",false,false,false,false,true,true,false,false ), Buffer("Buffer",false,false,false,false,false,true,false,false ), URL("URL",false,false,false,false,true,false,false,false ), ReplicatedVersionedCollapsingMergeTree("ReplicatedVersionedCollapsingMergeTree",true,true,true,true,true,true,true,true ), VersionedCollapsingMergeTree("VersionedCollapsingMergeTree",true,true,true,false,true,true,true,false ), Hive("Hive",false,true,false,false,true,false,false,false ), ReplacingMergeTree("ReplacingMergeTree",true,true,true,false,true,true,true,false ), ReplicatedAggregatingMergeTree("ReplicatedAggregatingMergeTree",true,true,true,true,true,true,true,true ), ReplicatedMergeTree("ReplicatedMergeTree",true,true,true,true,true,true,true,true ), DeltaLake("DeltaLake",false,false,false,false,false,false,false,false ), EmbeddedRocksDB("EmbeddedRocksDB",true,true,false,false,false,true,false,false ), ReplicatedCollapsingMergeTree("ReplicatedCollapsingMergeTree",true,true,true,true,true,true,true,true ), File("File",false,false,false,false,true,false,false,false ), TinyLog("TinyLog",false,false,false,false,true,false,false,false ), ReplicatedGraphiteMergeTree("ReplicatedGraphiteMergeTree",true,true,true,true,true,true,true,true ), SummingMergeTree("SummingMergeTree",true,true,true,false,true,true,true,false ), Hudi("Hudi",false,false,false,false,false,false,false,false ), GraphiteMergeTree("GraphiteMergeTree",true,true,true,false,true,true,true,false ), CollapsingMergeTree("CollapsingMergeTree",true,true,true,false,true,true,true,false ), Merge("Merge",false,false,false,false,false,false,false,false ), AggregatingMergeTree("AggregatingMergeTree",true,true,true,false,true,true,true,false ), ODBC("ODBC",false,false,false,false,false,false,false,false ), Null("Null",false,false,false,false,false,true,false,false ), StripeLog("StripeLog",false,false,false,false,true,false,false,false ), Log("Log",false,false,false,false,true,false,false,false ), ; private static Map ENGINE_TYPE_MAP = Maps.newHashMap(); static { for (ClickHouseEngineTypeEnum value : ClickHouseEngineTypeEnum.values()) { ENGINE_TYPE_MAP.put(value.getEngineType().getName(), value); } } private EngineType engineType; ClickHouseEngineTypeEnum(String name, boolean supportTTL, boolean supportSortOrder, boolean supportSkippingIndices, boolean supportDeduplication, boolean supportSettings, boolean supportParallelInsert, boolean supportProjections, boolean supportReplication) { this.engineType = new EngineType(name, supportTTL, supportSortOrder, supportSkippingIndices, supportDeduplication, supportSettings, supportParallelInsert, supportProjections, supportReplication); } public static ClickHouseEngineTypeEnum getByType(String dataType) { return ENGINE_TYPE_MAP.get(dataType.toUpperCase()); } public static List getTypes() { return Arrays.stream(ClickHouseEngineTypeEnum.values()).map(engineTypeEnum -> engineTypeEnum.getEngineType() ).toList(); } public EngineType getEngineType() { return engineType; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/type/ClickHouseIndexTypeEnum.java ================================================ package ai.chat2db.plugin.clickhouse.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum ClickHouseIndexTypeEnum { PRIMARY("Primary", "PRIMARY KEY"), MINMAX("MINMAX", "INDEX"), SET("SET", "INDEX"), BLOOM_FILTER("BLOOM_FILTER", "INDEX"), TOKENBF_V1("TOKENBF_V1", "INDEX"), NGRAMBF_V1("NGRAMBF_V1", "INDEX"), INVERTED("INVERTED", "INDEX"), HYPOTHESIS("HYPOTHESIS", "INDEX"), ANNOY("ANNOY", "INDEX"), USEARCH("USEARCH", "INDEX"), ; private String name; private String keyword; private IndexType indexType; ClickHouseIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static ClickHouseIndexTypeEnum getByType(String type) { for (ClickHouseIndexTypeEnum value : ClickHouseIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public static List getIndexTypes() { return Arrays.asList(ClickHouseIndexTypeEnum.values()).stream().map(ClickHouseIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } public String getName() { return name; } public String getKeyword() { return keyword; } public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); script.append(buildIndexType(tableIndex)).append(" "); return script.toString(); } private String buildIndexType(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } else { return "TYPE " + name ; } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("`").append(column.getColumnName()).append("`"); script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } else { return "`" + tableIndex.getName() + "`"; } } public String buildModifyIndex(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("DROP INDEX `", tableIndex.getOldName(), "`"); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("DROP INDEX `", tableIndex.getOldName(), "`,\n ADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("ADD ", buildIndexScript(tableIndex)); } return ""; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.clickhouse.ClickHousePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-db2 src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java ================================================ package ai.chat2db.plugin.db2; import ai.chat2db.plugin.db2.constant.SQLConstant; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @Slf4j public class DB2DBManage extends DefaultDBManage implements DBManage { private static String PROCEDURE_SQL = "SELECT COUNT(*) AS procedure_count\n" + "FROM SYSCAT.PROCEDURES\n" + "WHERE PROCNAME = '%s';"; @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTables(connection, databaseName, schemaName, asyncContext); exportViews(connection, schemaName, asyncContext); exportProceduresAndFunctions(connection, schemaName, asyncContext); exportTriggers(connection, schemaName, asyncContext); } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(null, schemaName, null, new String[]{"TABLE", "SYSTEM TABLE"})) { while (resultSet.next()) { exportTable(connection, databaseName, schemaName, resultSet.getString("TABLE_NAME"), asyncContext); } } } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { try { SQLExecutor.getInstance().execute(connection, SQLConstant.TABLE_DDL_FUNCTION_SQL, resultSet -> null); } catch (Exception e) { //log.error("Failed to create function", e); } String sql = String.format("select %s.GENERATE_TABLE_DDL('%s', '%s') as sql from %s;", schemaName, schemaName, tableName, tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("sql")).append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } } } } private void exportViews(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("select TEXT from syscat.views where VIEWSCHEMA='%s';", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String ddl = resultSet.getString("TEXT"); sqlBuilder.append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportProceduresAndFunctions(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("select TEXT from syscat.routines where ROUTINESCHEMA='%s';", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String ddl = resultSet.getString("TEXT"); sqlBuilder.append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("select * from SYSCAT.TRIGGERS where TRIGSCHEMA = '%s';", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String ddl = resultSet.getString("TEXT"); sqlBuilder.append(ddl).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, procedure.getProcedureName().toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrReplace) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> {}); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { return; } String schemaName = connectInfo.getSchemaName(); try { SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\""); } catch (SQLException e) { } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData) throws SQLException { String sql = "CREATE TABLE " + newTableName + " LIKE " + tableName + " INCLUDING INDEXES"; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); if (copyData) { sql = "INSERT INTO " + newTableName + " SELECT * FROM " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java ================================================ package ai.chat2db.plugin.db2; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import ai.chat2db.plugin.db2.builder.DB2SqlBuilder; import ai.chat2db.plugin.db2.constant.SQLConstant; import ai.chat2db.plugin.db2.type.DB2ColumnTypeEnum; import ai.chat2db.plugin.db2.type.DB2DefaultValueEnum; import ai.chat2db.plugin.db2.type.DB2IndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; public class DB2MetaData extends DefaultMetaService implements MetaData { private List systemSchemas = Arrays.asList("NULLID","SQLJ","SYSCAT","SYSFUN","SYSIBM","SYSIBMADM","SYSIBMINTERNAL","SYSIBMTS","SYSPROC","SYSPUBLIC","SYSSTAT","SYSTOOLS"); @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { SQLExecutor.getInstance().execute(connection, SQLConstant.TABLE_DDL_FUNCTION_SQL, resultSet -> null); } catch (Exception e) { //log.error("Failed to create function", e); } String ddlSql = String.format("select %s.GENERATE_TABLE_DDL('%s', '%s') as sql from %s;",schemaName,schemaName,tableName,tableName); return SQLExecutor.getInstance().execute(connection, ddlSql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); } } catch (SQLException e) { throw new RuntimeException(e); } return null; }); } @Override public SqlBuilder getSqlBuilder() { return new DB2SqlBuilder(); } private static String IDX_SQL = "SELECT i.INDNAME, i.UNIQUERULE, i.REMARKS, ic.COLNAME, ic.COLSEQ, ic.COLORDER FROM SYSCAT.INDEXES i JOIN SYSCAT.INDEXCOLUSE ic ON i.INDNAME = ic.INDNAME AND i.INDSCHEMA = ic.INDSCHEMA WHERE i.TABNAME = '%s' AND i.INDSCHEMA = '%s' ORDER BY i.INDNAME, ic.COLSEQ"; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(IDX_SQL, tableName, schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("INDNAME"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setComment(resultSet.getString("REMARKS")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); String uniquerule = resultSet.getString("UNIQUERULE"); if("P".equalsIgnoreCase(uniquerule)) { index.setType(DB2IndexTypeEnum.PRIMARY_KEY.getName()); index.setUnique(true); }else if("U".equalsIgnoreCase(uniquerule)){ index.setType(DB2IndexTypeEnum.UNIQUE.getName()); index.setUnique(true); }else { index.setType(DB2IndexTypeEnum.NORMAL.getName()); index.setUnique(false); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private static String VIEW_DDL_SQL="select TEXT from syscat.views where VIEWSCHEMA='%s' and VIEWNAME='%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_DDL_SQL, schemaName, viewName); Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { table.setDdl(resultSet.getString("TEXT")+";"); } }); return table; } private static String ROUTINE_DDL_SQL="select TEXT from syscat.routines where ROUTINESCHEMA='%s' and ROUTINENAME='%s' and ROUTINETYPE='%s';"; @Override public Function function(Connection connection, String databaseName, String schemaName, String functionName) { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); String sql = String.format(ROUTINE_DDL_SQL, schemaName, functionName,'F'); SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { function.setFunctionBody(resultSet.getString("TEXT")+";"); } }); return function; } @Override public Procedure procedure(Connection connection, String databaseName, String schemaName, String procedureName) { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); String sql = String.format(ROUTINE_DDL_SQL, schemaName, procedureName,'P'); SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("TEXT")+";"); } }); return procedure; } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("COLNAME")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("COLSEQ")); // tableIndexColumn.setCollation(resultSet.getString("Collation")); // tableIndexColumn.setCardinality(resultSet.getLong("Cardinality")); // tableIndexColumn.setSubPart(resultSet.getLong("Sub_part")); String collation = resultSet.getString("COLORDER"); if ("A".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("ASC"); } else if ("D".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("DESC"); } return tableIndexColumn; } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(DB2ColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(DB2IndexTypeEnum.getIndexTypes()) .defaultValues(DB2DefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemSchemas() { return systemSchemas; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2Plugin.java ================================================ package ai.chat2db.plugin.db2; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class DB2Plugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"db2.json", DBConfig.class); } @Override public MetaData getMetaData() { return new DB2MetaData(); } @Override public DBManage getDBManage() { return new DB2DBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java ================================================ package ai.chat2db.plugin.db2.builder; import ai.chat2db.plugin.db2.type.DB2ColumnTypeEnum; import ai.chat2db.plugin.db2.type.DB2IndexTypeEnum; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; public class DB2SqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } DB2ColumnTypeEnum typeEnum = DB2ColumnTypeEnum.getByType(column.getColumnType()); if (typeEnum == null) { continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n);"); for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } DB2IndexTypeEnum indexTypeEnum = DB2IndexTypeEnum.getByType(tableIndex.getType()); if (indexTypeEnum == null) { continue; } script.append("\n").append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); if(StringUtils.isNotBlank(tableIndex.getComment())){ script.append("\n").append(indexTypeEnum.buildIndexComment(tableIndex)).append(";"); } } for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildComment(column)).append(";"); } if (StringUtils.isNotBlank(table.getComment())) { script.append("\n").append(buildTableComment(table)).append(";"); } return script.toString(); } private String buildTableComment(Table table) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" IS '").append(table.getComment()).append("'"); return script.toString(); } private String buildComment(TableColumn column) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON COLUMN ").append("\"").append(column.getSchemaName()).append("\".\"").append(column.getTableName()).append("\".\"").append(column.getName()).append("\" IS '").append(column.getComment()).append("'"); return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { script.append("").append(buildTableComment(newTable)).append(";\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { DB2ColumnTypeEnum typeEnum = DB2ColumnTypeEnum.getByType(tableColumn.getColumnType()); if (typeEnum == null) { continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { DB2IndexTypeEnum mysqlIndexTypeEnum = DB2IndexTypeEnum.getByType(tableIndex.getType()); if (mysqlIndexTypeEnum == null) { continue; } script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); if(StringUtils.isNotBlank(tableIndex.getComment())) { script.append("\n").append(mysqlIndexTypeEnum.buildIndexComment(tableIndex)).append(";\n"); } } } if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { int startRow = offset + 1; int endRow = offset + pageSize; StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); sqlBuilder.append("SELECT * FROM (SELECT TMP_PAGE.*,ROWNUMBER() OVER() AS CAHT2DB_AUTO_ROW_ID FROM ( \n"); sqlBuilder.append(sql); sqlBuilder.append("\n ) AS TMP_PAGE) TMP_PAGE WHERE CAHT2DB_AUTO_ROW_ID BETWEEN "); sqlBuilder.append(startRow); sqlBuilder.append(" AND "); sqlBuilder.append(endRow); return sqlBuilder.toString(); } @Override public String buildCreateSchemaSql(Schema schema) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA \"" + schema.getName() + "\";"); if (StringUtils.isNotBlank(schema.getComment())) { sqlBuilder.append("\nCOMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/constant/SQLConstant.java ================================================ package ai.chat2db.plugin.db2.constant; /** * @author: zgq * @date: 2024年03月16日 10:11 */ public class SQLConstant { public static final String TABLE_DDL_FUNCTION_SQL = """ CREATE OR REPLACE FUNCTION generate_table_ddl(schema_name VARCHAR(128), table_name VARCHAR(128)) RETURNS CLOB LANGUAGE SQL BEGIN DECLARE ddl CLOB; -- 获取表的注释信息 DECLARE table_remarks CLOB; SELECT REMARKS INTO table_remarks FROM SYSCAT.TABLES WHERE TABSCHEMA = schema_name AND TABNAME = table_name; -- 拼接表的创建语句 SET ddl = 'CREATE TABLE ' || table_name || ' ('; -- 获取表的字段信息并拼接到DDL语句中 FOR col_info AS SELECT COLNAME, TYPENAME, LENGTH, SCALE, NULLS, DEFAULT as default, REMARKS FROM SYSCAT.COLUMNS WHERE TABSCHEMA = schema_name AND TABNAME = table_name ORDER BY COLNO DO SET ddl = ddl || col_info.COLNAME || ' '; IF col_info.TYPENAME = 'INTEGER' THEN SET ddl = ddl || col_info.TYPENAME; ELSE SET ddl = ddl || col_info.TYPENAME; IF col_info.LENGTH IS NOT NULL THEN SET ddl = ddl || '(' || col_info.LENGTH; IF col_info.TYPENAME != 'VARCHAR' AND col_info.SCALE IS NOT NULL THEN SET ddl = ddl || ',' || col_info.SCALE; END IF; SET ddl = ddl || ')'; END IF; END IF; IF col_info.NULLS = 'N' THEN SET ddl = ddl || ' NOT NULL'; END IF; IF col_info.default IS NOT NULL THEN SET ddl = ddl || ' DEFAULT ' || col_info.default; END IF; SET ddl = ddl || ','; -- 添加字段定义结束符 END FOR; -- 删除最后一个逗号 SET ddl = LEFT(ddl, LENGTH(ddl) - 1); SET ddl = ddl || ');'; -- 添加表的注释 IF table_remarks IS NOT NULL THEN SET ddl = ddl || 'comment on table ' || table_name || ' is ''' || table_remarks || ''';'; END IF; for column as SELECT COLNAME, REMARKS FROM SYSCAT.COLUMNS WHERE TABSCHEMA = schema_name AND TABNAME = table_name ORDER BY COLNO do if column.REMARKS is not null then set ddl = ddl || 'comment on column ' || table_name || '.' || column.COLNAME || ' is ''' || column.REMARKS || ''';'; end if; end for; -- 获取表的索引信息并拼接到DDL语句中 FOR index_info AS SELECT INDNAME, SUBSTR(COLNAMES, 2) AS COLNAMES, UNIQUERULE FROM SYSCAT.INDEXES WHERE TABSCHEMA = schema_name AND TABNAME = table_name DO IF index_info.UNIQUERULE = 'P' THEN SET ddl = ddl || ' ALTER TABLE ' || table_name || ' ADD PRIMARY KEY (' || index_info.COLNAMES || ');'; ELSEIF index_info.UNIQUERULE = 'U' THEN SET ddl = ddl || ' CREATE UNIQUE INDEX ' || index_info.INDNAME || ' ON ' || table_name || ' (' || index_info.COLNAMES || ');'; END IF; END FOR; RETURN ddl; END;"""; } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json ================================================ { "dbType": "DB2", "supportDatabase": false, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:db2://localhost:50000/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/db2jcc4_4.26.14.jar" ], "jdbcDriver": "db2jcc4_4.26.14.jar", "jdbcDriverClass": "com.ibm.db2.jcc.DB2Driver" } ], "name": "DB2" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2ColumnTypeEnum.java ================================================ package ai.chat2db.plugin.db2.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum DB2ColumnTypeEnum implements ColumnBuilder { ANCHOR("ANCHOR", false, false, true, false, false, false, true, true, false, false), BIGINT("BIGINT", false, false, true, false, false, false, true, true, false, false), BINARY("BINARY", false, false, true, false, false, false, true, true, false, false), // BIT("BIT", false, false, true, false, false, false, true, true, false, false), BLOB("BLOB", false, false, true, false, false, false, true, true, false, false), BOOLEAN("BOOLEAN", false, false, true, false, false, false, true, true, false, false), CHAR("CHAR", true, false, true, false, false, false, true, true, false, true), // CHAR_VARYING("CHAR VARYING", true, false, true, false, false, false, true, true, false, true), // CHARACTER("CHARACTER", true, false, true, false, false, false, true, true, false, true), // // CHARACTER_VARYING("CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), CLOB("CLOB", false, false, true, false, false, false, true, true, false, false), COURSE("COURSE", false, false, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DB2SECURITYLABEL("DB2SECURITYLABEL", false, false, true, false, false, false, true, true, false, false), DBCLOB("DBCLOB", false, false, true, false, false, false, true, true, false, false), DEC("DEC", true, true, true, false, false, false, true, true, false, false), DECFLOAT("DECFLOAT", false, false, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), DOUBLE("DOUBLE", false, false, true, false, false, false, true, true, false, false), FLOAT("FLOAT", true, false, true, false, false, false, true, true, false, false), GRAPHIC("GRAPHIC", false, false, true, false, false, false, true, true, false, false), INT("INT", false, false, true, false, false, false, true, true, false, false), INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false), LONG("LONG", false, false, true, false, false, false, true, true, false, false), NCHAR("NCHAR", true, false, true, false, false, false, true, true, false, true), NCLOB("NCLOB", false, false, true, false, false, false, true, true, false, false), // LONGVARBINARY("LONGVARBINARY", false, false, true, false, false, false, true, true, false, false), // LONGVARCHAR("LONGVARCHAR", true, false, true, false, false, false, true, true, false, false), NUM("NUM", true, true, true, false, false, false, true, true, false, false), NUMBERIC("NUMBERIC", true, true, true, false, false, false, true, true, false, false), NVARCHAR("NVARCHAR", true, false, true, false, false, false, true, true, false, true), REAL("REAL", false, false, true, false, false, false, true, true, false, false), REF("REF", false, false, true, false, false, false, true, true, false, false), SMALLINT("SMALLINT", false, false, true, false, false, false, true, true, false, false), TIME("TIME", false, false, true, false, false, false, true, true, false, false), // TIME_WITH_TIME_ZONE("TIME WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false), TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, true, true, false, false), // TIMESTAMP_WITH_TIME_ZONE("TIMESTAMP WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false), // TINYINT("TINYINT", false, false, true, false, false, false, true, true, false, false), VARBINARY("VARBINARY", false, false, true, false, false, false, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, false, false, true, true, false, true), //VARCHAR2("VARCHAR2", true, false, true, false, false, false, true, true, false, true), XML("XML", false, false, true, false, false, false, true, true, false, false), ; private ColumnType columnType; public static DB2ColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (DB2ColumnTypeEnum value : DB2ColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } public ColumnType getColumnType() { return columnType; } DB2ColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportUnit) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, false, supportUnit); } @Override public String buildCreateColumnSql(TableColumn column) { DB2ColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); return script.toString(); } private String buildNullable(TableColumn column, DB2ColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDefaultValue(TableColumn column, DB2ColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildDataType(TableColumn column, DB2ColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(CHAR, VARCHAR,NCHAR,CHARACTER,NVARCHAR).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")"); } return script.toString(); } if (Arrays.asList(DEC,DECIMAL, FLOAT, NUM, TIMESTAMP, NUMBERIC).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && column.getDecimalDigits() == null) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); } return script.toString(); } return columnType; } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\""); return script.toString(); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")"); return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append(") \n"); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { script.append(";"); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\""); } return script.toString(); } return ""; } public static List getTypes() { return Arrays.stream(DB2ColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2DefaultValueEnum.java ================================================ package ai.chat2db.plugin.db2.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum DB2DefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; DB2DefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(DB2DefaultValueEnum.values()).map(DB2DefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2IndexTypeEnum.java ================================================ package ai.chat2db.plugin.db2.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum DB2IndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"); // BITMAP("BITMAP", "BITMAP INDEX"); public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; DB2IndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static DB2IndexTypeEnum getByType(String type) { for (DB2IndexTypeEnum value : DB2IndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (PRIMARY_KEY.equals(this)) { script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex)); } else { if (UNIQUE.equals(this)) { script.append("CREATE UNIQUE INDEX "); } else { script.append("CREATE INDEX "); } script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); } return script.toString(); } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\""); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } public String buildIndexComment(TableIndex tableIndex) { if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return ""; } else if (NORMAL.equals(this) || UNIQUE.equals(this)) { return StringUtils.join("COMMENT ON INDEX", " ", "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';"); } else { return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(), "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';"); } } private String buildIndexName(TableIndex tableIndex) { return "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), ";\n", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (DB2IndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { String tableName = "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getTableName() + "\""; return StringUtils.join("ALTER TABLE ",tableName," DROP PRIMARY KEY"); } StringBuilder script = new StringBuilder(); script.append("DROP INDEX "); script.append(buildIndexName(tableIndex)); return script.toString(); } public static List getIndexTypes() { return Arrays.asList(DB2IndexTypeEnum.values()).stream().map(DB2IndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-db2/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.db2.DB2Plugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-dm src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java ================================================ package ai.chat2db.plugin.dm; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @Slf4j public class DMDBManage extends DefaultDBManage implements DBManage { private String format(String tableName) { return "\"" + tableName + "\""; } private static String ROUTINES_SQL = "SELECT OWNER, NAME, TEXT FROM ALL_SOURCE WHERE TYPE = '%s' AND OWNER = '%s' AND NAME = '%s' ORDER BY LINE"; private static String TRIGGER_SQL_LIST = "SELECT OWNER, TRIGGER_NAME FROM ALL_TRIGGERS WHERE OWNER = '%s'"; private static String TRIGGER_SQL = "SELECT OWNER, TRIGGER_NAME, TABLE_OWNER, TABLE_NAME, TRIGGERING_TYPE, TRIGGERING_EVENT, STATUS, TRIGGER_BODY " + "FROM ALL_TRIGGERS WHERE OWNER = '%s' AND TRIGGER_NAME = '%s'"; private static String PROCEDURE_SQL = "SELECT COUNT(*)\n" + "FROM DBA_OBJECTS\n" + "WHERE OBJECT_TYPE = 'PROCEDURE' \n" + "AND OWNER = '%s' \n" + "AND OBJECT_NAME = '%s'"; @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTables(connection, databaseName, schemaName, asyncContext); exportViews(connection, schemaName, asyncContext); exportProcedures(connection, schemaName, asyncContext); exportTriggers(connection, schemaName, asyncContext); } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT TABLE_NAME FROM ALL_TABLES where OWNER='%s' and TABLESPACE_NAME='MAIN'", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String tableName = resultSet.getString("TABLE_NAME"); exportTable(connection, databaseName, tableName, schemaName, asyncContext); } } } public void exportTable(Connection connection, String databaseName, String tableName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = """ SELECT (SELECT comments FROM user_tab_comments WHERE table_name = '%s') AS comments, (SELECT dbms_metadata.get_ddl('TABLE', '%s', '%s') FROM dual) AS ddl FROM dual; """; try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(String.format(sql, tableName, tableName, schemaName))) { String formatSchemaName = format(schemaName); String formatTableName = format(tableName); if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP TABLE IF EXISTS ").append(formatSchemaName).append(".").append(formatTableName) .append(";").append("\n") .append(resultSet.getString("ddl")).append("\n"); String comment = resultSet.getString("comments"); if (StringUtils.isNotBlank(comment)) { sqlBuilder.append("COMMENT ON TABLE ").append(formatSchemaName).append(".").append(formatTableName) .append(" IS ").append("'").append(comment).append("';"); } asyncContext.write(sqlBuilder.toString()); exportTableColumnComment(connection, schemaName, tableName, asyncContext); } if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } } } private void exportTableColumnComment(Connection connection, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { String sql = String.format("select COLNAME,COMMENT$ from SYS.SYSCOLUMNCOMMENTS\n" + "where SCHNAME = '%s' and TVNAME = '%s'and TABLE_TYPE = 'TABLE';", schemaName, tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String columnName = resultSet.getString("COLNAME"); String comment = resultSet.getString("COMMENT$"); sqlBuilder.append("COMMENT ON COLUMN ").append(format(schemaName)).append(".").append(format(tableName)) .append(".").append(format(columnName)).append(" IS ").append("'").append(comment).append("';").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportViews(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(null, schemaName, null, new String[]{"VIEW"})) { while (resultSet.next()) { String viewName = resultSet.getString("TABLE_NAME"); exportView(connection, viewName, schemaName, asyncContext); } } } private void exportView(Connection connection, String viewName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT DBMS_METADATA.GET_DDL('VIEW','%s','%s') as ddl FROM DUAL;", viewName, schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("ddl")).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportProcedures(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getProcedures(null, schemaName, null)) { while (resultSet.next()) { String procedureName = resultSet.getString("PROCEDURE_NAME"); exportProcedure(connection, schemaName, procedureName, asyncContext); } } } private void exportProcedure(Connection connection, String schemaName, String procedureName, AsyncContext asyncContext) throws SQLException { String sql = String.format(ROUTINES_SQL, "PROC", schemaName, procedureName); try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("TEXT")).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format(TRIGGER_SQL_LIST, schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String triggerName = resultSet.getString("TRIGGER_NAME"); exportTrigger(connection, schemaName, triggerName, asyncContext); } } } private void exportTrigger(Connection connection, String schemaName, String triggerName, AsyncContext asyncContext) throws SQLException { String sql = String.format(TRIGGER_SQL, schemaName, triggerName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("TRIGGER_BODY")).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, schemaName.toUpperCase(),procedure.getProcedureName().toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrReplace) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> {}); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { return; } String schemaName = connectInfo.getSchemaName(); try { SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\""); } catch (SQLException e) { log.error("connectDatabase error", e); } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE IF EXISTS " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java ================================================ package ai.chat2db.plugin.dm; import ai.chat2db.plugin.dm.builder.DMSqlBuilder; import ai.chat2db.plugin.dm.type.DMColumnTypeEnum; import ai.chat2db.plugin.dm.type.DMDefaultValueEnum; import ai.chat2db.plugin.dm.type.DMIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; @Slf4j public class DMMetaData extends DefaultMetaService implements MetaData { private List systemSchemas = Arrays.asList("CTISYS", "SYS", "SYSDBA", "SYSSSO", "SYSAUDITOR"); @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } private String format(String tableName) { return "\"" + tableName + "\""; } private static String tableDDL = "SELECT dbms_metadata.get_ddl('TABLE', '%s','%s') as ddl FROM dual ;"; public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String tableDDLSql = String.format(tableDDL, tableName, schemaName); StringBuilder ddlBuilder = new StringBuilder(); SQLExecutor.getInstance().execute(connection, tableDDLSql, resultSet -> { if (resultSet.next()) { String ddl = resultSet.getString("ddl"); ddlBuilder.append(ddl).append("\n"); } }); MetaData metaData = Chat2DBContext.getMetaData(); List

tables = metaData.tables(connection, databaseName, schemaName, tableName); if (CollectionUtils.isNotEmpty(tables)) { String tableComment = tables.get(0).getComment(); if (StringUtils.isNotBlank(tableComment)) { ddlBuilder.append("COMMENT ON TABLE ").append(format(schemaName)).append(".").append(format(tableName)) .append(" IS '").append(tableComment.replace("'", "''")).append("'").append(";").append("\n"); } } List columns = metaData.columns(connection, databaseName, schemaName, tableName); if (CollectionUtils.isNotEmpty(columns)) { for (TableColumn column : columns) { String columnName = column.getName(); String comment = column.getComment(); if (StringUtils.isNotBlank(comment)) { ddlBuilder.append("COMMENT ON COLUMN ").append(format(schemaName)).append(".").append(format(tableName)) .append(".").append(format(columnName)).append(" IS ") .append("'").append(comment.replace("'", "''")) .append("';").append("\n"); } } } if (tableName.startsWith("V$")){ return ddlBuilder.toString(); } List indexes = metaData.indexes(connection, databaseName, schemaName, tableName); if (CollectionUtils.isNotEmpty(indexes)) { for (TableIndex index : indexes) { String indexName = index.getName(); if (StringUtils.isNotBlank(indexName)) { String sql = "select DBMS_METADATA.GET_DDL('INDEX','%s') as INDEX_DDL"; try { SQLExecutor.getInstance().execute(connection, String.format(sql,indexName), resultSet -> { if (resultSet.next()) { ddlBuilder.append(resultSet.getString("INDEX_DDL")).append("\n"); } }); } catch (Exception e) { log.warn("Failed to get the DDL of the index."); for (TableIndex tableIndex : indexes) { DMIndexTypeEnum indexTypeEnum = DMIndexTypeEnum.getByType(tableIndex.getType()); ddlBuilder.append("\n").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); } } } } } return ddlBuilder.toString(); } private static String ROUTINES_SQL = "SELECT OWNER, NAME, TEXT FROM ALL_SOURCE WHERE TYPE = '%s' AND OWNER = '%s' AND NAME = '%s' ORDER BY LINE"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "PROC", schemaName, functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { StringBuilder sb = new StringBuilder(); while (resultSet.next()) { sb.append(resultSet.getString("TEXT") + "\n"); } Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); function.setFunctionBody(sb.toString()); return function; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "PROC", schemaName, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { StringBuilder sb = new StringBuilder(); while (resultSet.next()) { sb.append(resultSet.getString("TEXT") + "\n"); } Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); procedure.setProcedureBody(sb.toString()); return procedure; }); } private static String TRIGGER_SQL = "SELECT OWNER, TRIGGER_NAME, TABLE_OWNER, TABLE_NAME, TRIGGERING_TYPE, TRIGGERING_EVENT, STATUS, TRIGGER_BODY " + "FROM ALL_TRIGGERS WHERE OWNER = '%s' AND TRIGGER_NAME = '%s'"; private static String TRIGGER_SQL_LIST = "SELECT OWNER, TRIGGER_NAME FROM ALL_TRIGGERS WHERE OWNER = '%s'"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); String sql = String.format(TRIGGER_SQL_LIST, schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, schemaName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("TRIGGER_BODY")); } return trigger; }); } private static String VIEW_SQL = "SELECT OWNER, VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s'"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("TEXT")); } return table; }); } private static String INDEX_SQL = "SELECT i.TABLE_NAME, i.INDEX_TYPE, i.INDEX_NAME, i.UNIQUENESS ,c.COLUMN_NAME, c.COLUMN_POSITION, c.DESCEND, cons.CONSTRAINT_TYPE FROM ALL_INDEXES i JOIN ALL_IND_COLUMNS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_NAME = c.TABLE_NAME AND i.TABLE_OWNER = c.TABLE_OWNER LEFT JOIN ALL_CONSTRAINTS cons ON i.INDEX_NAME = cons.INDEX_NAME AND i.TABLE_NAME = cons.TABLE_NAME AND i.TABLE_OWNER = cons.OWNER WHERE i.TABLE_OWNER = '%s' AND i.TABLE_NAME = '%s' ORDER BY i.INDEX_NAME, c.COLUMN_POSITION;"; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(INDEX_SQL, schemaName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("INDEX_NAME"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique("UNIQUE".equalsIgnoreCase(resultSet.getString("UNIQUENESS"))); // index.setType(resultSet.getString("Index_type")); // index.setComment(resultSet.getString("Index_comment")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); if ("P".equalsIgnoreCase(resultSet.getString("CONSTRAINT_TYPE"))) { index.setType(DMIndexTypeEnum.PRIMARY_KEY.getName()); } else if (index.getUnique()) { index.setType(DMIndexTypeEnum.UNIQUE.getName()); } else if ("BITMAP".equalsIgnoreCase(resultSet.getString("INDEX_TYPE"))) { index.setType(DMIndexTypeEnum.BITMAP.getName()); } else { index.setType(DMIndexTypeEnum.NORMAL.getName()); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("COLUMN_NAME")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("COLUMN_POSITION")); // tableIndexColumn.setCollation(resultSet.getString("Collation")); // tableIndexColumn.setCardinality(resultSet.getLong("Cardinality")); // tableIndexColumn.setSubPart(resultSet.getLong("Sub_part")); String collation = resultSet.getString("DESCEND"); if ("ASC".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("ASC"); } else if ("DESC".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("DESC"); } return tableIndexColumn; } @Override public SqlBuilder getSqlBuilder() { return new DMSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(DMColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(DMIndexTypeEnum.getIndexTypes()) .defaultValues(DMDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemSchemas() { return systemSchemas; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMPlugin.java ================================================ package ai.chat2db.plugin.dm; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class DMPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"dm.json", DBConfig.class); } @Override public MetaData getMetaData() { return new DMMetaData(); } @Override public DBManage getDBManage() { return new DMDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DMSqlBuilder.java ================================================ package ai.chat2db.plugin.dm.builder; import ai.chat2db.plugin.dm.type.DMColumnTypeEnum; import ai.chat2db.plugin.dm.type.DMIndexTypeEnum; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; import java.util.Objects; public class DMSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } DMColumnTypeEnum typeEnum = DMColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n);"); for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } DMIndexTypeEnum indexTypeEnum = DMIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } script.append("\n").append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); } for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildComment(column)).append(";"); } if (StringUtils.isNotBlank(table.getComment())) { script.append("\n").append(buildTableComment(table)).append(";"); } return script.toString(); } private String buildTableComment(Table table) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" IS '").append(table.getComment()).append("'"); return script.toString(); } private String buildComment(TableColumn column) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON COLUMN ").append("\"").append(column.getSchemaName()).append("\".\"").append(column.getTableName()).append("\".\"").append(column.getName()).append("\" IS '").append(column.getComment()).append("'"); return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { script.append("").append(buildTableComment(newTable)).append(";\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { String editStatus = tableColumn.getEditStatus(); if (StringUtils.isNotBlank(editStatus)) { DMColumnTypeEnum typeEnum = DMColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())&&!Objects.equals(EditStatus.DELETE.toString(),editStatus)) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { DMIndexTypeEnum mysqlIndexTypeEnum = DMIndexTypeEnum.getByType(tableIndex.getType()); if(mysqlIndexTypeEnum == null){ continue; } script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlStr = new StringBuilder(sql.length() + 17); sqlStr.append(sql); if (offset == 0) { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); } else { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); sqlStr.append(" OFFSET "); sqlStr.append(offset); } return sqlStr.toString(); } @Override public String buildCreateSchemaSql(Schema schema) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); if(StringUtils.isNotBlank(schema.getOwner())){ sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json ================================================ { "dbType": "DM", "supportDatabase": false, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:dm://localhost:5236/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/DmJdbcDriver18-8.1.2.141.jar" ], "jdbcDriver": "DmJdbcDriver18-8.1.2.141.jar", "jdbcDriverClass": "dm.jdbc.driver.DmDriver" } ], "name": "DM" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMColumnTypeEnum.java ================================================ package ai.chat2db.plugin.dm.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum DMColumnTypeEnum implements ColumnBuilder { BFILE("BFILE", false, false, true, false, false, false, true, true, false, false), BIGINT("BIGINT", false, false, true, false, false, false, true, true, false, false), BINARY("BINARY", false, false, true, false, false, false, true, true, false, false), BIT("BIT", false, false, true, false, false, false, true, true, false, false), BLOB("BLOB", false, false, true, false, false, false, true, true, false, false), CHAR("CHAR", true, false, true, false, false, false, true, true, false, true), // CHAR_VARYING("CHAR VARYING", true, false, true, false, false, false, true, true, false, true), // // CHARACTER("CHARACTER", true, false, true, false, false, false, true, true, false, true), // // CHARACTER_VARYING("CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), CLOB("CLOB", false, false, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), DEC("DEC", true, true, true, false, false, false, true, true, false, false), DOUBLE("DOUBLE", false, false, true, false, false, false, true, true, false, false), FLOAT("FLOAT", true, false, true, false, false, false, true, true, false, false), INT("INT", false, false, true, true, false, false, true, true, false, false), // INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false), INTERVAL_DAY("INTERVAL DAY", false, false, true, false, false, false, true, true, false, false), INTERVAL_DAY_TO_HOUR("INTERVAL DAY TO HOUR", true, false, true, false, false, false, true, true, false, false), INTERVAL_DAY_TO_MINUTE("INTERVAL DAY TO MINUTE", true, false, true, false, false, false, true, true, false, false), INTERVAL_DAY_TO_SECOND("INTERVAL DAY TO SECOND", true, false, true, false, false, false, true, true, false, false), INTERVAL_HOUR("INTERVAL HOUR", false, false, true, false, false, false, true, true, false, false), INTERVAL_HOUR_TO_MINUTE("INTERVAL HOUR TO MINUTE", true, false, true, false, false, false, true, true, false, false), INTERVAL_HOUR_TO_SECOND("INTERVAL HOUR TO SECOND", true, false, true, false, false, false, true, true, false, false), INTERVAL_MINUTE("INTERVAL MINUTE", false, false, true, false, false, false, true, true, false, false), INTERVAL_MINUTE_TO_SECOND("INTERVAL MINUTE TO SECOND", true, false, true, false, false, false, true, true, false, false), INTERVAL_MONTH("INTERVAL MONTH", false, false, true, false, false, false, true, true, false, false), INTERVAL_SECOND("INTERVAL SECOND", false, false, true, false, false, false, true, true, false, false), INTERVAL_YEAR("INTERVAL YEAR", false, false, true, false, false, false, true, true, false, false), INTERVAL_YEAR_TO_MONTH("INTERVAL YEAR TO MONTH", true, false, true, false, false, false, true, true, false, false), LONGVARBINARY("LONGVARBINARY", false, false, true, false, false, false, true, true, false, false), LONGVARCHAR("LONGVARCHAR", false, false, true, false, false, false, true, true, false, false), TEXT("TEXT", false, false, true, false, false, false, true, true, false, false), NUMERIC("NUMERIC", true, true, true, false, false, false, true, true, false, false), NUMBER("NUMBER", true, true, true, false, false, false, true, true, false, false), REAL("REAL", false, false, true, false, false, false, true, true, false, false), SMALLINT("SMALLINT", false, false, true, false, false, false, true, true, false, false), TIME("TIME", false, false, true, false, false, false, true, true, false, false), TIME_WITH_TIME_ZONE("TIME WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false), TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, true, true, false, false), TIMESTAMP_WITH_TIME_ZONE("TIMESTAMP WITH TIME ZONE", false, false, true, false, false, false, true, true, false, false), TINYINT("TINYINT", false, false, true, false, false, false, true, true, false, false), VARBINARY("VARBINARY", false, false, true, false, false, false, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, false, false, true, true, false, true), VARCHAR2("VARCHAR2", true, false, true, false, false, false, true, true, false, true), DATETIME("DATETIME", false, false, true, false, false, false, true, true, false, false), ; private ColumnType columnType; public static DMColumnTypeEnum getByType(String dataType) { String type = SqlUtils.removeDigits(dataType.toUpperCase()); return COLUMN_TYPE_MAP.get(type); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (DMColumnTypeEnum value : DMColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } public ColumnType getColumnType() { return columnType; } DMColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportUnit) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, false, supportUnit); } @Override public String buildCreateColumnSql(TableColumn column) { DMColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildAutoIncrement(column,type)).append(" "); script.append(buildNullable(column, type)).append(" "); return script.toString(); } private String buildAutoIncrement(TableColumn column, DMColumnTypeEnum type) { if(!type.getColumnType().isSupportAutoIncrement()){ return ""; } if (column.getAutoIncrement() != null && column.getAutoIncrement() && column.getSeed() != null && column.getSeed() > 0 && column.getIncrement() != null && column.getIncrement() > 0) { return "IDENTITY(" + column.getSeed() + "," + column.getIncrement() + ")"; } if (column.getAutoIncrement() != null && column.getAutoIncrement()) { return "IDENTITY(1,1)"; } return ""; } private String buildNullable(TableColumn column, DMColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDefaultValue(TableColumn column, DMColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildDataType(TableColumn column, DMColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(CHAR, VARCHAR, VARCHAR2, LONGVARCHAR,TEXT).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")"); } return script.toString(); } if (Arrays.asList(DECIMAL, DEC, FLOAT, NUMBER, TIMESTAMP, NUMERIC).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && column.getDecimalDigits() == null) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); } return script.toString(); } if (Arrays.asList(TIMESTAMP_WITH_TIME_ZONE).contains(type)) { StringBuilder script = new StringBuilder(); if (column.getColumnSize() == null) { script.append(columnType); } else { String[] split = columnType.split("TIMESTAMP"); script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]); } return script.toString(); } if (Arrays.asList(INTERVAL_DAY_TO_HOUR, INTERVAL_DAY_TO_MINUTE, INTERVAL_DAY_TO_SECOND, INTERVAL_HOUR_TO_MINUTE, INTERVAL_HOUR_TO_SECOND, INTERVAL_MINUTE_TO_SECOND, INTERVAL_YEAR_TO_MONTH).contains(type)) { StringBuilder script = new StringBuilder(); if (column.getColumnSize() == null) { script.append(columnType); } else { String[] split = columnType.split(" "); if (split.length == 4) { script.append(split[0]).append(" ").append(split[1]).append(" (").append(column.getColumnSize()).append(") ").append(split[2]).append(" ").append(split[3]); } } return script.toString(); } return columnType; } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\""); return script.toString(); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")"); return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append(") \n"); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { script.append(";"); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\""); } return script.toString(); } return ""; } public static List getTypes() { return Arrays.stream(DMColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMDefaultValueEnum.java ================================================ package ai.chat2db.plugin.dm.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum DMDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; DMDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(DMDefaultValueEnum.values()).map(DMDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMIndexTypeEnum.java ================================================ package ai.chat2db.plugin.dm.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum DMIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"), BITMAP("BITMAP", "BITMAP INDEX"); public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; DMIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static DMIndexTypeEnum getByType(String type) { for (DMIndexTypeEnum value : DMIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (PRIMARY_KEY.equals(this)) { script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex)); } else { if (UNIQUE.equals(this)) { script.append("CREATE UNIQUE INDEX "); } else { script.append("CREATE INDEX "); } script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); } return script.toString(); } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\""); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { return "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), ";\n", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (DMIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { String tableName = "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getTableName() + "\""; return StringUtils.join("ALTER TABLE ",tableName," DROP PRIMARY KEY"); } StringBuilder script = new StringBuilder(); script.append("DROP INDEX "); script.append(buildIndexName(tableIndex)); return script.toString(); } public static List getIndexTypes() { return Arrays.asList(DMIndexTypeEnum.values()).stream().map(DMIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-dm/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.dm.DMPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-h2 src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java ================================================ package ai.chat2db.plugin.h2; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Objects; public class H2DBManage extends DefaultDBManage implements DBManage { @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportSchema(connection, schemaName, asyncContext); } private void exportSchema(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SCRIPT NODATA NOPASSWORDS NOSETTINGS DROP SCHEMA %s;", schemaName); if (asyncContext.isContainsData()) { sql = sql.replace("NODATA", ""); } try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String script = resultSet.getString("SCRIPT"); if (!(script.startsWith("CREATE USER")||script.startsWith("--"))) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(script); sqlBuilder.append("\n"); asyncContext.write(sqlBuilder.toString()); } } } } @Override public void connectDatabase(Connection connection, String database) { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { return; } String schemaName = connectInfo.getSchemaName(); try { SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\""); } catch (SQLException e) { } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java ================================================ package ai.chat2db.plugin.h2; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import ai.chat2db.plugin.h2.builder.H2SqlBuilder; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import jakarta.validation.constraints.NotEmpty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @Slf4j public class H2Meta extends DefaultMetaService implements MetaData { private List systemSchemas = Arrays.asList("INFORMATION_SCHEMA"); @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { return getDDL(connection, databaseName, schemaName, tableName); } private String getDDL(Connection connection, String databaseName, String schemaName, String tableName) { try{ List columnDefinitions = getColumnDefinitions(connection, databaseName, schemaName, tableName); Map> indexMap = getIndexInfo(connection, databaseName, schemaName, tableName); StringBuilder createTableDDL = new StringBuilder("CREATE TABLE "); createTableDDL.append(tableName).append(" (\n"); createTableDDL.append(String.join(",\n", columnDefinitions)); createTableDDL.append("\n);\n"); // Output index information for (Map.Entry> entry : indexMap.entrySet()) { String indexName = entry.getKey(); List columnList = entry.getValue(); String indexColumns = String.join(", ", columnList); String createIndexDDL = String.format("CREATE INDEX %s ON %s (%s);", indexName, tableName, indexColumns); createTableDDL.append(createIndexDDL); } return createTableDDL.toString(); } catch (Exception e) { e.printStackTrace(); } return ""; } private List getColumnDefinitions(Connection connection, String databaseName, String schemaName, String tableName) { List columnDefinitions = new ArrayList<>(); try (ResultSet columns = connection.getMetaData().getColumns(databaseName, schemaName, tableName, null)) { while (columns.next()) { String columnName = columns.getString("COLUMN_NAME"); String columnType = columns.getString("TYPE_NAME"); int columnSize = columns.getInt("COLUMN_SIZE"); String remarks = columns.getString("REMARKS"); String defaultValue = columns.getString("COLUMN_DEF"); String nullable = columns.getInt("NULLABLE") == ResultSetMetaData.columnNullable ? "NULL" : "NOT NULL"; StringBuilder columnDefinition = new StringBuilder(); columnDefinition.append(columnName).append(" ").append(columnType); if (columnSize != 0) { columnDefinition.append("(").append(columnSize).append(")"); } columnDefinition.append(" ").append(nullable); if (defaultValue != null) { columnDefinition.append(" DEFAULT ").append(defaultValue); } if (remarks != null) { columnDefinition.append(" COMMENT '").append(remarks).append("'"); } columnDefinitions.add(columnDefinition.toString()); } } catch (Exception e) { throw new RuntimeException(e); } return columnDefinitions; } private Map> getIndexInfo(Connection connection, String databaseName, String schemaName, String tableName) { Map> indexMap = new HashMap<>(); try (ResultSet indexes = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, false)) { while (indexes.next()) { String indexName = indexes.getString("INDEX_NAME"); String columnName = indexes.getString("COLUMN_NAME"); if (indexName != null) { indexMap.computeIfAbsent(indexName, k -> new ArrayList<>()).add(columnName); } } } catch (SQLException e) { throw new RuntimeException(e); } return indexMap; } private static String ROUTINES_SQL = "SELECT SPECIFIC_NAME, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + "routine_name = '%s';"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); } return function; }); } private static String TRIGGER_SQL = "SELECT TRIGGER_NAME,JAVA_CLASS FROM INFORMATION_SCHEMA.TRIGGERS where " + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; private static String TRIGGER_SQL_LIST = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_CATALOG = '%s' AND TRIGGER_SCHEMA = '%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); String sql = String.format(TRIGGER_SQL_LIST, databaseName,schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, databaseName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("JAVA_CLASS")); } return trigger; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); } return procedure; }); } private static String VIEW_SQL = "SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_CATALOG = '%s' AND TABLE_SCHEMA = '%s' " + "AND TABLE_NAME = '%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, databaseName, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("VIEW_DEFINITION")); } return table; }); } @Override public SqlBuilder getSqlBuilder() { return new H2SqlBuilder(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemSchemas() { return systemSchemas; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Plugin.java ================================================ package ai.chat2db.plugin.h2; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.util.FileUtils; public class H2Plugin extends DefaultMetaService implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"h2.json", DBConfig.class); } @Override public MetaData getMetaData() { return new H2Meta(); } @Override public DBManage getDBManage() { return new H2DBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/H2SqlBuilder.java ================================================ package ai.chat2db.plugin.h2.builder; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Schema; import org.apache.commons.lang3.StringUtils; public class H2SqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateSchemaSql(Schema schema) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA \"" + schema.getName() + "\";"); if (StringUtils.isNotBlank(schema.getComment())) { sqlBuilder.append("\nCOMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json ================================================ { "dbType": "H2", "supportDatabase": true, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:h2:tcp://localhost:9092/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/h2-2.1.214.jar" ], "jdbcDriver": "h2-2.1.214.jar", "jdbcDriverClass": "org.h2.Driver" } ], "name": "H2" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-h2/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.h2.H2Plugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-hive src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveCommandExecutor.java ================================================ package ai.chat2db.plugin.hive; import ai.chat2db.spi.model.Command; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.model.Header; import ai.chat2db.spi.sql.SQLExecutor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @Slf4j public class HiveCommandExecutor extends SQLExecutor { /** * Execute command */ @Override public List execute(Command command) { List result = new ArrayList<>(); result = super.execute(command); if (CollectionUtils.isNotEmpty(result)) { for (ExecuteResult executeResult : result) { if (executeResult.getHeaderList() != null) { for (Header header : executeResult.getHeaderList()) { header.setName(formatTableName(header.getName())); } } } } return result; } /** * Execute command */ @Override public ExecuteResult executeUpdate(String sql, Connection connection, int n) throws SQLException { return super.executeUpdate(sql, connection, n); } /** * */ @Override public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count) throws SQLException { return super.execute(sql, connection, limitRowSize, offset, count); } public static String formatTableName(String tableName) { if (StringUtils.isBlank(tableName)) { return tableName; } if (tableName.contains(".")) { String[] split = tableName.split("\\."); return split[1]; } return tableName; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java ================================================ package ai.chat2db.plugin.hive; import java.sql.Connection; import java.sql.SQLException; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; import org.springframework.util.StringUtils; public class HiveDBManage extends DefaultDBManage implements DBManage { @Override public void connectDatabase(Connection connection, String database) { if (StringUtils.isEmpty(database)) { return; } try { SQLExecutor.getInstance().execute(connection,"use " + database ); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "drop table if exists " +tableName; SQLExecutor.getInstance().execute(connection,sql, resultSet -> null); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException { String sql = "CREATE TABLE " + newTableName + "LIKE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); if(copyData){ sql = "INSERT INTO " + newTableName + " SELECT * FROM " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveMetaData.java ================================================ package ai.chat2db.plugin.hive; import ai.chat2db.plugin.hive.builder.HiveSqlBuilder; import ai.chat2db.plugin.hive.type.HiveColumnTypeEnum; import ai.chat2db.plugin.hive.type.HiveIndexTypeEnum; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import java.io.Reader; import java.sql.Connection; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; public class HiveMetaData extends DefaultMetaService implements MetaData { @Override public List databases(Connection connection) { List databases = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection,"show databases", resultSet -> { try { while (resultSet.next()) { String databaseName = resultSet.getString("database_name"); Database database = new Database(); database.setName(databaseName); databases.add(database); } } catch (SQLException e) { throw new RuntimeException(e); } return databases; }); } @Override public List schemas(Connection connection, String databaseName) { List schemas = new ArrayList<>(); schemas.add(Schema.builder().databaseName(databaseName).name(databaseName).build()); return schemas; } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(databaseName) + "." + format(tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { StringBuilder sb = new StringBuilder(); while (resultSet.next()) { // 拼接建表语句 sb.append(resultSet.getString("createtab_stmt")); sb.append("\r\n"); } if (sb.length() > 0) { sb = sb.delete(sb.length() - 2, sb.length()); sb.append(";"); return sb.toString(); } return null; }); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).skip(1).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); } @Override public CommandExecutor getCommandExecutor() { return new HiveCommandExecutor(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(HiveColumnTypeEnum.getTypes()) //.charsets(HiveCharsetEnum.getCharsets()) //.collations(HiveCollationEnum.getCollations()) .indexTypes(HiveIndexTypeEnum.getIndexTypes()) //.defaultValues(HiveDefaultValueEnum.getDefaultValues()) .build(); } @Override public SqlBuilder getSqlBuilder() { return new HiveSqlBuilder(); } private static String SELECT_TAB_COLS = "DESCRIBE FORMATTED `%s`.`%s`"; // TODO 待完善 @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TAB_COLS, databaseName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { List tableColumns = new ArrayList<>(); Map detailTableInfo = new HashMap<>(); Map tableParams = new HashMap<>(); Map storageInfo = new HashMap<>(); Map storageDescParams = new HashMap<>(); Map> constraints = new HashMap<>(); List> columns = new ArrayList<>(); List> partitions = new ArrayList<>(); Map moduleMap = getDescTableModule(); String infoModule = ""; while (resultSet.next()) { String title = resultSet.getString(1).trim(); if (("".equals(title) && resultSet.getString(2) == null) || "# Constraints".equals(title)) { continue; } if (moduleMap.containsKey(title)) { if ("partition_info".equals(infoModule) && "col_name".equals(moduleMap.get(title))) { continue; } infoModule = moduleMap.get(title); continue; } String key = null; String value = null; switch (infoModule) { case "col_name": Map map = new HashMap<>(); int colNum = resultSet.getMetaData().getColumnCount(); for (int col = 1; col <= colNum; col++) { String columnName = resultSet.getMetaData().getColumnName(col); String columnValue = resultSet.getString(columnName); map.put(columnName, columnValue); } columns.add(map); break; case "table_info": key = resultSet.getString(1).trim().replace(":", ""); value = resultSet.getString(2).trim(); detailTableInfo.put(key, value); break; case "table_param": key = resultSet.getString(2).trim().replace(":", ""); value = resultSet.getString(3).trim(); tableParams.put(key, value); break; case "storage_info": key = resultSet.getString(1).trim().replace(":", ""); value = resultSet.getString(2).trim(); storageInfo.put(key, value); break; case "storage_desc": key = resultSet.getString(2).trim().replace(":", ""); value = resultSet.getString(3).trim(); storageDescParams.put(key, value); break; case "primary_key": Map primaryKeyMap = constraints.getOrDefault("primaryKey", new HashMap<>()); if ("Table:".equals(title.trim())) { resultSet.next(); } String primaryKeyName = resultSet.getString(2).trim(); resultSet.next(); key = resultSet.getString(2).trim(); primaryKeyMap.put(key, primaryKeyName); constraints.put("primaryKey", primaryKeyMap); break; case "not_null_constraint": Map notNullMap = constraints.getOrDefault("notnull", new HashMap<>()); if ("Table:".equals(title.trim())) { resultSet.next(); } String notNullConstraintName = resultSet.getString(2).trim(); resultSet.next(); key = resultSet.getString(2).trim(); notNullMap.put(key, notNullConstraintName); constraints.put("notnull", notNullMap); break; case "default_constraint": Map defaultMap = constraints.getOrDefault("default", new HashMap<>()); if ("Table:".equals(title.trim())) { resultSet.next();} String defaultConstraintName = resultSet.getString(2).trim(); resultSet.next(); key = resultSet.getString(1).trim().split(":")[1]; value = resultSet.getString(2).trim(); int valueIndex = value.indexOf(":"); value = value.substring(valueIndex + 1); defaultMap.put(key + "_constraintName", defaultConstraintName); constraints.put("default", defaultMap); break; case "partition_info": Map partitionMap = new HashMap<>(); int partitionColNum = resultSet.getMetaData().getColumnCount(); for (int col = 0; col < partitionColNum; col++) { String columnName = resultSet.getMetaData().getColumnName(col + 1); String columnValue = resultSet.getString(columnName); partitionMap.put(columnName, columnValue); } partitions.add(partitionMap); break; default: System.out.print("unknown module,please update method to support it : " + infoModule); } } for (Map columnMap : columns) { TableColumn tableColumn = new TableColumn(); tableColumn.setTableName(tableName); tableColumn.setSchemaName(schemaName); tableColumn.setName(columnMap.get("col_name")); tableColumn.setColumnType(columnMap.get("data_type")); tableColumn.setComment(columnMap.get("comment")); if (constraints.get("primaryKey") != null && constraints.get("primaryKey").keySet().contains(columnMap.get("col_name"))) { tableColumn.setPrimaryKey(true); } if (constraints.get("notnull") !=null && constraints.get("notnull").keySet().contains(columnMap.get("col_name"))) { tableColumn.setNullable(1); } tableColumns.add(tableColumn); } return tableColumns; }); } private static Map getDescTableModule() { Map descTableModule = new HashMap<>(); descTableModule.put("# col_name", "col_name"); descTableModule.put("# Detailed Table Information", "table_info"); descTableModule.put("Table Parameters:", "table_param"); descTableModule.put("# Storage Information", "storage_info"); descTableModule.put("Storage Desc Params:", "storage_desc"); descTableModule.put("# Not Null Constraints", "not_null_constraint"); descTableModule.put("# Default Constraints", "default_constraint"); descTableModule.put("# Partition Information", "partition_info"); descTableModule.put("# Primary Key", "primary_key"); return descTableModule; } public static String format(String name) { return "`" + name + "`"; } private static String VIEW_SQL = "SHOW CREATE TABLE `%s`.`%s`"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, databaseName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); StringBuilder sb = new StringBuilder(); while (resultSet.next()) { // 拼接建表语句 sb.append(resultSet.getString("createtab_stmt")); sb.append("\r\n"); } if (sb.length() > 0) { sb = sb.delete(sb.length() - 2, sb.length()); sb.append(";"); table.setDdl(sb.toString()); } return table; }); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HivePlugin.java ================================================ package ai.chat2db.plugin.hive; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class HivePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"hive.json", DBConfig.class); } @Override public MetaData getMetaData() { return new HiveMetaData(); } @Override public DBManage getDBManage() { return new HiveDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/HiveSqlBuilder.java ================================================ package ai.chat2db.plugin.hive.builder; import ai.chat2db.plugin.hive.type.HiveColumnTypeEnum; import ai.chat2db.plugin.hive.type.HiveIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; import java.util.List; public class HiveSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
{ @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); if (StringUtils.isNotBlank(table.getDatabaseName())) { script.append("`").append(table.getDatabaseName()).append("`").append("."); } script.append("`").append(table.getName()).append("`").append(" (").append("\n"); // append column appendColumns(script, table.getColumnList()); // append primary key and index appendIndexes(script, table.getIndexList()); script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); // append engine, charset, collate, auto_increment, comment, and partition appendTableOptions(script, table); script.append(";"); return script.toString(); } private void appendColumns(StringBuilder script, List columns) { for (TableColumn column : columns) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } HiveColumnTypeEnum typeEnum = HiveColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } } private void appendIndexes(StringBuilder script, List indexes) { for (TableIndex index : indexes) { if (StringUtils.isBlank(index.getName()) || StringUtils.isBlank(index.getType())) { continue; } HiveIndexTypeEnum hiveIndexTypeEnum = HiveIndexTypeEnum.getByType(index.getType()); script.append("\t").append("").append(hiveIndexTypeEnum.buildIndexScript(index)).append(",\n"); } } private void appendTableOptions(StringBuilder script, Table table) { if (StringUtils.isNotBlank(table.getEngine())) { script.append(" ENGINE=").append(table.getEngine()); } if (StringUtils.isNotBlank(table.getCharset())) { script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); } if (StringUtils.isNotBlank(table.getCollate())) { script.append(" COLLATE=").append(table.getCollate()); } if (table.getIncrementValue() != null) { script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); } if (StringUtils.isNotBlank(table.getComment())) { script.append(" COMMENT '").append(table.getComment()).append("'"); } if (StringUtils.isNotBlank(table.getPartition())) { script.append(" \n").append(table.getPartition()); } } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); boolean isModify = false; script.append("ALTER TABLE "); if (StringUtils.isNotBlank(newTable.getDatabaseName())) { script.append("`").append(newTable.getDatabaseName()).append("`").append("."); } script.append("`").append(oldTable.getName()).append("`").append("\n"); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(";\n"); isModify = true; } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { if (isModify) { script.append("ALTER TABLE "); if (StringUtils.isNotBlank(newTable.getDatabaseName())) { script.append("`").append(newTable.getDatabaseName()).append("`").append("."); } script.append("`").append(newTable.getName()).append("`").append("\n"); } script.append("\t").append("SET TBLPROPERTIES ('comment' = ").append("'").append(newTable.getComment()).append("'),\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { HiveColumnTypeEnum typeEnum = HiveColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { HiveIndexTypeEnum hiveIndexTypeEnum = HiveIndexTypeEnum.getByType(tableIndex.getType()); if(hiveIndexTypeEnum == null){ continue; } script.append("\t").append(hiveIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); } } // append reorder column // script.append(buildGenerateReorderColumnSql(oldTable, newTable)); if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if (offset == 0) { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(pageSize); } else { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(offset); sqlBuilder.append(","); sqlBuilder.append(pageSize); } return sqlBuilder.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`"); if (StringUtils.isNotBlank(database.getComment())) { sqlBuilder.append("\r\n COMMENT '").append(database.getComment()).append("'"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json ================================================ { "dbType": "HIVE", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:hive2://localhost:10000/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/hive-jdbc-3.1.2-standalone.jar" ], "jdbcDriver": "hive-jdbc-3.1.2-standalone.jar", "jdbcDriverClass": "org.apache.hive.jdbc.HiveDriver" } ], "name": "Hive" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/type/HiveColumnTypeEnum.java ================================================ package ai.chat2db.plugin.hive.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum HiveColumnTypeEnum implements ColumnBuilder { // Numeric Types TINYINT("TINYINT", true, false, true, true, false, false, true, true, false, false), SMALLINT("SMALLINT", false, false, true, true, false, false, true, true, false, false), INT("INT", false, false, true, true, false, false, true, true, false, false), INTEGER("INTEGER", false, false, true, true, false, false, true, true, false, false), BIGINT("BIGINT", false, false, true, true, false, false, true, true, false, false), FLOAT("FLOAT", true, true, true, false, false, false, true, true, false, false), DOUBLE("DOUBLE", true, true, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), // hive 3.0.0+ NUMERIC("NUMERIC", true, true, true, false, false, false, true, true, false, false), // Date/Time Types TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), INTERVAL("INTERVAL", true, false, true, false, false, false, true, true, true, false), // String Types STRING("STRING", true, false, true, false, true, true, true, true, false, false), CHAR("CHAR", true, false, true, false, true, true, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, true, true, true, true, false, false), // Misc Types BOOLEAN("BOOLEAN", false, false, true, true, false, false, true, true, false, false), BINARY("BINARY", true, false, true, false, false, false, true, true, false, false), // Complex Types ARRAY("ARRAY", true, false, true, false, true, true, true, true, false, false), MAP("MAP", true, false, true, false, true, true, true, true, false, false), STRUCT("STRUCT", true, false, true, false, true, true, true, true, false, false), UNIONTYPE("UNIONTYPE", true, false, true, false, true, true, true, true, false, false), ; private ColumnType columnType; public static HiveColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public ColumnType getColumnType() { return columnType; } HiveColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue,false); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (HiveColumnTypeEnum value : HiveColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } @Override public String buildCreateColumnSql(TableColumn column) { HiveColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildCharset(column,type)).append(" "); script.append(buildCollation(column,type)).append(" "); if(!EditStatus.ADD.name().equals(column.getEditStatus())) { script.append(buildNullable(column,type)).append(" "); } //script.append(buildDefaultValue(column,type)).append(" "); //script.append(buildExt(column,type)).append(" "); //script.append(buildAutoIncrement(column,type)).append(" "); script.append(buildComment(column,type)).append(" "); return script.toString(); } private String buildCharset(TableColumn column, HiveColumnTypeEnum type) { if(!type.getColumnType().isSupportCharset() || StringUtils.isEmpty(column.getCharSetName())){ return ""; } return StringUtils.join("CHARACTER SET ",column.getCharSetName()); } private String buildCollation(TableColumn column, HiveColumnTypeEnum type) { if(!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())){ return ""; } return StringUtils.join("COLLATE ",column.getCollationName()); } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("ADD COLUMNS (", buildCreateColumnSql(tableColumn), ")"); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); } else { return StringUtils.join("CHANGE `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); } } return ""; } private String buildAutoIncrement(TableColumn column, HiveColumnTypeEnum type) { if(!type.getColumnType().isSupportAutoIncrement()){ return ""; } if (column.getAutoIncrement() != null && column.getAutoIncrement()) { return "AUTO_INCREMENT"; } return ""; } private String buildComment(TableColumn column, HiveColumnTypeEnum type) { if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ return ""; } return StringUtils.join("COMMENT '",column.getComment(),"'"); } private String buildExt(TableColumn column, HiveColumnTypeEnum type) { if(!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())){ return ""; } return column.getComment(); } private String buildDefaultValue(TableColumn column, HiveColumnTypeEnum type) { if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ return ""; } if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ''"); } if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT NULL"); } if(Arrays.asList(CHAR,VARCHAR,BINARY).contains(type)){ return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } if(Arrays.asList(DATE).contains(type)){ return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } if(Arrays.asList(TIMESTAMP).contains(type)){ if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ",column.getDefaultValue()); } return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } return StringUtils.join("DEFAULT ",column.getDefaultValue()); } private String buildNullable(TableColumn column,HiveColumnTypeEnum type) { if(!type.getColumnType().isSupportNullable()){ return ""; } if (column.getNullable()!=null && 1==column.getNullable()) { return ""; } else { return "NOT NULL"; } } private String buildDataType(TableColumn column, HiveColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(BINARY, VARCHAR, CHAR).contains(type)) { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(TIMESTAMP).contains(type)) { if (column.getColumnSize() == null || column.getColumnSize() == 0) { return columnType; } else { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } } if (Arrays.asList(DECIMAL, FLOAT, DOUBLE,TINYINT).contains(type)) { if (column.getColumnSize() == null || column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } if (column.getColumnSize() != null && column.getDecimalDigits() != null) { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } return columnType; } public String buildColumn(TableColumn column) { HiveColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); return script.toString(); } private String unsignedDataType(String dataTypeName, String middle) { String[] split = dataTypeName.split(" "); if (split.length == 2) { return StringUtils.join(split[0], middle, split[1]); } return StringUtils.join(dataTypeName, middle); } public static List getTypes(){ return Arrays.stream(HiveColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/type/HiveIndexTypeEnum.java ================================================ package ai.chat2db.plugin.hive.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum HiveIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"), FULLTEXT("Fulltext", "FULLTEXT INDEX"), SPATIAL("Spatial", "SPATIAL INDEX"); public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; HiveIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static HiveIndexTypeEnum getByType(String type) { for (HiveIndexTypeEnum value : HiveIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); script.append(buildIndexComment(tableIndex)).append(" "); return script.toString(); } private String buildIndexComment(TableIndex tableIndex) { if(StringUtils.isBlank(tableIndex.getComment())){ return ""; }else { return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if(StringUtils.isNotBlank(column.getColumnName())) { script.append("`").append(column.getColumnName()).append("`"); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { if(this.equals(PRIMARY_KEY)){ return ""; }else { return "`"+tableIndex.getName()+"`"; } } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("ADD ", buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (HiveIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); } public static List getIndexTypes() { return Arrays.asList(HiveIndexTypeEnum.values()).stream().map(HiveIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-hive/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.hive.HivePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-kingbase src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java ================================================ package ai.chat2db.plugin.kingbase; import java.sql.Connection; import java.sql.SQLException; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @Slf4j public class KingBaseDBManage extends DefaultDBManage implements DBManage { @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); String parameterSignature = extractParameterSignature(procedureBody); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String dropSql = "DROP PROCEDURE IF EXISTS " + procedureNewName + parameterSignature; SQLExecutor.getInstance().execute(connection, dropSql, resultSet -> { }); SQLExecutor.getInstance().execute(connection, procedureBody, resultSet -> { }); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { try { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (!StringUtils.isEmpty(connectInfo.getSchemaName())) { SQLExecutor.getInstance().execute(connection, "SET search_path TO \"" + connectInfo.getSchemaName() + "\""); } } catch (Exception e) { log.error("connectDatabase error", e); } } @Override public Connection getConnection(ConnectInfo connectInfo) { String url = connectInfo.getUrl(); String database = connectInfo.getDatabaseName(); if (database != null && !database.isEmpty()) { url = replaceDatabaseInJdbcUrl(url, database); } connectInfo.setUrl(url); return super.getConnection(connectInfo); } public String replaceDatabaseInJdbcUrl(String url, String newDatabase) { // First split the string at the "?" character and process the query parameters String[] urlAndParams = url.split("\\?"); String urlWithoutParams = urlAndParams[0]; // Split string at "/" character in URL String[] parts = urlWithoutParams.split("/"); // Take the last part, the database name, and replace it with the new database name parts[parts.length - 1] = newDatabase; // Reassemble the modified parts into a URL String newUrlWithoutParams = String.join("/", parts); // If query parameters exist, add them again String newUrl = urlAndParams.length > 1 ? newUrlWithoutParams + "?" + urlAndParams[1] : newUrlWithoutParams; return newUrl; } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "drop table if exists " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) { String procedureBody = procedure.getProcedureBody(); String parameterSignature = extractParameterSignature(procedureBody); String procedureNewName = getSchemaOrProcedureName(procedure.getProcedureBody(), schemaName, procedure); String sql = "DROP PROCEDURE " + procedureNewName + parameterSignature; SQLExecutor.getInstance().execute(connection, sql, resultSet -> {}); } @Override public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) { String functionBody = function.getFunctionBody(); String parameterSignature = extractParameterSignature(functionBody); String functionNewName = getSchemaOrFunctionName(functionBody, schemaName, function); String sql = "DROP FUNCTION" + functionNewName + parameterSignature; SQLExecutor.getInstance().execute(connection, sql, resultSet -> {}); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData) throws SQLException { String sql = ""; if (copyData) { sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH DATA"; } else { sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH NO DATA"; } SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } private String extractParameterSignature(String input) { int depth = 0, start = -1; for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (c == '(') { if (depth++ == 0) start = i; } else if (c == ')' && --depth == 0 && start != -1) { return "(" + input.substring(start + 1, i) + ")"; } } if (depth == 0) { return ""; } return null; } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) { if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) { return function.getFunctionName(); } else { return schemaName + "." + function.getFunctionName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java ================================================ package ai.chat2db.plugin.kingbase; import ai.chat2db.plugin.kingbase.builder.KingBaseSqlBuilder; import ai.chat2db.plugin.kingbase.type.KingBaseColumnTypeEnum; import ai.chat2db.plugin.kingbase.type.KingBaseDefaultValueEnum; import ai.chat2db.plugin.kingbase.type.KingBaseIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class KingBaseMetaData extends DefaultMetaService implements MetaData { private static final String SELECT_KEY_INDEX = "SELECT ccu.table_schema AS Foreign_schema_name, ccu.table_name AS Foreign_table_name, ccu.column_name AS Foreign_column_name, constraint_type AS Constraint_type, tc.CONSTRAINT_NAME AS Key_name, tc.TABLE_NAME, kcu.Column_name, tc.is_deferrable, tc.initially_deferred FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.TABLE_SCHEMA = '%s' AND tc.TABLE_NAME = '%s';"; private List systemDatabases = Arrays.asList("SAMPLES", "SECURITY"); private List systemSchemas = Arrays.asList("pg_toast","pg_temp_1","pg_toast_temp_1","pg_catalog","information_schema"); @Override public List databases(Connection connection) { List list = SQLExecutor.getInstance().execute(connection, "SELECT datname FROM sys_database", resultSet -> { List databases = new ArrayList<>(); try { while (resultSet.next()) { String dbName = resultSet.getString("datname"); if ("template0".equalsIgnoreCase(dbName) || "template1".equalsIgnoreCase(dbName) || "template2".equalsIgnoreCase(dbName)) { continue; } Database database = new Database(); database.setName(dbName); databases.add(database); } } catch (SQLException e) { throw new RuntimeException(e); } return databases; }); return sortDatabase(list, systemDatabases, connection); } private static final String SELECT_TABLE_INDEX = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, trim(BOTH '\"' FROM sys_get_indexdef( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE) ) AS Column_name, CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, i.INDISPRIMARY , i.indisclustered , ( information_schema._sys_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, sys_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM sys_class ct JOIN sys_namespace n ON ( ct.relnamespace = n.OID ) JOIN sys_index i ON ( ct.OID = i.indrelid ) JOIN sys_class ci ON ( ci.OID = i.indexrelid ) JOIN sys_am am ON ( ci.relam = am.OID ) left outer join sys_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp"; private static final String SELECT_TABLE_INDEX_8R6 = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, trim(BOTH '\"' FROM sys_get_indexdef( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE) ) AS Column_name, CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, i.INDISPRIMARY , i.indisclustered , ( information_schema._pg_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, sys_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM sys_class ct JOIN sys_namespace n ON ( ct.relnamespace = n.OID ) JOIN sys_index i ON ( ct.OID = i.indrelid ) JOIN sys_class ci ON ( ci.OID = i.indexrelid ) JOIN sys_am am ON ( ci.relam = am.OID ) left outer join sys_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp"; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String constraintSql = String.format(SELECT_KEY_INDEX, schemaName, tableName); Map constraintMap = new HashMap(); LinkedHashMap foreignMap = new LinkedHashMap(); SQLExecutor.getInstance().execute(connection, constraintSql, resultSet -> { while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); String constraintType = resultSet.getString("Constraint_type"); constraintMap.put(keyName, constraintType); if (StringUtils.equalsIgnoreCase(constraintType, KingBaseIndexTypeEnum.FOREIGN.getKeyword())) { TableIndex tableIndex = foreignMap.get(keyName); String columnName = resultSet.getString("Column_name"); if (tableIndex == null) { tableIndex = new TableIndex(); tableIndex.setDatabaseName(databaseName); tableIndex.setSchemaName(schemaName); tableIndex.setTableName(tableName); tableIndex.setName(keyName); tableIndex.setForeignSchemaName(resultSet.getString("Foreign_schema_name")); tableIndex.setForeignTableName(resultSet.getString("Foreign_table_name")); tableIndex.setForeignColumnNamelist(Lists.newArrayList(columnName)); tableIndex.setType(KingBaseIndexTypeEnum.FOREIGN.getName()); foreignMap.put(keyName, tableIndex); } else { tableIndex.getForeignColumnNamelist().add(columnName); } } } return null; }); String version = getDbVersion(); String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName); if(version.startsWith("12.")|| version.startsWith("9.")) { sql = String.format(SELECT_TABLE_INDEX_8R6, schemaName, tableName); } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(foreignMap); while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique(!StringUtils.equals("t", resultSet.getString("NON_UNIQUE"))); index.setMethod(resultSet.getString("Index_method")); index.setComment(resultSet.getString("Index_comment")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); String constraintType = constraintMap.get(keyName); if (StringUtils.equals("t", resultSet.getString("Index_primary"))) { index.setType(KingBaseIndexTypeEnum.PRIMARY.getName()); } else if (StringUtils.equalsIgnoreCase(constraintType, KingBaseIndexTypeEnum.UNIQUE.getName())) { index.setType(KingBaseIndexTypeEnum.UNIQUE.getName()); } else { index.setType(KingBaseIndexTypeEnum.NORMAL.getName()); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private String getDbVersion(){ String version = Chat2DBContext.getDbVersion(); if(StringUtils.isNotBlank(version)){ return version; } return ""; } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("Column_name")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("Collation")); tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); return tableIndexColumn; } private static String ROUTINES_SQL = " SELECT p.proname, p.prokind, sys_catalog.sys_get_functiondef(p.oid) as \"code\" FROM sys_catalog.sys_proc p where p.proname='%s'"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "f", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("code")); } return function; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("code")); } return procedure; }); } @Override public SqlBuilder getSqlBuilder() { return new KingBaseSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(KingBaseColumnTypeEnum.getTypes()) //.charsets(PostgreSQLCharsetEnum.getCharsets()) //.collations(PostgreSQLCollationEnum.getCollations()) .indexTypes(KingBaseIndexTypeEnum.getIndexTypes()) .defaultValues(KingBaseDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemDatabases() { return systemDatabases; } @Override public List getSystemSchemas() { return systemSchemas; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBasePlugin.java ================================================ package ai.chat2db.plugin.kingbase; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class KingBasePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"kingbase.json", DBConfig.class); } @Override public MetaData getMetaData() { return new KingBaseMetaData(); } @Override public DBManage getDBManage() { return new KingBaseDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java ================================================ package ai.chat2db.plugin.kingbase.builder; import ai.chat2db.plugin.kingbase.type.KingBaseColumnTypeEnum; import ai.chat2db.plugin.kingbase.type.KingBaseIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class KingBaseSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n"); // append column for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum ==null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } Map> tableIndexMap = table.getIndexList().stream() .collect(Collectors.partitioningBy(v -> KingBaseIndexTypeEnum.NORMAL.getName().equals(v.getType()))); // append constraint key List constraintList = tableIndexMap.get(Boolean.FALSE); if (CollectionUtils.isNotEmpty(constraintList)) { for (TableIndex index : constraintList) { if (StringUtils.isBlank(index.getName()) || StringUtils.isBlank(index.getType())) { continue; } KingBaseIndexTypeEnum indexTypeEnum = KingBaseIndexTypeEnum.getByType(index.getType()); if(indexTypeEnum == null){ continue; } script.append("\t").append("").append(indexTypeEnum.buildIndexScript(index)); script.append(",\n"); } } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); if(StringUtils.isNotBlank(table.getTablespace())){ script.append(" TABLESPACE \"").append(table.getTablespace()).append("\";"); }else { script.append(" TABLESPACE \"SYS_DEFAULT\";"); } // append index List tableIndexList = tableIndexMap.get(Boolean.TRUE); for (TableIndex tableIndex : tableIndexList) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } script.append("\n"); KingBaseIndexTypeEnum indexTypeEnum = KingBaseIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } script.append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); } // append comment if (StringUtils.isNotBlank(table.getComment())) { script.append("\n"); script.append("COMMENT ON TABLE").append(" ").append("\"").append(table.getName()).append("\" IS '") .append(table.getComment()).append("';\n"); } List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableColumn tableColumn : tableColumnList) { KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } List indexList = table.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableIndex index : indexList) { KingBaseIndexTypeEnum indexEnum = KingBaseIndexTypeEnum.getByType(index.getType()); if(indexEnum == null){ continue; } script.append(indexEnum.buildIndexComment(index)).append("\n"); } return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\""); script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } newTable.setColumnList(newTable.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); newTable.setIndexList(newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); //update name List columnNameList = newTable.getColumnList().stream().filter(v -> v.getOldName() != null && !StringUtils.equals(v.getOldName(), v.getName())).toList(); for (TableColumn tableColumn : columnNameList) { script.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" ").append("RENAME COLUMN \"") .append(tableColumn.getOldName()).append("\" TO \"").append(tableColumn.getName()).append("\";\n"); } Map> tableIndexMap = newTable.getIndexList().stream() .collect(Collectors.partitioningBy(v -> KingBaseIndexTypeEnum.NORMAL.getName().equals(v.getType()))); StringBuilder scriptModify = new StringBuilder(); Boolean modify = false; scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n"); // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); modify = true; } // append modify constraint for (TableIndex tableIndex : tableIndexMap.get(Boolean.FALSE)) { if (StringUtils.isNotBlank(tableIndex.getType())) { KingBaseIndexTypeEnum indexTypeEnum = KingBaseIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } scriptModify.append("\t").append(indexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); modify = true; } } if (BooleanUtils.isTrue(modify)) { script.append(scriptModify); script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";\n"); } // append modify index for (TableIndex tableIndex : tableIndexMap.get(Boolean.TRUE)) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { KingBaseIndexTypeEnum indexTypeEnum = KingBaseIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } script.append(indexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } // append comment if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) { script.append("\n"); script.append("COMMENT ON TABLE").append(" ").append("\"").append(newTable.getName()).append("\" IS '") .append(newTable.getComment()).append("';\n"); } for (TableColumn tableColumn : newTable.getColumnList()) { KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } List indexList = newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableIndex index : indexList) { KingBaseIndexTypeEnum indexEnum = KingBaseIndexTypeEnum.getByType(index.getType()); if(indexEnum == null){ continue; } script.append(indexEnum.buildIndexComment(index)).append("\n"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlStr = new StringBuilder(sql.length() + 17); sqlStr.append(sql); if (offset == 0) { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); } else { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); sqlStr.append(" OFFSET "); sqlStr.append(offset); } return sqlStr.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE "+database.getName()); String owner = database.getOwner(); if (StringUtils.isBlank(owner)) { owner = "SYSTEM"; } sqlBuilder.append(" WITH OWNER = \"").append(owner).append("\""); if (StringUtils.isNotBlank(database.getCharset())) { sqlBuilder.append(" ENCODING ").append(database.getCharset()).append(""); } sqlBuilder.append(";\n"); if (StringUtils.isNotBlank(database.getComment())) { sqlBuilder.append("COMMENT ON DATABASE ").append(database.getName()).append(" IS '").append(database.getComment()).append("';"); } return sqlBuilder.toString(); } @Override public String buildCreateSchemaSql(Schema schema){ StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA "+schema.getName()+""); String owner = schema.getOwner(); if(StringUtils.isBlank(schema.getOwner())){ owner = "SYSTEM"; } sqlBuilder.append(" AUTHORIZATION \"").append(owner).append("\""); return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json ================================================ { "dbType": "KINGBASE", "supportDatabase": true, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:kingbase8://localhost:54321/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/kingbase8-8.6.0.jar" ], "jdbcDriver": "kingbase8-8.6.0.jar", "jdbcDriverClass": "com.kingbase8.Driver" }, { "url": "jdbc:kingbase8://localhost:54321/", "custom": false, "defaultDriver": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/kingbase8-8.2.0.jar" ], "jdbcDriver": "kingbase8-8.2.0.jar", "jdbcDriverClass": "com.kingbase8.Driver" } ], "name": "KingBase" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseColumnTypeEnum.java ================================================ package ai.chat2db.plugin.kingbase.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum KingBaseColumnTypeEnum implements ColumnBuilder { BIGSERIAL("BIGSERIAL", false, false, true, false, false, false, true, true, false, false), BIT("BIT", true, false, true, false, false, false, true, true, false, false), BOOL("BOOL", false, false, true, false, false, false, true, true, false, false), BOX("BOX", false, false, true, false, false, false, true, true, false, false), BYTEA("BYTEA", false, false, true, false, false, false, true, true, false, false), CHARACTER("CHARACTER", true, false, true, false, false, true, true, true, false, false), CHARACTER_VARYING("CHARACTER VARYING", true, false, true, false, false, true, true, true, false, false), CHAR("CHAR", true, false, true, false, false, true, true, true, false, false), CID("CID", false, false, true, false, false, false, true, true, false, false), CIDR("CIDR", false, false, true, false, false, false, true, true, false, false), CIRCLE("CIRCLE", false, false, true, false, false, false, true, true, false, false), CLOB("CLOB", false, false, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, false, true, false, false, false, true, true, false, false), FLOAT4("FLOAT4", false, false, true, false, false, false, true, true, false, false), FLOAT8("FLOAT8", false, false, true, false, false, false, true, true, false, false), INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false), INET("INET", false, false, true, false, false, false, true, true, false, false), INT2("INT2", false, false, true, false, false, false, true, true, false, false), INT4("INT4", false, false, true, false, false, false, true, true, false, false), INT8("INT8", false, false, true, false, false, false, true, true, false, false), INTERVAL("INTERVAL", false, false, true, false, false, false, true, true, false, false), JSON("JSON", false, false, true, false, false, false, true, true, false, false), JSONB("JSONB", false, false, true, false, false, false, true, true, false, false), LINE("LINE", false, false, true, false, false, false, true, true, false, false), LSEG("LSEG", false, false, true, false, false, false, true, true, false, false), MACADDR("MACADDR", false, false, true, false, false, false, true, true, false, false), MONEY("MONEY", false, false, true, false, false, false, true, true, false, false), NUMERIC("NUMERIC", true, false, true, false, false, false, true, true, false, false), PATH("PATH", false, false, true, false, false, false, true, true, false, false), POINT("POINT", false, false, true, false, false, false, true, true, false, false), POLYGON("POLYGON", false, false, true, false, false, false, true, true, false, false), SERIAL("SERIAL", false, false, true, false, false, false, true, true, false, false), SERIAL2("SERIAL2", false, false, true, false, false, false, true, true, false, false), SERIAL4("SERIAL4", false, false, true, false, false, false, true, true, false, false), SERIAL8("SERIAL8", false, false, true, false, false, false, true, true, false, false), SMALLSERIAL("SMALLSERIAL", false, false, true, false, false, false, true, true, false, false), TEXT("TEXT", false, false, true, false, false, true, true, true, false, false), TIME("TIME", true, false, true, false, false, false, true, true, false, false), TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, false, false), TIMESTAMPTZ("TIMESTAMPTZ", true, false, true, false, false, false, true, true, false, false), TIMETZ("TIMETZ", true, false, true, false, false, false, true, true, false, false), TSQUERY("TSQUERY", false, false, true, false, false, false, true, true, false, false), TSVECTOR("TSVECTOR", false, false, true, false, false, false, true, true, false, false), TXID_SNAPSHOT("TXID_SNAPSHOT", false, false, true, false, false, false, true, true, false, false), UUID("UUID", false, false, true, false, false, false, true, true, false, false), VARBIT("VARBIT", true, false, true, false, false, false, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false, false), XML("XML", false, false, true, false, false, false, true, true, false, false), ; private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (KingBaseColumnTypeEnum value : KingBaseColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } private ColumnType columnType; KingBaseColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); } public static KingBaseColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public static List getTypes() { return Arrays.stream(KingBaseColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } public ColumnType getColumnType() { return columnType; } @Override public String buildCreateColumnSql(TableColumn column) { KingBaseColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildCollation(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); return script.toString(); } private String buildCollation(TableColumn column, KingBaseColumnTypeEnum type) { if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { return ""; } return StringUtils.join("\"", column.getCollationName(), "\""); } @Override public String buildModifyColumn(TableColumn column) { if (EditStatus.DELETE.name().equals(column.getEditStatus())) { return StringUtils.join("DROP COLUMN `", column.getName() + "`"); } if (EditStatus.ADD.name().equals(column.getEditStatus())) { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column)); } if (EditStatus.MODIFY.name().equals(column.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER COLUMN \"").append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n"); if (column.getNullable() != null && 1 == column.getNullable()) { script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" DROP NOT NULL ,\n"); } else { script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" SET NOT NULL ,\n"); } String defaultValue = buildDefaultValue(column, this); if (StringUtils.isNotBlank(defaultValue)) { script.append("ALTER COLUMN \"").append(column.getName()).append("\" SET ").append(defaultValue).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); return script.toString(); } return ""; } public String buildComment(TableColumn column, KingBaseColumnTypeEnum type) { if (!this.columnType.isSupportComments() || column.getComment() == null || EditStatus.DELETE.name().equals(column.getEditStatus())) { return ""; } return StringUtils.join("COMMENT ON COLUMN", " \"", column.getTableName(), "\".\"", column.getName(), "\" IS '", column.getComment(), "';"); } private String buildDefaultValue(TableColumn column, KingBaseColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ''"); } if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT NULL"); } if (Arrays.asList(CHAR, VARCHAR).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(TIMESTAMP, TIME, TIMETZ, TIMESTAMPTZ, DATE).contains(type)) { if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ", column.getDefaultValue()); } return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildNullable(TableColumn column, KingBaseColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDataType(TableColumn column, KingBaseColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(VARCHAR, CHAR,CHARACTER).contains(type)) { if (column.getColumnSize() == null ) { return columnType; } return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(VARBIT, BIT).contains(type)) { if (column.getColumnSize() == null ) { return columnType; } return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(TIME, TIMETZ, TIMESTAMPTZ, TIMESTAMP).contains(type)) { if (column.getColumnSize() == null || column.getColumnSize() == 0) { return columnType; } else { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } } if (Arrays.asList(DECIMAL, NUMERIC).contains(type)) { if (column.getColumnSize() == null && column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } else { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } return columnType; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseDefaultValueEnum.java ================================================ package ai.chat2db.plugin.kingbase.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum KingBaseDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; KingBaseDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(KingBaseDefaultValueEnum.values()).map(KingBaseDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseIndexTypeEnum.java ================================================ package ai.chat2db.plugin.kingbase.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum KingBaseIndexTypeEnum { PRIMARY("Primary", "PRIMARY KEY"), FOREIGN("Foreign", "FOREIGN KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE"), ; private String name; private String keyword; private IndexType indexType; KingBaseIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType =new IndexType(name); } public static KingBaseIndexTypeEnum getByType(String type) { for (KingBaseIndexTypeEnum value : KingBaseIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public static List getIndexTypes() { return Arrays.asList(KingBaseIndexTypeEnum.values()).stream().map(KingBaseIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } public IndexType getIndexType() { return indexType; } public String getName() { return name; } public String getKeyword() { return keyword; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (NORMAL.equals(this)) { script.append("CREATE").append(" "); script.append(buildIndexUnique(tableIndex)).append(" "); script.append(buildIndexConcurrently(tableIndex)).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append("ON ").append("\"").append(tableIndex.getTableName()).append("\"").append(" "); script.append(buildIndexMethod(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)); } else { script.append("CONSTRAINT").append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(keyword).append(" "); script.append(buildIndexColumn(tableIndex)); script.append(buildForeignColum(tableIndex)); } return script.toString(); } private String buildForeignColum(TableIndex tableIndex) { if (FOREIGN.equals(this)) { StringBuilder script = new StringBuilder(); script.append(" REFERENCES "); if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) { script.append(tableIndex.getForeignSchemaName()).append("."); } if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) { script.append(tableIndex.getForeignTableName()).append(" "); } if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) { script.append("("); for (String column : tableIndex.getForeignColumnNamelist()) { if (StringUtils.isNotBlank(column)) { script.append("\"").append(column).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); } return script.toString(); } return ""; } private String buildIndexMethod(TableIndex tableIndex) { if (StringUtils.isNotBlank(tableIndex.getMethod())) { return "USING " + tableIndex.getMethod(); } else { return ""; } } private String buildIndexConcurrently(TableIndex tableIndex) { if (BooleanUtils.isTrue(tableIndex.getConcurrently())) { return "CONCURRENTLY"; } else { return ""; } } private String buildIndexUnique(TableIndex tableIndex) { if (BooleanUtils.isTrue(tableIndex.getUnique())) { return "UNIQUE " + keyword; } else { return keyword; } } public String buildIndexComment(TableIndex tableIndex) { if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return ""; } else if (NORMAL.equals(this)) { return StringUtils.join("COMMENT ON INDEX", " ", "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';"); } else { return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(), "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';"); } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { return "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { boolean isNormal = NORMAL.equals(this); if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), isNormal ? ";\n" : ",\n\tADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(isNormal ? "" : "ADD ", buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (NORMAL.equals(this)) { return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\""); } return StringUtils.join("DROP CONSTRAINT \"", tableIndex.getOldName(), "\""); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.kingbase.KingBasePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi ai.chat2db chat2db-mysql 2.0.0-SNAPSHOT chat2db-mariadb src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java ================================================ package ai.chat2db.plugin.mariadb; import java.sql.Connection; import java.sql.SQLException; import ai.chat2db.plugin.mysql.MysqlDBManage; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.SQLExecutor; public class MariaDBManage extends MysqlDBManage implements DBManage { String PROCEDURE_SQL = "SELECT COUNT(*)\n" + "FROM information_schema.ROUTINES\n" + "WHERE ROUTINE_TYPE = 'PROCEDURE'\n" + "AND ROUTINE_NAME = '%s'\n" + "AND ROUTINE_SCHEMA = '%s'"; @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, databaseName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, procedure.getProcedureName().toUpperCase(),schemaName.toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrReplace) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> { }); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) { String procedureNewName = getSchemaOrProcedureName(procedure.getProcedureBody(), databaseName, procedure); String sql = "DROP PROCEDURE " + procedureNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) { String functionNewName = getSchemaOrFunctionName(function.getFunctionBody(), databaseName, function); String sql = "DROP FUNCTION " + functionNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) { if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) { return function.getFunctionName(); } else { return schemaName + "." + function.getFunctionName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java ================================================ package ai.chat2db.plugin.mariadb; import ai.chat2db.plugin.mariadb.value.MariaDBValueProcessor; import ai.chat2db.plugin.mysql.MysqlMetaData; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.ValueProcessor; public class MariaDBMetaData extends MysqlMetaData implements MetaData { @Override public ValueProcessor getValueProcessor() { return new MariaDBValueProcessor(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBPlugin.java ================================================ package ai.chat2db.plugin.mariadb; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class MariaDBPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"mariadb.json", DBConfig.class); } @Override public MetaData getMetaData() { return new MariaDBMetaData(); } @Override public DBManage getDBManage() { return new MariaDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json ================================================ { "dbType": "MARIADB", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:mariadb://localhost:3306/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/mariadb-java-client-3.0.8.jar" ], "jdbcDriver": "mariadb-java-client-3.0.8.jar", "jdbcDriverClass": "org.mariadb.jdbc.Driver" } ], "name": "MariaDB" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/MariaDBValueProcessor.java ================================================ package ai.chat2db.plugin.mariadb.value; import ai.chat2db.plugin.mariadb.value.factory.MariaDBValueProcessorFactory; import ai.chat2db.plugin.mysql.value.MysqlValueProcessor; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; /** * @author: zgq * @date: 2024年05月24日 21:02 *
* TODO: * attribute: [zerofill] example tinyint[5] zerofill 34->00034 */ public class MariaDBValueProcessor extends MysqlValueProcessor { private static final Logger log = LoggerFactory.getLogger(MariaDBValueProcessor.class); @Override public String getJdbcValue(JDBCDataValue dataValue) { Object value = dataValue.getObject(); if (Objects.isNull(value)) { // example: [date]->0000-00-00 String stringValue = dataValue.getStringValue(); if (Objects.nonNull(stringValue)) { return stringValue; } return null; } if (value instanceof String emptyStr) { if (StringUtils.isBlank(emptyStr)) { return emptyStr; } } return convertJDBCValueByType(dataValue); } @Override public String getJdbcSqlValueString(JDBCDataValue dataValue) { Object value = dataValue.getObject(); if (Objects.isNull(value)) { // example: [date]->0000-00-00 String stringValue = dataValue.getStringValue(); if (Objects.nonNull(stringValue)) { return EasyStringUtils.escapeAndQuoteString(stringValue); } return "NULL"; } if (value instanceof String stringValue) { if (StringUtils.isBlank(stringValue)) { return EasyStringUtils.quoteString(stringValue); } } return convertJDBCValueStrByType(dataValue); } @Override public String convertSQLValueByType(SQLDataValue dataValue) { try { DefaultValueProcessor valueProcessor = MariaDBValueProcessorFactory.getValueProcessor(dataValue.getDateTypeName()); if (Objects.isNull(valueProcessor)) { return super.convertSQLValueByType(dataValue); } return valueProcessor.convertSQLValueByType(dataValue); } catch (Exception e) { log.warn("convertSQLValueByType error", e); return super.convertSQLValueByType(dataValue); } } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { String type = dataValue.getType(); try { DefaultValueProcessor valueProcessor = MariaDBValueProcessorFactory.getValueProcessor(type); if (Objects.isNull(valueProcessor)) { return super.convertJDBCValueByType(dataValue); } return valueProcessor.convertJDBCValueByType(dataValue); } catch (Exception e) { log.warn("convertJDBCValueByType error", e); return super.convertJDBCValueByType(dataValue); } } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { String type = dataValue.getType(); try { DefaultValueProcessor valueProcessor = MariaDBValueProcessorFactory.getValueProcessor(type); if (Objects.isNull(valueProcessor)) { return super.convertJDBCValueByType(dataValue); } return valueProcessor.convertJDBCValueStrByType(dataValue); } catch (Exception e) { log.warn("convertJDBCValueStrByType error", e); return super.convertJDBCValueStrByType(dataValue); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/factory/MariaDBValueProcessorFactory.java ================================================ package ai.chat2db.plugin.mariadb.value.factory; import ai.chat2db.plugin.mariadb.value.sub.MariaDBBitProcessor; import ai.chat2db.plugin.mariadb.value.sub.MariaDBGeometryProcessor; import ai.chat2db.plugin.mariadb.value.sub.MariaDBTimestampProcessor; import ai.chat2db.plugin.mariadb.value.sub.MariaDBYearProcessor; import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; import ai.chat2db.plugin.mysql.value.sub.*; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import java.util.Map; /** * @author: zgq * @date: 2024年06月03日 23:16 */ public class MariaDBValueProcessorFactory { private static final Map PROCESSOR_MAP; static { MariaDBGeometryProcessor mariaDBGeometryProcessor = new MariaDBGeometryProcessor(); MysqlVarBinaryProcessor mysqlVarBinaryProcessor = new MysqlVarBinaryProcessor(); MariaDBTimestampProcessor mariaDBTimestampProcessor = new MariaDBTimestampProcessor(); MysqlTextProcessor mysqlTextProcessor = new MysqlTextProcessor(); PROCESSOR_MAP = Map.ofEntries( //text Map.entry(MysqlColumnTypeEnum.TEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.TINYTEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.MEDIUMTEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.LONGTEXT.name(), mysqlTextProcessor), // geometry Map.entry(MysqlColumnTypeEnum.GEOMETRY.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.POINT.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.LINESTRING.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.POLYGON.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTIPOINT.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTILINESTRING.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTIPOLYGON.name(), mariaDBGeometryProcessor), Map.entry(MysqlColumnTypeEnum.GEOMETRYCOLLECTION.name(), mariaDBGeometryProcessor), // binary Map.entry(MysqlColumnTypeEnum.VARBINARY.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.BLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.LONGBLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.TINYBLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.MEDIUMBLOB.name(), mysqlVarBinaryProcessor), // timestamp Map.entry(MysqlColumnTypeEnum.TIMESTAMP.name(), mariaDBTimestampProcessor), Map.entry(MysqlColumnTypeEnum.DATETIME.name(), mariaDBTimestampProcessor), //others Map.entry(MysqlColumnTypeEnum.YEAR.name(), new MariaDBYearProcessor()), Map.entry(MysqlColumnTypeEnum.BIT.name(), new MariaDBBitProcessor()), Map.entry(MysqlColumnTypeEnum.DECIMAL.name(), new MysqlDecimalProcessor()), Map.entry(MysqlColumnTypeEnum.BINARY.name(), new MysqlBinaryProcessor()) ); } public static DefaultValueProcessor getValueProcessor(String type) { return PROCESSOR_MAP.get(type); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/sub/MariaDBBitProcessor.java ================================================ package ai.chat2db.plugin.mariadb.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; import java.sql.SQLException; import java.util.Objects; import java.util.function.Function; /** * @author: zgq * @date: 2024年06月01日 13:08 */ public class MariaDBBitProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return getString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return getValue(dataValue, s -> s); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return getValue(dataValue, this::wrap); } private String getValue(JDBCDataValue dataValue, Function function) { try { //mariadb tinyint(1) if ((dataValue.getMetaData().getColumnType(dataValue.getColumnIndex()) == -7)) { return String.valueOf(dataValue.getInt()); } } catch (SQLException e) { super.convertJDBCValueByType(dataValue); } int precision = dataValue.getPrecision(); byte[] bytes = dataValue.getBytes(); if (precision == 1) { //bit(1) [1 -> true] [0 -> false] if (bytes.length == 1 && (bytes[0] == 0 || bytes[0] == 1)) { return String.valueOf(dataValue.getBoolean()); } } //bit(m) m: 2~64 return function.apply(EasyStringUtils.getBitString(bytes, precision)); } public String getString(String value) { if (Objects.equals("true", value.toLowerCase())) { return "1"; } if (Objects.equals("false", value.toLowerCase())) { return "0"; } if (StringUtils.isBlank(value)) { return "NULL"; } return MysqlDmlValueTemplate.wrapBit(value); } private String wrap(String value) { return MysqlDmlValueTemplate.wrapBit(value); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/sub/MariaDBGeometryProcessor.java ================================================ package ai.chat2db.plugin.mariadb.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKBReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.InputStream; /** * @author: zgq * @date: 2024年06月01日 12:42 */ public class MariaDBGeometryProcessor extends DefaultValueProcessor { private static final Logger log = LoggerFactory.getLogger(MariaDBGeometryProcessor.class); @Override public String convertSQLValueByType(SQLDataValue dataValue) { return MysqlDmlValueTemplate.wrapGeometry(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { try { Geometry dbGeometry = null; byte[] geometryAsBytes = dataValue.getBytes(); if (geometryAsBytes != null) { if (geometryAsBytes.length < 5) { throw new Exception("Invalid geometry inputStream - less than five bytes"); } //first four bytes of the geometry are the SRID, //followed by the actual WKB. Determine the SRID //here byte[] sridBytes = new byte[4]; System.arraycopy(geometryAsBytes, 0, sridBytes, 0, 4); boolean bigEndian = (geometryAsBytes[4] == 0x00); int srid = 0; if (bigEndian) { for (int i = 0; i < sridBytes.length; i++) { srid = (srid << 8) + (sridBytes[i] & 0xff); } } else { for (int i = 0; i < sridBytes.length; i++) { srid += (sridBytes[i] & 0xff) << (8 * i); } } //use the JTS WKBReader for WKB parsing WKBReader wkbReader = new WKBReader(); //copy the byte array, removing the first four //SRID bytes byte[] wkb = new byte[geometryAsBytes.length - 4]; System.arraycopy(geometryAsBytes, 4, wkb, 0, wkb.length); dbGeometry = wkbReader.read(wkb); dbGeometry.setSRID(srid); } return dbGeometry != null ? dbGeometry.toString() : null; } catch (Exception e) { log.warn("Error converting database geometry", e); return dataValue.getStringValue(); } } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return MysqlDmlValueTemplate.wrapGeometry(convertJDBCValueByType(dataValue)); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/sub/MariaDBTimestampProcessor.java ================================================ package ai.chat2db.plugin.mariadb.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月01日 18:26 */ public class MariaDBTimestampProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getStringValue()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/value/sub/MariaDBYearProcessor.java ================================================ package ai.chat2db.plugin.mariadb.value.sub; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * 功能描述 * * @author: zgq * @date: 2024年07月15日 20:19 */ public class MariaDBYearProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return dataValue.getValue(); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.mariadb.MariaDBPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-mongodb src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbCommandExecutor.java ================================================ package ai.chat2db.plugin.mongodb; import ai.chat2db.spi.model.Command; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.SQLExecutor; import java.util.List; public class MongodbCommandExecutor extends SQLExecutor { @Override public List executeSelectTable(Command command) { String sql = "db." + command.getTableName() + ".find()"; command.setScript(sql); return execute(command); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbManage.java ================================================ package ai.chat2db.plugin.mongodb; import java.sql.Connection; import java.sql.SQLException; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import org.apache.commons.lang3.ObjectUtils; import org.springframework.util.StringUtils; public class MongodbManage extends DefaultDBManage implements DBManage { @Override public void connectDatabase(Connection connection, String database) { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { return; } String schemaName = connectInfo.getSchemaName(); if (StringUtils.isEmpty(schemaName)) { return; } try { SQLExecutor.getInstance().execute(connection, "use " + schemaName + ";"); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = " db. " + tableName + ".drop();"; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java ================================================ package ai.chat2db.plugin.mongodb; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; import com.google.common.collect.Lists; import java.sql.Connection; import java.util.List; public class MongodbMetaData extends DefaultMetaService implements MetaData { @Override public List databases(Connection connection) { return Lists.newArrayList(); } @Override public CommandExecutor getCommandExecutor() { return new MongodbCommandExecutor(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbPlugin.java ================================================ package ai.chat2db.plugin.mongodb; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class MongodbPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"mongodb.json", DBConfig.class); } @Override public MetaData getMetaData() { return new MongodbMetaData(); } @Override public DBManage getDBManage() { return new MongodbManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json ================================================ { "dbType": "MONGODB", "supportDatabase": false, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:mongodb://localhost:27017", "defaultDriver":true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/mongo-jdbc-standalone-1.18.jar" ], "jdbcDriver": "mongo-jdbc-standalone-1.18.jar", "jdbcDriverClass": "com.dbschema.MongoJdbcDriver" } ], "name": "Mongodb" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.mongodb.MongodbPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml ================================================ ai.chat2db chat2db-plugins ${revision} ../pom.xml 4.0.0 chat2db-mysql ai.chat2db chat2db-spi src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java ================================================ package ai.chat2db.plugin.mysql; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.SQLExecutor; import cn.hutool.core.date.DateUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN; @Slf4j public class MysqlDBManage extends DefaultDBManage implements DBManage { private static String PROCEDURE_SQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES " + "WHERE ROUTINE_SCHEMA = '%s' AND ROUTINE_NAME = '%s' AND ROUTINE_TYPE = 'PROCEDURE'"; @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { asyncContext.write(String.format(EXPORT_TITLE, DateUtil.format(new Date(), NORM_DATETIME_PATTERN))); exportTables(connection, databaseName, schemaName, asyncContext); asyncContext.setProgress(50); exportViews(connection, databaseName, asyncContext); asyncContext.setProgress(60); exportProcedures(connection, asyncContext); asyncContext.setProgress(70); exportTriggers(connection, asyncContext); asyncContext.setProgress(90); exportFunctions(connection, databaseName, asyncContext); asyncContext.finish(); } private void exportFunctions(Connection connection, String databaseName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, null, null)) { while (resultSet.next()) { exportFunction(connection, resultSet.getString("FUNCTION_NAME"), asyncContext); } } } private void exportFunction(Connection connection, String functionName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SHOW CREATE FUNCTION %s;", functionName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { asyncContext.write(String.format(FUNCTION_TITLE, functionName)); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP FUNCTION IF EXISTS ").append(functionName).append(";").append("\n"); sqlBuilder.append("delimiter ;;").append("\n").append(resultSet.getString("Create Function")).append(";;") .append("\n").append("delimiter ;").append("\n\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { asyncContext.write("SET FOREIGN_KEY_CHECKS=0;"); try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"TABLE", "SYSTEM TABLE"})) { while (resultSet.next()) { String tableName = resultSet.getString("TABLE_NAME"); exportTable(connection, databaseName, schemaName, tableName, asyncContext); } } asyncContext.write("SET FOREIGN_KEY_CHECKS=1;"); } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { String sql = String.format("show create table %s ", tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); asyncContext.write(String.format(TABLE_TITLE, tableName)); sqlBuilder.append("DROP TABLE IF EXISTS ").append(format(tableName)).append(";").append("\n") .append(resultSet.getString("Create Table")).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } } } catch (Exception e) { log.error("export table error", e); asyncContext.error(String.format("export table %s error:%s", tableName, e.getMessage())); } } private void exportViews(Connection connection, String databaseName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"VIEW"})) { while (resultSet.next()) { exportView(connection, resultSet.getString("TABLE_NAME"), asyncContext); } } } private void exportView(Connection connection, String viewName, AsyncContext asyncContext) throws SQLException { String sql = String.format("show create view %s ", viewName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { asyncContext.write(String.format(VIEW_TITLE, viewName)); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP VIEW IF EXISTS ").append(format(viewName)).append(";").append("\n") .append(resultSet.getString("Create View")).append(";").append("\n\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportProcedures(Connection connection, AsyncContext asyncContext) throws SQLException { String sql = "SHOW PROCEDURE STATUS WHERE Db = DATABASE()"; try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { exportProcedure(connection, resultSet.getString("Name"), asyncContext); } } } private void exportProcedure(Connection connection, String procedureName, AsyncContext asyncContext) throws SQLException { String sql = String.format("show create procedure %s ", procedureName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { asyncContext.write(String.format(PROCEDURE_TITLE, procedureName)); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP PROCEDURE IF EXISTS ").append(format(procedureName)).append(";").append("\n") .append("delimiter ;;").append("\n").append(resultSet.getString("Create Procedure")).append(";;") .append("\n").append("delimiter ;").append("\n\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, AsyncContext asyncContext) throws SQLException { String sql = "SHOW TRIGGERS"; try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String triggerName = resultSet.getString("Trigger"); exportTrigger(connection, triggerName, asyncContext); } } } private void exportTrigger(Connection connection, String triggerName, AsyncContext asyncContext) throws SQLException { String sql = String.format("show create trigger %s ", triggerName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { asyncContext.write(String.format(TRIGGER_TITLE, triggerName)); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP TRIGGER IF EXISTS ").append(format(triggerName)).append(";").append("\n") .append("delimiter ;;").append("\n").append(resultSet.getString("SQL Original Statement")).append(";;") .append("\n").append("delimiter ;").append("\n\n"); asyncContext.write(sqlBuilder.toString()); } } } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, databaseName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, databaseName, procedure.getProcedureName()); SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { try { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { throw new Exception("Procedure already exists"); } } } catch (Exception e) { e.printStackTrace(); } }); SQLExecutor.getInstance().execute(connection, procedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { if (StringUtils.isEmpty(database)) { return; } try { SQLExecutor.getInstance().execute(connection, "use `" + database + "`;"); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + format(tableName); SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) { String procedureNewName = getSchemaOrProcedureName(procedure.getProcedureBody(), databaseName, procedure); String sql = "DROP PROCEDURE " + procedureNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) { String functionNewName = getSchemaOrFunctionName(function.getFunctionBody(), databaseName, function); String sql = "DROP FUNCTION " + functionNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) { if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) { return function.getFunctionName(); } else { return schemaName + "." + function.getFunctionName(); } } public static String format(String tableName) { return "`" + tableName + "`"; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java ================================================ package ai.chat2db.plugin.mysql; import ai.chat2db.plugin.mysql.builder.MysqlSqlBuilder; import ai.chat2db.plugin.mysql.type.*; import ai.chat2db.plugin.mysql.value.MysqlValueProcessor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class MysqlMetaData extends DefaultMetaService implements MetaData { private List systemDatabases = Arrays.asList("information_schema", "performance_schema", "mysql", "sys"); @Override public List databases(Connection connection) { List databases = SQLExecutor.getInstance().databases(connection); return sortDatabase(databases, systemDatabases, connection); } private static String TABLES_SQL = "SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, VERSION, TABLE_ROWS, DATA_LENGTH, AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, TABLE_COLLATION, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = '%s'"; @Override public List
tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName) { String sql = String.format(TABLES_SQL, databaseName); if(StringUtils.isNotBlank(tableName)){ sql += " AND TABLE_NAME = '" + tableName + "'"; } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { List
tables = new ArrayList<>(); while (resultSet.next()) { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(resultSet.getString("TABLE_NAME")); table.setEngine(resultSet.getString("ENGINE")); table.setRows(resultSet.getLong("TABLE_ROWS")); table.setDataLength(resultSet.getLong("DATA_LENGTH")); table.setCreateTime(resultSet.getString("CREATE_TIME")); table.setUpdateTime(resultSet.getString("UPDATE_TIME")); table.setCollate(resultSet.getString("TABLE_COLLATION")); table.setComment(resultSet.getString("TABLE_COMMENT")); tables.add(table); } return tables; }); } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(databaseName) + "." + format(tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { return resultSet.getString("Create Table"); } return null; }); } public static String format(String tableName) { return "`" + tableName + "`"; } private static String ROUTINES_SQL = "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + "routine_name = '%s';"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String functionInfoSql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); Function function = SQLExecutor.getInstance().execute(connection, functionInfoSql, resultSet -> { Function f = new Function(); f.setDatabaseName(databaseName); f.setSchemaName(schemaName); f.setFunctionName(functionName); if (resultSet.next()) { f.setSpecificName(resultSet.getString("SPECIFIC_NAME")); f.setRemarks(resultSet.getString("ROUTINE_COMMENT")); } return f; }); String functionDDlSql = String.format("SHOW CREATE FUNCTION %s", functionName); SQLExecutor.getInstance().execute(connection, functionDDlSql, resultSet -> { if (resultSet.next()) { function.setFunctionBody(resultSet.getString("Create Function")); } }); return function; } private static String TRIGGER_SQL = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; private static String TRIGGER_SQL_LIST = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); String sql = String.format(TRIGGER_SQL_LIST, databaseName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, databaseName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); } return trigger; }); } @Override public List procedures(Connection connection, String databaseName, String schemaName) { String sql = "SHOW PROCEDURE STATUS WHERE Db = DATABASE()"; return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { ArrayList procedures = new ArrayList<>(); while (resultSet.next()) { Procedure procedure = new Procedure(); procedure.setProcedureName(resultSet.getString("Name")); procedures.add(procedure); } return procedures; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String routinesSql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); String showCreateProcedureSql = "SHOW CREATE PROCEDURE " + procedureName; Procedure procedure = SQLExecutor.getInstance().execute(connection, routinesSql, resultSet -> { Procedure p = new Procedure(); p.setDatabaseName(databaseName); p.setSchemaName(schemaName); p.setProcedureName(procedureName); if (resultSet.next()) { p.setSpecificName(resultSet.getString("SPECIFIC_NAME")); p.setRemarks(resultSet.getString("ROUTINE_COMMENT")); } return p; }); SQLExecutor.getInstance().execute(connection, showCreateProcedureSql, resultSet -> { if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("Create Procedure")); } }); return procedure; } private static String SELECT_TABLE_COLUMNS = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' order by ORDINAL_POSITION"; @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName); List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn column = new TableColumn(); column.setDatabaseName(databaseName); column.setTableName(tableName); column.setOldName(resultSet.getString("COLUMN_NAME")); column.setName(resultSet.getString("COLUMN_NAME")); //column.setColumnType(resultSet.getString("COLUMN_TYPE")); column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase()); //column.setDataType(resultSet.getInt("DATA_TYPE")); column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT")); column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); column.setComment(resultSet.getString("COLUMN_COMMENT")); column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY"))); column.setNullable("YES".equalsIgnoreCase(resultSet.getString("IS_NULLABLE")) ? 1 : 0); column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE")); column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); column.setCollationName(resultSet.getString("COLLATION_NAME")); setColumnSize(column, resultSet.getString("COLUMN_TYPE")); tableColumns.add(column); } return tableColumns; }); } private void setColumnSize(TableColumn column, String columnType) { try { if (columnType.contains("(")) { String size = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")")); if ("SET".equalsIgnoreCase(column.getColumnType()) || "ENUM".equalsIgnoreCase(column.getColumnType())) { column.setValue(size); } else { if (size.contains(",")) { String[] sizes = size.split(","); if (StringUtils.isNotBlank(sizes[0])) { column.setColumnSize(Integer.parseInt(sizes[0])); } if (StringUtils.isNotBlank(sizes[1])) { column.setDecimalDigits(Integer.parseInt(sizes[1])); } } else { column.setColumnSize(Integer.parseInt(size)); } } } } catch (Exception e) { } } private static String VIEW_DDL_SQL = "show create view %s"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_DDL_SQL, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("Create View")); } return table; }); } @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM "); queryBuf.append("`").append(tableName).append("`"); queryBuf.append(" FROM "); queryBuf.append("`").append(databaseName).append("`"); return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique(!resultSet.getBoolean("Non_unique")); index.setType(resultSet.getString("Index_type")); index.setComment(resultSet.getString("Index_comment")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); if ("PRIMARY".equalsIgnoreCase(keyName)) { index.setType(MysqlIndexTypeEnum.PRIMARY_KEY.getName()); } else if (index.getUnique()) { index.setType(MysqlIndexTypeEnum.UNIQUE.getName()); } else if ("SPATIAL".equalsIgnoreCase(index.getType())) { index.setType(MysqlIndexTypeEnum.SPATIAL.getName()); } else if ("FULLTEXT".equalsIgnoreCase(index.getType())) { index.setType(MysqlIndexTypeEnum.FULLTEXT.getName()); } else { index.setType(MysqlIndexTypeEnum.NORMAL.getName()); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("Column_name")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("Collation")); tableIndexColumn.setCardinality(resultSet.getLong("Cardinality")); tableIndexColumn.setSubPart(resultSet.getLong("Sub_part")); String collation = resultSet.getString("Collation"); if ("a".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("ASC"); } else if ("d".equalsIgnoreCase(collation)) { tableIndexColumn.setAscOrDesc("DESC"); } return tableIndexColumn; } @Override public SqlBuilder getSqlBuilder() { return new MysqlSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(MysqlColumnTypeEnum.getTypes()) .charsets(MysqlCharsetEnum.getCharsets()) .collations(MysqlCollationEnum.getCollations()) .indexTypes(MysqlIndexTypeEnum.getIndexTypes()) .defaultValues(MysqlDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); } // @Override // public ValueHandler getValueHandler() { // return new MysqlValueHandler(); // } @Override public ValueProcessor getValueProcessor() { return new MysqlValueProcessor(); } @Override public List getSystemDatabases() { return systemDatabases; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java ================================================ package ai.chat2db.plugin.mysql; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class MysqlPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"mysql.json", DBConfig.class); } @Override public MetaData getMetaData() { return new MysqlMetaData(); } @Override public DBManage getDBManage() { return new MysqlDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlValueHandler.java ================================================ //package ai.chat2db.plugin.mysql; // //import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; //import ai.chat2db.plugin.mysql.value.GeometryValueHandler; //import ai.chat2db.spi.ValueHandler; //import ai.chat2db.spi.jdbc.DefaultValueHandler; // //import java.sql.ResultSet; //import java.sql.SQLException; //import java.util.Map; // //public class MysqlValueHandler extends DefaultValueHandler { // // private static final Map VALUE_HANDLER_MAP = Map.of( // MysqlColumnTypeEnum.GEOMETRY.name(), new GeometryValueHandler() // ); // // @Override // public String getString(ResultSet rs, int index, boolean limitSize) throws SQLException { // try { // Object obj = rs.getObject(index); // if (obj == null) { // return null; // } // String columnTypeName = rs.getMetaData().getColumnTypeName(index); // if (MysqlColumnTypeEnum.GEOMETRY.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.POINT.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.LINESTRING.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.POLYGON.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.MULTIPOINT.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.MULTILINESTRING.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.MULTIPOLYGON.name().equalsIgnoreCase(columnTypeName) // || MysqlColumnTypeEnum.GEOMETRYCOLLECTION.name().equalsIgnoreCase(columnTypeName) // ) { // ValueHandler handler = VALUE_HANDLER_MAP.get(MysqlColumnTypeEnum.GEOMETRY.name()); // return handler.getString(rs, index, limitSize); // } else { // return super.getString(rs, index, limitSize); // } // }catch (Exception e){ // return rs.getString(index); // } // } // //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java ================================================ package ai.chat2db.plugin.mysql.builder; import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; import ai.chat2db.plugin.mysql.type.MysqlIndexTypeEnum; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.util.SqlUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class MysqlSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); if (StringUtils.isNotBlank(table.getDatabaseName())) { script.append("`").append(table.getDatabaseName()).append("`").append("."); } script.append("`").append(table.getName()).append("`").append(" (").append("\n"); // append column for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); if (typeEnum == null) { continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } // append primary key and index for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); if (mysqlIndexTypeEnum == null) { continue; } script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); if (StringUtils.isNotBlank(table.getEngine())) { script.append(" ENGINE=").append(table.getEngine()); } if (StringUtils.isNotBlank(table.getCharset())) { script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); } if (StringUtils.isNotBlank(table.getCollate())) { script.append(" COLLATE=").append(table.getCollate()); } if (table.getIncrementValue() != null) { script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); } if (StringUtils.isNotBlank(table.getComment())) { script.append(" COMMENT='").append(table.getComment()).append("'"); } if (StringUtils.isNotBlank(table.getPartition())) { script.append(" \n").append(table.getPartition()); } script.append(";"); return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder tableBuilder = new StringBuilder(); tableBuilder.append("ALTER TABLE "); if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { tableBuilder.append("`").append(oldTable.getDatabaseName()).append("`").append("."); } tableBuilder.append("`").append(oldTable.getName()).append("`").append("\n"); StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); } if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); } // 判断新增字段 List addColumnList = new ArrayList<>(); for (TableColumn tableColumn : newTable.getColumnList()) { if (tableColumn.getEditStatus() != null ? tableColumn.getEditStatus().equals("ADD") : false) { addColumnList.add(tableColumn); } } // 判断移动的字段 List moveColumnList = movedElements(oldTable.getColumnList(), newTable.getColumnList()); // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if ((StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) || moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) { MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(tableColumn.getColumnType()); if (typeEnum == null) { continue; } if (moveColumnList.contains(tableColumn) || addColumnList.contains(tableColumn)) { script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, true, findPrevious(tableColumn, newTable))).append(",\n"); } else { script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); } } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); if (mysqlIndexTypeEnum == null) { continue; } script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); } } // append reorder column // script.append(buildGenerateReorderColumnSql(oldTable, newTable)); if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); return tableBuilder.append(script).toString(); } else { return StringUtils.EMPTY; } } private String findPrevious(TableColumn tableColumn, Table newTable) { int index = newTable.getColumnList().indexOf(tableColumn); if (index == 0) { return "-1"; } // Find the previous column that is not deleted for (int i = index - 1; i >= 0; i--) { if (newTable.getColumnList().get(i).getEditStatus() == null || !newTable.getColumnList().get(i).getEditStatus().equals(EditStatus.DELETE.name())) { return newTable.getColumnList().get(i).getName(); } } return "-1"; } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if (offset == 0) { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(pageSize); } else { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(offset); sqlBuilder.append(","); sqlBuilder.append(pageSize); } return sqlBuilder.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`"); if (StringUtils.isNotBlank(database.getCharset())) { sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); } if (StringUtils.isNotBlank(database.getCollation())) { sqlBuilder.append(" COLLATE=").append(database.getCollation()); } return sqlBuilder.toString(); } public static List movedElements(List original, List modified) { int[][] dp = new int[original.size() + 1][modified.size() + 1]; // 构建DP表 for (int i = 1; i <= original.size(); i++) { for (int j = 1; j <= modified.size(); j++) { if (original.get(i - 1).getName().equals(modified.get(j - 1).getOldName())) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } // 追踪LCS,找出移动了位置的元素 List moved = new ArrayList<>(); int i = original.size(); int j = modified.size(); while (i > 0 && j > 0) { if (original.get(i - 1).equals(modified.get(j - 1))) { i--; j--; } else if (dp[i - 1][j] >= dp[i][j - 1]) { moved.add(original.get(i - 1)); // modified List中找到original.get(i-1)的位置 // System.out.println("Moved elements:"+ original.get(i-1).getName() + " after " + modified.indexOf(original.get(i-1)) ); i--; } else { j--; } } // 这里添加原始列表中未被包含在LCS中的元素 while (i > 0) { moved.add(original.get(i - 1)); i--; } return moved; } public String buildGenerateReorderColumnSql(Table oldTable, Table newTable) { StringBuilder sql = new StringBuilder(); int n = 0; // Create a map to store the index of each column in the old table's column list Map oldColumnIndexMap = new HashMap<>(); for (int i = 0; i < oldTable.getColumnList().size(); i++) { oldColumnIndexMap.put(oldTable.getColumnList().get(i).getName(), i); } String[] oldColumnArray = oldTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new); String[] newColumnArray = newTable.getColumnList().stream().map(TableColumn::getName).toArray(String[]::new); Set oldColumnSet = new HashSet<>(Arrays.asList(oldColumnArray)); Set newColumnSet = new HashSet<>(Arrays.asList(newColumnArray)); if (!oldColumnSet.equals(newColumnSet)) { return ""; } buildSql(oldColumnArray, newColumnArray, sql, oldTable, newTable, n); return sql.toString(); } private String[] buildSql(String[] originalArray, String[] targetArray, StringBuilder sql, Table oldTable, Table newTable, int n) { // Complete the first move first if (!originalArray[0].equals(targetArray[0])) { int a = findIndex(originalArray, targetArray[0]); TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get(); String[] newArray = moveElement(originalArray, a, 0, targetArray, new AtomicInteger(0)); sql.append(" MODIFY COLUMN "); MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); sql.append(typeEnum.buildColumn(column)); sql.append(" FIRST;\n"); n++; if (Arrays.equals(newArray, targetArray)) { return newArray; } String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); if (Arrays.equals(resultArray, targetArray)) { return resultArray; } } // After completing the last move int max = originalArray.length - 1; if (!originalArray[max].equals(targetArray[max])) { int a = findIndex(originalArray, targetArray[max]); //System.out.println("Move " + originalArray[a] + " after " + (a > 0 ? originalArray[max] : "start")); TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[a])).findFirst().get(); String[] newArray = moveElement(originalArray, a, max, targetArray, new AtomicInteger(0)); if (n > 0) { sql.append("ALTER TABLE "); if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { sql.append("`").append(oldTable.getDatabaseName()).append("`").append("."); } sql.append("`").append(oldTable.getName()).append("`").append("\n"); } sql.append(" MODIFY COLUMN "); MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); sql.append(typeEnum.buildColumn(column)); sql.append(" AFTER "); sql.append(oldTable.getColumnList().get(max).getName()); sql.append(";\n"); n++; if (Arrays.equals(newArray, targetArray)) { return newArray; } String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); if (Arrays.equals(resultArray, targetArray)) { return resultArray; } } for (int i = 0; i < originalArray.length; i++) { int a = findIndex(targetArray, originalArray[i]); if (i != a && isMoveValid(originalArray, targetArray, i, a)) { // Find name a in oldTable.getColumnList int finalI = i; TableColumn column = oldTable.getColumnList().stream().filter(col -> StringUtils.equals(col.getName(), originalArray[finalI])).findFirst().get(); if (n > 0) { sql.append("ALTER TABLE "); if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { sql.append("`").append(oldTable.getDatabaseName()).append("`").append("."); } sql.append("`").append(oldTable.getName()).append("`").append("\n"); } sql.append(" MODIFY COLUMN "); MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); sql.append(typeEnum.buildColumn(column)); sql.append(" AFTER "); AtomicInteger continuousDataCount = new AtomicInteger(0); String[] newArray = moveElement(originalArray, i, a, targetArray, continuousDataCount); if (i < a) { sql.append(originalArray[a + continuousDataCount.get()]); } else { sql.append(originalArray[a - 1]); } sql.append(";\n"); n++; if (Arrays.equals(newArray, targetArray)) { return newArray; } String[] resultArray = buildSql(newArray, targetArray, sql, oldTable, newTable, n); if (Arrays.equals(resultArray, targetArray)) { return resultArray; } } } return null; } private static int findIndex(String[] array, String element) { for (int i = 0; i < array.length; i++) { if (array[i].equals(element)) { return i; } } return -1; } private static boolean isMoveValid(String[] originalArray, String[] targetArray, int i, int a) { return ((i == 0 || a == 0 || !originalArray[i - 1].equals(targetArray[a - 1])) && (i >= originalArray.length - 1 || a >= targetArray.length - 1 || !originalArray[i + 1].equals(targetArray[a + 1]))) || (i > 0 && a > 0 && !originalArray[i - 1].equals(targetArray[a - 1])); } private static String[] moveElement(String[] originalArray, int from, int to, String[] targetArray, AtomicInteger continuousDataCount) { String[] newArray = new String[originalArray.length]; System.arraycopy(originalArray, 0, newArray, 0, originalArray.length); String temp = newArray[from]; // 是否有连续移动数据 boolean isContinuousData = false; // 连续数据数量 if (from < to) { for (int i = to; i < originalArray.length - 1; i++) { if (originalArray[i + 1].equals(targetArray[findIndex(targetArray, originalArray[i]) + 1])) { continuousDataCount.set(continuousDataCount.incrementAndGet()); } else { break; } } if (continuousDataCount.get() > 0) { System.arraycopy(originalArray, from + 1, newArray, from, to - from + 1); isContinuousData = true; } else { System.arraycopy(originalArray, from + 1, newArray, from, to - from); } } else { System.arraycopy(originalArray, to, newArray, to + 1, from - to); } if (isContinuousData) { newArray[to + continuousDataCount.get()] = temp; } else { newArray[to] = temp; } return newArray; } @Override protected void buildTableName(String databaseName, String schemaName, String tableName, StringBuilder script) { if (StringUtils.isNotBlank(databaseName)) { script.append(SqlUtils.quoteObjectName(databaseName, "`")).append('.'); } script.append(SqlUtils.quoteObjectName(tableName, "`")); } /** * @param columnList * @param script */ @Override protected void buildColumns(List columnList, StringBuilder script) { if (CollectionUtils.isNotEmpty(columnList)) { script.append(" (") .append(columnList.stream().map(s -> SqlUtils.quoteObjectName(s, "`")).collect(Collectors.joining(","))) .append(") "); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/form.json ================================================ { "baseInfo": { "items": [ { "defaultValue": "@localhost", "inputType": "INPUT", "labelNameCN": "名称", "labelNameEN": "Name", "name": "alias", "required": true, "width": 100, }, { "defaultValue": "localhost", "inputType": "INPUT", "labelNameCN": "主机", "labelNameEN": "Host", "name": "host", "required": true, "width": 70, }, { "defaultValue": "3306", "inputType": "INPUT", "labelNameCN": "端口", "labelNameEN": "Port", "name": "port", "labelTextAlign": "right", "required": true, "width": 30, }, { "defaultValue": AuthenticationType.USERANDPASSWORD, "inputType": InputType.SELECT, "labelNameCN": "身份验证", "labelNameEN": "Authentication", "name": "authentication", "required": true, "selects": [ { "items": [ { "defaultValue": "root", "inputType": "INPUT", "labelNameCN": "用户名", "labelNameEN": "User", "name": "user", "required": true, "width": 100, }, { "defaultValue": ", "inputType": InputType.PASSWORD, "labelNameCN": "密码", "labelNameEN": "Password", "name": "password", "required": true, "width": 100, }, ], "label": "User&Password", "value": AuthenticationType.USERANDPASSWORD, }, { "label": "NONE", "value": "NONE, }, ], "width": 50 }, { "defaultValue": "", "inputType": "INPUT", "labelNameCN": "数据库", "labelNameEN": "Database", "name": "database", "required": false, "width": 100 }, { "defaultValue": "jdbc:mysql://localhost:3306", "inputType": "INPUT", "labelNameCN": "URL", "labelNameEN": "URL", "name": "url", "required": true, "width": 100 }, { "defaultValue": "8.0", "inputType": "SELECT", "labelNameCN": "JDBC驱动", "labelNameEN": "JDBC Driver", "name": "jdbc", "required": true, "selects": [ { "value": "8.0" }, { "value": "5.0" } ], "width": 100 } ], "pattern": "/jdbc:mysql:\/\/(.*):(\\d+)(\/(\\w+))?/", "template": "jdbc:mysql://{host}:{port}/{database}" }, "ssh": { "items": [ { "defaultValue": "false", "inputType": "SELECT", "labelNameCN": "使用SSH", "labelNameEN": "USE SSH", "name": "use", "required": false, "selects": [ { "value": "false" }, { "value": "true" } ], "width": 100 }, { "defaultValue": "", "inputType": "INPUT", "labelNameCN": "SSH 主机", "labelNameEN": "SSH Hostname", "name": "hostName", "required": false, "width": 70 }, { "defaultValue": "22", "inputType": "INPUT", "labelNameCN": "SSH 端口", "labelNameEN": "Port", "name": "port", "required": false, "width": 28 }, { "defaultValue": "root", "inputType": "INPUT", "labelNameCN": "用户名", "labelNameEN": "SSH UserName", "name": "userName", "required": false, "width": 70 }, { "defaultValue": "3306", "inputType": "INPUT", "labelNameCN": "本地端口", "labelNameEN": "LocalPort", "name": "localPort", "required": false, "width": 28 }, { "defaultValue": "", "inputType": "PASSWORD", "labelNameCN": "密码", "labelNameEN": "Password", "name": "password", "required": true, "width": 100 } ] }, "extendInfo": [ { "key":"zeroDateTimeBehavior", "value":"convertToNull" } ], "type":"MYSQL" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json ================================================ { "dbType": "MYSQL", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:mysql://localhost:3306/", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/mysql-connector-java-8.0.30.jar" ], "jdbcDriver": "mysql-connector-java-8.0.30.jar", "jdbcDriverClass": "com.mysql.cj.jdbc.Driver", "extendInfo": [ { "key": "zeroDateTimeBehavior", "value": "convertToNull", "required": false }, { "key": "tinyInt1isBit", "value": "false", "required": false } ] }, { "url": "jdbc:mysql://localhost:3306/", "defaultDriver": false, "custom": false, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/mysql-connector-java-5.1.47.jar" ], "jdbcDriver": "mysql-connector-java-5.1.47.jar", "jdbcDriverClass": "com.mysql.jdbc.Driver", "extendInfo": [ { "key": "characterEncoding", "value": "UTF-8", "required": false }, { "key": "tinyInt1isBit", "value": "false", "required": false } ] } ], "name": "Mysql" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCharsetEnum.java ================================================ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.model.Charset; import org.checkerframework.checker.units.qual.C; import java.util.Arrays; import java.util.List; public enum MysqlCharsetEnum { UTF8("utf8", "utf8_general_ci"), BIG5("big5", "big5_chinese_ci"), DEC8("dec8", "dec8_swedish_ci"), CP850("cp850", "cp850_general_ci"), HP8("hp8", "hp8_english_ci"), KOI8R("koi8r", "koi8r_general_ci"), LATIN1("latin1", "latin1_swedish_ci"), LATIN2("latin2", "latin2_general_ci"), SWE7("swe7", "swe7_swedish_ci"), ASCII("ascii", "ascii_general_ci"), UJIS("ujis", "ujis_japanese_ci"), SJIS("sjis", "sjis_japanese_ci"), HEBREW("hebrew", "hebrew_general_ci"), TIS620("tis620", "tis620_thai_ci"), EUCKR("euckr", "euckr_korean_ci"), KOI8U("koi8u", "koi8u_general_ci"), GB2312("gb2312", "gb2312_chinese_ci"), GREEK("greek", "greek_general_ci"), CP1250("cp1250", "cp1250_general_ci"), GBK("gbk", "gbk_chinese_ci"), LATIN5("latin5", "latin5_turkish_ci"), ARMSCII8("armscii8", "armscii8_general_ci"), UCS2("ucs2", "ucs2_general_ci"), CP866("cp866", "cp866_general_ci"), KEYBCS2("keybcs2", "keybcs2_general_ci"), MACCE("macce", "macce_general_ci"), MACROMAN("macroman", "macroman_general_ci"), CP852("cp852", "cp852_general_ci"), LATIN7("latin7", "latin7_general_ci"), UTF8MB4("utf8mb4", "utf8mb4_general_ci"), CP1251("cp1251", "cp1251_general_ci"), UTF16("utf16", "utf16_general_ci"), UTF16LE("utf16le", "utf16le_general_ci"), CP1256("cp1256", "cp1256_general_ci"), CP1257("cp1257", "cp1257_general_ci"), UTF32("utf32", "utf32_general_ci"), BINARY("binary", "binary"), GEOSTD8("geostd8", "geostd8_general_ci"), CP932("cp932", "cp932_japanese_ci"), EUCJPMS("eucjpms", "eucjpms_japanese_ci"), GB18030("gb18030", "gb18030_chinese_ci"); private Charset charset; MysqlCharsetEnum(String charsetName, String defaultCollationName) { this.charset = new Charset(charsetName, defaultCollationName); } public Charset getCharset() { return charset; } public static List getCharsets() { return Arrays.stream(MysqlCharsetEnum.values()).map(MysqlCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java ================================================ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.model.Collation; import java.util.Arrays; import java.util.List; public enum MysqlCollationEnum { UTF8_GENERAL_CI("utf8_general_ci"), UTF8MB4_GENERAL_CI("utf8mb4_general_ci"), BIG5_CHINESE_CI("big5_chinese_ci"), DEC8_SWEDISH_CI("dec8_swedish_ci"), HP8_ENGLISH_CI("hp8_english_ci"), KOI8R_GENERAL_CI("koi8r_general_ci"), LATIN1_SWEDISH_CI("latin1_swedish_ci"), LATIN2_GENERAL_CI("latin2_general_ci"), SWE7_SWEDISH_CI("swe7_swedish_ci"), ASCII_GENERAL_CI("ascii_general_ci"), UJIS_JAPANESE_CI("ujis_japanese_ci"), SJIS_JAPANESE_CI("sjis_japanese_ci"), HEBREW_GENERAL_CI("hebrew_general_ci"), TIS620_THAI_CI("tis620_thai_ci"), EUCKR_KOREAN_CI("euckr_korean_ci"), KOI8U_GENERAL_CI("koi8u_general_ci"), GB2312_CHINESE_CI("gb2312_chinese_ci"), GREEK_GENERAL_CI("greek_general_ci"), CP1250_GENERAL_CI("cp1250_general_ci"), GBK_CHINESE_CI("gbk_chinese_ci"), LATIN5_TURKISH_CI("latin5_turkish_ci"), ARMSCII8_GENERAL_CI("armscii8_general_ci"), CP1250_CZECH_CS("cp1250_czech_cs"), UCS2_GENERAL_CI("ucs2_general_ci"), CP866_GENERAL_CI("cp866_general_ci"), KEYBCS2_GENERAL_CI("keybcs2_general_ci"), MACCE_GENERAL_CI("macce_general_ci"), MACROMAN_GENERAL_CI("macroman_general_ci"), CP852_GENERAL_CI("cp852_general_ci"), LATIN7_GENERAL_CI("latin7_general_ci"), CP1251_GENERAL_CI("cp1251_general_ci"), UTF16_GENERAL_CI("utf16_general_ci"), UTF16LE_GENERAL_CI("utf16le_general_ci"), CP1256_GENERAL_CI("cp1256_general_ci"), CP1257_GENERAL_CI("cp1257_general_ci"), UTF32_GENERAL_CI("utf32_general_ci"), BINARY("binary"), GEOSTD8_GENERAL_CI("geostd8_general_ci"), CP932_JAPANESE_CI("cp932_japanese_ci"), EUCJPMS_JAPANESE_CI("eucjpms_japanese_ci"), GB18030_CHINESE_CI("gb18030_chinese_ci"), ; private Collation collation; MysqlCollationEnum(String collationName) { this.collation = new Collation(collationName); } public Collation getCollation() { return collation; } public static List getCollations() { return Arrays.asList(MysqlCollationEnum.values()).stream().map(MysqlCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java ================================================ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum MysqlColumnTypeEnum implements ColumnBuilder { BIT("BIT", true, false, true, false, false, false, true, true, false, false), TINYINT("TINYINT", false, false, true, true, false, false, true, true, false, false), TINYINT_UNSIGNED("TINYINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), SMALLINT("SMALLINT", false, false, true, true, false, false, true, true, false, false), SMALLINT_UNSIGNED("SMALLINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), MEDIUMINT("MEDIUMINT", false, false, true, true, false, false, true, true, false, false), MEDIUMINT_UNSIGNED("MEDIUMINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), INT("INT", false, false, true, true, false, false, true, true, false, false), INT_UNSIGNED("INT UNSIGNED", false, false, true, true, false, false, true, true, false, false), BIGINT("BIGINT", false, false, true, true, false, false, true, true, false, false), BIGINT_UNSIGNED("BIGINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), DECIMAL_UNSIGNED("DECIMAL UNSIGNED", true, true, true, false, false, false, true, true, false, false), FLOAT("FLOAT", true, true, true, false, false, false, true, true, false, false), FLOAT_UNSIGNED("FLOAT UNSIGNED", true, true, true, false, false, false, true, true, false, false), DOUBLE("DOUBLE", true, true, true, false, false, false, true, true, false, false), DOUBLE_UNSIGNED("DOUBLE UNSIGNED", true, true, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DATETIME("DATETIME", true, false, true, false, false, false, true, true, true, false), TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true, false), TIME("TIME", true, false, true, false, false, false, true, true, false, false), YEAR("YEAR", false, false, true, false, false, false, true, true, false, false), CHAR("CHAR", true, false, true, false, true, true, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, true, true, true, true, false, false), BINARY("BINARY", true, false, true, false, false, false, true, true, false, false), VARBINARY("VARBINARY", true, false, true, false, false, false, true, true, false, false), TINYBLOB("TINYBLOB", false, false, true, false, false, false, true, false, false, false), BLOB("BLOB", false, false, true, false, false, false, true, false, false, false), MEDIUMBLOB("MEDIUMBLOB", false, false, true, false, false, false, true, false, false, false), LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false, false), TINYTEXT("TINYTEXT", false, false, true, false, true, true, true, false, false, false), TEXT("TEXT", false, false, true, false, true, true, true, false, false, false), MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, true, true, true, false, false, false), LONGTEXT("LONGTEXT", false, false, true, false, true, true, true, false, false, false), ENUM("ENUM", false, false, true, false, true, true, true, true, true, true), BOOL("BOOL", false, false, true, true, false, false, true, true, false, false), INTEGER("INTEGER", false, false, true, true, false, false, true, true, false, false), INTEGER_UNSIGNED("INTEGER UNSIGNED", false, false, true, true, false, false, true, true, false, false), REAL("REAL", true, true, true, false, false, false, true, true, false, false), SET("SET", false, false, true, false, true, true, true, true, true, true), GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false, false), POINT("POINT", false, false, true, false, false, false, true, false, false, false), LINESTRING("LINESTRING", false, false, true, false, false, false, true, false, false, false), POLYGON("POLYGON", false, false, true, false, false, false, true, false, false, false), MULTIPOINT("MULTIPOINT", false, false, true, false, false, false, true, false, false, false), MULTILINESTRING("MULTILINESTRING", false, false, true, false, false, false, true, false, false, false), MULTIPOLYGON("MULTIPOLYGON", false, false, true, false, false, false, true, false, false, false), GEOMETRYCOLLECTION("GEOMETRYCOLLECTION", false, false, true, false, false, false, true, false, false, false), JSON("JSON", false, false, true, false, false, false, true, false, false, false); private ColumnType columnType; public static MysqlColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public ColumnType getColumnType() { return columnType; } MysqlColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent,boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue,false); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (MysqlColumnTypeEnum value : MysqlColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } @Override public String buildCreateColumnSql(TableColumn column) { MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildCharset(column,type)).append(" "); script.append(buildCollation(column,type)).append(" "); script.append(buildNullable(column,type)).append(" "); script.append(buildDefaultValue(column,type)).append(" "); script.append(buildExt(column,type)).append(" "); script.append(buildAutoIncrement(column,type)).append(" "); script.append(buildComment(column,type)).append(" "); return script.toString(); } private String buildCharset(TableColumn column, MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportCharset() || StringUtils.isEmpty(column.getCharSetName())){ return ""; } return StringUtils.join("CHARACTER SET ",column.getCharSetName()); } private String buildCollation(TableColumn column, MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())){ return ""; } return StringUtils.join("COLLATE ",column.getCollationName()); } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); } else { return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); } } return ""; } public String buildModifyColumn(TableColumn tableColumn, boolean isMove, String columnName) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { if (isMove){ if (columnName.equals("-1")) { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)," FIRST"); } else { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn), " AFTER ", columnName); } } return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); } else { return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); } } if (isMove) { if (columnName.equals("-1")) { return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)," FIRST"); } else { return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn), " AFTER ", columnName); } } return ""; } private String buildAutoIncrement(TableColumn column, MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportAutoIncrement()){ return ""; } if (column.getAutoIncrement() != null && column.getAutoIncrement()) { return "AUTO_INCREMENT"; } return ""; } private String buildComment(TableColumn column, MysqlColumnTypeEnum type) { if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ return ""; } return StringUtils.join("COMMENT '",column.getComment(),"'"); } private String buildExt(TableColumn column, MysqlColumnTypeEnum type) { if(!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())){ return ""; } return column.getComment(); } private String buildDefaultValue(TableColumn column, MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ return ""; } if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ''"); } if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT NULL"); } if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY, SET,ENUM).contains(type)){ return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } if(Arrays.asList(DATE,TIME,YEAR).contains(type)){ return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } if(Arrays.asList(DATETIME,TIMESTAMP).contains(type)){ if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ",column.getDefaultValue()); } return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } return StringUtils.join("DEFAULT ",column.getDefaultValue()); } private String buildNullable(TableColumn column,MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportNullable()){ return ""; } if (column.getNullable()!=null && 1==column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(BINARY, VARBINARY, VARCHAR, CHAR).contains(type)) { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(BIT).contains(type)) { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(TIME, DATETIME, TIMESTAMP).contains(type)) { if (column.getColumnSize() == null || column.getColumnSize() == 0) { return columnType; } else { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } } if (Arrays.asList(DECIMAL, FLOAT, DOUBLE).contains(type)) { if (column.getColumnSize() == null || column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } if (column.getColumnSize() != null && column.getDecimalDigits() != null) { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } if (Arrays.asList(DECIMAL_UNSIGNED, FLOAT_UNSIGNED, DECIMAL_UNSIGNED).contains(type)) { if (column.getColumnSize() == null || column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return unsignedDataType(columnType, "(" + column.getColumnSize() + ")"); } if (column.getColumnSize() != null && column.getDecimalDigits() != null) { return unsignedDataType(columnType, "(" + column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } if(Arrays.asList(SET,ENUM).contains(type)){ if(!StringUtils.isEmpty( column.getValue())){ return StringUtils.join(columnType,"(",column.getValue(),")"); } //List enumList = column. } return columnType; } public String buildColumn(TableColumn column) { MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); if (StringUtils.isNoneBlank(column.getComment())) { script.append("COMMENT").append(" ").append("'").append(column.getComment()).append("'").append(" "); } return script.toString(); } private String unsignedDataType(String dataTypeName, String middle) { String[] split = dataTypeName.split(" "); if (split.length == 2) { return StringUtils.join(split[0], middle, split[1]); } return StringUtils.join(dataTypeName, middle); } public static List getTypes(){ return Arrays.stream(MysqlColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlDefaultValueEnum.java ================================================ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum MysqlDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), CURRENT_TIMESTAMP("CURRENT_TIMESTAMP"), ; private DefaultValue defaultValue; MysqlDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(MysqlDefaultValueEnum.values()).map(MysqlDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java ================================================ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum MysqlIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"), FULLTEXT("Fulltext", "FULLTEXT INDEX"), SPATIAL("Spatial", "SPATIAL INDEX"); public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; MysqlIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static MysqlIndexTypeEnum getByType(String type) { for (MysqlIndexTypeEnum value : MysqlIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); script.append(buildIndexComment(tableIndex)).append(" "); return script.toString(); } private String buildIndexComment(TableIndex tableIndex) { if(StringUtils.isBlank(tableIndex.getComment())){ return ""; }else { return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if(StringUtils.isNotBlank(column.getColumnName())) { script.append("`").append(column.getColumnName()).append("`"); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { if(this.equals(PRIMARY_KEY)){ return ""; }else { return "`"+tableIndex.getName()+"`"; } } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("ADD ", buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (MysqlIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); } public static List getIndexTypes() { return Arrays.asList(MysqlIndexTypeEnum.values()).stream().map(MysqlIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/GeometryValueHandler.java ================================================ //package ai.chat2db.plugin.mysql.value; // //import ai.chat2db.spi.ValueHandler; //import org.locationtech.jts.geom.Geometry; //import org.locationtech.jts.io.WKBReader; // //import java.io.InputStream; //import java.io.ByteArrayOutputStream; //import java.sql.ResultSet; //import java.sql.SQLException; // //public class GeometryValueHandler implements ValueHandler { // @Override // public String getString(ResultSet rs, int index, boolean limitSize) throws SQLException { // try { // InputStream inputStream = rs.getBinaryStream(index); // Geometry dbGeometry = null; // if (inputStream != null) { // // //convert the stream to a byte[] array // //so it can be passed to the WKBReader // byte[] buffer = new byte[255]; // // int bytesRead = 0; // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // while ((bytesRead = inputStream.read(buffer)) != -1) { // baos.write(buffer, 0, bytesRead); // } // // byte[] geometryAsBytes = baos.toByteArray(); // // if (geometryAsBytes.length < 5) { // throw new Exception("Invalid geometry inputStream - less than five bytes"); // } // // //first four bytes of the geometry are the SRID, // //followed by the actual WKB. Determine the SRID // //here // byte[] sridBytes = new byte[4]; // System.arraycopy(geometryAsBytes, 0, sridBytes, 0, 4); // boolean bigEndian = (geometryAsBytes[4] == 0x00); // // int srid = 0; // if (bigEndian) { // for (int i = 0; i < sridBytes.length; i++) { // srid = (srid << 8) + (sridBytes[i] & 0xff); // } // } else { // for (int i = 0; i < sridBytes.length; i++) { // srid += (sridBytes[i] & 0xff) << (8 * i); // } // } // // //use the JTS WKBReader for WKB parsing // WKBReader wkbReader = new WKBReader(); // // //copy the byte array, removing the first four // //SRID bytes // byte[] wkb = new byte[geometryAsBytes.length - 4]; // System.arraycopy(geometryAsBytes, 4, wkb, 0, wkb.length); // dbGeometry = wkbReader.read(wkb); // dbGeometry.setSRID(srid); // } // return dbGeometry.toString(); // } catch (Exception e) { // return rs.getString(index); // } // } //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/MysqlValueProcessor.java ================================================ package ai.chat2db.plugin.mysql.value; import ai.chat2db.plugin.mysql.value.factory.MysqlValueProcessorFactory; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; import java.util.Set; /** * @author: zgq * @date: 2024年05月24日 21:02 *
* TODO: * attribute: [zerofill] example tinyint[5] zerofill 34->00034 */ public class MysqlValueProcessor extends DefaultValueProcessor { public static final Set FUNCTION_SET = Set.of("now()", "default"); private static final Logger log = LoggerFactory.getLogger(MysqlValueProcessor.class); @Override public String getJdbcValue(JDBCDataValue dataValue) { Object value = dataValue.getObject(); if (Objects.isNull(value)) { // mysql -> example: [date]->0000-00-00 String stringValue = dataValue.getStringValue(); if (Objects.nonNull(stringValue)) { return stringValue; } return null; } if (value instanceof String emptyStr) { if (StringUtils.isBlank(emptyStr)) { return emptyStr; } } return convertJDBCValueByType(dataValue); } @Override public String getJdbcSqlValueString(JDBCDataValue dataValue) { Object value = dataValue.getObject(); if (Objects.isNull(value)) { // mysql -> example: [date]->0000-00-00 String stringValue = dataValue.getStringValue(); if (Objects.nonNull(stringValue)) { return EasyStringUtils.escapeAndQuoteString(stringValue); } return "NULL"; } if (value instanceof String stringValue) { if (StringUtils.isBlank(stringValue)) { return EasyStringUtils.quoteString(stringValue); } } return convertJDBCValueStrByType(dataValue); } @Override public String convertSQLValueByType(SQLDataValue dataValue) { if (FUNCTION_SET.contains(dataValue.getValue().toLowerCase())) { return dataValue.getValue(); } try { DefaultValueProcessor valueProcessor = MysqlValueProcessorFactory.getValueProcessor(dataValue.getDateTypeName()); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertSQLValueByType(dataValue); } } catch (Exception e) { log.warn("convertSQLValueByType error", e); return super.convertSQLValueByType(dataValue); } return super.convertSQLValueByType(dataValue); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { String type = dataValue.getType(); try { DefaultValueProcessor valueProcessor = MysqlValueProcessorFactory.getValueProcessor(type); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertJDBCValueByType(dataValue); } } catch (Exception e) { log.warn("convertJDBCValueByType error", e); return super.convertJDBCValueByType(dataValue); } return super.convertJDBCValueByType(dataValue); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { String type = dataValue.getType(); DefaultValueProcessor valueProcessor; try { valueProcessor = MysqlValueProcessorFactory.getValueProcessor(type); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertJDBCValueStrByType(dataValue); } } catch (Exception e) { log.warn("convertJDBCValueStrByType error", e); return super.convertJDBCValueStrByType(dataValue); } return super.convertJDBCValueStrByType(dataValue); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/factory/MysqlValueProcessorFactory.java ================================================ package ai.chat2db.plugin.mysql.value.factory; import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; import ai.chat2db.plugin.mysql.value.sub.*; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import java.util.Map; /** * @author: zgq * @date: 2024年06月03日 23:16 */ public class MysqlValueProcessorFactory { private static final Map PROCESSOR_MAP; static { MysqlGeometryProcessor mysqlGeometryProcessor = new MysqlGeometryProcessor(); MysqlVarBinaryProcessor mysqlVarBinaryProcessor = new MysqlVarBinaryProcessor(); MysqlTimestampProcessor mysqlTimestampProcessor = new MysqlTimestampProcessor(); MysqlTextProcessor mysqlTextProcessor = new MysqlTextProcessor(); PROCESSOR_MAP = Map.ofEntries( //text Map.entry(MysqlColumnTypeEnum.TEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.TINYTEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.MEDIUMTEXT.name(), mysqlTextProcessor), Map.entry(MysqlColumnTypeEnum.LONGTEXT.name(), mysqlTextProcessor), // geometry Map.entry(MysqlColumnTypeEnum.GEOMETRY.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.POINT.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.LINESTRING.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.POLYGON.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTIPOINT.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTILINESTRING.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.MULTIPOLYGON.name(), mysqlGeometryProcessor), Map.entry(MysqlColumnTypeEnum.GEOMETRYCOLLECTION.name(), mysqlGeometryProcessor), // binary Map.entry(MysqlColumnTypeEnum.VARBINARY.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.BLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.LONGBLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.TINYBLOB.name(), mysqlVarBinaryProcessor), Map.entry(MysqlColumnTypeEnum.MEDIUMBLOB.name(), mysqlVarBinaryProcessor), // timestamp Map.entry(MysqlColumnTypeEnum.TIMESTAMP.name(), mysqlTimestampProcessor), Map.entry(MysqlColumnTypeEnum.DATETIME.name(), mysqlTimestampProcessor), //others Map.entry(MysqlColumnTypeEnum.YEAR.name(), new MysqlYearProcessor()), Map.entry(MysqlColumnTypeEnum.BIT.name(), new MysqlBitProcessor()), Map.entry(MysqlColumnTypeEnum.DECIMAL.name(), new MysqlDecimalProcessor()), Map.entry(MysqlColumnTypeEnum.BINARY.name(), new MysqlBinaryProcessor()) ); } public static DefaultValueProcessor getValueProcessor(String type) { return PROCESSOR_MAP.get(type); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlBinaryProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月03日 19:43 */ public class MysqlBinaryProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { String value = dataValue.getValue(); if (value.startsWith("0x")) { return value; } return MysqlDmlValueTemplate.wrapHex(dataValue.getBlobHexString()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { byte[] bytes = dataValue.getBytes(); if (bytes.length == 1) { if (bytes[0] >= 32 && bytes[0] <= 126) { return new String(bytes); } } return MysqlDmlValueTemplate.wrapHex(dataValue.getBlobHexString()); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return MysqlDmlValueTemplate.wrapHex(dataValue.getBlobHexString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlBitProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.apache.commons.lang3.StringUtils; import java.util.Objects; import java.util.function.Function; /** * @author: zgq * @date: 2024年06月01日 13:08 */ public class MysqlBitProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return getString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return getValue(dataValue, s -> s); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return getValue(dataValue, this::wrap); } private String getValue(JDBCDataValue dataValue, Function function) { int precision = dataValue.getPrecision(); byte[] bytes = dataValue.getBytes(); if (precision == 1) { //bit(1) [1 -> true] [0 -> false] if (bytes.length == 1 && (bytes[0] == 0 || bytes[0] == 1)) { return String.valueOf(dataValue.getBoolean()); } // tinyint(1) return String.valueOf(dataValue.getInt()); } //bit(m) m: 2~64 return function.apply(EasyStringUtils.getBitString(bytes, precision)); } public String getString(String value) { if (Objects.equals("true", value.toLowerCase())) { return "1"; } if (Objects.equals("false", value.toLowerCase())) { return "0"; } if (StringUtils.isBlank(value)) { return "NULL"; } return MysqlDmlValueTemplate.wrapBit(value); } private String wrap(String value) { return MysqlDmlValueTemplate.wrapBit(value); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlDecimalProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月01日 18:01 */ public class MysqlDecimalProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return dataValue.getValue(); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBigDecimalString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return dataValue.getBigDecimalString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlGeometryProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKBReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.InputStream; /** * @author: zgq * @date: 2024年06月01日 12:42 */ public class MysqlGeometryProcessor extends DefaultValueProcessor { private static final Logger log = LoggerFactory.getLogger(MysqlGeometryProcessor.class); @Override public String convertSQLValueByType(SQLDataValue dataValue) { return MysqlDmlValueTemplate.wrapGeometry(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { try { InputStream inputStream = dataValue.getBinaryStream(); Geometry dbGeometry = null; if (inputStream != null) { //convert the stream to a byte[] array //so it can be passed to the WKBReader byte[] buffer = new byte[255]; int bytesRead = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ((bytesRead = inputStream.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } byte[] geometryAsBytes = baos.toByteArray(); if (geometryAsBytes.length < 5) { throw new Exception("Invalid geometry inputStream - less than five bytes"); } //first four bytes of the geometry are the SRID, //followed by the actual WKB. Determine the SRID //here byte[] sridBytes = new byte[4]; System.arraycopy(geometryAsBytes, 0, sridBytes, 0, 4); boolean bigEndian = (geometryAsBytes[4] == 0x00); int srid = 0; if (bigEndian) { for (int i = 0; i < sridBytes.length; i++) { srid = (srid << 8) + (sridBytes[i] & 0xff); } } else { for (int i = 0; i < sridBytes.length; i++) { srid += (sridBytes[i] & 0xff) << (8 * i); } } //use the JTS WKBReader for WKB parsing WKBReader wkbReader = new WKBReader(); //copy the byte array, removing the first four //SRID bytes byte[] wkb = new byte[geometryAsBytes.length - 4]; System.arraycopy(geometryAsBytes, 4, wkb, 0, wkb.length); dbGeometry = wkbReader.read(wkb); dbGeometry.setSRID(srid); } return dbGeometry != null ? dbGeometry.toString() : null; } catch (Exception e) { log.warn("Error converting database geometry", e); return dataValue.getStringValue(); } } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return MysqlDmlValueTemplate.wrapGeometry(convertJDBCValueByType(dataValue)); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlTextProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import lombok.extern.slf4j.Slf4j; /** * @author: zgq * @date: 2024年06月05日 0:11 */ @Slf4j public class MysqlTextProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return EasyStringUtils.escapeAndQuoteString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getClobString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.escapeAndQuoteString(dataValue.getClobString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlTimestampProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月01日 18:26 */ public class MysqlTimestampProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getStringValue()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlVarBinaryProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.plugin.mysql.value.template.MysqlDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import lombok.extern.slf4j.Slf4j; /** * @author: zgq * @date: 2024年06月03日 20:48 */ @Slf4j public class MysqlVarBinaryProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { String value = dataValue.getValue(); if (value.startsWith("0x")) { return value; } return MysqlDmlValueTemplate.wrapHex(dataValue.getBlobHexString()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBlobString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return MysqlDmlValueTemplate.wrapHex(dataValue.getBlobHexString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/sub/MysqlYearProcessor.java ================================================ package ai.chat2db.plugin.mysql.value.sub; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * 功能描述 * * @author: zgq * @date: 2024年06月01日 12:57 */ public class MysqlYearProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return dataValue.getValue(); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return new String(dataValue.getBytes()); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return new String(dataValue.getBytes()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/value/template/MysqlDmlValueTemplate.java ================================================ package ai.chat2db.plugin.mysql.value.template; /** * @author: zgq * @date: 2024年06月01日 13:31 */ public class MysqlDmlValueTemplate { public static final String GEOMETRY_TEMPLATE = "ST_GeomFromText('%s')"; public static final String BIT_TEMPLATE = "b'%s'"; public static final String HEX_TEMPLATE = "0x%s"; public static String wrapGeometry(String value) { return String.format(GEOMETRY_TEMPLATE, value); } public static String wrapBit(String value) { return String.format(BIT_TEMPLATE, value); } public static String wrapHex(String value) { return String.format(HEX_TEMPLATE, value); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-mysql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.mysql.MysqlPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi ai.chat2db chat2db-mysql 2.0.0-SNAPSHOT chat2db-oceanbase src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseDBManage.java ================================================ package ai.chat2db.plugin.oceanbase; import ai.chat2db.plugin.mysql.MysqlDBManage; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.SQLExecutor; import java.sql.Connection; import java.sql.SQLException; public class OceanBaseDBManage extends MysqlDBManage implements DBManage { private static String PROCEDURE_SQL = "SELECT COUNT(*) FROM information_schema.routines " + "WHERE routine_type='PROCEDURE' AND routine_schema='%s' AND routine_name='%s';"; @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, schemaName.toUpperCase(), procedure.getProcedureName().toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrReplace) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> {}); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseMetaData.java ================================================ package ai.chat2db.plugin.oceanbase; import ai.chat2db.plugin.mysql.MysqlMetaData; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; public class OceanBaseMetaData extends MysqlMetaData implements MetaData { } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBasePlugin.java ================================================ package ai.chat2db.plugin.oceanbase; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class OceanBasePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"oceanbase.json", DBConfig.class); } @Override public MetaData getMetaData() { return new OceanBaseMetaData(); } @Override public DBManage getDBManage() { return new OceanBaseDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/OceanBaseSqlBuilder.java ================================================ //package ai.chat2db.plugin.oceanbase.builder; // //import ai.chat2db.plugin.oceanbase.type.OceanBaseColumnTypeEnum; //import ai.chat2db.plugin.oceanbase.type.OceanBaseIndexTypeEnum; //import ai.chat2db.spi.SqlBuilder; //import ai.chat2db.spi.jdbc.DefaultSqlBuilder; //import ai.chat2db.spi.model.*; //import org.apache.commons.lang3.StringUtils; // //public class OceanBaseSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { // // @Override // public String buildCreateTableSql(Table table) { // StringBuilder script = new StringBuilder(); // script.append("CREATE TABLE "); // if(StringUtils.isNotBlank(table.getDatabaseName())) { // script.append("`").append(table.getName()).append("`").append("."); // } // script.append("`").append(table.getName()).append("`").append(" (").append("\n"); // // // append column // for (TableColumn column : table.getColumnList()) { // if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ // continue; // } // OceanBaseColumnTypeEnum typeEnum = OceanBaseColumnTypeEnum.getByType(column.getColumnType()); // script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); // } // // // append primary key and index // for (TableIndex tableIndex : table.getIndexList()) { // if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ // continue; // } // OceanBaseIndexTypeEnum mysqlIndexTypeEnum = OceanBaseIndexTypeEnum.getByType(tableIndex.getType()); // script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); // } // // script = new StringBuilder(script.substring(0, script.length() - 2)); // script.append("\n)"); // // // if (StringUtils.isNotBlank(table.getEngine())) { // script.append(" ENGINE=").append(table.getEngine()); // } // // if (StringUtils.isNotBlank(table.getCharset())) { // script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); // } // // if (StringUtils.isNotBlank(table.getCollate())) { // script.append(" COLLATE=").append(table.getCollate()); // } // // if (table.getIncrementValue() != null) { // script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); // } // // if (StringUtils.isNotBlank(table.getComment())) { // script.append(" COMMENT='").append(table.getComment()).append("'"); // } // // if (StringUtils.isNotBlank(table.getPartition())) { // script.append(" \n").append(table.getPartition()); // } // script.append(";"); // // return script.toString(); // } // // @Override // public String buildModifyTaleSql(Table oldTable, Table newTable) { // StringBuilder script = new StringBuilder(); // script.append("ALTER TABLE "); // if(StringUtils.isNotBlank(oldTable.getDatabaseName())) { // script.append("`").append(oldTable.getDatabaseName()).append("`").append("."); // } // script.append("`").append(oldTable.getName()).append("`").append("\n"); // if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { // script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); // } // if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { // script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); // } // if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { // script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); // } // // // append modify column // for (TableColumn tableColumn : newTable.getColumnList()) { // if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ // OceanBaseColumnTypeEnum typeEnum = OceanBaseColumnTypeEnum.getByType(tableColumn.getColumnType()); // script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); // } // } // // // append modify index // for (TableIndex tableIndex : newTable.getIndexList()) { // if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { // OceanBaseIndexTypeEnum mysqlIndexTypeEnum = OceanBaseIndexTypeEnum.getByType(tableIndex.getType()); // script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); // } // } // if(script.length()>2) { // script = new StringBuilder(script.substring(0, script.length() - 2)); // script.append(";"); // } // // return script.toString(); // } // // // // @Override // public String pageLimit(String sql, int offset, int pageNo, int pageSize) { // StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); // sqlBuilder.append(sql); // if (offset == 0) { // sqlBuilder.append("\n LIMIT "); // sqlBuilder.append(pageSize); // } else { // sqlBuilder.append("\n LIMIT "); // sqlBuilder.append(offset); // sqlBuilder.append(","); // sqlBuilder.append(pageSize); // } // return sqlBuilder.toString(); // } // // // // // @Override // public String buildCreateDatabaseSql(Database database) { // StringBuilder sqlBuilder = new StringBuilder(); // sqlBuilder.append("CREATE DATABASE `"+database.getName()+"`"); // if (StringUtils.isNotBlank(database.getCharset())) { // sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); // } // if (StringUtils.isNotBlank(database.getCollation())) { // sqlBuilder.append(" COLLATE=").append(database.getCollation()); // } // return sqlBuilder.toString(); // } //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json ================================================ { "dbType": "OCEANBASE", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:oceanbase://localhost:2883/", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/oceanbase-client-2.4.2.jar" ], "jdbcDriver": "oceanbase-client-2.4.2.jar", "jdbcDriverClass": "com.oceanbase.jdbc.Driver" } ], "name": "OceanBase" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/type/OceanBaseColumnTypeEnum.java ================================================ //package ai.chat2db.plugin.oceanbase.type; // //import ai.chat2db.spi.ColumnBuilder; //import ai.chat2db.spi.enums.EditStatus; //import ai.chat2db.spi.model.ColumnType; //import ai.chat2db.spi.model.TableColumn; //import com.google.common.collect.Maps; //import org.apache.commons.lang3.StringUtils; // //import java.util.Arrays; //import java.util.List; //import java.util.Map; // //public enum OceanBaseColumnTypeEnum implements ColumnBuilder { // // BIT("BIT", true, false, true, false, false, false, true, true, false, false), // // TINYINT("TINYINT", false, false, true, true, false, false, true, true, false, false), // // TINYINT_UNSIGNED("TINYINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // SMALLINT("SMALLINT", false, false, true, true, false, false, true, true, false, false), // // SMALLINT_UNSIGNED("SMALLINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // MEDIUMINT("MEDIUMINT", false, false, true, true, false, false, true, true, false, false), // // MEDIUMINT_UNSIGNED("MEDIUMINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // INT("INT", false, false, true, true, false, false, true, true, false, false), // // // INT_UNSIGNED("INT UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // BIGINT("BIGINT", false, false, true, true, false, false, true, true, false, false), // // // BIGINT_UNSIGNED("BIGINT UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // // DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), // // DECIMAL_UNSIGNED("DECIMAL UNSIGNED", true, true, true, false, false, false, true, true, false, false), // // // FLOAT("FLOAT", true, true, true, false, false, false, true, true, false, false), // // FLOAT_UNSIGNED("FLOAT UNSIGNED", true, true, true, false, false, false, true, true, false, false), // // DOUBLE("DOUBLE", true, true, true, false, false, false, true, true, false, false), // // DOUBLE_UNSIGNED("DOUBLE UNSIGNED", true, true, true, false, false, false, true, true, false, false), // DATE("DATE", false, false, true, false, false, false, true, true, false, false), // DATETIME("DATETIME", true, false, true, false, false, false, true, true, true, false), // // TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true, false), // TIME("TIME", true, false, true, false, false, false, true, true, false, false), // YEAR("YEAR", false, false, true, false, false, false, true, true, false, false), // CHAR("CHAR", true, false, true, false, true, true, true, true, false, false), // // VARCHAR("VARCHAR", true, false, true, false, true, true, true, true, false, false), // // BINARY("BINARY", true, false, true, false, false, false, true, true, false, false), // // VARBINARY("VARBINARY", true, false, true, false, false, false, true, true, false, false), // // TINYBLOB("TINYBLOB", false, false, true, false, false, false, true, false, false, false), // // BLOB("BLOB", false, false, true, false, false, false, true, false, false, false), // // MEDIUMBLOB("MEDIUMBLOB", false, false, true, false, false, false, true, false, false, false), // // LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false, false), // // TINYTEXT("TINYTEXT", false, false, true, false, true, true, true, false, false, false), // // TEXT("TEXT", false, false, true, false, true, true, true, false, false, false), // // MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, true, true, true, false, false, false), // // LONGTEXT("LONGTEXT", false, false, true, false, true, true, true, false, false, false), // // // ENUM("ENUM", false, false, true, false, true, true, true, true, true, true), // // // BOOL("BOOL", false, false, true, true, false, false, true, true, false, false), // // INTEGER("INTEGER", false, false, true, true, false, false, true, true, false, false), // // INTEGER_UNSIGNED("INTEGER UNSIGNED", false, false, true, true, false, false, true, true, false, false), // // REAL("REAL", true, true, true, false, false, false, true, true, false, false), // // SET("SET", false, false, true, false, true, true, true, true, true, true), // // // GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false, false), // // POINT("POINT", false, false, true, false, false, false, true, false, false, false), // // LINESTRING("LINESTRING", false, false, true, false, false, false, true, false, false, false), // // POLYGON("POLYGON", false, false, true, false, false, false, true, false, false, false), // // MULTIPOINT("MULTIPOINT", false, false, true, false, false, false, true, false, false, false), // // MULTILINESTRING("MULTILINESTRING", false, false, true, false, false, false, true, false, false, false), // // MULTIPOLYGON("MULTIPOLYGON", false, false, true, false, false, false, true, false, false, false), // // GEOMETRYCOLLECTION("GEOMETRYCOLLECTION", false, false, true, false, false, false, true, false, false, false), // // JSON("JSON", false, false, true, false, false, false, true, false, false, false); // // private ColumnType columnType; // // public static OceanBaseColumnTypeEnum getByType(String dataType) { // return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); // } // // public ColumnType getColumnType() { // return columnType; // } // // // OceanBaseColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { // this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue,false); // } // // private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); // // static { // for (OceanBaseColumnTypeEnum value : OceanBaseColumnTypeEnum.values()) { // COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); // } // } // // // @Override // public String buildCreateColumnSql(TableColumn column) { // OceanBaseColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); // if (type == null) { // return ""; // } // StringBuilder script = new StringBuilder(); // // script.append("`").append(column.getName()).append("`").append(" "); // // script.append(buildDataType(column, type)).append(" "); // // script.append(buildCharset(column,type)).append(" "); // // script.append(buildCollation(column,type)).append(" "); // // script.append(buildNullable(column,type)).append(" "); // // script.append(buildDefaultValue(column,type)).append(" "); // // script.append(buildExt(column,type)).append(" "); // // script.append(buildAutoIncrement(column,type)).append(" "); // // script.append(buildComment(column,type)).append(" "); // // return script.toString(); // } // // private String buildCharset(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.getColumnType().isSupportCharset() || StringUtils.isEmpty(column.getCharSetName())){ // return ""; // } // return StringUtils.join("CHARACTER SET ",column.getCharSetName()); // } // // private String buildCollation(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())){ // return ""; // } // return StringUtils.join("COLLATE ",column.getCollationName()); // } // // @Override // public String buildModifyColumn(TableColumn tableColumn) { // // if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { // return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); // } // if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { // return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); // } // if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { // if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { // return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); // } else { // return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); // } // } // return ""; // } // // private String buildAutoIncrement(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.getColumnType().isSupportAutoIncrement()){ // return ""; // } // if (column.getAutoIncrement() != null && column.getAutoIncrement()) { // return "AUTO_INCREMENT"; // } // return ""; // } // // private String buildComment(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ // return ""; // } // return StringUtils.join("COMMENT '",column.getComment(),"'"); // } // // private String buildExt(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())){ // return ""; // } // return column.getComment(); // } // // private String buildDefaultValue(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ // return ""; // } // // if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ // return StringUtils.join("DEFAULT ''"); // } // // if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ // return StringUtils.join("DEFAULT NULL"); // } // // if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY, SET,ENUM).contains(type)){ // return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); // } // // if(Arrays.asList(DATE,TIME,YEAR).contains(type)){ // return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); // } // // if(Arrays.asList(DATETIME,TIMESTAMP).contains(type)){ // if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())){ // return StringUtils.join("DEFAULT ",column.getDefaultValue()); // } // return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); // } // // return StringUtils.join("DEFAULT ",column.getDefaultValue()); // } // // private String buildNullable(TableColumn column, OceanBaseColumnTypeEnum type) { // if(!type.getColumnType().isSupportNullable()){ // return ""; // } // if (column.getNullable()!=null && 1==column.getNullable()) { // return "NULL"; // } else { // return "NOT NULL"; // } // } // // private String buildDataType(TableColumn column, OceanBaseColumnTypeEnum type) { // String columnType = type.columnType.getTypeName(); // if (Arrays.asList(BINARY, VARBINARY, VARCHAR, CHAR).contains(type)) { // return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); // } // // if (BIT.equals(type)) { // return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); // } // // if (Arrays.asList(TIME, DATETIME, TIMESTAMP).contains(type)) { // if (column.getColumnSize() == null || column.getColumnSize() == 0) { // return columnType; // } else { // return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); // } // } // // // if (Arrays.asList(DECIMAL, FLOAT, DOUBLE).contains(type)) { // if (column.getColumnSize() == null || column.getDecimalDigits() == null) { // return columnType; // } // if (column.getColumnSize() != null && column.getDecimalDigits() == null) { // return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); // } // if (column.getColumnSize() != null && column.getDecimalDigits() != null) { // return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); // } // } // // if (Arrays.asList(DECIMAL_UNSIGNED, FLOAT_UNSIGNED, DECIMAL_UNSIGNED).contains(type)) { // if (column.getColumnSize() == null || column.getDecimalDigits() == null) { // return columnType; // } // if (column.getColumnSize() != null && column.getDecimalDigits() == null) { // return unsignedDataType(columnType, "(" + column.getColumnSize() + ")"); // } // if (column.getColumnSize() != null && column.getDecimalDigits() != null) { // return unsignedDataType(columnType, "(" + column.getColumnSize() + "," + column.getDecimalDigits() + ")"); // } // } // // if(Arrays.asList(SET,ENUM).contains(type)){ // if(!StringUtils.isEmpty( column.getValue())){ // return StringUtils.join(columnType,"(",column.getValue(),")"); // } // //List enumList = column. // } // // return columnType; // } // // private String unsignedDataType(String dataTypeName, String middle) { // String[] split = dataTypeName.split(" "); // if (split.length == 2) { // return StringUtils.join(split[0], middle, split[1]); // } // return StringUtils.join(dataTypeName, middle); // } // // public static List getTypes(){ // return Arrays.stream(OceanBaseColumnTypeEnum.values()).map(columnTypeEnum -> // columnTypeEnum.getColumnType() // ).toList(); // } // // //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/type/OceanBaseIndexTypeEnum.java ================================================ //package ai.chat2db.plugin.oceanbase.type; // //import ai.chat2db.spi.enums.EditStatus; //import ai.chat2db.spi.model.IndexType; //import ai.chat2db.spi.model.TableIndex; //import ai.chat2db.spi.model.TableIndexColumn; //import org.apache.commons.lang3.StringUtils; // //import java.util.Arrays; //import java.util.List; // //public enum OceanBaseIndexTypeEnum { // // PRIMARY_KEY("Primary", "PRIMARY KEY"), // // NORMAL("Normal", "INDEX"), // // UNIQUE("Unique", "UNIQUE INDEX"), // // FULLTEXT("Fulltext", "FULLTEXT INDEX"), // // SPATIAL("Spatial", "SPATIAL INDEX"); // // public String getName() { // return name; // } // // private String name; // // // public String getKeyword() { // return keyword; // } // // private String keyword; // // public IndexType getIndexType() { // return indexType; // } // // public void setIndexType(IndexType indexType) { // this.indexType = indexType; // } // // private IndexType indexType; // // OceanBaseIndexTypeEnum(String name, String keyword) { // this.name = name; // this.keyword = keyword; // this.indexType = new IndexType(name); // } // // // public static OceanBaseIndexTypeEnum getByType(String type) { // for (OceanBaseIndexTypeEnum value : OceanBaseIndexTypeEnum.values()) { // if (value.name.equalsIgnoreCase(type)) { // return value; // } // } // return null; // } // // public String buildIndexScript(TableIndex tableIndex) { // StringBuilder script = new StringBuilder(); // // script.append(keyword).append(" "); // // script.append(buildIndexName(tableIndex)).append(" "); // // script.append(buildIndexColumn(tableIndex)).append(" "); // // script.append(buildIndexComment(tableIndex)).append(" "); // // return script.toString(); // } // // private String buildIndexComment(TableIndex tableIndex) { // if(StringUtils.isBlank(tableIndex.getComment())){ // return ""; // }else { // return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); // } // // } // // private String buildIndexColumn(TableIndex tableIndex) { // StringBuilder script = new StringBuilder(); // script.append("("); // for (TableIndexColumn column : tableIndex.getColumnList()) { // if(StringUtils.isNotBlank(column.getColumnName())) { // script.append("`").append(column.getColumnName()).append("`"); // if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { // script.append(" ").append(column.getAscOrDesc()); // } // script.append(","); // } // } // script.deleteCharAt(script.length() - 1); // script.append(")"); // return script.toString(); // } // // private String buildIndexName(TableIndex tableIndex) { // if(this.equals(PRIMARY_KEY)){ // return ""; // }else { // return "`"+tableIndex.getName()+"`"; // } // } // // public String buildModifyIndex(TableIndex tableIndex) { // if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { // return buildDropIndex(tableIndex); // } // if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { // return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); // } // if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { // return StringUtils.join("ADD ", buildIndexScript(tableIndex)); // } // return ""; // } // // private String buildDropIndex(TableIndex tableIndex) { // if (OceanBaseIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { // return StringUtils.join("DROP PRIMARY KEY"); // } // return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); // } // public static List getIndexTypes() { // return Arrays.asList(OceanBaseIndexTypeEnum.values()).stream().map(OceanBaseIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); // } //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.oceanbase.OceanBasePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml chat2db-oracle ai.chat2db chat2db-spi src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java ================================================ package ai.chat2db.plugin.oracle; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SqlUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @Slf4j public class OracleDBManage extends DefaultDBManage implements DBManage { private static String PROCEDURE_SQL = "SELECT COUNT(*) FROM ALL_OBJECTS " + "WHERE OWNER = '%s' AND OBJECT_NAME = '%s' AND OBJECT_TYPE = 'PROCEDURE'"; public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTables(connection, databaseName, schemaName, asyncContext); exportViews(connection, asyncContext, schemaName); exportProcedures(connection, schemaName, asyncContext); exportFunctions(connection, schemaName, asyncContext); // exportTriggers(connection, schemaName, asyncContext); } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(null, schemaName, null, new String[]{"TABLE", "SYSTEM TABLE"})) { while (resultSet.next()) { String tableName = resultSet.getString("TABLE_NAME"); exportTable(connection, databaseName, schemaName, tableName, asyncContext); } } } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { String tableDDL = Chat2DBContext.getMetaData().tableDDL(connection, databaseName, schemaName, tableName); String sqlBuilder = "DROP TABLE " + SqlUtils.quoteObjectName(tableName) + ";" + tableDDL + "\n"; asyncContext.write(sqlBuilder); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } } private void exportViews(Connection connection, AsyncContext asyncContext, String schemaName) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(null, schemaName, null, new String[]{"VIEW"})) { while (resultSet.next()) { String viewName = resultSet.getString("TABLE_NAME"); exportView(connection, asyncContext, schemaName, viewName); } } } private void exportView(Connection connection, AsyncContext asyncContext, String schemaName, String viewName) { Table view = Chat2DBContext.getMetaData().view(connection, null, schemaName, viewName); asyncContext.write(view.getDdl() + ";" + "\n"); } private void exportProcedures(Connection connection, String schemaName, AsyncContext asyncContext) { List procedures = Chat2DBContext.getMetaData().procedures(connection, null, schemaName); if (CollectionUtils.isNotEmpty(procedures)) { for (Procedure procedure : procedures) { String procedureName = procedure.getProcedureName(); exportProcedure(connection, schemaName, procedureName, asyncContext); } } } private void exportProcedure(Connection connection, String schemaName, String procedureName, AsyncContext asyncContext) { Procedure procedure = Chat2DBContext.getMetaData().procedure(connection, null, schemaName, procedureName); asyncContext.write(procedure.getProcedureBody() + "\n"); } private void exportTriggers(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT TRIGGER_NAME FROM all_triggers where OWNER='%s'", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String triggerName = resultSet.getString("TRIGGER_NAME"); exportTrigger(connection, schemaName, triggerName, asyncContext); } } } private void exportTrigger(Connection connection, String schemaName, String triggerName, AsyncContext asyncContext) { Trigger trigger = Chat2DBContext.getMetaData().trigger(connection, null, schemaName, triggerName); asyncContext.write(trigger.getTriggerBody() + ";" + "\n"); } private void exportFunctions(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getFunctions(null, schemaName, null)) { while (resultSet.next()) { String functionName = resultSet.getString("FUNCTION_NAME"); exportFunction(connection, schemaName, functionName, asyncContext); } } } private void exportFunction(Connection connection, String schemaName, String functionName, AsyncContext asyncContext) { Function function = Chat2DBContext.getMetaData().function(connection, null, schemaName, functionName); asyncContext.write(function.getFunctionBody() + "\n"); } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, schemaName.toUpperCase(), procedure.getProcedureName().toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrReplace) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> {}); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { return; } String schemaName = connectInfo.getSchemaName(); try { SQLExecutor.getInstance().execute(connection, "ALTER SESSION SET CURRENT_SCHEMA = \"" + schemaName + "\""); } catch (SQLException e) { log.error("connectDatabase error", e); } } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData) throws SQLException { String sql = ""; if (copyData) { sql = "CREATE TABLE " + SqlUtils.quoteObjectName(newTableName) + " AS SELECT * FROM " + SqlUtils.quoteObjectName(tableName); } else { sql = "CREATE TABLE " + SqlUtils.quoteObjectName(newTableName) + " AS SELECT * FROM " + SqlUtils.quoteObjectName(tableName) + " WHERE 1=0"; } SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + SqlUtils.quoteObjectName(tableName); SQLExecutor.getInstance().execute(connection, sql, (resultSet) -> null); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java ================================================ package ai.chat2db.plugin.oracle; import ai.chat2db.plugin.oracle.builder.OracleSqlBuilder; import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.type.OracleDefaultValueEnum; import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; import ai.chat2db.plugin.oracle.value.OracleValueProcessor; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.io.Reader; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; @Slf4j public class OracleMetaData extends DefaultMetaService implements MetaData { private static final String TABLE_DDL_SQL = "select dbms_metadata.get_ddl('TABLE','%s','%s') as sql from dual"; private static final String TABLE_COMMENT_SQL = "select owner, table_name, comments from ALL_TAB_COMMENTS where OWNER = '%s' and TABLE_NAME = '%s'"; private static final String TABLE_COLUMN_COMMENT_SQL = """ SELECT owner, table_name, column_name, comments FROM all_col_comments WHERE owner = '%s' and table_name = '%s' and comments is not null"""; private List systemSchemas = Arrays.asList("ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "BI", "CTXSYS", "DBSNMP", "DIP", "EXFSYS", "FLOWS_FILES", "HR", "IX", "MDDATA", "MDSYS", "MGMT_VIEW", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "OWBSYS_AUDIT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "WMSYS", "XDB", "XS$NULL"); private static final String PROCEDURE_LIST_DDL = """ SELECT OBJECT_NAME, OBJECT_TYPE FROM ALL_OBJECTS WHERE OBJECT_TYPE IN ('PROCEDURE') AND OWNER = '%s'"""; private static final String TABLE_INDEX_DDL_SQL = """ SELECT DBMS_METADATA.GET_DDL('INDEX', index_name, table_owner) AS ddl, index_name AS INDEX_NAME FROM all_indexes WHERE table_owner = '%s' AND table_name = '%s'"""; private static final String PU_INDEX_NAME_SQL = """ SELECT DISTINCT AC.INDEX_NAME FROM ALL_CONSTRAINTS AC WHERE AC.OWNER = '%s' AND AC.TABLE_NAME = '%s' AND AC.CONSTRAINT_TYPE IN ('P', 'U')"""; @Override public List procedures(Connection connection, String databaseName, String schemaName) { String sql = String.format(PROCEDURE_LIST_DDL, schemaName); ArrayList procedures = new ArrayList<>(); SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Procedure procedure = new Procedure(); procedure.setProcedureName(resultSet.getString("object_name")); procedures.add(procedure); } }); return procedures; } @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { // TODO: only_read user can not get ddl String sql = String.format(TABLE_DDL_SQL, tableName, schemaName); String tableCommentSql = String.format(TABLE_COMMENT_SQL, schemaName, tableName); String tableColumnCommentSql = String.format(TABLE_COLUMN_COMMENT_SQL, schemaName, tableName); String tableIndexSql = String.format(TABLE_INDEX_DDL_SQL, schemaName, tableName); String PUIndexSql = String.format(PU_INDEX_NAME_SQL, schemaName, tableName); StringBuilder ddlBuilder = new StringBuilder(); SQLExecutor.getInstance().execute(connection, sql, resultSet -> { try { if (resultSet.next()) { ddlBuilder.append(resultSet.getString("sql")).append(";"); } } catch (SQLException e) { throw new RuntimeException(e); } }); SQLExecutor.getInstance().execute(connection, tableCommentSql, resultSet -> { if (resultSet.next()) { String tableComment = resultSet.getString("comments"); if (StringUtils.isNotBlank(tableComment)) { ddlBuilder.append("\nCOMMENT ON TABLE ").append(SqlUtils.quoteObjectName(tableName)).append(" IS ") .append(EasyStringUtils.escapeAndQuoteString(tableComment)).append(";"); } } }); SQLExecutor.getInstance().execute(connection, tableColumnCommentSql, resultSet -> { while (resultSet.next()) { String columnName = resultSet.getString("column_name"); String columnComment = resultSet.getString("comments"); if (StringUtils.isNotBlank(columnComment)) { ddlBuilder.append("\nCOMMENT ON COLUMN ") .append(SqlUtils.quoteObjectName(tableName)).append(".") .append(SqlUtils.quoteObjectName(columnName)).append(" IS ") .append(EasyStringUtils.escapeAndQuoteString(columnComment)).append(";"); } } }); List indexNames = SQLExecutor.getInstance().execute(connection, PUIndexSql, resultSet -> { List PUIndexNames = new ArrayList<>(); while (resultSet.next()) { String indexName = resultSet.getString("index_name"); if (StringUtils.isNotBlank(indexName)) { PUIndexNames.add(indexName); } } return PUIndexNames; }); SQLExecutor.getInstance().execute(connection, tableIndexSql, resultSet -> { while (resultSet.next()) { String indexName = resultSet.getString("INDEX_NAME"); if (CollectionUtils.isNotEmpty(indexNames) && indexNames.contains(indexName)) { continue; } String ddl = resultSet.getString("ddl"); if (StringUtils.isNotBlank(ddl)) { ddlBuilder.append("\n").append(ddl).append(";"); } } }); return ddlBuilder.toString(); } private static String SELECT_TABLE_SQL = "SELECT A.OWNER, A.TABLE_NAME, B.COMMENTS " + "FROM ALL_TABLES A LEFT JOIN ALL_TAB_COMMENTS B ON A.OWNER = B.OWNER AND A.TABLE_NAME = B.TABLE_NAME\n" + "where A.OWNER = '%s' "; @Override public List
tables(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TABLE_SQL, schemaName); if (StringUtils.isNotBlank(tableName)) { sql = sql + " and A.TABLE_NAME = '" + tableName + "'"; } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { List
tables = new ArrayList<>(); while (resultSet.next()) { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(resultSet.getString("TABLE_NAME")); table.setComment(resultSet.getString("COMMENTS")); tables.add(table); } return tables; }); } private static String SELECT_TAB_COLS = "SELECT atc.column_id , atc.column_name as COLUMN_NAME, atc.data_type as DATA_TYPE , atc.data_length as DATA_LENGTH , atc.data_type_mod , atc.nullable , atc.data_default as DATA_DEFAULT, acc.comments , atc.DATA_PRECISION , atc.DATA_SCALE , atc.CHAR_USED FROM all_tab_columns atc, all_col_comments acc WHERE atc.owner = acc.owner AND atc.table_name = acc.table_name AND atc.column_name = acc.column_name AND atc.owner = '%s' AND atc.table_name = '%s' order by atc.column_id"; @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { List tableColumns = super.columns(connection, databaseName, schemaName, tableName); if (CollectionUtils.isNotEmpty(tableColumns)) { Map tableColumnMap = getTableColumns(connection, databaseName, schemaName, tableName); for (TableColumn tableColumn : tableColumns) { tableColumn.setColumnType(SqlUtils.removeDigits(tableColumn.getColumnType())); TableColumn column = tableColumnMap.get(tableColumn.getName()); if (column != null) { tableColumn.setUnit(column.getUnit()); tableColumn.setComment(column.getComment()); tableColumn.setDefaultValue(column.getDefaultValue()); tableColumn.setOrdinalPosition(column.getOrdinalPosition()); tableColumn.setNullable(column.getNullable()); } } } return tableColumns; } private Map getTableColumns(Connection connection, String databaseName, String schemaName, String tableName) { Map tableColumns = new HashMap<>(); String sql = String.format(SELECT_TAB_COLS, schemaName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn tableColumn = new TableColumn(); tableColumn.setTableName(tableName); tableColumn.setSchemaName(schemaName); try { // // Fields of the LONG type cannot be retrieved using getObject. They need to be accessed using getCharacterStream, and must be read first in the sequence. Reader reader = resultSet.getCharacterStream("DATA_DEFAULT"); if (reader != null) { StringBuilder sb = new StringBuilder(); int charValue; while ((charValue = reader.read()) != -1) { sb.append((char) charValue); } tableColumn.setDefaultValue(sb.toString()); } } catch (Exception e) { log.error("getDefaultValue error", e); } tableColumn.setName(resultSet.getString("COLUMN_NAME")); String dataType = resultSet.getString("DATA_TYPE"); if (dataType.contains("(")) { dataType = dataType.substring(0, dataType.indexOf("(")).trim(); } tableColumn.setColumnType(dataType); Integer dataPrecision = resultSet.getInt("DATA_PRECISION"); if (resultSet.getString("DATA_PRECISION") != null) { tableColumn.setColumnSize(dataPrecision); } else { tableColumn.setColumnSize(resultSet.getInt("DATA_LENGTH")); } // Object dataDefault = resultSet.getObject(7); // if(dataDefault!=null) { // tableColumn.setDefaultValue(dataDefault.toString()); // } tableColumn.setComment(resultSet.getString("COMMENTS")); tableColumn.setNullable("Y".equalsIgnoreCase(resultSet.getString("NULLABLE")) ? 1 : 0); tableColumn.setOrdinalPosition(resultSet.getInt("COLUMN_ID")); tableColumn.setDecimalDigits(resultSet.getInt("DATA_SCALE")); String charUsed = resultSet.getString("CHAR_USED"); if ("B".equalsIgnoreCase(charUsed)) { tableColumn.setUnit("BYTE"); } else if ("C".equalsIgnoreCase(charUsed)) { tableColumn.setUnit("CHAR"); } tableColumns.put(tableColumn.getName(), tableColumn); } return tableColumns; }); } private static String ROUTINES_SQL = "SELECT LINE, TEXT " + "FROM ALL_SOURCE " + "WHERE TYPE = '%s' AND OWNER = '%s' AND NAME = '%s'" + "ORDER BY LINE"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "FUNCTION", schemaName, functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); StringBuilder bodyBuilder = new StringBuilder("CREATE OR REPLACE "); while (resultSet.next()) { bodyBuilder.append(resultSet.getString("TEXT")).append("\n"); } String functionBody = bodyBuilder.toString().trim(); if (!functionBody.endsWith("/")) { functionBody += "\n/"; } function.setFunctionBody(functionBody); return function; }); } private static String TRIGGER_SQL_LIST = "SELECT TRIGGER_NAME " + "FROM ALL_TRIGGERS WHERE OWNER = '%s'"; private static String SELECT_PK_SQL = "select acc.CONSTRAINT_NAME from all_cons_columns acc, all_constraints ac where acc.constraint_name = ac.constraint_name and acc.owner = ac.owner and acc.owner = '%s' and ac.constraint_type = 'P' and ac.table_name = '%s' "; private static String SELECT_TABLE_INDEX = "SELECT ai.index_name AS Key_name, aic.column_name AS Column_name, ai.index_type AS Index_type, ai.uniqueness AS Unique_name, aic.COLUMN_POSITION as Seq_in_index, aic.descend AS Collation, ex.COLUMN_EXPRESSION as COLUMN_EXPRESSION FROM all_ind_columns aic JOIN all_indexes ai ON aic.table_owner = ai.table_owner and aic.table_name = ai.table_name and aic.index_name = ai.index_name LEFT JOIN ALL_IND_EXPRESSIONS ex ON aic.table_owner = ex.table_owner and aic.table_name = ex.table_name and aic.index_name = ex.index_name where ai.table_owner = '%s' AND ai.table_name = '%s' "; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String pkSql = String.format(SELECT_PK_SQL, schemaName, tableName); Set pkSet = new HashSet<>(); SQLExecutor.getInstance().execute(connection, pkSql, resultSet -> { while (resultSet.next()) { pkSet.add(resultSet.getString("CONSTRAINT_NAME")); } return null; } ); String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique("unique".equalsIgnoreCase(resultSet.getString("Unique_name"))); index.setType(resultSet.getString("Index_type")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); if (index.getUnique()) { index.setType(OracleIndexTypeEnum.UNIQUE.getName()); } else if ("NORMAL".equalsIgnoreCase(index.getType())) { index.setType(OracleIndexTypeEnum.NORMAL.getName()); } else if ("BITMAP".equalsIgnoreCase(index.getType())) { index.setType(OracleIndexTypeEnum.BITMAP.getName()); } else if (StringUtils.isNotBlank(index.getType()) && index.getType().toUpperCase().contains("NORMAL")) { index.setType(OracleIndexTypeEnum.NORMAL.getName()); } if (pkSet.contains(keyName)) { index.setType(OracleIndexTypeEnum.PRIMARY_KEY.getName()); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("Column_name")); String expression = resultSet.getString("COLUMN_EXPRESSION"); if (!StringUtils.isBlank(expression)) { tableIndexColumn.setColumnName(expression.replace("\"", "")); } tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("Collation")); tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); return tableIndexColumn; } @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, String.format(TRIGGER_SQL_LIST, schemaName), resultSet -> { while (resultSet.next()) { String triggerName = resultSet.getString("TRIGGER_NAME"); Trigger trigger = new Trigger(); trigger.setTriggerName(triggerName == null ? "" : triggerName.trim()); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } private static final String TRIGGER_DDL_SQL = "SELECT DBMS_METADATA.GET_DDL('TRIGGER', '%s', '%s') as ddl FROM DUAL"; @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_DDL_SQL, triggerName, schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); while (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("ddl")); } return trigger; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "PROCEDURE", schemaName, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); StringBuilder bodyBuilder = new StringBuilder("CREATE OR REPLACE "); while (resultSet.next()) { bodyBuilder.append(resultSet.getString("TEXT")).append("\n"); } String procedureBody = bodyBuilder.toString().trim(); // 去掉最后的空白字符 if (!procedureBody.endsWith("/")) { procedureBody += "\n/"; } procedure.setProcedureBody(procedureBody); return procedure; }); } private static String VIEW_DDL_SQL = "SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s'"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_DDL_SQL, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl("CREATE OR REPLACE VIEW " + viewName + " AS " + resultSet.getString("TEXT")); } return table; }); } @Override public SqlBuilder getSqlBuilder() { return new OracleSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(OracleColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(OracleIndexTypeEnum.getIndexTypes()) .defaultValues(OracleDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemSchemas() { return systemSchemas; } /** * @return */ @Override public ValueProcessor getValueProcessor() { return new OracleValueProcessor(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OraclePlugin.java ================================================ package ai.chat2db.plugin.oracle; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class OraclePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"oracle.json", DBConfig.class); } @Override public MetaData getMetaData() { return new OracleMetaData(); } @Override public DBManage getDBManage() { return new OracleDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java ================================================ package ai.chat2db.plugin.oracle.builder; import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.util.SqlUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.stream.Collectors; public class OracleSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n);"); for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } OracleIndexTypeEnum oracleColumnTypeEnum = OracleIndexTypeEnum.getByType(tableIndex.getType()); if(oracleColumnTypeEnum == null){ continue; } script.append("\n").append("").append(oracleColumnTypeEnum.buildIndexScript(tableIndex)).append(";"); } for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildComment(column)).append(";"); } if (StringUtils.isNotBlank(table.getComment())) { script.append("\n").append(buildTableComment(table)).append(";"); } return script.toString(); } private String buildTableComment(Table table) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" IS '").append(table.getComment()).append("'"); return script.toString(); } private String buildComment(TableColumn column) { StringBuilder script = new StringBuilder(); script.append("COMMENT ON COLUMN ").append("\"").append(column.getSchemaName()).append("\".\"").append(column.getTableName()).append("\".\"").append(column.getName()).append("\" IS '").append(column.getComment()).append("'"); return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { script.append("").append(buildTableComment(newTable)).append(";\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { OracleIndexTypeEnum oracleIndexTypeEnum = OracleIndexTypeEnum.getByType(tableIndex.getType()); if(oracleIndexTypeEnum == null){ continue; } script.append("\t").append(oracleIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { int startRow = offset; int endRow = offset + pageSize; StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); if (startRow > 0) { sqlBuilder.append("SELECT * FROM ( "); } if (endRow > 0) { sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM CAHT2DB_AUTO_ROW_ID FROM ( "); } sqlBuilder.append("\n"); sqlBuilder.append(sql); sqlBuilder.append("\n"); if (endRow > 0) { sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= "); sqlBuilder.append(endRow); } if (startRow > 0) { sqlBuilder.append(" ) WHERE CAHT2DB_AUTO_ROW_ID > "); sqlBuilder.append(startRow); } return sqlBuilder.toString(); } // @Override // public String buildCreateSchemaSql(Schema schema){ // StringBuilder sqlBuilder = new StringBuilder(); // sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); // if(StringUtils.isNotBlank(schema.getOwner())){ // sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); // } // if(StringUtils.isNotBlank(schema.getComment())){ // sqlBuilder.append("; COMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); // } // return sqlBuilder.toString(); // } @Override protected void buildTableName(String databaseName, String schemaName, String tableName, StringBuilder script) { if (StringUtils.isNotBlank(databaseName)) { script.append(SqlUtils.quoteObjectName(databaseName)).append('.'); } script.append(SqlUtils.quoteObjectName(tableName)); } /** * @param columnList * @param script */ @Override protected void buildColumns(List columnList, StringBuilder script) { if (CollectionUtils.isNotEmpty(columnList)) { script.append(" (") .append(columnList.stream().map(SqlUtils::quoteObjectName).collect(Collectors.joining(","))) .append(") "); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json ================================================ { "dbType": "ORACLE", "supportDatabase": false, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:oracle:thin:@localhost:1521:XE", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/ojdbc11-21.5.0.0.jar", "https://cdn.chat2db-ai.com/lib/orai18n-21.5.0.0.jar", "https://cdn.chat2db-ai.com/lib/xmlparserv2-21.5.0.0.jar", "https://cdn.chat2db-ai.com/lib/xdb-21.5.0.0.jar" ], "jdbcDriver": "ojdbc11-21.5.0.0.jar,orai18n-21.5.0.0.jar,xmlparserv2-21.5.0.0.jar,xdb-21.5.0.0.jar", "jdbcDriverClass": "oracle.jdbc.driver.OracleDriver" } ], "name": "Oracle" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java ================================================ package ai.chat2db.plugin.oracle.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum OracleColumnTypeEnum implements ColumnBuilder { //JSON("JSON", false, false, true, false, false, false, true, false, false, false) BFILE("BFILE", false, false, true, false, false, false, true, true, false, false), BINARY_DOUBLE("BINARY_DOUBLE", false, false, true, false, false, false, true, true, false, false), BINARY_FLOAT("BINARY_FLOAT", false, false, true, false, false, false, true, true, false, false), BLOB("BLOB", false, false, true, false, false, false, true, true, false, false), CHAR("CHAR", true, false, true, false, false, false, true, true, false, true), CHAR_VARYING("CHAR VARYING", true, false, true, false, false, false, true, true, false, true), CHARACTER("CHARACTER", true, false, true, false, false, false, true, true, false, true), CHARACTER_VARYING("CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), CLOB("CLOB", false, false, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), DOUBLE_PRECISION("DOUBLE PRECISION", false, false, true, false, false, false, true, true, false, false), FLOAT("FLOAT", true, false, true, false, false, false, true, true, false, false), INT("INT", false, false, true, false, false, false, true, true, false, false), INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false), LONG("LONG", false, false, true, false, false, false, true, true, false, false), LONG_RAW("LONG RAW", false, false, true, false, false, false, true, true, false, false), LONG_VARCHAR("LONG VARCHAR", false, false, true, false, false, false, true, true, false, false), NATIONAL_CHAR("NATIONAL CHAR", true, false, true, false, false, false, true, true, false, true), NATIONAL_CHAR_VARYING("NATIONAL CHAR VARYING", true, false, true, false, false, false, true, true, false, true), NATIONAL_CHARACTER("NATIONAL CHARACTER", true, false, true, false, false, false, true, true, false, true), NATIONAL_CHARACTER_VARYING("NATIONAL CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), NCHAR("NCHAR", true, false, true, false, false, false, true, true, false, false), NCHAR_VARYING("NCHAR VARYING", true, false, true, false, false, false, true, true, false, false), NCLOB("NCLOB", false, false, true, false, false, false, true, true, false, false), NUMBER("NUMBER", true, true, true, false, false, false, true, true, false, false), NVARCHAR2("NVARCHAR2", true, false, true, false, false, false, true, true, false, true), RAW("RAW", true, false, true, false, false, false, true, true, false, false), REAL("REAL", false, false, true, false, false, false, true, true, false, false), ROWID("ROWID", false, false, true, false, false, false, true, true, false, false), SMALLINT("SMALLINT", false, false, true, false, false, false, true, true, false, false), TIMESTAMP("TIMESTAMP", false, true, true, false, false, false, true, true, false, false), TIMESTAMP_WITH_LOCAL_TIME_ZONE("TIMESTAMP WITH LOCAL TIME ZONE", false, true, true, false, false, false, true, true, false, false), TIMESTAMP_WITH_TIME_ZONE("TIMESTAMP WITH TIME ZONE", false, true, true, false, false, false, true, true, false, false), INTERVAL_YEAR_TO_MONTH("INTERVAL YEAR TO MONTH", true, false, true, false, false, false, true, true, false, false), INTERVAL_DAY_TO_SECOND("INTERVAL DAY TO SECOND", true, true, true, false, false, false, true, true, false, false), UROWID("UROWID", true, false, true, false, false, false, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, false, false, true, true, false, true), VARCHAR2("VARCHAR2", true, false, true, false, false, false, true, true, false, true), XMLTYPE("XMLTYPE", false, false, true, false, false, false, true, true, false, false), ; private ColumnType columnType; public static OracleColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (OracleColumnTypeEnum value : OracleColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } public ColumnType getColumnType() { return columnType; } OracleColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportUnit) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, false, supportUnit); } @Override public String buildCreateColumnSql(TableColumn column) { OracleColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); return script.toString(); } private String buildNullable(TableColumn column, OracleColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDefaultValue(TableColumn column, OracleColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildDataType(TableColumn column, OracleColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(CHAR, CHAR_VARYING, CHARACTER, CHARACTER_VARYING, NVARCHAR2, VARCHAR, VARCHAR2, NATIONAL_CHAR, NATIONAL_CHAR_VARYING, NATIONAL_CHARACTER, NATIONAL_CHARACTER_VARYING, NCHAR, NCHAR_VARYING).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) { script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")"); } return script.toString(); } if (Arrays.asList(DECIMAL, FLOAT, NUMBER, UROWID, RAW).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && column.getDecimalDigits() == null) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); } return script.toString(); } if (Arrays.asList(TIMESTAMP).contains(type)) { int decimalDigits = column.getDecimalDigits() != null ? column.getDecimalDigits() : 6; String valueTemplate = "TIMESTAMP(%s)"; return String.format(valueTemplate, decimalDigits); } if (Arrays.asList(TIMESTAMP_WITH_TIME_ZONE).contains(type)) { int decimalDigits = column.getDecimalDigits() != null ? column.getDecimalDigits() : 6; String valueTemplate = "TIMESTAMP(%s) WITH TIME ZONE"; return String.format(valueTemplate, decimalDigits); } if (Arrays.asList(TIMESTAMP_WITH_LOCAL_TIME_ZONE).contains(type)) { int decimalDigits = column.getDecimalDigits() != null ? column.getDecimalDigits() : 6; String valueTemplate = "TIMESTAMP(%s) WITH LOCAL TIME ZONE"; return String.format(valueTemplate, decimalDigits); } if (Arrays.asList(INTERVAL_DAY_TO_SECOND).contains(type)) { int columnSize = column.getColumnSize() != null ? column.getColumnSize() : 2; int decimalDigits = column.getDecimalDigits() != null ? column.getDecimalDigits() : 6; String valueTemplate = "INTERVAL DAY(%s) TO SECOND(%s)"; return String.format(valueTemplate, columnSize, decimalDigits); } if (Arrays.asList(INTERVAL_YEAR_TO_MONTH).contains(type)) { int columnSize = column.getColumnSize() != null ? column.getColumnSize() : 2; String valueTemplate = "INTERVAL YEAR(%s) TO MONTH"; return String.format(valueTemplate, columnSize); } return columnType; } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\""); return script.toString(); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")"); return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("MODIFY (").append(buildModifyColumnSql(tableColumn, tableColumn.getOldColumn())).append(") \n"); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { script.append(";"); script.append("ALTER TABLE ").append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\""); } return script.toString(); } return ""; } public String buildModifyColumnSql(TableColumn column, TableColumn oldColumn) { OracleColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); if (oldColumn.getNullable() != column.getNullable()) { script.append(buildNullable(column, type)).append(" "); } return script.toString(); } public static List getTypes() { return Arrays.stream(OracleColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleDefaultValueEnum.java ================================================ package ai.chat2db.plugin.oracle.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum OracleDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; OracleDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(OracleDefaultValueEnum.values()).map(OracleDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java ================================================ package ai.chat2db.plugin.oracle.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum OracleIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"), BITMAP("BITMAP", "BITMAP INDEX"); public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; OracleIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static OracleIndexTypeEnum getByType(String type) { for (OracleIndexTypeEnum value : OracleIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (PRIMARY_KEY.equals(this)) { script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex)); } else { if (UNIQUE.equals(this)) { script.append("CREATE UNIQUE INDEX "); } else { script.append("CREATE INDEX "); } script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); } return script.toString(); } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\""); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { return "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), ";\n", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (OracleIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { String tableName = "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getTableName() + "\""; return StringUtils.join("ALTER TABLE ",tableName," DROP PRIMARY KEY"); } StringBuilder script = new StringBuilder(); script.append("DROP INDEX "); script.append(buildIndexName(tableIndex)); return script.toString(); } public static List getIndexTypes() { return Arrays.asList(OracleIndexTypeEnum.values()).stream().map(OracleIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/OracleValueProcessor.java ================================================ package ai.chat2db.plugin.oracle.value; import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.value.factory.OracleValueProcessorFactory; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; /** * @author: zgq * @date: 2024年06月03日 22:30 */ public class OracleValueProcessor extends DefaultValueProcessor { private static final Logger log = LoggerFactory.getLogger(OracleValueProcessor.class); @Override public String getJdbcValue(JDBCDataValue dataValue) { if (OracleColumnTypeEnum.LONG_RAW.getColumnType().getTypeName().equalsIgnoreCase(dataValue.getType())) { return convertJDBCValueByType(dataValue); } Object value = dataValue.getObject(); if (Objects.isNull(value)) { return null; } if (value instanceof String emptyStr) { if (StringUtils.isBlank(emptyStr)) { return emptyStr; } } return convertJDBCValueByType(dataValue); } @Override public String getJdbcSqlValueString(JDBCDataValue dataValue) { if (OracleColumnTypeEnum.LONG_RAW.getColumnType().getTypeName().equalsIgnoreCase(dataValue.getType())) { return convertJDBCValueStrByType(dataValue); } Object value = dataValue.getObject(); if (Objects.isNull(value)) { return "NULL"; } if (value instanceof String stringValue) { if (StringUtils.isBlank(stringValue)) { return EasyStringUtils.quoteString(stringValue); } } return convertJDBCValueStrByType(dataValue); } @Override public String convertSQLValueByType(SQLDataValue dataValue) { try { DefaultValueProcessor valueProcessor = OracleValueProcessorFactory.getValueProcessor(dataValue.getDateTypeName()); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertSQLValueByType(dataValue); } } catch (Exception e) { log.warn("convertSQLValueByType error", e); return super.convertSQLValueByType(dataValue); } return super.convertSQLValueByType(dataValue); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { String type = dataValue.getType(); try { DefaultValueProcessor valueProcessor = OracleValueProcessorFactory.getValueProcessor(type); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertJDBCValueByType(dataValue); } } catch (Exception e) { log.warn("convertJDBCValueByType error", e); return super.convertJDBCValueByType(dataValue); } return super.convertJDBCValueByType(dataValue); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { String type = dataValue.getType(); try { DefaultValueProcessor valueProcessor = OracleValueProcessorFactory.getValueProcessor(type); if (Objects.nonNull(valueProcessor)) { return valueProcessor.convertJDBCValueStrByType(dataValue); } } catch (Exception e) { log.warn("convertJDBCValueStrByType error", e); return super.convertJDBCValueStrByType(dataValue); } return super.convertJDBCValueStrByType(dataValue); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/factory/OracleValueProcessorFactory.java ================================================ package ai.chat2db.plugin.oracle.value.factory; import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.value.sub.*; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import java.util.Map; /** * @author: zgq * @date: 2024年06月03日 23:21 */ // TODO: 1.空间数据类型 2.动态类型数据 public class OracleValueProcessorFactory { private static final Map PROCESSOR_MAP; static { OracleClobProcessor oracleClobProcessor = new OracleClobProcessor(); OracleTimeStampProcessor oracleTimeStampProcessor = new OracleTimeStampProcessor(); OracleBlobProcessor oracleBlobProcessor = new OracleBlobProcessor(); OracleRawValueProcessor oracleRawValueProcessor = new OracleRawValueProcessor(); PROCESSOR_MAP = Map.ofEntries( //clob Map.entry(OracleColumnTypeEnum.CLOB.name(), oracleClobProcessor), Map.entry(OracleColumnTypeEnum.NCLOB.name(), oracleClobProcessor), Map.entry(OracleColumnTypeEnum.LONG.name(), oracleClobProcessor), //date Map.entry(OracleColumnTypeEnum.DATE.name(), new OracleDateProcessor()), //timestamp Map.entry(OracleColumnTypeEnum.TIMESTAMP.name(), oracleTimeStampProcessor), Map.entry(OracleColumnTypeEnum.TIMESTAMP_WITH_LOCAL_TIME_ZONE.getColumnType().getTypeName(), new OracleTimeStampLTZProcessor()), Map.entry(OracleColumnTypeEnum.TIMESTAMP_WITH_TIME_ZONE.getColumnType().getTypeName(), new OracleTimeStampTZProcessor()), //INTERVAL Map.entry("INTERVALDS", new OracleIntervalDSProcessor()), Map.entry("INTERVALYM", new OracleIntervalYMProcessor()), //number Map.entry(OracleColumnTypeEnum.NUMBER.name(), new OracleNumberProcessor()), //blob Map.entry(OracleColumnTypeEnum.BLOB.name(), oracleBlobProcessor), //raw Map.entry(OracleColumnTypeEnum.RAW.name(), oracleRawValueProcessor), //long raw Map.entry(OracleColumnTypeEnum.LONG_RAW.getColumnType().getTypeName(), new OracleLongRawProcessor()), //xml Map.entry("SYS.XMLTYPE", new OracleXmlValueProcessor()), Map.entry("SYS.ANYDATA", new OracleAnyDataProcessor()) ); } public static DefaultValueProcessor getValueProcessor(String type) { return PROCESSOR_MAP.get(type); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleAnyDataProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年07月07日 10:42 */ public class OracleAnyDataProcessor extends DefaultValueProcessor { /** * @param dataValue * @return */ @Override public String convertSQLValueByType(SQLDataValue dataValue) { return dataValue.getValue(); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { // byte[] bytes = dataValue.getBytes(); // int length = bytes.length; // String rawString = new String(bytes); // // // Filter printable characters // StringBuilder printableString = new StringBuilder(); // for (char c : rawString.toCharArray()) { // if (c >= 32 && c <= 126) { // ASCII printable characters range // printableString.append(c); // } // } return "SYS.ANYDATA"; } /** * @param dataValue * @return */ @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return "SYS.ANYDATA"; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleBlobProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import lombok.extern.slf4j.Slf4j; /** * @author: zgq * @date: 2024年06月05日 20:06 */ @Slf4j public class OracleBlobProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { String value = dataValue.getValue(); if (value.startsWith("0x")) { // 0xabcd return EasyStringUtils.quoteString(value.substring(2)); } else { //example: hello,world for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); boolean isDigit = (c >= '0' && c <= '9'); boolean isUpperCaseHex = (c >= 'A' && c <= 'F'); boolean isLowerCaseHex = (c >= 'a' && c <= 'f'); if (!isDigit && !isUpperCaseHex && !isLowerCaseHex) { return EasyStringUtils.quoteString(dataValue.getBlobHexString()); } } // example: abcd1234 return EasyStringUtils.quoteString(value); } } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBlobString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getBlobHexString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleClobProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月04日 17:06 */ public class OracleClobProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return EasyStringUtils.escapeAndQuoteString(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getClobString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.escapeAndQuoteString(dataValue.getClobString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleDateProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月04日 16:33 */ public class OracleDateProcessor extends DefaultValueProcessor { /** * @param dataValue * @return */ @Override public String convertSQLValueByType(SQLDataValue dataValue) { return OracleDmlValueTemplate.wrapDate(dataValue.getValue()); } /** * @param dataValue * @return */ @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return OracleDmlValueTemplate.wrapDate(dataValue.getStringValue()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleIntervalDSProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月05日 18:56 */ public class OracleIntervalDSProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return OracleDmlValueTemplate.wrapIntervalDayToSecond(dataValue.getValue(), dataValue.getPrecision(), dataValue.getScale()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return OracleDmlValueTemplate.wrapIntervalDayToSecond(convertJDBCValueByType(dataValue), dataValue.getPrecision(), dataValue.getScale()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleIntervalYMProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * 功能描述 * * @author: zgq * @date: 2024年06月05日 18:58 */ public class OracleIntervalYMProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return OracleDmlValueTemplate.wrapIntervalYearToMonth(dataValue.getValue(), dataValue.getPrecision()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return OracleDmlValueTemplate.wrapIntervalYearToMonth(dataValue.getStringValue(), dataValue.getPrecision()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleLongRawProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import java.util.Objects; /** * @author: zgq * @date: 2024年07月07日 16:58 */ public class OracleLongRawProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { String value = dataValue.getValue(); if (value.startsWith("0x")) { // 0xabcd return EasyStringUtils.quoteString(value.substring(2)); } else { //example: hello,world // TODO: Need to optimize recognition of hexadecimal strings for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); boolean isDigit = (c >= '0' && c <= '9'); boolean isUpperCaseHex = (c >= 'A' && c <= 'F'); boolean isLowerCaseHex = (c >= 'a' && c <= 'f'); if (!isDigit && !isUpperCaseHex && !isLowerCaseHex) { return EasyStringUtils.quoteString(dataValue.getBlobHexString()); } } // example: abcd1234 return EasyStringUtils.quoteString(value); } } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBinaryDataString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { String blobHexString = dataValue.getBlobHexString(); if (Objects.isNull(blobHexString)) { return "NULL"; } return EasyStringUtils.quoteString(blobHexString); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleNumberProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * 功能描述 * * @author: zgq * @date: 2024年06月05日 20:00 */ public class OracleNumberProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return dataValue.getValue(); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBigDecimalString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return dataValue.getBigDecimalString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleRawValueProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月28日 下午1:59 */ public class OracleRawValueProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { String value = dataValue.getValue(); if (value.startsWith("0x")) { // 0xabcd return EasyStringUtils.quoteString(value.substring(2)); } else { //example: hello,world for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); boolean isDigit = (c >= '0' && c <= '9'); boolean isUpperCaseHex = (c >= 'A' && c <= 'F'); boolean isLowerCaseHex = (c >= 'a' && c <= 'f'); if (!isDigit && !isUpperCaseHex && !isLowerCaseHex) { return EasyStringUtils.quoteString(dataValue.getBlobHexString()); } } // example: abcd1234 return EasyStringUtils.quoteString(value); } } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getBinaryDataString(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return EasyStringUtils.quoteString(dataValue.getBlobHexString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleTimeStampLTZProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; /** * @author: zgq * @date: 2024年07月05日 下午4:19 */ public class OracleTimeStampLTZProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return wrap(dataValue.getValue(), dataValue.getScale()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { Timestamp timestamp = dataValue.getTimestamp(); int scale = dataValue.getScale(); LocalDateTime localDateTime = timestamp.toLocalDateTime(); StringBuilder templateBuilder = new StringBuilder("yyyy-MM-dd HH:mm:ss"); if (scale != 0) { templateBuilder.append("."); templateBuilder.append("S".repeat(scale)); } DateTimeFormatter formatter = DateTimeFormatter.ofPattern(templateBuilder.toString()); return localDateTime.format(formatter); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { Timestamp timestamp = dataValue.getTimestamp(); int scale = dataValue.getScale(); // 将 Timestamp 转换为 Instant 对象 Instant instant = timestamp.toInstant(); // 将 Instant 转换为 UTC 时区的 ZonedDateTime ZonedDateTime utcZonedDateTime = instant.atZone(ZoneId.of("UTC")); StringBuilder templateBuilder = new StringBuilder("yyyy-MM-dd HH:mm:ss"); if (scale != 0) { templateBuilder.append("."); templateBuilder.append("S".repeat(scale)); } // 定义日期时间格式化器 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(templateBuilder.toString()); // 格式化 UTC 时区的 ZonedDateTime String formattedUtcTime = utcZonedDateTime.format(formatter); return wrap(formattedUtcTime, scale); } private String wrap(String value, int scale) { if (scale == 0) { return OracleDmlValueTemplate.wrapDate(value); } return OracleDmlValueTemplate.wrapTimestamp(value, scale); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleTimeStampProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * @author: zgq * @date: 2024年06月05日 16:20 */ public class OracleTimeStampProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return wrap(dataValue.getValue(), dataValue.getScale()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { Timestamp timestamp = dataValue.getTimestamp(); int scale = dataValue.getScale(); LocalDateTime localDateTime = timestamp.toLocalDateTime(); StringBuilder templateBuilder = new StringBuilder("yyyy-MM-dd HH:mm:ss"); if (scale != 0) { templateBuilder.append("."); templateBuilder.append("S".repeat(scale)); } DateTimeFormatter formatter = DateTimeFormatter.ofPattern(templateBuilder.toString()); return localDateTime.format(formatter); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return wrap(convertJDBCValueByType(dataValue), dataValue.getScale()); } private String wrap(String value, int scale) { if (scale == 0) { return OracleDmlValueTemplate.wrapDate(value); } return OracleDmlValueTemplate.wrapTimestamp(value, scale); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleTimeStampTZProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * 功能描述 * * @author: zgq * @date: 2024年06月05日 17:32 */ public class OracleTimeStampTZProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return wrap(dataValue.getValue(), dataValue.getScale()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { String timeStampString = dataValue.getStringValue(); int scale = dataValue.getScale(); int lastSpaceIndex = timeStampString.lastIndexOf(" "); int lastDotIndex = timeStampString.indexOf("."); int nanosLength = lastSpaceIndex - lastDotIndex - 1; if (scale == 0) { return timeStampString.substring(0, lastDotIndex) + timeStampString.substring(lastDotIndex + 2); } else if (nanosLength < scale) { // 计算需要补充的零的数量 int zerosToAdd = scale - nanosLength; StringBuilder sb = new StringBuilder(timeStampString); for (int i = 0; i < zerosToAdd; i++) { sb.insert(lastSpaceIndex, '0'); } return sb.toString(); } return timeStampString; } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return wrap(convertJDBCValueByType(dataValue), dataValue.getScale()); } private String wrap(String value, int scale) { if (scale == 0) { return OracleDmlValueTemplate.wrapTimestampTzWithOutNanos(value); } return OracleDmlValueTemplate.wrapTimestampTz(value, scale); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/sub/OracleXmlValueProcessor.java ================================================ package ai.chat2db.plugin.oracle.value.sub; import ai.chat2db.plugin.oracle.value.template.OracleDmlValueTemplate; import ai.chat2db.spi.jdbc.DefaultValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; /** * @author: zgq * @date: 2024年06月21日 12:55 */ public class OracleXmlValueProcessor extends DefaultValueProcessor { @Override public String convertSQLValueByType(SQLDataValue dataValue) { return OracleDmlValueTemplate.wrapXml(dataValue.getValue()); } @Override public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getStringValue(); } @Override public String convertJDBCValueStrByType(JDBCDataValue dataValue) { return OracleDmlValueTemplate.wrapXml(dataValue.getString()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/value/template/OracleDmlValueTemplate.java ================================================ package ai.chat2db.plugin.oracle.value.template; /** * @author: zgq * @date: 2024年06月01日 13:35 */ public class OracleDmlValueTemplate { public static final String DATE_TEMPLATE = "TO_DATE('%s', 'SYYYY-MM-DD HH24:MI:SS')"; public static final String TIMESTAMP_TEMPLATE = "TO_TIMESTAMP('%s', 'SYYYY-MM-DD HH24:MI:SS.FF%d')"; public static final String TIMESTAMP_TZ_TEMPLATE = "TO_TIMESTAMP_TZ('%s', 'SYYYY-MM-DD HH24:MI:SS.FF%d TZR')"; public static final String TIMESTAMP_TZ_WITHOUT_NANOS_TEMPLATE = "TO_TIMESTAMP_TZ('%s', 'SYYYY-MM-DD HH24:MI:SS TZR')"; public static final String INTERVAL_YEAR_TO_MONTH_TEMPLATE = "INTERVAL '%s' YEAR(%d) TO MONTH"; public static final String INTERVAL_DAY_TO_SECOND_TEMPLATE = "INTERVAL '%s' DAY(%d) TO SECOND(%d)"; public static final String XML_TEMPLATE = "XMLType('%s')"; public static String wrapDate(String date) { return String.format(DATE_TEMPLATE, date); } public static String wrapTimestamp(String timestamp, int scale) { return String.format(TIMESTAMP_TEMPLATE, timestamp, scale); } public static String wrapTimestampTz(String timestamp, int scale) { return String.format(TIMESTAMP_TZ_TEMPLATE, timestamp, scale); } public static String wrapTimestampTzWithOutNanos(String timestamp) { return String.format(TIMESTAMP_TZ_WITHOUT_NANOS_TEMPLATE, timestamp); } public static String wrapIntervalYearToMonth(String year, int precision) { return String.format(INTERVAL_YEAR_TO_MONTH_TEMPLATE, year, precision); } public static String wrapIntervalDayToSecond(String day, int precision, int scale) { return String.format(INTERVAL_DAY_TO_SECOND_TEMPLATE, day, precision, scale); } public static String wrapXml(String xml) { return String.format(XML_TEMPLATE, xml); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-oracle/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.oracle.OraclePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml chat2db-postgresql ai.chat2db chat2db-spi src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java ================================================ package ai.chat2db.plugin.postgresql; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import static ai.chat2db.plugin.postgresql.consts.SQLConst.ENUM_TYPE_DDL_SQL; public class PostgreSQLDBManage extends DefaultDBManage implements DBManage { public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTypes(connection, asyncContext); exportTables(connection, databaseName, schemaName, asyncContext); exportViews(connection, schemaName, asyncContext); exportFunctions(connection, schemaName, asyncContext); exportTriggers(connection, asyncContext); } private void exportTypes(Connection connection, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.createStatement().executeQuery(ENUM_TYPE_DDL_SQL)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("ddl")).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null, new String[]{"TABLE", "SYSTEM TABLE", "PARTITIONED TABLE"})) { ArrayList tableNames = new ArrayList<>(); while (resultSet.next()) { String tableName = resultSet.getString("TABLE_NAME"); tableNames.add(tableName); } for (String tableName : tableNames) { exportTable(connection, databaseName, schemaName, tableName, asyncContext); } } } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { String sql = String.format("select pg_get_tabledef('%s','%s',true,'COMMENTS') as ddl;", schemaName, tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("\n").append("DROP TABLE IF EXISTS ").append(tableName).append(";").append("\n") .append(resultSet.getString("ddl")).append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } } } } private void exportViews(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT table_name, view_definition FROM information_schema.views WHERE table_schema = '%s'", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String viewName = resultSet.getString("table_name"); String viewDefinition = resultSet.getString("view_definition"); sqlBuilder.append("CREATE OR REPLACE VIEW ").append(viewName).append(" AS ").append(viewDefinition).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportFunctions(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT proname, pg_get_functiondef(oid) AS function_definition FROM pg_proc " + "WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = '%s')", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); String functionName = resultSet.getString("proname"); String functionDefinition = resultSet.getString("function_definition"); sqlBuilder.append("DROP FUNCTION IF EXISTS ").append(schemaName).append(".").append(functionName).append(";\n"); sqlBuilder.append(functionDefinition).append(";\n\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, AsyncContext asyncContext) throws SQLException { String sql = "SELECT pg_get_triggerdef(oid) AS trigger_definition FROM pg_trigger"; try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("trigger_definition")).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } @Override public void connectDatabase(Connection connection, String database) { try { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); if (!StringUtils.isEmpty(connectInfo.getSchemaName())) { SQLExecutor.getInstance().execute(connection, "SET search_path TO \"" + connectInfo.getSchemaName() + "\""); } } catch (Exception e) { } } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrReplace = procedureBody.trim().toUpperCase().startsWith("CREATE OR REPLACE "); String parameterSignature = extractParameterSignature(procedureBody); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String dropSql = "DROP PROCEDURE IF EXISTS " + procedureNewName + parameterSignature; SQLExecutor.getInstance().execute(connection, dropSql, resultSet -> {}); SQLExecutor.getInstance().execute(connection, procedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public Connection getConnection(ConnectInfo connectInfo) { String url = connectInfo.getUrl(); String database = connectInfo.getDatabaseName(); if (database != null && !database.isEmpty()) { url = replaceDatabaseInJdbcUrl(url, database); } connectInfo.setUrl(url); return super.getConnection(connectInfo); } public String replaceDatabaseInJdbcUrl(String url, String newDatabase) { // First split the string at the "?" character and process the query parameters String[] urlAndParams = url.split("\\?"); String urlWithoutParams = urlAndParams[0]; // Split string at "/" character in URL String[] parts=new String[4]; String[] splitParts = urlWithoutParams.split("/"); for (int i = 0; i < splitParts.length; i++) { parts[i] = splitParts[i]; } // Take the last part, the database name, and replace it with the new database name parts[parts.length - 1] = newDatabase; // Reassemble the modified parts into a URL String newUrlWithoutParams = String.join("/", parts); // If query parameters exist, add them again String newUrl = urlAndParams.length > 1 ? newUrlWithoutParams + "?" + urlAndParams[1] : newUrlWithoutParams; return newUrl; } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData) throws SQLException { String sql = ""; if (copyData) { sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH DATA"; } else { sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH NO DATA"; } SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) { String procedureBody = procedure.getProcedureBody(); String parameterSignature = extractParameterSignature(procedureBody); String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); String sql = "DROP PROCEDURE " + procedureNewName + parameterSignature; SQLExecutor.getInstance().execute(connection, sql, resultSet -> {}); } @Override public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) { String functionBody = function.getFunctionBody(); String parameterSignature = extractParameterSignature(functionBody); String functionNewName = getSchemaOrFunctionName(functionBody, schemaName, function); String sql = "DROP FUNCTION" + functionNewName + parameterSignature; SQLExecutor.getInstance().execute(connection,sql,resultSet -> {}); } private String extractParameterSignature(String input) { int depth = 0, start = -1; for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); if (c == '(') { if (depth++ == 0) start = i; } else if (c == ')' && --depth == 0 && start != -1) { return "(" + input.substring(start + 1, i) + ")"; } } if (depth == 0) { return ""; } return null; } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) { if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) { return function.getFunctionName(); } else { return schemaName + "." + function.getFunctionName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java ================================================ package ai.chat2db.plugin.postgresql; import ai.chat2db.plugin.postgresql.builder.PostgreSQLSqlBuilder; import ai.chat2db.plugin.postgresql.type.*; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import static ai.chat2db.plugin.postgresql.consts.SequenceCommonConst.*; import static ai.chat2db.plugin.postgresql.consts.SQLConst.*; import static ai.chat2db.server.tools.base.constant.SymbolConstant.*; import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { private static final String SELECT_KEY_INDEX = "SELECT ccu.table_schema AS Foreign_schema_name, ccu.table_name AS Foreign_table_name, ccu.column_name AS Foreign_column_name, constraint_type AS Constraint_type, tc.CONSTRAINT_NAME AS Key_name, tc.TABLE_NAME, kcu.Column_name, tc.is_deferrable, tc.initially_deferred FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.TABLE_SCHEMA = '%s' AND tc.TABLE_NAME = '%s';"; private List systemDatabases = Arrays.asList("postgres"); @Override public List databases(Connection connection) { List list = SQLExecutor.getInstance().execute(connection, "SELECT datname FROM pg_database;", resultSet -> { List databases = new ArrayList<>(); try { while (resultSet.next()) { String dbName = resultSet.getString("datname"); if ("template0".equals(dbName) || "template1".equals(dbName)) { continue; } Database database = new Database(); database.setName(dbName); databases.add(database); } } catch (SQLException e) { throw new RuntimeException(e); } return databases; }); return sortDatabase(list, systemDatabases, connection); } private List systemSchemas = Arrays.asList("pg_toast", "pg_temp_1", "pg_toast_temp_1", "pg_catalog", "information_schema"); /* @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().execute(connection, "SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> { List databases = new ArrayList<>(); while (resultSet.next()) { Schema schema = new Schema(); String name = resultSet.getString("schema_name"); String catalogName = resultSet.getString("catalog_name"); schema.setName(name); schema.setDatabaseName(catalogName); databases.add(schema); } return databases; }); return SortUtils.sortSchema(schemas, systemSchemas); }*/ private static final String SELECT_TABLE_INDEX = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, TRIM ( BOTH '\"' FROM pg_get_indexdef ( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE ) ) AS Column_name,CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME,i.INDISPRIMARY , i.indisclustered , ( information_schema._pg_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, pg_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM pg_class ct JOIN pg_namespace n ON ( ct.relnamespace = n.OID ) JOIN pg_index i ON ( ct.OID = i.indrelid ) JOIN pg_class ci ON ( ci.OID = i.indexrelid ) JOIN pg_am am ON ( ci.relam = am.OID ) left outer join pg_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp ;"; private static String ROUTINES_SQL = "SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p where p.prokind = '%s' and p.proname='%s'"; private static String TRIGGER_SQL = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';"; private static String TRIGGER_SQL_LIST = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';"; private static String VIEW_SQL = "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); String sql = String.format(TRIGGER_SQL_LIST, schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("trigger_name")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { SQLExecutor.getInstance().execute(connection, String.format(DROP_TYPE_SQL, schemaName, "tabledefs"), resultSet -> null); SQLExecutor.getInstance().execute(connection, TABLE_DEF_FUNCTION_SQL, resultSet -> null); String ddlSql = String.format("select * from pg_get_tabledef('%s','%s',false,'COMMENTS') as ddl;", schemaName, tableName); return SQLExecutor.getInstance().execute(connection, ddlSql, resultSet -> { if (resultSet.next()) { return resultSet.getString("ddl"); } return null; }); } @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "f", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("code")); } return function; }); } @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("definition")); } return table; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, schemaName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("trigger_body")); } return trigger; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "p", procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("code")); } return procedure; }); } @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String constraintSql = String.format(SELECT_KEY_INDEX, schemaName, tableName); Map constraintMap = new HashMap(); LinkedHashMap foreignMap = new LinkedHashMap(); SQLExecutor.getInstance().execute(connection, constraintSql, resultSet -> { while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); String constraintType = resultSet.getString("Constraint_type"); constraintMap.put(keyName, constraintType); if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.FOREIGN.getKeyword())) { TableIndex tableIndex = foreignMap.get(keyName); String columnName = resultSet.getString("Column_name"); if (tableIndex == null) { tableIndex = new TableIndex(); tableIndex.setDatabaseName(databaseName); tableIndex.setSchemaName(schemaName); tableIndex.setTableName(tableName); tableIndex.setName(keyName); tableIndex.setForeignSchemaName(resultSet.getString("Foreign_schema_name")); tableIndex.setForeignTableName(resultSet.getString("Foreign_table_name")); tableIndex.setForeignColumnNamelist(Lists.newArrayList(columnName)); tableIndex.setType(PostgreSQLIndexTypeEnum.FOREIGN.getName()); foreignMap.put(keyName, tableIndex); } else { tableIndex.getForeignColumnNamelist().add(columnName); } } } return null; }); String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(foreignMap); while (resultSet.next()) { String keyName = resultSet.getString("Key_name"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); if (columnList == null) { columnList = new ArrayList<>(); tableIndex.setColumnList(columnList); } columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); index.setUnique(!StringUtils.equals("t", resultSet.getString("NON_UNIQUE"))); index.setMethod(resultSet.getString("Index_method")); index.setComment(resultSet.getString("Index_comment")); List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); String constraintType = constraintMap.get(keyName); if (StringUtils.equals("t", resultSet.getString("Index_primary"))) { index.setType(PostgreSQLIndexTypeEnum.PRIMARY.getName()); } else if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.UNIQUE.getName())) { index.setType(PostgreSQLIndexTypeEnum.UNIQUE.getName()); } else { index.setType(PostgreSQLIndexTypeEnum.NORMAL.getName()); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { List columnList = super.columns(connection, databaseName, schemaName, tableName); EasyCollectionUtils.stream(columnList).forEach(v -> { if (StringUtils.equalsIgnoreCase(v.getColumnType(), "bpchar")) { v.setColumnType(PostgreSQLColumnTypeEnum.CHAR.getColumnType().getTypeName().toUpperCase()); } else { v.setColumnType(v.getColumnType().toUpperCase()); } }); return columnList; } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("Column_name")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("Collation")); tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); return tableIndexColumn; } @Override public SqlBuilder getSqlBuilder() { return new PostgreSQLSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(PostgreSQLColumnTypeEnum.getTypes()) .charsets(PostgreSQLCharsetEnum.getCharsets()) .collations(PostgreSQLCollationEnum.getCollations()) .indexTypes(PostgreSQLIndexTypeEnum.getIndexTypes()) .defaultValues(PostgreSQLDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } @Override public List getSystemDatabases() { return systemDatabases; } @Override public List getSystemSchemas() { return systemSchemas; } @Override @SneakyThrows public String sequenceDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String sequenceName) { DatabaseMetaData metaData = connection.getMetaData(); double databaseProductVersion = Double.parseDouble(metaData.getDatabaseProductVersion()); String[] args = new String[]{sequenceName, schemaName}; return SQLExecutor.getInstance().preExecute(connection, EXPORT_SEQUENCE_DDL_SQL, args, resultSet -> { StringBuilder stringBuilder = new StringBuilder(); if (resultSet.next()) { String nspname = resultSet.getString("nspname"); String relname = resultSet.getString("relname"); String typname = getConversionType(resultSet.getString("typname")); String seqcache = resultSet.getString("seqcache"); String rolname = resultSet.getString("rolname"); String comment = resultSet.getString("comment"); String seqstart = resultSet.getString("seqstart"); String seqincrement = resultSet.getString("seqincrement"); String seqmax = resultSet.getString("seqmax"); String seqmin = resultSet.getString("seqmin"); Boolean seqcycle = resultSet.getBoolean("seqcycle"); stringBuilder.append(CREATE_SEQUENCE).append(getMetaDataName(nspname, relname)).append(NEW_LINE); if (Double.compare(databaseProductVersion, 10.0) >= 0) { stringBuilder.append(AS).append(typname).append(NEW_LINE); } Optional.ofNullable(seqstart).ifPresent(v -> stringBuilder.append(START_WITH).append(v).append(NEW_LINE)); Optional.ofNullable(seqincrement).ifPresent(v -> stringBuilder.append(INCREMENT_BY).append(v).append(NEW_LINE)); Optional.ofNullable(seqmin).ifPresent(v -> stringBuilder.append(MINVALUE).append(v).append(NEW_LINE)); Optional.ofNullable(seqmax).ifPresent(v -> stringBuilder.append(MAXVALUE).append(v).append(NEW_LINE)); Optional.ofNullable(seqcache).ifPresent(v -> stringBuilder.append(CACHE).append(v).append(NEW_LINE)); Optional.ofNullable(seqcycle).ifPresent(v -> { if (Boolean.TRUE.equals(seqcycle)) { stringBuilder.append(CYCLE).append(NEW_LINE); } }); stringBuilder.append(SEMICOLON).append(BLANK_LINE); Optional.ofNullable(comment).ifPresent(v -> stringBuilder.append(COMMENT_ON_SEQUENCE) .append(getMetaDataName(nspname, relname)) .append(IS).append(SQUOT).append(v).append(SQUOT).append(SEMICOLON).append(BLANK_LINE)); Optional.ofNullable(rolname).ifPresent(v -> stringBuilder.append(ALTER_SEQUENCE) .append(getMetaDataName(nspname, relname)) .append(OWNER_TO).append(getMetaDataName(v)).append(SEMICOLON)); } return stringBuilder.toString(); }); } @Override public List sequences(Connection connection, String databaseName, String schemaName) { List simpleSequences = new ArrayList<>(); String[] args = new String[]{schemaName}; return SQLExecutor.getInstance().preExecute(connection, EXPORT_SEQUENCES_SQL, args, resultSet -> { while (resultSet.next()) { String relname = resultSet.getString("relname"); String comment = resultSet.getString("comment"); simpleSequences.add(SimpleSequence.builder() .name(relname) .comment(comment) .build()); } return simpleSequences; }); } @Override public Sequence sequences(Connection connection, @NotEmpty String databaseName, String schemaName, String sequenceName) { String[] args = new String[]{sequenceName, schemaName}; return SQLExecutor.getInstance().preExecute(connection, EXPORT_SEQUENCE_DDL_SQL, args, resultSet -> { if (resultSet.next()) { return Sequence.builder() .nspname(resultSet.getString("nspname")) .relname(resultSet.getString("relname")) .typname(getConversionType(resultSet.getString("typname"))) .seqcache(resultSet.getString("seqcache")) .rolname(resultSet.getString("rolname")) .comment(resultSet.getString("comment")) .seqstart(resultSet.getString("seqstart")) .seqincrement(resultSet.getString("seqincrement")) .seqmax(resultSet.getString("seqmax")) .seqmin(resultSet.getString("seqmin")) .seqcycle(resultSet.getBoolean("seqcycle")) .build(); } return null; }); } @Override public List usernames(Connection connection) { List usernames = new ArrayList<>(); return SQLExecutor.getInstance().preExecute(connection, EXPORT_USERS_SQL, null, resultSet -> { while (resultSet.next()) { String username = resultSet.getString("username"); usernames.add(username); } return usernames; }); } private String getConversionType(String typname) { switch (typname) { case "int2" -> typname = "SMALLINT"; case "int8" -> typname = "BIGINT"; default -> typname = "INTEGER"; } return typname; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLPlugin.java ================================================ package ai.chat2db.plugin.postgresql; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class PostgreSQLPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"pg.json", DBConfig.class); } @Override public MetaData getMetaData() { return new PostgreSQLMetaData(); } @Override public DBManage getDBManage() { return new PostgreSQLDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java ================================================ package ai.chat2db.plugin.postgresql.builder; import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum; import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import cn.hutool.core.util.BooleanUtil; import lombok.SneakyThrows; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import static ai.chat2db.plugin.postgresql.consts.SequenceCommonConst.*; import static ai.chat2db.server.tools.base.constant.SymbolConstant.*; public class PostgreSQLSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n"); // append column for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } Map> tableIndexMap = table.getIndexList().stream() .collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType()))); // append constraint key List constraintList = tableIndexMap.get(Boolean.FALSE); if (CollectionUtils.isNotEmpty(constraintList)) { for (TableIndex index : constraintList) { if (StringUtils.isBlank(index.getName()) || StringUtils.isBlank(index.getType())) { continue; } PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); if(indexTypeEnum == null){ continue; } script.append("\t").append("").append(indexTypeEnum.buildIndexScript(index)); script.append(",\n"); } } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)").append(";"); // append index List tableIndexList = tableIndexMap.get(Boolean.TRUE); for (TableIndex tableIndex : tableIndexList) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } script.append("\n"); PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } script.append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); } // append comment if (StringUtils.isNotBlank(table.getComment())) { script.append("\n"); script.append("COMMENT ON TABLE").append(" ").append("\"").append(table.getName()).append("\" IS '") .append(table.getComment()).append("';\n"); } List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableColumn tableColumn : tableColumnList) { PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } List indexList = table.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableIndex index : indexList) { PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); if(indexEnum == null){ continue; } script.append(indexEnum.buildIndexComment(index)).append("\n"); ; } return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\""); script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } newTable.setColumnList(newTable.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); newTable.setIndexList(newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); //update name List columnNameList = newTable.getColumnList().stream().filter(v -> v.getOldName() != null && !StringUtils.equals(v.getOldName(), v.getName())).toList(); for (TableColumn tableColumn : columnNameList) { script.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" ").append("RENAME COLUMN \"") .append(tableColumn.getOldName()).append("\" TO \"").append(tableColumn.getName()).append("\";\n"); } Map> tableIndexMap = newTable.getIndexList().stream() .collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType()))); StringBuilder scriptModify = new StringBuilder(); Boolean modify = false; scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n"); // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); modify = true; } // append modify constraint for (TableIndex tableIndex : tableIndexMap.get(Boolean.FALSE)) { if (StringUtils.isNotBlank(tableIndex.getType())) { PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } scriptModify.append("\t").append(indexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); modify = true; } } if (BooleanUtils.isTrue(modify)) { script.append(scriptModify); script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";\n"); } // append modify index for (TableIndex tableIndex : tableIndexMap.get(Boolean.TRUE)) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); if(indexTypeEnum == null){ continue; } script.append(indexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } // append comment if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) { script.append("\n"); script.append("COMMENT ON TABLE").append(" ").append("\"").append(newTable.getName()).append("\" IS '") .append(newTable.getComment()).append("';\n"); } for (TableColumn tableColumn : newTable.getColumnList()) { PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum ==null){ continue; } script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); ; } List indexList = newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); for (TableIndex index : indexList) { PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); if(indexEnum == null){ continue; } script.append(indexEnum.buildIndexComment(index)).append("\n"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlStr = new StringBuilder(sql.length() + 17); sqlStr.append(sql); if (offset == 0) { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); } else { sqlStr.append(" LIMIT "); sqlStr.append(pageSize); sqlStr.append(" OFFSET "); sqlStr.append(offset); } return sqlStr.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE \""+database.getName()+"\""); sqlBuilder.append("\nWITH "); if(StringUtils.isNotBlank(database.getCharset())){ sqlBuilder.append("\n LC_CTYPE = '").append(database.getCharset()).append("' "); } if(StringUtils.isNotBlank(database.getCollation())){ sqlBuilder.append("\n LC_COLLATE = '").append(database.getCollation()).append("' "); } if(StringUtils.isNotBlank(database.getComment())){ sqlBuilder.append("; COMMENT ON DATABASE \"").append(database.getName()).append("\" IS '").append(database.getComment()).append("';"); } return sqlBuilder.toString(); } @Override public String buildCreateSchemaSql(Schema schema){ StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); if(StringUtils.isNotBlank(schema.getOwner())){ sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); } if(StringUtils.isNotBlank(schema.getComment())){ sqlBuilder.append("; COMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); } return sqlBuilder.toString(); } @Override @SneakyThrows public String buildCreateSequenceSql(Sequence sequence) { double databaseProductVersion = Double.parseDouble(Chat2DBContext.getConnection().getMetaData().getDatabaseProductVersion()); StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(CREATE_SEQUENCE).append(getMetaDataName(sequence.getNspname(), sequence.getRelname())).append("\n "); if (Double.compare(databaseProductVersion, 10.0) >= 0) { sqlBuilder.append(AS).append(sequence.getTypname()).append("\n "); } Optional.ofNullable(sequence.getSeqstart()).ifPresent(v -> sqlBuilder.append(START_WITH).append(v).append("\n ")); Optional.ofNullable(sequence.getSeqincrement()).ifPresent(v -> sqlBuilder.append(INCREMENT_BY).append(v).append("\n ")); Optional.ofNullable(sequence.getSeqmin()).ifPresent(v -> sqlBuilder.append(MINVALUE).append(v).append("\n ")); Optional.ofNullable(sequence.getSeqmax()).ifPresent(v -> sqlBuilder.append(MAXVALUE).append(v).append("\n ")); Optional.ofNullable(sequence.getSeqcache()).ifPresent(v -> sqlBuilder.append(CACHE).append(v).append("\n ")); Optional.ofNullable(sequence.getSeqcycle()).ifPresent(v -> { if (Boolean.TRUE.equals(sequence.getSeqcycle())) { sqlBuilder.append(CYCLE).append("\n "); } }); sqlBuilder.append(SEMICOLON).append("\n ").append("\n "); Optional.ofNullable(sequence.getComment()).ifPresent(v -> sqlBuilder.append(COMMENT_ON_SEQUENCE) .append(getMetaDataName(sequence.getNspname(), sequence.getRelname())) .append(IS).append(SQUOT).append(v).append(SQUOT).append(SEMICOLON).append("\n ").append("\n ")); Optional.ofNullable(sequence.getRolname()).ifPresent(v -> sqlBuilder.append(ALTER_SEQUENCE) .append(getMetaDataName(sequence.getNspname(), sequence.getRelname())) .append(OWNER_TO).append(getMetaDataName(v)).append(SEMICOLON)); return sqlBuilder.toString(); } @Override public String buildModifySequenceSql(Sequence oldSequence, Sequence newSequence) { StringBuilder sqlBuilder = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldSequence.getRelname(), newSequence.getRelname())) { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(oldSequence.getNspname(), oldSequence.getRelname())).append(RENAME_TO).append(getMetaDataName(newSequence.getRelname())).append(SEMICOLON).append(BLANK_LINE); } if (!StringUtils.equals(oldSequence.getComment(), newSequence.getComment())) { sqlBuilder.append(COMMENT_ON_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(IS).append(SQUOT).append(newSequence.getComment()).append(SQUOT).append(SEMICOLON).append(BLANK_LINE); } if (!StringUtils.equals(oldSequence.getSeqcache(), newSequence.getSeqcache())) { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(CACHE).append(getMetaDataName(newSequence.getSeqcache())).append(SEMICOLON).append(BLANK_LINE); } if (BooleanUtil.xor(oldSequence.getSeqcycle(), newSequence.getSeqcycle())) { if (Boolean.TRUE.equals(newSequence.getSeqcycle())) { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(CYCLE).append(BLANK_LINE); } else { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(NO_CYCLE).append(BLANK_LINE); } } if (!StringUtils.equals(oldSequence.getSeqstart(), newSequence.getSeqstart()) || !StringUtils.equals(oldSequence.getSeqincrement(), newSequence.getSeqincrement()) || !StringUtils.equals(oldSequence.getSeqmax(), newSequence.getSeqmax()) || !StringUtils.equals(oldSequence.getSeqmin(), newSequence.getSeqmin())) { sqlBuilder.append(ALTER_SEQUENCE); if (!StringUtils.equals(oldSequence.getSeqstart(), newSequence.getSeqstart())) { sqlBuilder.append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(RESTART_WITH).append(newSequence.getSeqstart()); } if (!StringUtils.equals(oldSequence.getSeqincrement(), newSequence.getSeqincrement())) { sqlBuilder.append(INCREMENT_BY).append(newSequence.getSeqincrement()); } if (!StringUtils.equals(oldSequence.getSeqmax(), newSequence.getSeqmax())) { sqlBuilder.append(MAXVALUE).append(newSequence.getSeqmax()); } if (!StringUtils.equals(oldSequence.getSeqmin(), newSequence.getSeqmin())) { sqlBuilder.append(MINVALUE).append(newSequence.getSeqmin()); } sqlBuilder.append(SEMICOLON).append(BLANK_LINE); } if (!StringUtils.equals(oldSequence.getTypname(), newSequence.getTypname())) { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(AS).append(newSequence.getTypname()).append(SEMICOLON).append(BLANK_LINE); } if (!StringUtils.equals(oldSequence.getRolname(), newSequence.getRolname())) { sqlBuilder.append(ALTER_SEQUENCE).append(getMetaDataName(newSequence.getNspname(), newSequence.getRelname())).append(OWNER_TO).append(getMetaDataName(newSequence.getRolname())).append(SEMICOLON).append(BLANK_LINE); } return sqlBuilder.toString(); } private String getMetaDataName(String... names) { return Arrays.stream(names).filter(StringUtils::isNotBlank).map(name -> DOUBLE_SQUOT + name + DOUBLE_SQUOT).collect(Collectors.joining(DOT)); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/consts/SQLConst.java ================================================ package ai.chat2db.plugin.postgresql.consts; public class SQLConst { public static String TABLE_DEF_FUNCTION_SQL = """ CREATE TYPE tabledefs AS ENUM ('PKEY_INTERNAL','PKEY_EXTERNAL','FKEYS_INTERNAL', 'FKEYS_EXTERNAL', 'COMMENTS', 'FKEYS_NONE', 'INCLUDE_TRIGGERS', 'NO_TRIGGERS'); CREATE OR REPLACE FUNCTION pg_get_coldef( in_schema text, in_table text, in_column text, oldway boolean default False ) RETURNS text LANGUAGE plpgsql VOLATILE AS $$ DECLARE v_coldef text; v_dt1 text; v_dt2 text; v_dt3 text; v_nullable boolean; v_position int; v_identity text; v_generated text; v_hasdflt boolean; v_dfltexpr text; BEGIN IF oldway THEN SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum; -- RAISE NOTICE 'DEBUG: oldway=%',v_coldef; ELSE SELECT CASE WHEN a.atttypid = ANY ('{int,int8,int2}'::regtype[]) AND EXISTS (SELECT FROM pg_attrdef ad WHERE ad.adrelid = a.attrelid AND ad.adnum = a.attnum AND pg_get_expr(ad.adbin, ad.adrelid) = 'nextval(''' || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass || '''::regclass)') THEN CASE a.atttypid WHEN 'int'::regtype THEN 'serial' WHEN 'int8'::regtype THEN 'bigserial' WHEN 'int2'::regtype THEN 'smallserial' END ELSE format_type(a.atttypid, a.atttypmod) END AS data_type INTO v_coldef FROM pg_namespace n, pg_class c, pg_attribute a, pg_type t WHERE n.nspname = in_schema AND n.oid = c.relnamespace AND c.relname = in_table AND a.attname = in_column and a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid ORDER BY a.attnum; END IF; RETURN v_coldef; END; $$; -- SELECT * FROM pg_get_tabledef('sample', 'address', false); DROP FUNCTION IF EXISTS pg_get_tabledef(character varying,character varying,boolean,tabledefs[]); CREATE OR REPLACE FUNCTION pg_get_tabledef( in_schema varchar, in_table varchar, _verbose boolean, VARIADIC arr tabledefs[] DEFAULT '{}':: tabledefs[] ) RETURNS text LANGUAGE plpgsql VOLATILE AS $$ DECLARE v_qualified text := ''; v_table_ddl text; v_table_oid int; v_colrec record; v_constraintrec record; v_trigrec record; v_indexrec record; v_rec record; v_constraint_name text; v_constraint_def text; v_pkey_def text := ''; v_fkey_def text := ''; v_fkey_defs text := ''; v_trigger text := ''; v_partition_key text := ''; v_partbound text; v_parent text; v_parent_schema text; v_persist text; v_temp text := ''; v_temp2 text; v_relopts text; v_tablespace text; v_pgversion int; bSerial boolean; bPartition boolean; bInheritance boolean; bRelispartition boolean; constraintarr text[] := '{}'; constraintelement text; bSkip boolean; bVerbose boolean := False; v_cnt1 integer; v_cnt2 integer; search_path_old text := ''; search_path_new text := ''; v_partial boolean; v_pos integer; pkcnt int := 0; fkcnt int := 0; trigcnt int := 0; cmtcnt int := 0; pktype tabledefs := 'PKEY_INTERNAL'; fktype tabledefs := 'FKEYS_INTERNAL'; trigtype tabledefs := 'NO_TRIGGERS'; arglen integer; vargs text; avarg tabledefs; v_ret text; v_diag1 text; v_diag2 text; v_diag3 text; v_diag4 text; v_diag5 text; v_diag6 text; BEGIN SET client_min_messages = 'notice'; IF _verbose THEN bVerbose = True; END IF; arglen := array_length($4, 1); IF arglen IS NULL THEN -- nothing to do, so assume defaults NULL; ELSE IF bVerbose THEN RAISE NOTICE 'arguments=%', $4; END IF; FOREACH avarg IN ARRAY $4 LOOP IF bVerbose THEN RAISE NOTICE 'arg=%', avarg; END IF; IF avarg = 'FKEYS_INTERNAL' OR avarg = 'FKEYS_EXTERNAL' OR avarg = 'FKEYS_NONE' THEN fkcnt = fkcnt + 1; fktype = avarg; ELSEIF avarg = 'INCLUDE_TRIGGERS' OR avarg = 'NO_TRIGGERS' THEN trigcnt = trigcnt + 1; trigtype = avarg; ELSEIF avarg = 'PKEY_EXTERNAL' THEN pkcnt = pkcnt + 1; pktype = avarg; ELSEIF avarg = 'COMMENTS' THEN cmtcnt = cmtcnt + 1; END IF; END LOOP; IF fkcnt > 1 THEN RAISE WARNING 'Only one foreign key option can be provided. You provided %', fkcnt; RETURN ''; ELSEIF trigcnt > 1 THEN RAISE WARNING 'Only one trigger option can be provided. You provided %', trigcnt; RETURN ''; ELSEIF pkcnt > 1 THEN RAISE WARNING 'Only one pkey option can be provided. You provided %', pkcnt; RETURN ''; ELSEIF cmtcnt > 1 THEN RAISE WARNING 'Only one comments option can be provided. You provided %', cmtcnt; RETURN ''; END IF; END IF; SELECT c.oid, (select setting from pg_settings where name = 'server_version_num') INTO v_table_oid, v_pgversion FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind in ('r','p') AND c.relname = in_table AND n.nspname = in_schema; SELECT setting INTO search_path_old FROM pg_settings WHERE name = 'search_path'; SELECT REPLACE(REPLACE(setting, '"$user"', '$user'), '$user', '"$user"') INTO search_path_old FROM pg_settings WHERE name = 'search_path'; EXECUTE 'SET search_path = "public"'; SELECT setting INTO search_path_new FROM pg_settings WHERE name = 'search_path'; IF (v_table_oid IS NULL) THEN RAISE EXCEPTION 'table does not exist'; END IF; SELECT tablespace INTO v_temp FROM pg_tables WHERE schemaname = in_schema and tablename = in_table and tablespace IS NOT NULL; IF v_temp IS NULL THEN v_tablespace := 'TABLESPACE pg_default'; ELSE v_tablespace := 'TABLESPACE ' || v_temp; END IF; WITH relopts AS (SELECT unnest(c.reloptions) relopts FROM pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table) SELECT string_agg(r.relopts, ', ') as relopts INTO v_temp from relopts r; IF v_temp IS NULL THEN v_relopts := ''; ELSE v_relopts := ' WITH (' || v_temp || ')'; END IF; v_partbound := ''; bPartition := False; bInheritance := False; IF v_pgversion < 100000 THEN SELECT c2.relname parent, c2.relnamespace::regnamespace INTO v_parent, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2 WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r'; IF (v_parent IS NOT NULL) THEN bPartition := True; bInheritance := True; END IF; ELSE SELECT c2.relname parent, c1.relispartition, pg_get_expr(c1.relpartbound, c1.oid, true), c2.relnamespace::regnamespace INTO v_parent, bRelispartition, v_partbound, v_parent_schema from pg_class c1, pg_namespace n, pg_inherits i, pg_class c2 WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.oid = i.inhrelid and i.inhparent = c2.oid and c1.relkind = 'r'; IF (v_parent IS NOT NULL) THEN bPartition := True; IF bRelispartition THEN bInheritance := False; ELSE bInheritance := True; END IF; END IF; END IF; IF bPartition THEN SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE'); SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = in_table AND catcode = 'R'; IF bInheritance THEN IF v_cnt1 > 0 OR v_cnt2 > 0 THEN v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '"( '|| E'\\n'; ELSE v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || '( '|| E'\\n'; END IF; ELSE IF v_relopts <> '' THEN IF v_cnt1 > 0 OR v_cnt2 > 0 THEN v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n'; ELSE v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || v_relopts || ' ' || v_tablespace || '; ' || E'\\n'; END IF; ELSE IF v_cnt1 > 0 OR v_cnt2 > 0 THEN v_table_ddl := 'CREATE TABLE ' || in_schema || '."' || in_table || '" PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n'; ELSE v_table_ddl := 'CREATE TABLE ' || in_schema || '.' || in_table || ' PARTITION OF ' || in_schema || '.' || v_parent || ' ' || v_partbound || ' ' || v_tablespace || '; ' || E'\\n'; END IF; END IF; END IF; END IF; IF bVerbose THEN RAISE NOTICE '(1)tabledef so far: %', v_table_ddl; END IF; IF NOT bPartition THEN select c.relpersistence into v_persist from pg_class c, pg_namespace n where n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r'; IF v_persist = 'u' THEN v_temp := 'UNLOGGED'; ELSIF v_persist = 't' THEN v_temp := 'TEMPORARY'; ELSE v_temp := ''; END IF; END IF; IF NOT bPartition THEN SELECT count(*) INTO v_cnt1 FROM information_schema.tables t WHERE EXISTS (SELECT REGEXP_MATCHES(s.table_name, '([A-Z]+)','g') FROM information_schema.tables s WHERE t.table_schema=s.table_schema AND t.table_name=s.table_name AND t.table_schema = in_schema AND t.table_name = in_table AND t.table_type = 'BASE TABLE'); IF v_cnt1 > 0 THEN v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '."' || in_table || '" (' || E'\\n'; ELSE v_table_ddl := 'CREATE ' || v_temp || ' TABLE ' || in_schema || '.' || in_table || ' (' || E'\\n'; END IF; END IF; IF NOT bPartition THEN FOR v_colrec IN SELECT c.column_name, c.data_type, c.udt_name, c.udt_schema, c.character_maximum_length, c.is_nullable, c.column_default, c.numeric_precision, c.numeric_scale, c.is_identity, c.identity_generation, c.is_generated, c.generation_expression FROM information_schema.columns c WHERE (table_schema, table_name) = (in_schema, in_table) ORDER BY ordinal_position LOOP IF bVerbose THEN RAISE NOTICE '(col loop) name=% type=% udt_name=% default=% is_generated=% gen_expr=%', v_colrec.column_name, v_colrec.data_type, v_colrec.udt_name, v_colrec.column_default, v_colrec.is_generated, v_colrec.generation_expression; END IF; SELECT CASE WHEN pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN True ELSE False END into bSerial; IF bVerbose THEN SELECT pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) into v_temp; IF v_temp IS NULL THEN v_temp = 'NA'; END IF; SELECT pg_get_coldef(in_schema, in_table,v_colrec.column_name) INTO v_diag1; RAISE NOTICE 'DEBUG table: % Column: % datatype: % Serial=% serialval=% coldef=%', v_qualified, v_colrec.column_name, v_colrec.data_type, bSerial, v_temp, v_diag1; RAISE NOTICE 'DEBUG tabledef: %', v_table_ddl; END IF; SELECT COUNT(*) INTO v_cnt1 FROM information_schema.columns t WHERE EXISTS (SELECT REGEXP_MATCHES(s.column_name, '([A-Z]+)','g') FROM information_schema.columns s WHERE t.table_schema=s.table_schema and t.table_name=s.table_name and t.column_name=s.column_name AND t.table_schema = quote_ident(in_schema) AND column_name = v_colrec.column_name); SELECT COUNT(*) INTO v_cnt2 FROM pg_get_keywords() WHERE word = v_colrec.column_name AND catcode = 'R'; IF v_cnt1 > 0 OR v_cnt2 > 0 THEN v_table_ddl := v_table_ddl || ' "' || v_colrec.column_name || '" '; ELSE v_table_ddl := v_table_ddl || ' ' || v_colrec.column_name || ' '; END IF; IF v_colrec.is_generated = 'ALWAYS' and v_colrec.generation_expression IS NOT NULL THEN v_temp = v_colrec.data_type || ' GENERATED ALWAYS AS (' || v_colrec.generation_expression || ') STORED '; ELSEIF v_colrec.udt_name in ('geometry', 'box2d', 'box2df', 'box3d', 'geography', 'geometry_dump', 'gidx', 'spheroid', 'valid_detail') THEN v_temp = v_colrec.udt_name; ELSEIF v_colrec.data_type = 'USER-DEFINED' THEN v_temp = v_colrec.udt_schema || '.' || v_colrec.udt_name; ELSEIF v_colrec.data_type = 'ARRAY' THEN v_temp = pg_get_coldef(in_schema, in_table,v_colrec.column_name); ELSEIF pg_get_serial_sequence(quote_ident(in_schema) || '.' || quote_ident(in_table), v_colrec.column_name) IS NOT NULL THEN -- Issue#8 fix: handle serial. Note: NOT NULL is implied so no need to declare it explicitly v_temp = pg_get_coldef(in_schema, in_table,v_colrec.column_name); ELSE v_temp = v_colrec.data_type; END IF; IF v_colrec.is_identity = 'YES' THEN IF v_colrec.identity_generation = 'ALWAYS' THEN v_temp = v_temp || ' GENERATED ALWAYS AS IDENTITY NOT NULL'; ELSE v_temp = v_temp || ' GENERATED BY DEFAULT AS IDENTITY NOT NULL'; END IF; ELSEIF v_colrec.character_maximum_length IS NOT NULL THEN v_temp = v_temp || ('(' || v_colrec.character_maximum_length || ')'); ELSEIF v_colrec.numeric_precision > 0 AND v_colrec.numeric_scale > 0 THEN v_temp = v_temp || '(' || v_colrec.numeric_precision || ',' || v_colrec.numeric_scale || ')'; END IF; IF bSerial THEN v_temp = v_temp || ' NOT NULL'; ELSEIF v_colrec.is_nullable = 'NO' THEN v_temp = v_temp || ' NOT NULL'; ELSEIF v_colrec.is_nullable = 'YES' THEN v_temp = v_temp || ' NULL'; END IF; IF v_colrec.column_default IS NOT null AND NOT bSerial THEN v_temp = v_temp || (' DEFAULT ' || v_colrec.column_default); END IF; v_temp = v_temp || ',' || E'\\n'; v_table_ddl := v_table_ddl || v_temp; END LOOP; END IF; IF bVerbose THEN RAISE NOTICE '(2)tabledef so far: %', v_table_ddl; END IF; IF v_pgversion < 110000 THEN FOR v_constraintrec IN SELECT con.conname as constraint_name, con.contype as constraint_type, CASE WHEN con.contype = 'p' THEN 1 -- primary key constraint WHEN con.contype = 'u' THEN 2 -- unique constraint WHEN con.contype = 'f' THEN 3 -- foreign key constraint WHEN con.contype = 'c' THEN 4 ELSE 5 END as type_rank, pg_get_constraintdef(con.oid) as constraint_definition FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace WHERE nsp.nspname = in_schema AND rel.relname = in_table ORDER BY type_rank LOOP v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; IF v_constraintrec.type_rank = 1 THEN IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; ELSE SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table and r.conname = v_constraint_name; END IF; IF bPartition THEN continue; END IF; ELSIF v_constraintrec.type_rank = 3 THEN IF fktype = 'FKEYS_NONE' THEN continue; ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; ELSE SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table; v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n'; END IF; ELSE v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; END IF; if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; constraintarr := constraintarr || v_constraintrec.constraint_name:: text; END LOOP; ELSE FOR v_constraintrec IN SELECT con.conname as constraint_name, con.contype as constraint_type, CASE WHEN con.contype = 'p' THEN 1 -- primary key constraint WHEN con.contype = 'u' THEN 2 -- unique constraint WHEN con.contype = 'f' THEN 3 -- foreign key constraint WHEN con.contype = 'c' THEN 4 ELSE 5 END as type_rank, pg_get_constraintdef(con.oid) as constraint_definition FROM pg_catalog.pg_constraint con JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace WHERE nsp.nspname = in_schema AND rel.relname = in_table --Issue#13 added this condition: AND con.conparentid = 0 ORDER BY type_rank LOOP v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; IF v_constraintrec.type_rank = 1 THEN IF pkcnt = 0 OR pktype = 'PKEY_INTERNAL' THEN -- internal def v_constraint_name := v_constraintrec.constraint_name; v_constraint_def := v_constraintrec.constraint_definition; v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; ELSE SELECT 'ALTER TABLE ONLY ' || in_schema || '.' || c.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_pkey_def FROM pg_catalog.pg_constraint r, pg_class c, pg_namespace n where r.conrelid = c.oid and r.contype = 'p' and n.oid = r.connamespace and n.nspname = in_schema AND c.relname = in_table; END IF; IF bPartition THEN continue; END IF; ELSIF v_constraintrec.type_rank = 3 THEN IF fktype = 'FKEYS_NONE' THEN -- skip continue; ELSIF fkcnt = 0 OR fktype = 'FKEYS_INTERNAL' THEN -- internal def v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; ELSE SELECT 'ALTER TABLE ONLY ' || n.nspname || '.' || c2.relname || ' ADD CONSTRAINT ' || r.conname || ' ' || pg_catalog.pg_get_constraintdef(r.oid, true) || ';' INTO v_fkey_def FROM pg_constraint r, pg_class c1, pg_namespace n, pg_class c2 where r.conrelid = c1.oid and r.contype = 'f' and n.nspname = in_schema and n.oid = r.connamespace and r.conrelid = c2.oid and c2.relname = in_table and r.conname = v_constraint_name and r.conparentid = 0; v_fkey_defs = v_fkey_defs || v_fkey_def || E'\\n'; END IF; ELSE v_table_ddl := v_table_ddl || ' ' -- note: two char spacer to start, to indent the column || 'CONSTRAINT' || ' ' || v_constraint_name || ' ' || v_constraint_def || ',' || E'\\n'; END IF; if bVerbose THEN RAISE NOTICE 'DEBUG4: constraint name=% constraint_def=%', v_constraint_name,v_constraint_def; END IF; constraintarr := constraintarr || v_constraintrec.constraint_name:: text; END LOOP; END IF; select substring(v_table_ddl, length(v_table_ddl) - 1, 1) INTO v_temp; IF v_temp = ',' THEN v_table_ddl = substr(v_table_ddl, 0, length(v_table_ddl) - 1) || E'\\n'; END IF; IF bVerbose THEN RAISE NOTICE '(3)tabledef so far: %', trim(v_table_ddl); END IF; IF bVerbose THEN RAISE NOTICE '(4)tabledef so far: %', v_table_ddl; END IF; IF bPartition and bInheritance THEN IF v_parent_schema = '' OR v_parent_schema IS NULL THEN v_parent_schema = in_schema; END IF; v_table_ddl := v_table_ddl || ') INHERITS (' || v_parent_schema || '.' || v_parent || ') ' || E'\\n' || v_relopts || ' ' || v_tablespace || ';' || E'\\n'; END IF; IF v_pgversion >= 100000 AND NOT bPartition and NOT bInheritance THEN SELECT pg_get_partkeydef(c1.oid) as partition_key INTO v_partition_key FROM pg_class c1 JOIN pg_namespace n ON (n.oid = c1.relnamespace) LEFT JOIN pg_partitioned_table p ON (c1.oid = p.partrelid) WHERE n.nspname = in_schema and n.oid = c1.relnamespace and c1.relname = in_table and c1.relkind = 'p'; IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN v_table_ddl := v_table_ddl || ') PARTITION BY ' || v_partition_key || ';' || E'\\n'; ELSEIF v_relopts <> '' THEN v_table_ddl := v_table_ddl || ') ' || v_relopts || ' ' || v_tablespace || ';' || E'\\n'; ELSE v_table_ddl := v_table_ddl || ') ' || v_tablespace || ';' || E'\\n'; END IF; END IF; IF bVerbose THEN RAISE NOTICE '(5)tabledef so far: %', v_table_ddl; END IF; IF v_pkey_def <> '' THEN v_table_ddl := v_table_ddl || v_pkey_def || E'\\n'; END IF; IF v_fkey_defs <> '' THEN v_table_ddl := v_table_ddl || v_fkey_defs || E'\\n'; END IF; IF bVerbose THEN RAISE NOTICE '(6)tabledef so far: %', v_table_ddl; END IF; FOR v_indexrec IN SELECT indexdef, COALESCE(tablespace, 'pg_default') as tablespace, indexname FROM pg_indexes WHERE (schemaname, tablename) = (in_schema, in_table) LOOP bSkip = False; FOREACH constraintelement IN ARRAY constraintarr LOOP IF constraintelement = v_indexrec.indexname THEN -- RAISE NOTICE 'DEBUG7: skipping index, %', v_indexrec.indexname; bSkip = True; EXIT; END IF; END LOOP; if bSkip THEN CONTINUE; END IF; v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE INDEX', 'CREATE INDEX IF NOT EXISTS'); v_indexrec.indexdef := REPLACE(v_indexrec.indexdef, 'CREATE UNIQUE INDEX', 'CREATE UNIQUE INDEX IF NOT EXISTS'); IF v_partition_key IS NOT NULL AND v_partition_key <> '' THEN v_table_ddl := v_table_ddl || v_indexrec.indexdef || ';' || E'\\n'; ELSE select CASE WHEN i.indpred IS NOT NULL THEN True ELSE False END INTO v_partial FROM pg_index i JOIN pg_class c1 ON (i.indexrelid = c1.oid) JOIN pg_class c2 ON (i.indrelid = c2.oid) WHERE c1.relnamespace::regnamespace::text = in_schema AND c2.relnamespace::regnamespace::text = in_schema AND c2.relname = in_table AND c1.relname = v_indexrec.indexname; IF v_partial THEN -- Put tablespace def before WHERE CLAUSE v_temp = v_indexrec.indexdef; v_pos = POSITION(' WHERE ' IN v_temp); v_temp2 = SUBSTRING(v_temp, v_pos); v_temp = SUBSTRING(v_temp, 1, v_pos); v_table_ddl := v_table_ddl || v_temp || ' TABLESPACE ' || v_indexrec.tablespace || v_temp2 || ';' || E'\\n'; ELSE v_table_ddl := v_table_ddl || v_indexrec.indexdef || ' TABLESPACE ' || v_indexrec.tablespace || ';' || E'\\n'; END IF; END IF; END LOOP; IF bVerbose THEN RAISE NOTICE '(7)tabledef so far: %', v_table_ddl; END IF; -- Issue#20: added logic for table and column comments IF cmtcnt > 0 THEN FOR v_rec IN SELECT c.relname, 'COMMENT ON ' || CASE WHEN c.relkind in ('r','p') AND a.attname IS NULL THEN 'TABLE ' WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN 'COLUMN ' WHEN c.relkind = 'f' THEN 'FOREIGN TABLE ' WHEN c.relkind = 'm' THEN 'MATERIALIZED VIEW ' WHEN c.relkind = 'v' THEN 'VIEW ' WHEN c.relkind = 'i' THEN 'INDEX ' WHEN c.relkind = 'S' THEN 'SEQUENCE ' ELSE 'XX' END || n.nspname || '.' || CASE WHEN c.relkind in ('r','p') AND a.attname IS NOT NULL THEN quote_ident(c.relname) || '.' || a.attname ELSE quote_ident(c.relname) END || ' IS ' || quote_literal(d.description) || ';' as ddl FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace) LEFT JOIN pg_description d ON (c.oid = d.objoid) LEFT JOIN pg_attribute a ON (c.oid = a.attrelid AND a.attnum > 0 and a.attnum = d.objsubid) WHERE d.description IS NOT NULL AND n.nspname = in_schema AND c.relname = in_table ORDER BY 2 desc, ddl LOOP --RAISE NOTICE 'comments:%', v_rec.ddl; v_table_ddl = v_table_ddl || v_rec.ddl || E'\\n'; END LOOP; END IF; IF bVerbose THEN RAISE NOTICE '(8)tabledef so far: %', v_table_ddl; END IF; IF trigtype = 'INCLUDE_TRIGGERS' THEN -- Issue#14: handle multiple triggers for a table FOR v_trigrec IN select pg_get_triggerdef(t.oid, True) || ';' as triggerdef FROM pg_trigger t, pg_class c, pg_namespace n WHERE n.nspname = in_schema and n.oid = c.relnamespace and c.relname = in_table and c.relkind = 'r' and t.tgrelid = c.oid and NOT t.tgisinternal LOOP v_table_ddl := v_table_ddl || v_trigrec.triggerdef; v_table_ddl := v_table_ddl || E'\\n'; IF bVerbose THEN RAISE NOTICE 'triggerdef = %', v_trigrec.triggerdef; END IF; END LOOP; END IF; IF bVerbose THEN RAISE NOTICE '(9)tabledef so far: %', v_table_ddl; END IF; v_table_ddl := v_table_ddl || E'\\n'; IF bVerbose THEN RAISE NOTICE '(10)tabledef so far: %', v_table_ddl; END IF; IF search_path_old = '' THEN SELECT set_config('search_path', '', false) into v_temp; ELSE EXECUTE 'SET search_path = ' || search_path_old; END IF; RETURN v_table_ddl; EXCEPTION WHEN others THEN BEGIN GET STACKED DIAGNOSTICS v_diag1 = MESSAGE_TEXT, v_diag2 = PG_EXCEPTION_DETAIL, v_diag3 = PG_EXCEPTION_HINT, v_diag4 = RETURNED_SQLSTATE, v_diag5 = PG_CONTEXT, v_diag6 = PG_EXCEPTION_CONTEXT; v_ret := 'line=' || v_diag6 || '. '|| v_diag4 || '. ' || v_diag1; RAISE EXCEPTION '%', v_ret; RETURN ''; END; END; $$;""".indent(1); public static final String DROP_TYPE_SQL = "DROP TYPE IF EXISTS %s.%s CASCADE;"; public static final String ENUM_TYPE_DDL_SQL = """ SELECT 'CREATE TYPE "' || n.nspname || '"."' || t.typname || '" AS ENUM (' || string_agg(quote_literal(e.enumlabel), ', ') || ');' AS ddl FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE t.typtype = 'e' GROUP BY n.nspname, t.typname;"""; public static final String EXPORT_SEQUENCE_DDL_SQL = """ SELECT n.nspname, c.relname, t.typname, a.rolname, obj_description(c.oid, 'pg_class') AS comment, s.seqstart, s.seqincrement, s.seqmax, s.seqmin, s.seqcycle, s.seqcache FROM pg_sequence s JOIN pg_class c ON c.oid = s.seqrelid JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_roles a ON a.oid = c.relowner JOIN pg_type t ON s.seqtypid = t.oid WHERE c.relname = ? AND n.nspname = ?; """; public static final String EXPORT_SEQUENCES_SQL = """ SELECT c.relname, obj_description(c.oid, 'pg_class') AS comment FROM pg_sequence s JOIN pg_class c ON c.oid = s.seqrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ?; """; public static final String EXPORT_USERS_SQL = """ SELECT rolname AS username FROM pg_roles ORDER BY rolname; """; } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/consts/SequenceCommonConst.java ================================================ package ai.chat2db.plugin.postgresql.consts; /** * Sequence operation common constants * * @author Sylphy */ public class SequenceCommonConst { private SequenceCommonConst() { throw new IllegalStateException("Utility class"); } public static final String CREATE_SEQUENCE = "CREATE SEQUENCE "; public static final String START_WITH = "\tSTART WITH "; public static final String INCREMENT_BY = "\tINCREMENT BY "; public static final String MAXVALUE = "\tMAXVALUE "; public static final String MINVALUE = "\tMINVALUE "; public static final String CYCLE = "\tCYCLE "; public static final String NO_CYCLE = "\tNO CYCLE "; public static final String CACHE = "\tCACHE "; public static final String RESTART_WITH = "\tRESTART WITH "; public static final String ALTER_SEQUENCE = "ALTER SEQUENCE "; public static final String COMMENT_ON_SEQUENCE = "COMMENT ON SEQUENCE "; public static final String RENAME_TO = " RENAME TO "; public static final String OWNER_TO = " OWNER TO "; public static final String OWNED_BY = " OWNED BY "; public static final String IS = " IS "; public static final String AS = " AS "; } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json ================================================ { "dbType": "POSTGRESQL", "supportDatabase": true, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:postgresql://localhost:5432/postgres", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/postgresql-42.5.1.jar" ], "jdbcDriver": "postgresql-42.5.1.jar", "jdbcDriverClass": "org.postgresql.Driver" } ], "name": "PostgreSQL" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCharsetEnum.java ================================================ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.model.Charset; import java.util.Arrays; import java.util.List; public enum PostgreSQLCharsetEnum { BIG5("BIG5",null), EUC_CN("EUC_CN",null), EUC_JP("EUC_JP",null), EUC_JIS_2004("EUC_JIS_2004",null), EUC_KR("EUC_KR",null), EUC_TW("EUC_TW",null), GB18030("GB18030",null), GBK("GBK",null), ISO_8859_5("ISO_8859_5",null), ISO_8859_6("ISO_8859_6",null), ISO_8859_7("ISO_8859_7",null), ISO_8859_8("ISO_8859_8",null), JOHAB("JOHAB",null), KOI8R("KOI8R",null), KOI8U("KOI8U",null), LATIN1("LATIN1",null), LATIN2("LATIN2",null), LATIN3("LATIN3",null), LATIN4("LATIN4",null), LATIN5("LATIN5",null), LATIN6("LATIN6",null), LATIN7("LATIN7",null), LATIN8("LATIN8",null), LATIN9("LATIN9",null), LATIN10("LATIN10",null), MULE_INTERNAL("MULE_INTERNAL",null), SJIS("SJIS",null), SHIFT_JIS_2004("SHIFT_JIS_2004",null), SQL_ASCII("SQL_ASCII",null), UHC("UHC",null), UTF8("UTF8",null), WIN866("WIN866",null), WIN874("WIN874",null), WIN1250("WIN1250",null), WIN1251("WIN1251",null), WIN1252("WIN1252",null), WIN1253("WIN1253",null), WIN1254("WIN1254",null), WIN1255("WIN1255",null), WIN1256("WIN1256",null), WIN1257("WIN1257",null), WIN1258("WIN1258",null), ; private Charset charset; PostgreSQLCharsetEnum(String charsetName, String defaultCollationName) { this.charset = new Charset(charsetName, defaultCollationName); } public static List getCharsets() { return Arrays.stream(PostgreSQLCharsetEnum.values()).map(PostgreSQLCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); } public Charset getCharset() { return charset; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCollationEnum.java ================================================ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.model.Collation; import java.util.Arrays; import java.util.List; public enum PostgreSQLCollationEnum { COLLATION1("default"), COLLATION2("C"), COLLATION3("POSIX"), COLLATION4("ucs_basic"), COLLATION5("unicode"), COLLATION6("und-x-icu"), COLLATION7("af-x-icu"), COLLATION8("af-NA-x-icu"), COLLATION9("af-ZA-x-icu"), COLLATION10("agq-x-icu"), COLLATION11("agq-CM-x-icu"), COLLATION12("ak-x-icu"), COLLATION13("ak-GH-x-icu"), COLLATION14("am-x-icu"), COLLATION15("am-ET-x-icu"), COLLATION16("ar-x-icu"), COLLATION17("ar-001-x-icu"), COLLATION18("ar-AE-x-icu"), COLLATION19("ar-BH-x-icu"), COLLATION20("ar-DJ-x-icu"), COLLATION21("ar-DZ-x-icu"), COLLATION22("ar-EG-x-icu"), COLLATION23("ar-EH-x-icu"), COLLATION24("ar-ER-x-icu"), COLLATION25("ar-IL-x-icu"), COLLATION26("ar-IQ-x-icu"), COLLATION27("ar-JO-x-icu"), COLLATION28("ar-KM-x-icu"), COLLATION29("ar-KW-x-icu"), COLLATION30("ar-LB-x-icu"), COLLATION31("ar-LY-x-icu"), COLLATION32("ar-MA-x-icu"), COLLATION33("ar-MR-x-icu"), COLLATION34("ar-OM-x-icu"), COLLATION35("ar-PS-x-icu"), COLLATION36("ar-QA-x-icu"), COLLATION37("ar-SA-x-icu"), COLLATION38("ar-SD-x-icu"), COLLATION39("ar-SO-x-icu"), COLLATION40("ar-SS-x-icu"), COLLATION41("ar-SY-x-icu"), COLLATION42("ar-TD-x-icu"), COLLATION43("ar-TN-x-icu"), COLLATION44("ar-YE-x-icu"), COLLATION45("as-x-icu"), COLLATION46("as-IN-x-icu"), COLLATION47("asa-x-icu"), COLLATION48("asa-TZ-x-icu"), COLLATION49("ast-x-icu"), COLLATION50("ast-ES-x-icu"), COLLATION51("az-x-icu"), COLLATION52("az-Cyrl-x-icu"), COLLATION53("az-Cyrl-AZ-x-icu"), COLLATION54("az-Latn-x-icu"), COLLATION55("az-Latn-AZ-x-icu"), COLLATION56("bas-x-icu"), COLLATION57("bas-CM-x-icu"), COLLATION58("be-x-icu"), COLLATION59("be-BY-x-icu"), COLLATION60("bem-x-icu"), COLLATION61("bem-ZM-x-icu"), COLLATION62("bez-x-icu"), COLLATION63("bez-TZ-x-icu"), COLLATION64("bg-x-icu"), COLLATION65("bg-BG-x-icu"), COLLATION66("bm-x-icu"), COLLATION67("bm-ML-x-icu"), COLLATION68("bn-x-icu"), COLLATION69("bn-BD-x-icu"), COLLATION70("bn-IN-x-icu"), COLLATION71("bo-x-icu"), COLLATION72("bo-CN-x-icu"), COLLATION73("bo-IN-x-icu"), COLLATION74("br-x-icu"), COLLATION75("br-FR-x-icu"), COLLATION76("brx-x-icu"), COLLATION77("brx-IN-x-icu"), COLLATION78("bs-x-icu"), COLLATION79("bs-Cyrl-x-icu"), COLLATION80("bs-Cyrl-BA-x-icu"), COLLATION81("bs-Latn-x-icu"), COLLATION82("bs-Latn-BA-x-icu"), COLLATION83("ca-x-icu"), COLLATION84("ca-AD-x-icu"), COLLATION85("ca-ES-x-icu"), COLLATION86("ca-FR-x-icu"), COLLATION87("ca-IT-x-icu"), COLLATION88("ccp-x-icu"), COLLATION89("ccp-BD-x-icu"), COLLATION90("ccp-IN-x-icu"), COLLATION91("ce-x-icu"), COLLATION92("ce-RU-x-icu"), COLLATION93("ceb-x-icu"), COLLATION94("ceb-PH-x-icu"), COLLATION95("cgg-x-icu"), COLLATION96("cgg-UG-x-icu"), COLLATION97("chr-x-icu"), COLLATION98("chr-US-x-icu"), COLLATION99("ckb-x-icu"), COLLATION100("ckb-IQ-x-icu"), COLLATION101("ckb-IR-x-icu"), COLLATION102("cs-x-icu"), COLLATION103("cs-CZ-x-icu"), COLLATION104("cy-x-icu"), COLLATION105("cy-GB-x-icu"), COLLATION106("da-x-icu"), COLLATION107("da-DK-x-icu"), COLLATION108("da-GL-x-icu"), COLLATION109("dav-x-icu"), COLLATION110("dav-KE-x-icu"), COLLATION111("de-x-icu"), COLLATION112("de-AT-x-icu"), COLLATION113("de-BE-x-icu"), COLLATION114("de-CH-x-icu"), COLLATION115("de-DE-x-icu"), COLLATION116("de-IT-x-icu"), COLLATION117("id-x-icu"), COLLATION118("de-LI-x-icu"), COLLATION119("de-LU-x-icu"), COLLATION120("dje-x-icu"), COLLATION121("dje-NE-x-icu"), COLLATION122("dsb-x-icu"), COLLATION123("dsb-DE-x-icu"), COLLATION124("dua-x-icu"), COLLATION125("dua-CM-x-icu"), COLLATION126("dyo-x-icu"), COLLATION127("dyo-SN-x-icu"), COLLATION128("dz-x-icu"), COLLATION129("dz-BT-x-icu"), COLLATION130("ebu-x-icu"), COLLATION131("ebu-KE-x-icu"), COLLATION132("ee-x-icu"), COLLATION133("ee-GH-x-icu"), COLLATION134("ee-TG-x-icu"), COLLATION135("el-x-icu"), COLLATION136("el-CY-x-icu"), COLLATION137("el-GR-x-icu"), COLLATION138("en-x-icu"), COLLATION139("en-001-x-icu"), COLLATION140("en-150-x-icu"), COLLATION141("en-AE-x-icu"), COLLATION142("en-AG-x-icu"), COLLATION143("en-AI-x-icu"), COLLATION144("en-AS-x-icu"), COLLATION145("en-AT-x-icu"), COLLATION146("en-AU-x-icu"), COLLATION147("en-BB-x-icu"), COLLATION148("en-BE-x-icu"), COLLATION149("en-BI-x-icu"), COLLATION150("en-BM-x-icu"), COLLATION151("en-BS-x-icu"), COLLATION152("en-BW-x-icu"), COLLATION153("en-BZ-x-icu"), COLLATION154("en-CA-x-icu"), COLLATION155("en-CC-x-icu"), COLLATION156("en-CH-x-icu"), COLLATION157("en-CK-x-icu"), COLLATION158("en-CM-x-icu"), COLLATION159("en-CX-x-icu"), COLLATION160("en-CY-x-icu"), COLLATION161("en-DE-x-icu"), COLLATION162("en-DG-x-icu"), COLLATION163("en-DK-x-icu"), COLLATION164("en-DM-x-icu"), COLLATION165("en-ER-x-icu"), COLLATION166("en-FI-x-icu"), COLLATION167("en-FJ-x-icu"), COLLATION168("en-FK-x-icu"), COLLATION169("en-FM-x-icu"), COLLATION170("en-GB-x-icu"), COLLATION171("en-GD-x-icu"), COLLATION172("en-GG-x-icu"), COLLATION173("en-GH-x-icu"), COLLATION174("en-GI-x-icu"), COLLATION175("en-GM-x-icu"), COLLATION176("en-GU-x-icu"), COLLATION177("en-GY-x-icu"), COLLATION178("en-HK-x-icu"), COLLATION179("en-IE-x-icu"), COLLATION180("en-IL-x-icu"), COLLATION181("en-IM-x-icu"), COLLATION182("en-IN-x-icu"), COLLATION183("en-IO-x-icu"), COLLATION184("en-JE-x-icu"), COLLATION185("en-JM-x-icu"), COLLATION186("en-KE-x-icu"), COLLATION187("en-KI-x-icu"), COLLATION188("en-KN-x-icu"), COLLATION189("en-KY-x-icu"), COLLATION190("en-LC-x-icu"), COLLATION191("en-LR-x-icu"), COLLATION192("en-LS-x-icu"), COLLATION193("en-MG-x-icu"), COLLATION194("en-MH-x-icu"), COLLATION195("en-MO-x-icu"), COLLATION196("en-MP-x-icu"), COLLATION197("en-MS-x-icu"), COLLATION198("en-MT-x-icu"), COLLATION199("en-MU-x-icu"), COLLATION200("en-MW-x-icu"), COLLATION201("en-MY-x-icu"), COLLATION202("en-NA-x-icu"), COLLATION203("en-NF-x-icu"), COLLATION204("en-NG-x-icu"), COLLATION205("en-NL-x-icu"), COLLATION206("en-NR-x-icu"), COLLATION207("en-NU-x-icu"), COLLATION208("en-NZ-x-icu"), COLLATION209("en-PG-x-icu"), COLLATION210("en-PH-x-icu"), COLLATION211("en-PK-x-icu"), COLLATION212("en-PN-x-icu"), COLLATION213("en-PR-x-icu"), COLLATION214("en-PW-x-icu"), COLLATION215("en-RW-x-icu"), COLLATION216("en-SB-x-icu"), COLLATION217("en-SC-x-icu"), COLLATION218("en-SD-x-icu"), COLLATION219("en-SE-x-icu"), COLLATION220("en-SG-x-icu"), COLLATION221("en-SH-x-icu"), COLLATION222("en-SI-x-icu"), COLLATION223("en-SL-x-icu"), COLLATION224("en-SS-x-icu"), COLLATION225("en-SX-x-icu"), COLLATION226("en-SZ-x-icu"), COLLATION227("en-TC-x-icu"), COLLATION228("en-TK-x-icu"), COLLATION229("en-TO-x-icu"), COLLATION230("en-TT-x-icu"), COLLATION231("en-TV-x-icu"), COLLATION232("en-TZ-x-icu"), COLLATION233("en-UG-x-icu"), COLLATION234("en-UM-x-icu"), COLLATION235("en-US-x-icu"), COLLATION236("en-US-u-va-posix-x-icu"), COLLATION237("en-VC-x-icu"), COLLATION238("en-VG-x-icu"), COLLATION239("en-VI-x-icu"), COLLATION240("en-VU-x-icu"), COLLATION241("en-WS-x-icu"), COLLATION242("en-ZA-x-icu"), COLLATION243("en-ZM-x-icu"), COLLATION244("en-ZW-x-icu"), COLLATION245("eo-x-icu"), COLLATION246("eo-001-x-icu"), COLLATION247("es-x-icu"), COLLATION248("es-419-x-icu"), COLLATION249("es-AR-x-icu"), COLLATION250("es-BO-x-icu"), COLLATION251("es-BR-x-icu"), COLLATION252("es-BZ-x-icu"), COLLATION253("es-CL-x-icu"), COLLATION254("es-CO-x-icu"), COLLATION255("es-CR-x-icu"), COLLATION256("es-CU-x-icu"), COLLATION257("es-DO-x-icu"), COLLATION258("es-EA-x-icu"), COLLATION259("es-EC-x-icu"), COLLATION260("es-ES-x-icu"), COLLATION261("es-GQ-x-icu"), COLLATION262("es-GT-x-icu"), COLLATION263("es-HN-x-icu"), COLLATION264("es-IC-x-icu"), COLLATION265("es-MX-x-icu"), COLLATION266("es-NI-x-icu"), COLLATION267("es-PA-x-icu"), COLLATION268("es-PE-x-icu"), COLLATION269("es-PH-x-icu"), COLLATION270("es-PR-x-icu"), COLLATION271("es-PY-x-icu"), COLLATION272("es-SV-x-icu"), COLLATION273("es-US-x-icu"), COLLATION274("es-UY-x-icu"), COLLATION275("es-VE-x-icu"), COLLATION276("et-x-icu"), COLLATION277("et-EE-x-icu"), COLLATION278("eu-x-icu"), COLLATION279("eu-ES-x-icu"), COLLATION280("ewo-x-icu"), COLLATION281("ewo-CM-x-icu"), COLLATION282("fa-x-icu"), COLLATION283("fa-AF-x-icu"), COLLATION284("fa-IR-x-icu"), COLLATION285("ff-x-icu"), COLLATION286("ff-Adlm-x-icu"), COLLATION287("ff-Adlm-BF-x-icu"), COLLATION288("ff-Adlm-CM-x-icu"), COLLATION289("ff-Adlm-GH-x-icu"), COLLATION290("ff-Adlm-GM-x-icu"), COLLATION291("ff-Adlm-GN-x-icu"), COLLATION292("ff-Adlm-GW-x-icu"), COLLATION293("ff-Adlm-LR-x-icu"), COLLATION294("ff-Adlm-MR-x-icu"), COLLATION295("ff-Adlm-NE-x-icu"), COLLATION296("ff-Adlm-NG-x-icu"), COLLATION297("ff-Adlm-SL-x-icu"), COLLATION298("ff-Adlm-SN-x-icu"), COLLATION299("ff-Latn-x-icu"), COLLATION300("ff-Latn-BF-x-icu"), COLLATION301("ff-Latn-CM-x-icu"), COLLATION302("ff-Latn-GH-x-icu"), COLLATION303("ff-Latn-GM-x-icu"), COLLATION304("ff-Latn-GN-x-icu"), COLLATION305("ff-Latn-GW-x-icu"), COLLATION306("ff-Latn-LR-x-icu"), COLLATION307("ff-Latn-MR-x-icu"), COLLATION308("ff-Latn-NE-x-icu"), COLLATION309("ff-Latn-NG-x-icu"), COLLATION310("ff-Latn-SL-x-icu"), COLLATION311("ff-Latn-SN-x-icu"), COLLATION312("fi-x-icu"), COLLATION313("fi-FI-x-icu"), COLLATION314("fil-x-icu"), COLLATION315("fil-PH-x-icu"), COLLATION316("fo-x-icu"), COLLATION317("fo-DK-x-icu"), COLLATION318("fo-FO-x-icu"), COLLATION319("fr-x-icu"), COLLATION320("fr-BE-x-icu"), COLLATION321("fr-BF-x-icu"), COLLATION322("fr-BI-x-icu"), COLLATION323("fr-BJ-x-icu"), COLLATION324("fr-BL-x-icu"), COLLATION325("fr-CA-x-icu"), COLLATION326("fr-CD-x-icu"), COLLATION327("fr-CF-x-icu"), COLLATION328("fr-CG-x-icu"), COLLATION329("fr-CH-x-icu"), COLLATION330("fr-CI-x-icu"), COLLATION331("fr-CM-x-icu"), COLLATION332("fr-DJ-x-icu"), COLLATION333("fr-DZ-x-icu"), COLLATION334("fr-FR-x-icu"), COLLATION335("fr-GA-x-icu"), COLLATION336("fr-GF-x-icu"), COLLATION337("fr-GN-x-icu"), COLLATION338("fr-GP-x-icu"), COLLATION339("fr-GQ-x-icu"), COLLATION340("fr-HT-x-icu"), COLLATION341("fr-KM-x-icu"), COLLATION342("fr-LU-x-icu"), COLLATION343("fr-MA-x-icu"), COLLATION344("fr-MC-x-icu"), COLLATION345("fr-MF-x-icu"), COLLATION346("fr-MG-x-icu"), COLLATION347("fr-ML-x-icu"), COLLATION348("fr-MQ-x-icu"), COLLATION349("fr-MR-x-icu"), COLLATION350("fr-MU-x-icu"), COLLATION351("fr-NC-x-icu"), COLLATION352("fr-NE-x-icu"), COLLATION353("fr-PF-x-icu"), COLLATION354("fr-PM-x-icu"), COLLATION355("fr-RE-x-icu"), COLLATION356("fr-RW-x-icu"), COLLATION357("fr-SC-x-icu"), COLLATION358("fr-SN-x-icu"), COLLATION359("fr-SY-x-icu"), COLLATION360("fr-TD-x-icu"), COLLATION361("fr-TG-x-icu"), COLLATION362("fr-TN-x-icu"), COLLATION363("fr-VU-x-icu"), COLLATION364("fr-WF-x-icu"), COLLATION365("fr-YT-x-icu"), COLLATION366("fur-x-icu"), COLLATION367("fur-IT-x-icu"), COLLATION368("fy-x-icu"), COLLATION369("fy-NL-x-icu"), COLLATION370("ga-x-icu"), COLLATION371("ga-GB-x-icu"), COLLATION372("ga-IE-x-icu"), COLLATION373("gd-x-icu"), COLLATION374("gd-GB-x-icu"), COLLATION375("gl-x-icu"), COLLATION376("gl-ES-x-icu"), COLLATION377("gsw-x-icu"), COLLATION378("gsw-CH-x-icu"), COLLATION379("gsw-FR-x-icu"), COLLATION380("gsw-LI-x-icu"), COLLATION381("gu-x-icu"), COLLATION382("gu-IN-x-icu"), COLLATION383("guz-x-icu"), COLLATION384("guz-KE-x-icu"), COLLATION385("gv-x-icu"), COLLATION386("gv-IM-x-icu"), COLLATION387("ha-x-icu"), COLLATION388("ha-GH-x-icu"), COLLATION389("ha-NE-x-icu"), COLLATION390("ha-NG-x-icu"), COLLATION391("haw-x-icu"), COLLATION392("haw-US-x-icu"), COLLATION393("he-x-icu"), COLLATION394("he-IL-x-icu"), COLLATION395("hi-x-icu"), COLLATION396("hi-IN-x-icu"), COLLATION397("hr-x-icu"), COLLATION398("hr-BA-x-icu"), COLLATION399("hr-HR-x-icu"), COLLATION400("hsb-x-icu"), COLLATION401("hsb-DE-x-icu"), COLLATION402("hu-x-icu"), COLLATION403("hu-HU-x-icu"), COLLATION404("hy-x-icu"), COLLATION405("hy-AM-x-icu"), COLLATION406("ia-x-icu"), COLLATION407("ia-001-x-icu"), COLLATION408("id-ID-x-icu"), COLLATION409("ig-x-icu"), COLLATION410("ig-NG-x-icu"), COLLATION411("ii-x-icu"), COLLATION412("ii-CN-x-icu"), COLLATION413("is-x-icu"), COLLATION414("is-IS-x-icu"), COLLATION415("it-x-icu"), COLLATION416("it-CH-x-icu"), COLLATION417("it-IT-x-icu"), COLLATION418("it-SM-x-icu"), COLLATION419("it-VA-x-icu"), COLLATION420("ja-x-icu"), COLLATION421("ja-JP-x-icu"), COLLATION422("jgo-x-icu"), COLLATION423("jgo-CM-x-icu"), COLLATION424("jmc-x-icu"), COLLATION425("jmc-TZ-x-icu"), COLLATION426("jv-x-icu"), COLLATION427("jv-ID-x-icu"), COLLATION428("ka-x-icu"), COLLATION429("ka-GE-x-icu"), COLLATION430("kab-x-icu"), COLLATION431("kab-DZ-x-icu"), COLLATION432("kam-x-icu"), COLLATION433("kam-KE-x-icu"), COLLATION434("kde-x-icu"), COLLATION435("kde-TZ-x-icu"), COLLATION436("kea-x-icu"), COLLATION437("kea-CV-x-icu"), COLLATION438("khq-x-icu"), COLLATION439("khq-ML-x-icu"), COLLATION440("ki-x-icu"), COLLATION441("ki-KE-x-icu"), COLLATION442("kk-x-icu"), COLLATION443("kk-KZ-x-icu"), COLLATION444("kkj-x-icu"), COLLATION445("kkj-CM-x-icu"), COLLATION446("kl-x-icu"), COLLATION447("kl-GL-x-icu"), COLLATION448("kln-x-icu"), COLLATION449("kln-KE-x-icu"), COLLATION450("km-x-icu"), COLLATION451("km-KH-x-icu"), COLLATION452("kn-x-icu"), COLLATION453("kn-IN-x-icu"), COLLATION454("ko-x-icu"), COLLATION455("ko-KP-x-icu"), COLLATION456("ko-KR-x-icu"), COLLATION457("kok-x-icu"), COLLATION458("kok-IN-x-icu"), COLLATION459("ks-x-icu"), COLLATION460("ks-Arab-x-icu"), COLLATION461("ks-Arab-IN-x-icu"), COLLATION462("ksb-x-icu"), COLLATION463("ksb-TZ-x-icu"), COLLATION464("ksf-x-icu"), COLLATION465("ksf-CM-x-icu"), COLLATION466("ksh-x-icu"), COLLATION467("ksh-DE-x-icu"), COLLATION468("ku-x-icu"), COLLATION469("ku-TR-x-icu"), COLLATION470("kw-x-icu"), COLLATION471("kw-GB-x-icu"), COLLATION472("ky-x-icu"), COLLATION473("ky-KG-x-icu"), COLLATION474("lag-x-icu"), COLLATION475("lag-TZ-x-icu"), COLLATION476("lb-x-icu"), COLLATION477("lb-LU-x-icu"), COLLATION478("lg-x-icu"), COLLATION479("lg-UG-x-icu"), COLLATION480("lkt-x-icu"), COLLATION481("lkt-US-x-icu"), COLLATION482("ln-x-icu"), COLLATION483("ln-AO-x-icu"), COLLATION484("ln-CD-x-icu"), COLLATION485("ln-CF-x-icu"), COLLATION486("ln-CG-x-icu"), COLLATION487("lo-x-icu"), COLLATION488("lo-LA-x-icu"), COLLATION489("lrc-x-icu"), COLLATION490("lrc-IQ-x-icu"), COLLATION491("lrc-IR-x-icu"), COLLATION492("lt-x-icu"), COLLATION493("lt-LT-x-icu"), COLLATION494("lu-x-icu"), COLLATION495("lu-CD-x-icu"), COLLATION496("luo-x-icu"), COLLATION497("luo-KE-x-icu"), COLLATION498("luy-x-icu"), COLLATION499("luy-KE-x-icu"), COLLATION500("lv-x-icu"), COLLATION501("lv-LV-x-icu"), COLLATION502("mai-x-icu"), COLLATION503("mai-IN-x-icu"), COLLATION504("mas-x-icu"), COLLATION505("mas-KE-x-icu"), COLLATION506("mas-TZ-x-icu"), COLLATION507("mer-x-icu"), COLLATION508("mer-KE-x-icu"), COLLATION509("mfe-x-icu"), COLLATION510("mfe-MU-x-icu"), COLLATION511("mg-x-icu"), COLLATION512("mg-MG-x-icu"), COLLATION513("mgh-x-icu"), COLLATION514("mgh-MZ-x-icu"), COLLATION515("mgo-x-icu"), COLLATION516("mgo-CM-x-icu"), COLLATION517("mi-x-icu"), COLLATION518("mi-NZ-x-icu"), COLLATION519("mk-x-icu"), COLLATION520("mk-MK-x-icu"), COLLATION521("ml-x-icu"), COLLATION522("ml-IN-x-icu"), COLLATION523("mn-x-icu"), COLLATION524("mn-MN-x-icu"), COLLATION525("mni-x-icu"), COLLATION526("mni-Beng-x-icu"), COLLATION527("mni-Beng-IN-x-icu"), COLLATION528("mr-x-icu"), COLLATION529("mr-IN-x-icu"), COLLATION530("ms-x-icu"), COLLATION531("ms-BN-x-icu"), COLLATION532("ms-ID-x-icu"), COLLATION533("ms-MY-x-icu"), COLLATION534("ms-SG-x-icu"), COLLATION535("mt-x-icu"), COLLATION536("mt-MT-x-icu"), COLLATION537("mua-x-icu"), COLLATION538("mua-CM-x-icu"), COLLATION539("my-x-icu"), COLLATION540("my-MM-x-icu"), COLLATION541("mzn-x-icu"), COLLATION542("mzn-IR-x-icu"), COLLATION543("naq-x-icu"), COLLATION544("naq-NA-x-icu"), COLLATION545("nb-x-icu"), COLLATION546("nb-NO-x-icu"), COLLATION547("nb-SJ-x-icu"), COLLATION548("nd-x-icu"), COLLATION549("nd-ZW-x-icu"), COLLATION550("nds-x-icu"), COLLATION551("nds-DE-x-icu"), COLLATION552("nds-NL-x-icu"), COLLATION553("ne-x-icu"), COLLATION554("ne-IN-x-icu"), COLLATION555("ne-NP-x-icu"), COLLATION556("nl-x-icu"), COLLATION557("nl-AW-x-icu"), COLLATION558("nl-BE-x-icu"), COLLATION559("nl-BQ-x-icu"), COLLATION560("nl-CW-x-icu"), COLLATION561("nl-NL-x-icu"), COLLATION562("nl-SR-x-icu"), COLLATION563("nl-SX-x-icu"), COLLATION564("nmg-x-icu"), COLLATION565("nmg-CM-x-icu"), COLLATION566("nn-x-icu"), COLLATION567("nn-NO-x-icu"), COLLATION568("nnh-x-icu"), COLLATION569("nnh-CM-x-icu"), COLLATION570("nus-x-icu"), COLLATION571("nus-SS-x-icu"), COLLATION572("nyn-x-icu"), COLLATION573("nyn-UG-x-icu"), COLLATION574("om-x-icu"), COLLATION575("om-ET-x-icu"), COLLATION576("om-KE-x-icu"), COLLATION577("or-x-icu"), COLLATION578("or-IN-x-icu"), COLLATION579("os-x-icu"), COLLATION580("os-GE-x-icu"), COLLATION581("os-RU-x-icu"), COLLATION582("pa-x-icu"), COLLATION583("pa-Arab-x-icu"), COLLATION584("pa-Arab-PK-x-icu"), COLLATION585("pa-Guru-x-icu"), COLLATION586("pa-Guru-IN-x-icu"), COLLATION587("pcm-x-icu"), COLLATION588("pcm-NG-x-icu"), COLLATION589("pl-x-icu"), COLLATION590("pl-PL-x-icu"), COLLATION591("ps-x-icu"), COLLATION592("ps-AF-x-icu"), COLLATION593("ps-PK-x-icu"), COLLATION594("pt-x-icu"), COLLATION595("pt-AO-x-icu"), COLLATION596("pt-BR-x-icu"), COLLATION597("pt-CH-x-icu"), COLLATION598("pt-CV-x-icu"), COLLATION599("pt-GQ-x-icu"), COLLATION600("pt-GW-x-icu"), COLLATION601("pt-LU-x-icu"), COLLATION602("pt-MO-x-icu"), COLLATION603("pt-MZ-x-icu"), COLLATION604("pt-PT-x-icu"), COLLATION605("pt-ST-x-icu"), COLLATION606("pt-TL-x-icu"), COLLATION607("qu-x-icu"), COLLATION608("qu-BO-x-icu"), COLLATION609("qu-EC-x-icu"), COLLATION610("qu-PE-x-icu"), COLLATION611("rm-x-icu"), COLLATION612("rm-CH-x-icu"), COLLATION613("rn-x-icu"), COLLATION614("rn-BI-x-icu"), COLLATION615("ro-x-icu"), COLLATION616("ro-MD-x-icu"), COLLATION617("ro-RO-x-icu"), COLLATION618("rof-x-icu"), COLLATION619("rof-TZ-x-icu"), COLLATION620("ru-x-icu"), COLLATION621("ru-BY-x-icu"), COLLATION622("ru-KG-x-icu"), COLLATION623("ru-KZ-x-icu"), COLLATION624("ru-MD-x-icu"), COLLATION625("ru-RU-x-icu"), COLLATION626("ru-UA-x-icu"), COLLATION627("rw-x-icu"), COLLATION628("rw-RW-x-icu"), COLLATION629("rwk-x-icu"), COLLATION630("rwk-TZ-x-icu"), COLLATION631("sah-x-icu"), COLLATION632("sah-RU-x-icu"), COLLATION633("saq-x-icu"), COLLATION634("saq-KE-x-icu"), COLLATION635("sat-x-icu"), COLLATION636("sat-Olck-x-icu"), COLLATION637("sat-Olck-IN-x-icu"), COLLATION638("sbp-x-icu"), COLLATION639("sbp-TZ-x-icu"), COLLATION640("sd-x-icu"), COLLATION641("sd-Arab-x-icu"), COLLATION642("sd-Arab-PK-x-icu"), COLLATION643("sd-Deva-x-icu"), COLLATION644("sd-Deva-IN-x-icu"), COLLATION645("se-x-icu"), COLLATION646("se-FI-x-icu"), COLLATION647("se-NO-x-icu"), COLLATION648("se-SE-x-icu"), COLLATION649("seh-x-icu"), COLLATION650("seh-MZ-x-icu"), COLLATION651("ses-x-icu"), COLLATION652("ses-ML-x-icu"), COLLATION653("sg-x-icu"), COLLATION654("sg-CF-x-icu"), COLLATION655("shi-x-icu"), COLLATION656("shi-Latn-x-icu"), COLLATION657("shi-Latn-MA-x-icu"), COLLATION658("shi-Tfng-x-icu"), COLLATION659("shi-Tfng-MA-x-icu"), COLLATION660("si-x-icu"), COLLATION661("si-LK-x-icu"), COLLATION662("sk-x-icu"), COLLATION663("sk-SK-x-icu"), COLLATION664("sl-x-icu"), COLLATION665("sl-SI-x-icu"), COLLATION666("smn-x-icu"), COLLATION667("smn-FI-x-icu"), COLLATION668("sn-x-icu"), COLLATION669("sn-ZW-x-icu"), COLLATION670("so-x-icu"), COLLATION671("so-DJ-x-icu"), COLLATION672("so-ET-x-icu"), COLLATION673("so-KE-x-icu"), COLLATION674("so-SO-x-icu"), COLLATION675("sq-x-icu"), COLLATION676("sq-AL-x-icu"), COLLATION677("sq-MK-x-icu"), COLLATION678("sq-XK-x-icu"), COLLATION679("sr-x-icu"), COLLATION680("sr-Cyrl-x-icu"), COLLATION681("sr-Cyrl-BA-x-icu"), COLLATION682("sr-Cyrl-ME-x-icu"), COLLATION683("sr-Cyrl-RS-x-icu"), COLLATION684("sr-Cyrl-XK-x-icu"), COLLATION685("sr-Latn-x-icu"), COLLATION686("sr-Latn-BA-x-icu"), COLLATION687("sr-Latn-ME-x-icu"), COLLATION688("sr-Latn-RS-x-icu"), COLLATION689("sr-Latn-XK-x-icu"), COLLATION690("su-x-icu"), COLLATION691("su-Latn-x-icu"), COLLATION692("su-Latn-ID-x-icu"), COLLATION693("sv-x-icu"), COLLATION694("sv-AX-x-icu"), COLLATION695("sv-FI-x-icu"), COLLATION696("sv-SE-x-icu"), COLLATION697("sw-x-icu"), COLLATION698("sw-CD-x-icu"), COLLATION699("sw-KE-x-icu"), COLLATION700("sw-TZ-x-icu"), COLLATION701("sw-UG-x-icu"), COLLATION702("ta-x-icu"), COLLATION703("ta-IN-x-icu"), COLLATION704("ta-LK-x-icu"), COLLATION705("ta-MY-x-icu"), COLLATION706("ta-SG-x-icu"), COLLATION707("te-x-icu"), COLLATION708("te-IN-x-icu"), COLLATION709("teo-x-icu"), COLLATION710("teo-KE-x-icu"), COLLATION711("teo-UG-x-icu"), COLLATION712("tg-x-icu"), COLLATION713("tg-TJ-x-icu"), COLLATION714("th-x-icu"), COLLATION715("th-TH-x-icu"), COLLATION716("ti-x-icu"), COLLATION717("ti-ER-x-icu"), COLLATION718("ti-ET-x-icu"), COLLATION719("tk-x-icu"), COLLATION720("tk-TM-x-icu"), COLLATION721("to-x-icu"), COLLATION722("to-TO-x-icu"), COLLATION723("tr-x-icu"), COLLATION724("tr-CY-x-icu"), COLLATION725("tr-TR-x-icu"), COLLATION726("tt-x-icu"), COLLATION727("tt-RU-x-icu"), COLLATION728("twq-x-icu"), COLLATION729("twq-NE-x-icu"), COLLATION730("tzm-x-icu"), COLLATION731("tzm-MA-x-icu"), COLLATION732("ug-x-icu"), COLLATION733("ug-CN-x-icu"), COLLATION734("uk-x-icu"), COLLATION735("uk-UA-x-icu"), COLLATION736("ur-x-icu"), COLLATION737("ur-IN-x-icu"), COLLATION738("ur-PK-x-icu"), COLLATION739("uz-x-icu"), COLLATION740("uz-Arab-x-icu"), COLLATION741("uz-Arab-AF-x-icu"), COLLATION742("uz-Cyrl-x-icu"), COLLATION743("uz-Cyrl-UZ-x-icu"), COLLATION744("uz-Latn-x-icu"), COLLATION745("uz-Latn-UZ-x-icu"), COLLATION746("vai-x-icu"), COLLATION747("vai-Latn-x-icu"), COLLATION748("vai-Latn-LR-x-icu"), COLLATION749("vai-Vaii-x-icu"), COLLATION750("vai-Vaii-LR-x-icu"), COLLATION751("vi-x-icu"), COLLATION752("vi-VN-x-icu"), COLLATION753("vun-x-icu"), COLLATION754("vun-TZ-x-icu"), COLLATION755("wae-x-icu"), COLLATION756("wae-CH-x-icu"), COLLATION757("wo-x-icu"), COLLATION758("wo-SN-x-icu"), COLLATION759("xh-x-icu"), COLLATION760("xh-ZA-x-icu"), COLLATION761("xog-x-icu"), COLLATION762("xog-UG-x-icu"), COLLATION763("yav-x-icu"), COLLATION764("yav-CM-x-icu"), COLLATION765("yi-x-icu"), COLLATION766("yi-001-x-icu"), COLLATION767("yo-x-icu"), COLLATION768("yo-BJ-x-icu"), COLLATION769("yo-NG-x-icu"), COLLATION770("yue-x-icu"), COLLATION771("yue-Hans-x-icu"), COLLATION772("yue-Hans-CN-x-icu"), COLLATION773("yue-Hant-x-icu"), COLLATION774("yue-Hant-HK-x-icu"), COLLATION775("zgh-x-icu"), COLLATION776("zgh-MA-x-icu"), COLLATION777("zh-x-icu"), COLLATION778("zh-Hans-x-icu"), COLLATION779("zh-Hans-CN-x-icu"), COLLATION780("zh-Hans-HK-x-icu"), COLLATION781("zh-Hans-MO-x-icu"), COLLATION782("zh-Hans-SG-x-icu"), COLLATION783("zh-Hant-x-icu"), COLLATION784("zh-Hant-HK-x-icu"), COLLATION785("zh-Hant-MO-x-icu"), COLLATION786("zh-Hant-TW-x-icu"), COLLATION787("zu-x-icu"), COLLATION788("zu-ZA-x-icu"), COLLATION789("aa"), COLLATION790("aa-DJ"), COLLATION791("aa_DJ"), COLLATION792("aa-ER"), COLLATION793("aa_ER"), COLLATION794("aa-ET"), COLLATION795("aa_ET"), COLLATION796("af"), COLLATION797("af-NA"), COLLATION798("af_NA"), COLLATION799("af-ZA"), COLLATION800("af_ZA"), COLLATION801("agq"), COLLATION802("agq-CM"), COLLATION803("agq_CM"), COLLATION804("ak"), COLLATION805("ak-GH"), COLLATION806("ak_GH"), COLLATION807("am"), COLLATION808("am-ET"), COLLATION809("am_ET"), COLLATION810("ar"), COLLATION811("ar-001"), COLLATION812("ar_001"), COLLATION813("ar-AE"), COLLATION814("ar_AE"), COLLATION815("ar-BH"), COLLATION816("ar_BH"), COLLATION817("ar-DJ"), COLLATION818("ar_DJ"), COLLATION819("ar-DZ"), COLLATION820("ar_DZ"), COLLATION821("ar-EG"), COLLATION822("ar_EG"), COLLATION823("ar-ER"), COLLATION824("ar_ER"), COLLATION825("ar-IL"), COLLATION826("ar_IL"), COLLATION827("ar-IQ"), COLLATION828("ar_IQ"), COLLATION829("ar-JO"), COLLATION830("ar_JO"), COLLATION831("ar-KM"), COLLATION832("ar_KM"), COLLATION833("ar-KW"), COLLATION834("ar_KW"), COLLATION835("ar-LB"), COLLATION836("ar_LB"), COLLATION837("ar-LY"), COLLATION838("ar_LY"), COLLATION839("ar-MA"), COLLATION840("ar_MA"), COLLATION841("ar-MR"), COLLATION842("ar_MR"), COLLATION843("ar-OM"), COLLATION844("ar_OM"), COLLATION845("ar-PS"), COLLATION846("ar_PS"), COLLATION847("ar-QA"), COLLATION848("ar_QA"), COLLATION849("ar-SA"), COLLATION850("ar_SA"), COLLATION851("ar-SD"), COLLATION852("ar_SD"), COLLATION853("ar-SO"), COLLATION854("ar_SO"), COLLATION855("ar-SS"), COLLATION856("ar_SS"), COLLATION857("ar-SY"), COLLATION858("ar_SY"), COLLATION859("ar-TD"), COLLATION860("ar_TD"), COLLATION861("ar-TN"), COLLATION862("ar_TN"), COLLATION863("ar-YE"), COLLATION864("ar_YE"), COLLATION865("arn"), COLLATION866("arn-CL"), COLLATION867("arn_CL"), COLLATION868("as"), COLLATION869("as-IN"), COLLATION870("as_IN"), COLLATION871("asa"), COLLATION872("asa-TZ"), COLLATION873("asa_TZ"), COLLATION874("ast"), COLLATION875("ast-ES"), COLLATION876("ast_ES"), COLLATION877("az"), COLLATION878("az-Cyrl"), COLLATION879("az_Cyrl"), COLLATION880("az-Cyrl-AZ"), COLLATION881("az_Cyrl_AZ"), COLLATION882("az-Latn"), COLLATION883("az_Latn"), COLLATION884("az-Latn-AZ"), COLLATION885("az_Latn_AZ"), COLLATION886("ba"), COLLATION887("ba-RU"), COLLATION888("ba_RU"), COLLATION889("bas"), COLLATION890("bas-CM"), COLLATION891("bas_CM"), COLLATION892("be"), COLLATION893("be-BY"), COLLATION894("be_BY"), COLLATION895("bem"), COLLATION896("bem-ZM"), COLLATION897("bem_ZM"), COLLATION898("bez"), COLLATION899("bez-TZ"), COLLATION900("bez_TZ"), COLLATION901("bg"), COLLATION902("bg-BG"), COLLATION903("bg_BG"), COLLATION904("bin"), COLLATION905("bin-NG"), COLLATION906("bin_NG"), COLLATION907("bm"), COLLATION908("bm-Latn"), COLLATION909("bm_Latn"), COLLATION910("bm-Latn-ML"), COLLATION911("bm_Latn_ML"), COLLATION912("bn"), COLLATION913("bn-BD"), COLLATION914("bn_BD"), COLLATION915("bn-IN"), COLLATION916("bn_IN"), COLLATION917("bo"), COLLATION918("bo-CN"), COLLATION919("bo_CN"), COLLATION920("bo-IN"), COLLATION921("bo_IN"), COLLATION922("br"), COLLATION923("br-FR"), COLLATION924("br_FR"), COLLATION925("brx"), COLLATION926("brx-IN"), COLLATION927("brx_IN"), COLLATION928("bs"), COLLATION929("bs-Cyrl"), COLLATION930("bs_Cyrl"), COLLATION931("bs-Cyrl-BA"), COLLATION932("bs_Cyrl_BA"), COLLATION933("bs-Latn"), COLLATION934("bs_Latn"), COLLATION935("bs-Latn-BA"), COLLATION936("bs_Latn_BA"), COLLATION937("byn"), COLLATION938("byn-ER"), COLLATION939("byn_ER"), COLLATION940("ca"), COLLATION941("ca-AD"), COLLATION942("ca_AD"), COLLATION943("ca-ES"), COLLATION944("ca_ES"), COLLATION945("ca-ES-valencia"), COLLATION946("ca_ES_valencia"), COLLATION947("ca-FR"), COLLATION948("ca_FR"), COLLATION949("ca-IT"), COLLATION950("ca_IT"), COLLATION951("ccp"), COLLATION952("ccp-Cakm"), COLLATION953("ccp_Cakm"), COLLATION954("ccp-Cakm-BD"), COLLATION955("ccp_Cakm_BD"), COLLATION956("ccp-Cakm-IN"), COLLATION957("ccp_Cakm_IN"), COLLATION958("ce"), COLLATION959("ce-RU"), COLLATION960("ce_RU"), COLLATION961("ceb"), COLLATION962("ceb-Latn"), COLLATION963("ceb_Latn"), COLLATION964("ceb-Latn-PH"), COLLATION965("ceb_Latn_PH"), COLLATION966("cgg"), COLLATION967("cgg-UG"), COLLATION968("cgg_UG"), COLLATION969("chr"), COLLATION970("chr-Cher"), COLLATION971("chr_Cher"), COLLATION972("chr-Cher-US"), COLLATION973("chr_Cher_US"), COLLATION974("co"), COLLATION975("co-FR"), COLLATION976("co_FR"), COLLATION977("cs"), COLLATION978("cs-CZ"), COLLATION979("cs_CZ"), COLLATION980("cu"), COLLATION981("cu-RU"), COLLATION982("cu_RU"), COLLATION983("cy"), COLLATION984("cy-GB"), COLLATION985("cy_GB"), COLLATION986("da"), COLLATION987("da-DK"), COLLATION988("da_DK"), COLLATION989("da-GL"), COLLATION990("da_GL"), COLLATION991("dav"), COLLATION992("dav-KE"), COLLATION993("dav_KE"), COLLATION994("de"), COLLATION995("de-AT"), COLLATION996("de_AT"), COLLATION997("de-BE"), COLLATION998("de_BE"), COLLATION999("de-CH"), COLLATION1000("de_CH"), COLLATION1001("de-DE"), COLLATION1002("de_DE"), COLLATION1003("de-DE_phoneb"), COLLATION1004("de_DE_phoneb"), COLLATION1005("de-IT"), COLLATION1006("de_IT"), COLLATION1007("de-LI"), COLLATION1008("de_LI"), COLLATION1009("de-LU"), COLLATION1010("de_LU"), COLLATION1011("dje"), COLLATION1012("dje-NE"), COLLATION1013("dje_NE"), COLLATION1014("dsb"), COLLATION1015("dsb-DE"), COLLATION1016("dsb_DE"), COLLATION1017("dua"), COLLATION1018("dua-CM"), COLLATION1019("dua_CM"), COLLATION1020("dv"), COLLATION1021("dv-MV"), COLLATION1022("dv_MV"), COLLATION1023("dyo"), COLLATION1024("dyo-SN"), COLLATION1025("dyo_SN"), COLLATION1026("dz"), COLLATION1027("dz-BT"), COLLATION1028("dz_BT"), COLLATION1029("ebu"), COLLATION1030("ebu-KE"), COLLATION1031("ebu_KE"), COLLATION1032("ee"), COLLATION1033("ee-GH"), COLLATION1034("ee_GH"), COLLATION1035("ee-TG"), COLLATION1036("ee_TG"), COLLATION1037("el"), COLLATION1038("el-CY"), COLLATION1039("el_CY"), COLLATION1040("el-GR"), COLLATION1041("el_GR"), COLLATION1042("en"), COLLATION1043("en-001"), COLLATION1044("en_001"), COLLATION1045("en-029"), COLLATION1046("en_029"), COLLATION1047("en-150"), COLLATION1048("en_150"), COLLATION1049("en-AE"), COLLATION1050("en_AE"), COLLATION1051("en-AG"), COLLATION1052("en_AG"), COLLATION1053("en-AI"), COLLATION1054("en_AI"), COLLATION1055("en-AS"), COLLATION1056("en_AS"), COLLATION1057("en-AT"), COLLATION1058("en_AT"), COLLATION1059("en-AU"), COLLATION1060("en_AU"), COLLATION1061("en-BB"), COLLATION1062("en_BB"), COLLATION1063("en-BE"), COLLATION1064("en_BE"), COLLATION1065("en-BI"), COLLATION1066("en_BI"), COLLATION1067("en-BM"), COLLATION1068("en_BM"), COLLATION1069("en-BS"), COLLATION1070("en_BS"), COLLATION1071("en-BW"), COLLATION1072("en_BW"), COLLATION1073("en-BZ"), COLLATION1074("en_BZ"), COLLATION1075("en-CA"), COLLATION1076("en_CA"), COLLATION1077("en-CC"), COLLATION1078("en_CC"), COLLATION1079("en-CH"), COLLATION1080("en_CH"), COLLATION1081("en-CK"), COLLATION1082("en_CK"), COLLATION1083("en-CM"), COLLATION1084("en_CM"), COLLATION1085("en-CX"), COLLATION1086("en_CX"), COLLATION1087("en-CY"), COLLATION1088("en_CY"), COLLATION1089("en-DE"), COLLATION1090("en_DE"), COLLATION1091("en-DK"), COLLATION1092("en_DK"), COLLATION1093("en-DM"), COLLATION1094("en_DM"), COLLATION1095("en-ER"), COLLATION1096("en_ER"), COLLATION1097("en-FI"), COLLATION1098("en_FI"), COLLATION1099("en-FJ"), COLLATION1100("en_FJ"), COLLATION1101("en-FK"), COLLATION1102("en_FK"), COLLATION1103("en-FM"), COLLATION1104("en_FM"), COLLATION1105("en-GB"), COLLATION1106("en_GB"), COLLATION1107("en-GD"), COLLATION1108("en_GD"), COLLATION1109("en-GG"), COLLATION1110("en_GG"), COLLATION1111("en-GH"), COLLATION1112("en_GH"), COLLATION1113("en-GI"), COLLATION1114("en_GI"), COLLATION1115("en-GM"), COLLATION1116("en_GM"), COLLATION1117("en-GU"), COLLATION1118("en_GU"), COLLATION1119("en-GY"), COLLATION1120("en_GY"), COLLATION1121("en-HK"), COLLATION1122("en_HK"), COLLATION1123("en-ID"), COLLATION1124("en_ID"), COLLATION1125("en-IE"), COLLATION1126("en_IE"), COLLATION1127("en-IL"), COLLATION1128("en_IL"), COLLATION1129("en-IM"), COLLATION1130("en_IM"), COLLATION1131("en-IN"), COLLATION1132("en_IN"), COLLATION1133("en-IO"), COLLATION1134("en_IO"), COLLATION1135("en-JE"), COLLATION1136("en_JE"), COLLATION1137("en-JM"), COLLATION1138("en_JM"), COLLATION1139("en-KE"), COLLATION1140("en_KE"), COLLATION1141("en-KI"), COLLATION1142("en_KI"), COLLATION1143("en-KN"), COLLATION1144("en_KN"), COLLATION1145("en-KY"), COLLATION1146("en_KY"), COLLATION1147("en-LC"), COLLATION1148("en_LC"), COLLATION1149("en-LR"), COLLATION1150("en_LR"), COLLATION1151("en-LS"), COLLATION1152("en_LS"), COLLATION1153("en-MG"), COLLATION1154("en_MG"), COLLATION1155("en-MH"), COLLATION1156("en_MH"), COLLATION1157("en-MO"), COLLATION1158("en_MO"), COLLATION1159("en-MP"), COLLATION1160("en_MP"), COLLATION1161("en-MS"), COLLATION1162("en_MS"), COLLATION1163("en-MT"), COLLATION1164("en_MT"), COLLATION1165("en-MU"), COLLATION1166("en_MU"), COLLATION1167("en-MW"), COLLATION1168("en_MW"), COLLATION1169("en-MY"), COLLATION1170("en_MY"), COLLATION1171("en-NA"), COLLATION1172("en_NA"), COLLATION1173("en-NF"), COLLATION1174("en_NF"), COLLATION1175("en-NG"), COLLATION1176("en_NG"), COLLATION1177("en-NL"), COLLATION1178("en_NL"), COLLATION1179("en-NR"), COLLATION1180("en_NR"), COLLATION1181("en-NU"), COLLATION1182("en_NU"), COLLATION1183("en-NZ"), COLLATION1184("en_NZ"), COLLATION1185("en-PG"), COLLATION1186("en_PG"), COLLATION1187("en-PH"), COLLATION1188("en_PH"), COLLATION1189("en-PK"), COLLATION1190("en_PK"), COLLATION1191("en-PN"), COLLATION1192("en_PN"), COLLATION1193("en-PR"), COLLATION1194("en_PR"), COLLATION1195("en-PW"), COLLATION1196("en_PW"), COLLATION1197("en-RW"), COLLATION1198("en_RW"), COLLATION1199("en-SB"), COLLATION1200("en_SB"), COLLATION1201("en-SC"), COLLATION1202("en_SC"), COLLATION1203("en-SD"), COLLATION1204("en_SD"), COLLATION1205("en-SE"), COLLATION1206("en_SE"), COLLATION1207("en-SG"), COLLATION1208("en_SG"), COLLATION1209("en-SH"), COLLATION1210("en_SH"), COLLATION1211("en-SI"), COLLATION1212("en_SI"), COLLATION1213("en-SL"), COLLATION1214("en_SL"), COLLATION1215("en-SS"), COLLATION1216("en_SS"), COLLATION1217("en-SX"), COLLATION1218("en_SX"), COLLATION1219("en-SZ"), COLLATION1220("en_SZ"), COLLATION1221("en-TC"), COLLATION1222("en_TC"), COLLATION1223("en-TK"), COLLATION1224("en_TK"), COLLATION1225("en-TO"), COLLATION1226("en_TO"), COLLATION1227("en-TT"), COLLATION1228("en_TT"), COLLATION1229("en-TV"), COLLATION1230("en_TV"), COLLATION1231("en-TZ"), COLLATION1232("en_TZ"), COLLATION1233("en-UG"), COLLATION1234("en_UG"), COLLATION1235("en-UM"), COLLATION1236("en_UM"), COLLATION1237("en-US"), COLLATION1238("en_US"), COLLATION1239("en-VC"), COLLATION1240("en_VC"), COLLATION1241("en-VG"), COLLATION1242("en_VG"), COLLATION1243("en-VI"), COLLATION1244("en_VI"), COLLATION1245("en-VU"), COLLATION1246("en_VU"), COLLATION1247("en-WS"), COLLATION1248("en_WS"), COLLATION1249("en-ZA"), COLLATION1250("en_ZA"), COLLATION1251("en-ZM"), COLLATION1252("en_ZM"), COLLATION1253("en-ZW"), COLLATION1254("en_ZW"), COLLATION1255("eo"), COLLATION1256("eo-001"), COLLATION1257("eo_001"), COLLATION1258("es"), COLLATION1259("es-419"), COLLATION1260("es_419"), COLLATION1261("es-AR"), COLLATION1262("es_AR"), COLLATION1263("es-BO"), COLLATION1264("es_BO"), COLLATION1265("es-BR"), COLLATION1266("es_BR"), COLLATION1267("es-BZ"), COLLATION1268("es_BZ"), COLLATION1269("es-CL"), COLLATION1270("es_CL"), COLLATION1271("es-CO"), COLLATION1272("es_CO"), COLLATION1273("es-CR"), COLLATION1274("es_CR"), COLLATION1275("es-CU"), COLLATION1276("es_CU"), COLLATION1277("es-DO"), COLLATION1278("es_DO"), COLLATION1279("es-EC"), COLLATION1280("es_EC"), COLLATION1281("es-ES"), COLLATION1282("es_ES"), COLLATION1283("es-ES_tradnl"), COLLATION1284("es_ES_tradnl"), COLLATION1285("es-GQ"), COLLATION1286("es_GQ"), COLLATION1287("es-GT"), COLLATION1288("es_GT"), COLLATION1289("es-HN"), COLLATION1290("es_HN"), COLLATION1291("es-MX"), COLLATION1292("es_MX"), COLLATION1293("es-NI"), COLLATION1294("es_NI"), COLLATION1295("es-PA"), COLLATION1296("es_PA"), COLLATION1297("es-PE"), COLLATION1298("es_PE"), COLLATION1299("es-PH"), COLLATION1300("es_PH"), COLLATION1301("es-PR"), COLLATION1302("es_PR"), COLLATION1303("es-PY"), COLLATION1304("es_PY"), COLLATION1305("es-SV"), COLLATION1306("es_SV"), COLLATION1307("es-US"), COLLATION1308("es_US"), COLLATION1309("es-UY"), COLLATION1310("es_UY"), COLLATION1311("es-VE"), COLLATION1312("es_VE"), COLLATION1313("et"), COLLATION1314("et-EE"), COLLATION1315("et_EE"), COLLATION1316("eu"), COLLATION1317("eu-ES"), COLLATION1318("eu_ES"), COLLATION1319("ewo"), COLLATION1320("ewo-CM"), COLLATION1321("ewo_CM"), COLLATION1322("fa"), COLLATION1323("fa-IR"), COLLATION1324("fa_IR"), COLLATION1325("ff"), COLLATION1326("ff-Latn"), COLLATION1327("ff_Latn"), COLLATION1328("ff-Latn-BF"), COLLATION1329("ff_Latn_BF"), COLLATION1330("ff-Latn-CM"), COLLATION1331("ff_Latn_CM"), COLLATION1332("ff-Latn-GH"), COLLATION1333("ff_Latn_GH"), COLLATION1334("ff-Latn-GM"), COLLATION1335("ff_Latn_GM"), COLLATION1336("ff-Latn-GN"), COLLATION1337("ff_Latn_GN"), COLLATION1338("ff-Latn-GW"), COLLATION1339("ff_Latn_GW"), COLLATION1340("ff-Latn-LR"), COLLATION1341("ff_Latn_LR"), COLLATION1342("ff-Latn-MR"), COLLATION1343("ff_Latn_MR"), COLLATION1344("ff-Latn-NE"), COLLATION1345("ff_Latn_NE"), COLLATION1346("ff-Latn-NG"), COLLATION1347("ff_Latn_NG"), COLLATION1348("ff-Latn-SL"), COLLATION1349("ff_Latn_SL"), COLLATION1350("ff-Latn-SN"), COLLATION1351("ff_Latn_SN"), COLLATION1352("fi"), COLLATION1353("fi-FI"), COLLATION1354("fi_FI"), COLLATION1355("fil"), COLLATION1356("fil-PH"), COLLATION1357("fil_PH"), COLLATION1358("fo"), COLLATION1359("fo-DK"), COLLATION1360("fo_DK"), COLLATION1361("fo-FO"), COLLATION1362("fo_FO"), COLLATION1363("fr"), COLLATION1364("fr-029"), COLLATION1365("fr_029"), COLLATION1366("fr-BE"), COLLATION1367("fr_BE"), COLLATION1368("fr-BF"), COLLATION1369("fr_BF"), COLLATION1370("fr-BI"), COLLATION1371("fr_BI"), COLLATION1372("fr-BJ"), COLLATION1373("fr_BJ"), COLLATION1374("fr-BL"), COLLATION1375("fr_BL"), COLLATION1376("fr-CA"), COLLATION1377("fr_CA"), COLLATION1378("fr-CD"), COLLATION1379("fr_CD"), COLLATION1380("fr-CF"), COLLATION1381("fr_CF"), COLLATION1382("fr-CG"), COLLATION1383("fr_CG"), COLLATION1384("fr-CH"), COLLATION1385("fr_CH"), COLLATION1386("fr-CI"), COLLATION1387("fr_CI"), COLLATION1388("fr-CM"), COLLATION1389("fr_CM"), COLLATION1390("fr-DJ"), COLLATION1391("fr_DJ"), COLLATION1392("fr-DZ"), COLLATION1393("fr_DZ"), COLLATION1394("fr-FR"), COLLATION1395("fr_FR"), COLLATION1396("fr-GA"), COLLATION1397("fr_GA"), COLLATION1398("fr-GF"), COLLATION1399("fr_GF"), COLLATION1400("fr-GN"), COLLATION1401("fr_GN"), COLLATION1402("fr-GP"), COLLATION1403("fr_GP"), COLLATION1404("fr-GQ"), COLLATION1405("fr_GQ"), COLLATION1406("fr-HT"), COLLATION1407("fr_HT"), COLLATION1408("fr-KM"), COLLATION1409("fr_KM"), COLLATION1410("fr-LU"), COLLATION1411("fr_LU"), COLLATION1412("fr-MA"), COLLATION1413("fr_MA"), COLLATION1414("fr-MC"), COLLATION1415("fr_MC"), COLLATION1416("fr-MF"), COLLATION1417("fr_MF"), COLLATION1418("fr-MG"), COLLATION1419("fr_MG"), COLLATION1420("fr-ML"), COLLATION1421("fr_ML"), COLLATION1422("fr-MQ"), COLLATION1423("fr_MQ"), COLLATION1424("fr-MR"), COLLATION1425("fr_MR"), COLLATION1426("fr-MU"), COLLATION1427("fr_MU"), COLLATION1428("fr-NC"), COLLATION1429("fr_NC"), COLLATION1430("fr-NE"), COLLATION1431("fr_NE"), COLLATION1432("fr-PF"), COLLATION1433("fr_PF"), COLLATION1434("fr-PM"), COLLATION1435("fr_PM"), COLLATION1436("fr-RE"), COLLATION1437("fr_RE"), COLLATION1438("fr-RW"), COLLATION1439("fr_RW"), COLLATION1440("fr-SC"), COLLATION1441("fr_SC"), COLLATION1442("fr-SN"), COLLATION1443("fr_SN"), COLLATION1444("fr-SY"), COLLATION1445("fr_SY"), COLLATION1446("fr-TD"), COLLATION1447("fr_TD"), COLLATION1448("fr-TG"), COLLATION1449("fr_TG"), COLLATION1450("fr-TN"), COLLATION1451("fr_TN"), COLLATION1452("fr-VU"), COLLATION1453("fr_VU"), COLLATION1454("fr-WF"), COLLATION1455("fr_WF"), COLLATION1456("fr-YT"), COLLATION1457("fr_YT"), COLLATION1458("fur"), COLLATION1459("fur-IT"), COLLATION1460("fur_IT"), COLLATION1461("fy"), COLLATION1462("fy-NL"), COLLATION1463("fy_NL"), COLLATION1464("ga"), COLLATION1465("ga-IE"), COLLATION1466("ga_IE"), COLLATION1467("gd"), COLLATION1468("gd-GB"), COLLATION1469("gd_GB"), COLLATION1470("gl"), COLLATION1471("gl-ES"), COLLATION1472("gl_ES"), COLLATION1473("gn"), COLLATION1474("gn-PY"), COLLATION1475("gn_PY"), COLLATION1476("gsw"), COLLATION1477("gsw-CH"), COLLATION1478("gsw_CH"), COLLATION1479("gsw-FR"), COLLATION1480("gsw_FR"), COLLATION1481("gsw-LI"), COLLATION1482("gsw_LI"), COLLATION1483("gu"), COLLATION1484("gu-IN"), COLLATION1485("gu_IN"), COLLATION1486("guz"), COLLATION1487("guz-KE"), COLLATION1488("guz_KE"), COLLATION1489("gv"), COLLATION1490("gv-IM"), COLLATION1491("gv_IM"), COLLATION1492("ha"), COLLATION1493("ha-Latn"), COLLATION1494("ha_Latn"), COLLATION1495("ha-Latn-GH"), COLLATION1496("ha_Latn_GH"), COLLATION1497("ha-Latn-NE"), COLLATION1498("ha_Latn_NE"), COLLATION1499("ha-Latn-NG"), COLLATION1500("ha_Latn_NG"), COLLATION1501("haw"), COLLATION1502("haw-US"), COLLATION1503("haw_US"), COLLATION1504("he"), COLLATION1505("he-IL"), COLLATION1506("he_IL"), COLLATION1507("hi"), COLLATION1508("hi-IN"), COLLATION1509("hi_IN"), COLLATION1510("hr"), COLLATION1511("hr-BA"), COLLATION1512("hr_BA"), COLLATION1513("hr-HR"), COLLATION1514("hr_HR"), COLLATION1515("hsb"), COLLATION1516("hsb-DE"), COLLATION1517("hsb_DE"), COLLATION1518("hu"), COLLATION1519("hu-HU"), COLLATION1520("hu_HU"), COLLATION1521("hu-HU_technl"), COLLATION1522("hu_HU_technl"), COLLATION1523("hy"), COLLATION1524("hy-AM"), COLLATION1525("hy_AM"), COLLATION1526("ia"), COLLATION1527("ia-001"), COLLATION1528("ia_001"), COLLATION1529("ibb"), COLLATION1530("ibb-NG"), COLLATION1531("ibb_NG"), COLLATION1532("id"), COLLATION1533("id-ID"), COLLATION1534("id_ID"), COLLATION1535("ig"), COLLATION1536("ig-NG"), COLLATION1537("ig_NG"), COLLATION1538("ii"), COLLATION1539("ii-CN"), COLLATION1540("ii_CN"), COLLATION1541("is"), COLLATION1542("is-IS"), COLLATION1543("is_IS"), COLLATION1544("it"), COLLATION1545("it-CH"), COLLATION1546("it_CH"), COLLATION1547("it-IT"), COLLATION1548("it_IT"), COLLATION1549("it-SM"), COLLATION1550("it_SM"), COLLATION1551("it-VA"), COLLATION1552("it_VA"), COLLATION1553("iu"), COLLATION1554("iu-Cans"), COLLATION1555("iu_Cans"), COLLATION1556("iu-Cans-CA"), COLLATION1557("iu_Cans_CA"), COLLATION1558("iu-Latn"), COLLATION1559("iu_Latn"), COLLATION1560("iu-Latn-CA"), COLLATION1561("iu_Latn_CA"), COLLATION1562("jgo"), COLLATION1563("jgo-CM"), COLLATION1564("jgo_CM"), COLLATION1565("jmc"), COLLATION1566("jmc-TZ"), COLLATION1567("jmc_TZ"), COLLATION1568("jv"), COLLATION1569("jv-Java"), COLLATION1570("jv_Java"), COLLATION1571("jv-Java-ID"), COLLATION1572("jv_Java_ID"), COLLATION1573("jv-Latn"), COLLATION1574("jv_Latn"), COLLATION1575("jv-Latn-ID"), COLLATION1576("jv_Latn_ID"), COLLATION1577("ka"), COLLATION1578("ka-GE"), COLLATION1579("ka_GE"), COLLATION1580("ka-GE_modern"), COLLATION1581("ka_GE_modern"), COLLATION1582("kab"), COLLATION1583("kab-DZ"), COLLATION1584("kab_DZ"), COLLATION1585("kam"), COLLATION1586("kam-KE"), COLLATION1587("kam_KE"), COLLATION1588("kde"), COLLATION1589("kde-TZ"), COLLATION1590("kde_TZ"), COLLATION1591("kea"), COLLATION1592("kea-CV"), COLLATION1593("kea_CV"), COLLATION1594("khq"), COLLATION1595("khq-ML"), COLLATION1596("khq_ML"), COLLATION1597("ki"), COLLATION1598("ki-KE"), COLLATION1599("ki_KE"), COLLATION1600("kk"), COLLATION1601("kk-KZ"), COLLATION1602("kk_KZ"), COLLATION1603("kkj"), COLLATION1604("kkj-CM"), COLLATION1605("kkj_CM"), COLLATION1606("kl"), COLLATION1607("kl-GL"), COLLATION1608("kl_GL"), COLLATION1609("kln"), COLLATION1610("kln-KE"), COLLATION1611("kln_KE"), COLLATION1612("km"), COLLATION1613("km-KH"), COLLATION1614("km_KH"), COLLATION1615("kn"), COLLATION1616("kn-IN"), COLLATION1617("kn_IN"), COLLATION1618("ko-KP"), COLLATION1619("ko_KP"), COLLATION1620("kok"), COLLATION1621("kok-IN"), COLLATION1622("kok_IN"), COLLATION1623("kr"), COLLATION1624("kr-Latn"), COLLATION1625("kr_Latn"), COLLATION1626("kr-Latn-NG"), COLLATION1627("kr_Latn_NG"), COLLATION1628("ks"), COLLATION1629("ks-Arab"), COLLATION1630("ks_Arab"), COLLATION1631("ks-Arab-IN"), COLLATION1632("ks_Arab_IN"), COLLATION1633("ks-Deva"), COLLATION1634("ks_Deva"), COLLATION1635("ks-Deva-IN"), COLLATION1636("ks_Deva_IN"), COLLATION1637("ksb"), COLLATION1638("ksb-TZ"), COLLATION1639("ksb_TZ"), COLLATION1640("ksf"), COLLATION1641("ksf-CM"), COLLATION1642("ksf_CM"), COLLATION1643("ksh"), COLLATION1644("ksh-DE"), COLLATION1645("ksh_DE"), COLLATION1646("ku"), COLLATION1647("ku-Arab"), COLLATION1648("ku_Arab"), COLLATION1649("ku-Arab-IQ"), COLLATION1650("ku_Arab_IQ"), COLLATION1651("ku-Arab-IR"), COLLATION1652("ku_Arab_IR"), COLLATION1653("kw"), COLLATION1654("kw-GB"), COLLATION1655("kw_GB"), COLLATION1656("ky"), COLLATION1657("ky-KG"), COLLATION1658("ky_KG"), COLLATION1659("la"), COLLATION1660("la-001"), COLLATION1661("la_001"), COLLATION1662("lag"), COLLATION1663("lag-TZ"), COLLATION1664("lag_TZ"), COLLATION1665("lb"), COLLATION1666("lb-LU"), COLLATION1667("lb_LU"), COLLATION1668("lg"), COLLATION1669("lg-UG"), COLLATION1670("lg_UG"), COLLATION1671("lkt"), COLLATION1672("lkt-US"), COLLATION1673("lkt_US"), COLLATION1674("ln"), COLLATION1675("ln-AO"), COLLATION1676("ln_AO"), COLLATION1677("ln-CD"), COLLATION1678("ln_CD"), COLLATION1679("ln-CF"), COLLATION1680("ln_CF"), COLLATION1681("ln-CG"), COLLATION1682("ln_CG"), COLLATION1683("lo"), COLLATION1684("lo-LA"), COLLATION1685("lo_LA"), COLLATION1686("lrc"), COLLATION1687("lrc-IQ"), COLLATION1688("lrc_IQ"), COLLATION1689("lrc-IR"), COLLATION1690("lrc_IR"), COLLATION1691("lt"), COLLATION1692("lt-LT"), COLLATION1693("lt_LT"), COLLATION1694("lu"), COLLATION1695("lu-CD"), COLLATION1696("lu_CD"), COLLATION1697("luo"), COLLATION1698("luo-KE"), COLLATION1699("luo_KE"), COLLATION1700("luy"), COLLATION1701("luy-KE"), COLLATION1702("luy_KE"), COLLATION1703("lv"), COLLATION1704("lv-LV"), COLLATION1705("lv_LV"), COLLATION1706("mas"), COLLATION1707("mas-KE"), COLLATION1708("mas_KE"), COLLATION1709("mas-TZ"), COLLATION1710("mas_TZ"), COLLATION1711("mer"), COLLATION1712("mer-KE"), COLLATION1713("mer_KE"), COLLATION1714("mfe"), COLLATION1715("mfe-MU"), COLLATION1716("mfe_MU"), COLLATION1717("mg"), COLLATION1718("mg-MG"), COLLATION1719("mg_MG"), COLLATION1720("mgh"), COLLATION1721("mgh-MZ"), COLLATION1722("mgh_MZ"), COLLATION1723("mgo"), COLLATION1724("mgo-CM"), COLLATION1725("mgo_CM"), COLLATION1726("mi"), COLLATION1727("mi-NZ"), COLLATION1728("mi_NZ"), COLLATION1729("mk"), COLLATION1730("mk-MK"), COLLATION1731("mk_MK"), COLLATION1732("ml"), COLLATION1733("ml-IN"), COLLATION1734("ml_IN"), COLLATION1735("mn"), COLLATION1736("mn-Cyrl"), COLLATION1737("mn_Cyrl"), COLLATION1738("mn-MN"), COLLATION1739("mn_MN"), COLLATION1740("mn-Mong"), COLLATION1741("mn_Mong"), COLLATION1742("mn-Mong-CN"), COLLATION1743("mn_Mong_CN"), COLLATION1744("mn-Mong-MN"), COLLATION1745("mn_Mong_MN"), COLLATION1746("mni"), COLLATION1747("mni-IN"), COLLATION1748("mni_IN"), COLLATION1749("moh"), COLLATION1750("moh-CA"), COLLATION1751("moh_CA"), COLLATION1752("mr"), COLLATION1753("mr-IN"), COLLATION1754("mr_IN"), COLLATION1755("ms"), COLLATION1756("ms-BN"), COLLATION1757("ms_BN"), COLLATION1758("ms-MY"), COLLATION1759("ms_MY"), COLLATION1760("ms-SG"), COLLATION1761("ms_SG"), COLLATION1762("mt"), COLLATION1763("mt-MT"), COLLATION1764("mt_MT"), COLLATION1765("mua"), COLLATION1766("mua-CM"), COLLATION1767("mua_CM"), COLLATION1768("my"), COLLATION1769("my-MM"), COLLATION1770("my_MM"), COLLATION1771("mzn"), COLLATION1772("mzn-IR"), COLLATION1773("mzn_IR"), COLLATION1774("naq"), COLLATION1775("naq-NA"), COLLATION1776("naq_NA"), COLLATION1777("nb"), COLLATION1778("nb-NO"), COLLATION1779("nb_NO"), COLLATION1780("nb-SJ"), COLLATION1781("nb_SJ"), COLLATION1782("nd"), COLLATION1783("nd-ZW"), COLLATION1784("nd_ZW"), COLLATION1785("nds"), COLLATION1786("nds-DE"), COLLATION1787("nds_DE"), COLLATION1788("nds-NL"), COLLATION1789("nds_NL"), COLLATION1790("ne"), COLLATION1791("ne-IN"), COLLATION1792("ne_IN"), COLLATION1793("ne-NP"), COLLATION1794("ne_NP"), COLLATION1795("nl"), COLLATION1796("nl-AW"), COLLATION1797("nl_AW"), COLLATION1798("nl-BE"), COLLATION1799("nl_BE"), COLLATION1800("nl-BQ"), COLLATION1801("nl_BQ"), COLLATION1802("nl-CW"), COLLATION1803("nl_CW"), COLLATION1804("nl-NL"), COLLATION1805("nl_NL"), COLLATION1806("nl-SR"), COLLATION1807("nl_SR"), COLLATION1808("nl-SX"), COLLATION1809("nl_SX"), COLLATION1810("nmg"), COLLATION1811("nmg-CM"), COLLATION1812("nmg_CM"), COLLATION1813("nn"), COLLATION1814("nn-NO"), COLLATION1815("nn_NO"), COLLATION1816("nnh"), COLLATION1817("nnh-CM"), COLLATION1818("nnh_CM"), COLLATION1819("no"), COLLATION1820("nqo"), COLLATION1821("nqo-GN"), COLLATION1822("nqo_GN"), COLLATION1823("nr"), COLLATION1824("nr-ZA"), COLLATION1825("nr_ZA"), COLLATION1826("nso"), COLLATION1827("nso-ZA"), COLLATION1828("nso_ZA"), COLLATION1829("nus"), COLLATION1830("nus-SS"), COLLATION1831("nus_SS"), COLLATION1832("nyn"), COLLATION1833("nyn-UG"), COLLATION1834("nyn_UG"), COLLATION1835("oc"), COLLATION1836("oc-FR"), COLLATION1837("oc_FR"), COLLATION1838("om"), COLLATION1839("om-ET"), COLLATION1840("om_ET"), COLLATION1841("om-KE"), COLLATION1842("om_KE"), COLLATION1843("or"), COLLATION1844("or-IN"), COLLATION1845("or_IN"), COLLATION1846("os"), COLLATION1847("os-GE"), COLLATION1848("os_GE"), COLLATION1849("os-RU"), COLLATION1850("os_RU"), COLLATION1851("pa"), COLLATION1852("pa-Arab"), COLLATION1853("pa_Arab"), COLLATION1854("pa-Arab-PK"), COLLATION1855("pa_Arab_PK"), COLLATION1856("pa-Guru"), COLLATION1857("pa_Guru"), COLLATION1858("pa-IN"), COLLATION1859("pa_IN"), COLLATION1860("pap"), COLLATION1861("pap-029"), COLLATION1862("pap_029"), COLLATION1863("pl"), COLLATION1864("pl-PL"), COLLATION1865("pl_PL"), COLLATION1866("prg"), COLLATION1867("prg-001"), COLLATION1868("prg_001"), COLLATION1869("prs"), COLLATION1870("prs-AF"), COLLATION1871("prs_AF"), COLLATION1872("ps"), COLLATION1873("ps-AF"), COLLATION1874("ps_AF"), COLLATION1875("ps-PK"), COLLATION1876("ps_PK"), COLLATION1877("pt"), COLLATION1878("pt-AO"), COLLATION1879("pt_AO"), COLLATION1880("pt-BR"), COLLATION1881("pt_BR"), COLLATION1882("pt-CH"), COLLATION1883("pt_CH"), COLLATION1884("pt-CV"), COLLATION1885("pt_CV"), COLLATION1886("pt-GQ"), COLLATION1887("pt_GQ"), COLLATION1888("pt-GW"), COLLATION1889("pt_GW"), COLLATION1890("pt-LU"), COLLATION1891("pt_LU"), COLLATION1892("pt-MO"), COLLATION1893("pt_MO"), COLLATION1894("pt-MZ"), COLLATION1895("pt_MZ"), COLLATION1896("pt-PT"), COLLATION1897("pt_PT"), COLLATION1898("pt-ST"), COLLATION1899("pt_ST"), COLLATION1900("pt-TL"), COLLATION1901("pt_TL"), COLLATION1902("quc"), COLLATION1903("quc-Latn"), COLLATION1904("quc_Latn"), COLLATION1905("quc-Latn-GT"), COLLATION1906("quc_Latn_GT"), COLLATION1907("quz"), COLLATION1908("quz-BO"), COLLATION1909("quz_BO"), COLLATION1910("quz-EC"), COLLATION1911("quz_EC"), COLLATION1912("quz-PE"), COLLATION1913("quz_PE"), COLLATION1914("rm"), COLLATION1915("rm-CH"), COLLATION1916("rm_CH"), COLLATION1917("rn"), COLLATION1918("rn-BI"), COLLATION1919("rn_BI"), COLLATION1920("ro"), COLLATION1921("ro-MD"), COLLATION1922("ro_MD"), COLLATION1923("ro-RO"), COLLATION1924("ro_RO"), COLLATION1925("rof"), COLLATION1926("rof-TZ"), COLLATION1927("rof_TZ"), COLLATION1928("ru"), COLLATION1929("ru-BY"), COLLATION1930("ru_BY"), COLLATION1931("ru-KG"), COLLATION1932("ru_KG"), COLLATION1933("ru-KZ"), COLLATION1934("ru_KZ"), COLLATION1935("ru-MD"), COLLATION1936("ru_MD"), COLLATION1937("ru-RU"), COLLATION1938("ru_RU"), COLLATION1939("ru-UA"), COLLATION1940("ru_UA"), COLLATION1941("rw"), COLLATION1942("rw-RW"), COLLATION1943("rw_RW"), COLLATION1944("rwk"), COLLATION1945("rwk-TZ"), COLLATION1946("rwk_TZ"), COLLATION1947("sa"), COLLATION1948("sa-IN"), COLLATION1949("sa_IN"), COLLATION1950("sah"), COLLATION1951("sah-RU"), COLLATION1952("sah_RU"), COLLATION1953("saq"), COLLATION1954("saq-KE"), COLLATION1955("saq_KE"), COLLATION1956("sbp"), COLLATION1957("sbp-TZ"), COLLATION1958("sbp_TZ"), COLLATION1959("sd"), COLLATION1960("sd-Arab"), COLLATION1961("sd_Arab"), COLLATION1962("sd-Arab-PK"), COLLATION1963("sd_Arab_PK"), COLLATION1964("sd-Deva"), COLLATION1965("sd_Deva"), COLLATION1966("sd-Deva-IN"), COLLATION1967("sd_Deva_IN"), COLLATION1968("se"), COLLATION1969("se-FI"), COLLATION1970("se_FI"), COLLATION1971("se-NO"), COLLATION1972("se_NO"), COLLATION1973("se-SE"), COLLATION1974("se_SE"), COLLATION1975("seh"), COLLATION1976("seh-MZ"), COLLATION1977("seh_MZ"), COLLATION1978("ses"), COLLATION1979("ses-ML"), COLLATION1980("ses_ML"), COLLATION1981("sg"), COLLATION1982("sg-CF"), COLLATION1983("sg_CF"), COLLATION1984("shi"), COLLATION1985("shi-Latn"), COLLATION1986("shi_Latn"), COLLATION1987("shi-Latn-MA"), COLLATION1988("shi_Latn_MA"), COLLATION1989("shi-Tfng"), COLLATION1990("shi_Tfng"), COLLATION1991("shi-Tfng-MA"), COLLATION1992("shi_Tfng_MA"), COLLATION1993("si"), COLLATION1994("si-LK"), COLLATION1995("si_LK"), COLLATION1996("sk"), COLLATION1997("sk-SK"), COLLATION1998("sk_SK"), COLLATION1999("sl"), COLLATION2000("sl-SI"), COLLATION2001("sl_SI"), COLLATION2002("sma"), COLLATION2003("sma-NO"), COLLATION2004("sma_NO"), COLLATION2005("sma-SE"), COLLATION2006("sma_SE"), COLLATION2007("smj"), COLLATION2008("smj-NO"), COLLATION2009("smj_NO"), COLLATION2010("smj-SE"), COLLATION2011("smj_SE"), COLLATION2012("smn"), COLLATION2013("smn-FI"), COLLATION2014("smn_FI"), COLLATION2015("sms"), COLLATION2016("sms-FI"), COLLATION2017("sms_FI"), COLLATION2018("sn"), COLLATION2019("sn-Latn"), COLLATION2020("sn_Latn"), COLLATION2021("sn-Latn-ZW"), COLLATION2022("sn_Latn_ZW"), COLLATION2023("so"), COLLATION2024("so-DJ"), COLLATION2025("so_DJ"), COLLATION2026("so-ET"), COLLATION2027("so_ET"), COLLATION2028("so-KE"), COLLATION2029("so_KE"), COLLATION2030("so-SO"), COLLATION2031("so_SO"), COLLATION2032("sq"), COLLATION2033("sq-AL"), COLLATION2034("sq_AL"), COLLATION2035("sq-MK"), COLLATION2036("sq_MK"), COLLATION2037("sq-XK"), COLLATION2038("sq_XK"), COLLATION2039("sr"), COLLATION2040("sr-Cyrl"), COLLATION2041("sr_Cyrl"), COLLATION2042("sr-Cyrl-BA"), COLLATION2043("sr_Cyrl_BA"), COLLATION2044("sr-Cyrl-ME"), COLLATION2045("sr_Cyrl_ME"), COLLATION2046("sr-Cyrl-RS"), COLLATION2047("sr_Cyrl_RS"), COLLATION2048("sr-Cyrl-XK"), COLLATION2049("sr_Cyrl_XK"), COLLATION2050("sr-Latn"), COLLATION2051("sr_Latn"), COLLATION2052("sr-Latn-BA"), COLLATION2053("sr_Latn_BA"), COLLATION2054("sr-Latn-ME"), COLLATION2055("sr_Latn_ME"), COLLATION2056("sr-Latn-RS"), COLLATION2057("sr_Latn_RS"), COLLATION2058("sr-Latn-XK"), COLLATION2059("sr_Latn_XK"), COLLATION2060("ss"), COLLATION2061("ss-SZ"), COLLATION2062("ss_SZ"), COLLATION2063("ss-ZA"), COLLATION2064("ss_ZA"), COLLATION2065("ssy"), COLLATION2066("ssy-ER"), COLLATION2067("ssy_ER"), COLLATION2068("st"), COLLATION2069("st-LS"), COLLATION2070("st_LS"), COLLATION2071("st-ZA"), COLLATION2072("st_ZA"), COLLATION2073("sv"), COLLATION2074("sv-AX"), COLLATION2075("sv_AX"), COLLATION2076("sv-FI"), COLLATION2077("sv_FI"), COLLATION2078("sv-SE"), COLLATION2079("sv_SE"), COLLATION2080("sw"), COLLATION2081("sw-CD"), COLLATION2082("sw_CD"), COLLATION2083("sw-KE"), COLLATION2084("sw_KE"), COLLATION2085("sw-TZ"), COLLATION2086("sw_TZ"), COLLATION2087("sw-UG"), COLLATION2088("sw_UG"), COLLATION2089("syr"), COLLATION2090("syr-SY"), COLLATION2091("syr_SY"), COLLATION2092("ta"), COLLATION2093("ta-IN"), COLLATION2094("ta_IN"), COLLATION2095("ta-LK"), COLLATION2096("ta_LK"), COLLATION2097("ta-MY"), COLLATION2098("ta_MY"), COLLATION2099("ta-SG"), COLLATION2100("ta_SG"), COLLATION2101("te"), COLLATION2102("te-IN"), COLLATION2103("te_IN"), COLLATION2104("teo"), COLLATION2105("teo-KE"), COLLATION2106("teo_KE"), COLLATION2107("teo-UG"), COLLATION2108("teo_UG"), COLLATION2109("tg"), COLLATION2110("tg-Cyrl"), COLLATION2111("tg_Cyrl"), COLLATION2112("tg-Cyrl-TJ"), COLLATION2113("tg_Cyrl_TJ"), COLLATION2114("th"), COLLATION2115("th-TH"), COLLATION2116("th_TH"), COLLATION2117("ti"), COLLATION2118("ti-ER"), COLLATION2119("ti_ER"), COLLATION2120("ti-ET"), COLLATION2121("ti_ET"), COLLATION2122("tig"), COLLATION2123("tig-ER"), COLLATION2124("tig_ER"), COLLATION2125("tk"), COLLATION2126("tk-TM"), COLLATION2127("tk_TM"), COLLATION2128("tn"), COLLATION2129("tn-BW"), COLLATION2130("tn_BW"), COLLATION2131("tn-ZA"), COLLATION2132("tn_ZA"), COLLATION2133("to"), COLLATION2134("to-TO"), COLLATION2135("to_TO"), COLLATION2136("tr"), COLLATION2137("tr-CY"), COLLATION2138("tr_CY"), COLLATION2139("tr-TR"), COLLATION2140("tr_TR"), COLLATION2141("ts"), COLLATION2142("ts-ZA"), COLLATION2143("ts_ZA"), COLLATION2144("tt"), COLLATION2145("tt-RU"), COLLATION2146("tt_RU"), COLLATION2147("twq"), COLLATION2148("twq-NE"), COLLATION2149("twq_NE"), COLLATION2150("tzm"), COLLATION2151("tzm-Arab"), COLLATION2152("tzm_Arab"), COLLATION2153("tzm-Arab-MA"), COLLATION2154("tzm_Arab_MA"), COLLATION2155("tzm-Latn"), COLLATION2156("tzm_Latn"), COLLATION2157("tzm-Latn-DZ"), COLLATION2158("tzm_Latn_DZ"), COLLATION2159("tzm-Latn-MA"), COLLATION2160("tzm_Latn_MA"), COLLATION2161("tzm-Tfng"), COLLATION2162("tzm_Tfng"), COLLATION2163("tzm-Tfng-MA"), COLLATION2164("tzm_Tfng_MA"), COLLATION2165("ug"), COLLATION2166("ug-CN"), COLLATION2167("ug_CN"), COLLATION2168("uk"), COLLATION2169("uk-UA"), COLLATION2170("uk_UA"), COLLATION2171("ur"), COLLATION2172("ur-IN"), COLLATION2173("ur_IN"), COLLATION2174("ur-PK"), COLLATION2175("ur_PK"), COLLATION2176("uz"), COLLATION2177("uz-Arab"), COLLATION2178("uz_Arab"), COLLATION2179("uz-Arab-AF"), COLLATION2180("uz_Arab_AF"), COLLATION2181("uz-Cyrl"), COLLATION2182("uz_Cyrl"), COLLATION2183("uz-Cyrl-UZ"), COLLATION2184("uz_Cyrl_UZ"), COLLATION2185("uz-Latn"), COLLATION2186("uz_Latn"), COLLATION2187("uz-Latn-UZ"), COLLATION2188("uz_Latn_UZ"), COLLATION2189("vai"), COLLATION2190("vai-Latn"), COLLATION2191("vai_Latn"), COLLATION2192("vai-Latn-LR"), COLLATION2193("vai_Latn_LR"), COLLATION2194("vai-Vaii"), COLLATION2195("vai_Vaii"), COLLATION2196("vai-Vaii-LR"), COLLATION2197("vai_Vaii_LR"), COLLATION2198("ve"), COLLATION2199("ve-ZA"), COLLATION2200("ve_ZA"), COLLATION2201("vi"), COLLATION2202("vi-VN"), COLLATION2203("vi_VN"), COLLATION2204("vo"), COLLATION2205("vo-001"), COLLATION2206("vo_001"), COLLATION2207("vun"), COLLATION2208("vun-TZ"), COLLATION2209("vun_TZ"), COLLATION2210("wae"), COLLATION2211("wae-CH"), COLLATION2212("wae_CH"), COLLATION2213("wal"), COLLATION2214("wal-ET"), COLLATION2215("wal_ET"), COLLATION2216("wo"), COLLATION2217("wo-SN"), COLLATION2218("wo_SN"), COLLATION2219("x-IV_mathan"), COLLATION2220("x_IV_mathan"), COLLATION2221("xh"), COLLATION2222("xh-ZA"), COLLATION2223("xh_ZA"), COLLATION2224("xog"), COLLATION2225("xog-UG"), COLLATION2226("xog_UG"), COLLATION2227("yav"), COLLATION2228("yav-CM"), COLLATION2229("yav_CM"), COLLATION2230("yi"), COLLATION2231("yi-001"), COLLATION2232("yi_001"), COLLATION2233("yo"), COLLATION2234("yo-BJ"), COLLATION2235("yo_BJ"), COLLATION2236("yo-NG"), COLLATION2237("yo_NG"), COLLATION2238("zgh"), COLLATION2239("zgh-Tfng"), COLLATION2240("zgh_Tfng"), COLLATION2241("zgh-Tfng-MA"), COLLATION2242("zgh_Tfng_MA"), COLLATION2243("zu"), COLLATION2244("zu-ZA"), COLLATION2245("zu_ZA"), ; private Collation collation; PostgreSQLCollationEnum(String collationName) { this.collation = new Collation(collationName); } public static List getCollations() { return Arrays.asList(PostgreSQLCollationEnum.values()).stream().map(PostgreSQLCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); } public Collation getCollation() { return collation; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java ================================================ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum PostgreSQLColumnTypeEnum implements ColumnBuilder { BIGSERIAL("BIGSERIAL", false, false, true, false, false, false, true, true, false, false), BIT("BIT", true, false, true, false, false, false, true, true, false, false), BOOL("BOOL", false, false, true, false, false, false, true, true, false, false), BOX("BOX", false, false, true, false, false, false, true, true, false, false), BYTEA("BYTEA", false, false, true, false, false, false, true, true, false, false), CHAR("CHAR", true, false, true, false, false, true, true, true, false, false), CIDR("CIDR", false, false, true, false, false, false, true, true, false, false), CIRCLE("CIRCLE", false, false, true, false, false, false, true, true, false, false), DATE("DATE", false, false, true, false, false, false, true, true, false, false), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), FLOAT4("FLOAT4", false, false, true, false, false, false, true, true, false, false), FLOAT8("FLOAT8", false, false, true, false, false, false, true, true, false, false), INET("INET", false, false, true, false, false, false, true, true, false, false), INT2("INT2", false, false, true, false, false, false, true, true, false, false), INT4("INT4", false, false, true, false, false, false, true, true, false, false), INT8("INT8", false, false, true, false, false, false, true, true, false, false), INTERVAL("INTERVAL", false, false, true, false, false, false, true, true, false, false), JSON("JSON", false, false, true, false, false, false, true, true, false, false), JSONB("JSONB", false, false, true, false, false, false, true, true, false, false), LINE("LINE", false, false, true, false, false, false, true, true, false, false), LSEG("LSEG", false, false, true, false, false, false, true, true, false, false), MACADDR("MACADDR", false, false, true, false, false, false, true, true, false, false), MONEY("MONEY", false, false, true, false, false, false, true, true, false, false), NUMERIC("NUMERIC", true, true, true, false, false, false, true, true, false, false), PATH("PATH", false, false, true, false, false, false, true, true, false, false), POINT("POINT", false, false, true, false, false, false, true, true, false, false), POLYGON("POLYGON", false, false, true, false, false, false, true, true, false, false), SERIAL("SERIAL", false, false, true, false, false, false, true, true, false, false), SERIAL2("SERIAL2", false, false, true, false, false, false, true, true, false, false), SERIAL4("SERIAL4", false, false, true, false, false, false, true, true, false, false), SERIAL8("SERIAL8", false, false, true, false, false, false, true, true, false, false), SMALLSERIAL("SMALLSERIAL", false, false, true, false, false, false, true, true, false, false), TEXT("TEXT", false, false, true, false, false, true, true, true, false, false), TIME("TIME", true, false, true, false, false, false, true, true, false, false), TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, false, false), TIMESTAMPTZ("TIMESTAMPTZ", true, false, true, false, false, false, true, true, false, false), TIMETZ("TIMETZ", true, false, true, false, false, false, true, true, false, false), TSQUERY("TSQUERY", false, false, true, false, false, false, true, true, false, false), TSVECTOR("TSVECTOR", false, false, true, false, false, false, true, true, false, false), TXID_SNAPSHOT("TXID_SNAPSHOT", false, false, true, false, false, false, true, true, false, false), UUID("UUID", false, false, true, false, false, false, true, true, false, false), VARBIT("VARBIT", true, false, true, false, false, false, true, true, false, false), VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false, false), XML("XML", false, false, true, false, false, false, true, true, false, false), ; private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (PostgreSQLColumnTypeEnum value : PostgreSQLColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } private ColumnType columnType; PostgreSQLColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); } public static PostgreSQLColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public static List getTypes() { return Arrays.stream(PostgreSQLColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } public ColumnType getColumnType() { return columnType; } @Override public String buildCreateColumnSql(TableColumn column) { PostgreSQLColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildCollation(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); return script.toString(); } private String buildCollation(TableColumn column, PostgreSQLColumnTypeEnum type) { if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { return ""; } return StringUtils.join("\"", column.getCollationName(), "\""); } @Override public String buildModifyColumn(TableColumn column) { if (EditStatus.DELETE.name().equals(column.getEditStatus())) { return StringUtils.join("DROP COLUMN \"", column.getName() + "\""); } if (EditStatus.ADD.name().equals(column.getEditStatus())) { return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column)); } if (EditStatus.MODIFY.name().equals(column.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER COLUMN \"").append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n"); if (column.getNullable() != null && 1 == column.getNullable()) { script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" DROP NOT NULL ,\n"); } else { script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" SET NOT NULL ,\n"); } String defaultValue = buildDefaultValue(column, this); if (StringUtils.isNotBlank(defaultValue)) { script.append("ALTER COLUMN \"").append(column.getName()).append("\" SET ").append(defaultValue).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); return script.toString(); } return ""; } public String buildComment(TableColumn column, PostgreSQLColumnTypeEnum type) { if (!this.columnType.isSupportComments() || column.getComment() == null || EditStatus.DELETE.name().equals(column.getEditStatus())) { return ""; } return StringUtils.join("COMMENT ON COLUMN", " \"", column.getTableName(), "\".\"", column.getName(), "\" IS '", column.getComment(), "';"); } private String buildDefaultValue(TableColumn column, PostgreSQLColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT ''"); } if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ return StringUtils.join("DEFAULT NULL"); } if (Arrays.asList(CHAR, VARCHAR).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(TIMESTAMP, TIME, TIMETZ, TIMESTAMPTZ, DATE).contains(type)) { if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ", column.getDefaultValue()); } return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildNullable(TableColumn column, PostgreSQLColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDataType(TableColumn column, PostgreSQLColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(VARCHAR, CHAR).contains(type)) { if (column.getColumnSize() == null ) { return columnType; } return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(VARBIT, BIT).contains(type)) { if (column.getColumnSize() == null ) { return columnType; } return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } if (Arrays.asList(TIME, TIMETZ, TIMESTAMPTZ, TIMESTAMP).contains(type)) { if (column.getColumnSize() == null || column.getColumnSize() == 0) { return columnType; } else { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } } if (Arrays.asList(DECIMAL, NUMERIC).contains(type)) { if (column.getColumnSize() == null && column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } else { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } } return columnType; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLDefaultValueEnum.java ================================================ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum PostgreSQLDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; PostgreSQLDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(PostgreSQLDefaultValueEnum.values()).map(PostgreSQLDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java ================================================ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum PostgreSQLIndexTypeEnum { PRIMARY("Primary", "PRIMARY KEY"), FOREIGN("Foreign", "FOREIGN KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE"), ; private String name; private String keyword; private IndexType indexType; PostgreSQLIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType =new IndexType(name); } public static PostgreSQLIndexTypeEnum getByType(String type) { for (PostgreSQLIndexTypeEnum value : PostgreSQLIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public static List getIndexTypes() { return Arrays.asList(PostgreSQLIndexTypeEnum.values()).stream().map(PostgreSQLIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } public IndexType getIndexType() { return indexType; } public String getName() { return name; } public String getKeyword() { return keyword; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (NORMAL.equals(this)) { script.append("CREATE").append(" "); script.append(buildIndexUnique(tableIndex)).append(" "); script.append(buildIndexConcurrently(tableIndex)).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append("ON ").append("\"").append(tableIndex.getTableName()).append("\"").append(" "); script.append(buildIndexMethod(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)); } else { script.append("CONSTRAINT").append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(keyword).append(" "); script.append(buildIndexColumn(tableIndex)); script.append(buildForeignColum(tableIndex)); } return script.toString(); } private String buildForeignColum(TableIndex tableIndex) { if (FOREIGN.equals(this)) { StringBuilder script = new StringBuilder(); script.append(" REFERENCES "); if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) { script.append(tableIndex.getForeignSchemaName()).append("."); } if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) { script.append(tableIndex.getForeignTableName()).append(" "); } if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) { script.append("("); for (String column : tableIndex.getForeignColumnNamelist()) { if (StringUtils.isNotBlank(column)) { script.append("\"").append(column).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); } return script.toString(); } return ""; } private String buildIndexMethod(TableIndex tableIndex) { if (StringUtils.isNotBlank(tableIndex.getMethod())) { return "USING " + tableIndex.getMethod(); } else { return ""; } } private String buildIndexConcurrently(TableIndex tableIndex) { if (BooleanUtils.isTrue(tableIndex.getConcurrently())) { return "CONCURRENTLY"; } else { return ""; } } private String buildIndexUnique(TableIndex tableIndex) { if (BooleanUtils.isTrue(tableIndex.getUnique())) { return "UNIQUE " + keyword; } else { return keyword; } } public String buildIndexComment(TableIndex tableIndex) { if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return ""; } else if (NORMAL.equals(this)) { return StringUtils.join("COMMENT ON INDEX", " ", "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';"); } else { return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(), "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';"); } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { return "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { boolean isNormal = NORMAL.equals(this); if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), isNormal ? ";\n" : ",\n\tADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(isNormal ? "" : "ADD ", buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (NORMAL.equals(this)) { return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\""); } return StringUtils.join("DROP CONSTRAINT \"", tableIndex.getOldName(), "\""); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.postgresql.PostgreSQLPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-presto src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoDBManage.java ================================================ package ai.chat2db.plugin.presto; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; public class PrestoDBManage extends DefaultDBManage implements DBManage { } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoMetaData.java ================================================ package ai.chat2db.plugin.presto; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; public class PrestoMetaData extends DefaultMetaService implements MetaData { public String tableDDL(String databaseName, String schemaName,String tableName) { return ""; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoPlugin.java ================================================ package ai.chat2db.plugin.presto; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class PrestoPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"presto.json", DBConfig.class); } @Override public MetaData getMetaData() { return new PrestoMetaData(); } @Override public DBManage getDBManage() { return new PrestoDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json ================================================ { "dbType": "PRESTO", "supportDatabase": true, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:presto://localhost:8080/", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/presto-jdbc-0.245.1.jar" ], "jdbcDriver": "presto-jdbc-0.245.1.jar", "jdbcDriverClass": "com.facebook.presto.jdbc.PrestoDriver" } ], "name": "Presto" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-presto/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.presto.PrestoPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml chat2db-sqlite ai.chat2db chat2db-spi src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteDBManage.java ================================================ package ai.chat2db.plugin.sqlite; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; public class SqliteDBManage extends DefaultDBManage implements DBManage { @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTables(connection, databaseName, schemaName,asyncContext); exportViews(connection, databaseName, asyncContext); exportTriggers(connection, asyncContext); } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"TABLE", "SYSTEM TABLE"})) { while (resultSet.next()) { exportTable(connection, databaseName,schemaName, resultSet.getString("TABLE_NAME"), asyncContext); } } } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT sql FROM sqlite_master WHERE type='table' AND name='%s'", tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP TABLE IF EXISTS ").append(format(tableName)).append(";").append("\n") .append(resultSet.getString("sql")).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName,schemaName, tableName, asyncContext); } } } } private String format(String tableName) { return "\""+tableName+"\""; } private void exportViews(Connection connection, String databaseName, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, null, null, new String[]{"VIEW"})) { while (resultSet.next()) { exportView(connection, resultSet.getString("TABLE_NAME"), asyncContext); } } } private void exportView(Connection connection, String viewName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT * FROM sqlite_master WHERE type = 'view' and name='%s';", viewName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP VIEW IF EXISTS ").append(format(viewName)).append(";").append("\n") .append(resultSet.getString("sql")).append(";").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, AsyncContext asyncContext) throws SQLException { String sql = "SELECT * FROM sqlite_master WHERE type = 'trigger';"; try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String triggerName = resultSet.getString("name"); exportTrigger(connection, triggerName, asyncContext); } } } private void exportTrigger(Connection connection, String triggerName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT * FROM sqlite_master WHERE type = 'trigger' and name='%s';", triggerName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("sql")).append("\n"); asyncContext.write(sqlBuilder.toString()); } } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java ================================================ package ai.chat2db.plugin.sqlite; import ai.chat2db.plugin.sqlite.builder.SqliteBuilder; import ai.chat2db.plugin.sqlite.type.SqliteCollationEnum; import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; import ai.chat2db.plugin.sqlite.type.SqliteDefaultValueEnum; import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class SqliteMetaData extends DefaultMetaService implements MetaData { private static String VIEW_DDL_SQL="SELECT * FROM sqlite_master WHERE type = 'view' and name='%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { Table view = new Table(); String sql = String.format(VIEW_DDL_SQL,viewName); SQLExecutor.getInstance().execute(connection, sql, resultSet->{ if (resultSet.next()) { view.setDatabaseName(databaseName); view.setDdl(resultSet.getString("sql")); } }); return view; } private static final String TRIGGER_LIST_SQL = "SELECT * FROM sqlite_master WHERE type = 'trigger';"; private static String TRIGGER_DDL_SQL = "SELECT * FROM sqlite_master WHERE type = 'trigger' and name='%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, TRIGGER_LIST_SQL, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); String triggerName = resultSet.getString("name"); trigger.setTriggerName(triggerName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, String databaseName, String schemaName, String triggerName) { Trigger trigger = new Trigger(); String sql = String.format(TRIGGER_DDL_SQL, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { trigger.setTriggerName(triggerName); trigger.setDatabaseName(databaseName); trigger.setTriggerBody(resultSet.getString("sql")); } return trigger; }); } @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + tableName + "'"; return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); } } catch (SQLException e) { throw new RuntimeException(e); } return null; }); } @Override public List databases(Connection connection) { return Lists.newArrayList(Database.builder().name("main").build()); } @Override public List schemas(Connection connection, String databaseName) { return Lists.newArrayList(); } @Override public SqlBuilder getSqlBuilder() { return new SqliteBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(SqliteColumnTypeEnum.getTypes()) .charsets(null) .collations(SqliteCollationEnum.getCollations()) .indexTypes(SqliteIndexTypeEnum.getIndexTypes()) .defaultValues(SqliteDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqlitePlugin.java ================================================ package ai.chat2db.plugin.sqlite; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class SqlitePlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"sqlite.json", DBConfig.class); } @Override public MetaData getMetaData() { return new SqliteMetaData(); } @Override public DBManage getDBManage() { return new SqliteDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java ================================================ package ai.chat2db.plugin.sqlite.builder; import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; public class SqliteBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); script.append("\"").append(table.getDatabaseName()).append("\".\"").append(table.getName()).append("\"").append(" (").append("\n"); // append column for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(column.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } for (TableIndex tableIndex : table.getIndexList()) { if(SqliteIndexTypeEnum.PRIMARY_KEY.getName().equals( tableIndex.getType())) { SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); if(sqliteIndexTypeEnum == null){ continue; } script.append("\t").append(sqliteIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); } } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n);"); // append primary key and index for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } if(!SqliteIndexTypeEnum.PRIMARY_KEY.getName().equals( tableIndex.getType())) { SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); if(sqliteIndexTypeEnum == null){ continue; } script.append("\n").append("CREATE ").append(sqliteIndexTypeEnum.buildIndexScript(tableIndex)).append(";\n"); } } return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append("ALTER TABLE ").append("\"").append(oldTable.getDatabaseName()).append("\".\"").append(oldTable.getName()).append("\"").append("\n"); script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(tableColumn.getColumnType()); if(typeEnum == null){ continue; } script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { // script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); if(sqliteIndexTypeEnum == null){ continue; } script.append("\t").append(sqliteIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return "select * from(" + sql + ") t LIMIT " + pageSize + " OFFSET " + offset + ""; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json ================================================ { "dbType": "SQLITE", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:sqlite:identifier.sqlite", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/sqlite-jdbc-3.39.3.0.jar" ], "jdbcDriver": "sqlite-jdbc-3.39.3.0.jar", "jdbcDriverClass": "org.sqlite.JDBC" } ], "name": "SQLite" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCharsetEnum.java ================================================ //package ai.chat2db.plugin.sqlite.type; // //import ai.chat2db.spi.model.Charset; // //import java.util.Arrays; //import java.util.List; // //public enum SqliteCharsetEnum { // // UTF8("utf8", "utf8_general_ci"), // BIG5("big5", "big5_chinese_ci"), // DEC8("dec8", "dec8_swedish_ci"), // CP850("cp850", "cp850_general_ci"), // HP8("hp8", "hp8_english_ci"), // KOI8R("koi8r", "koi8r_general_ci"), // LATIN1("latin1", "latin1_swedish_ci"), // LATIN2("latin2", "latin2_general_ci"), // SWE7("swe7", "swe7_swedish_ci"), // ASCII("ascii", "ascii_general_ci"), // UJIS("ujis", "ujis_japanese_ci"), // SJIS("sjis", "sjis_japanese_ci"), // HEBREW("hebrew", "hebrew_general_ci"), // TIS620("tis620", "tis620_thai_ci"), // EUCKR("euckr", "euckr_korean_ci"), // KOI8U("koi8u", "koi8u_general_ci"), // GB2312("gb2312", "gb2312_chinese_ci"), // GREEK("greek", "greek_general_ci"), // CP1250("cp1250", "cp1250_general_ci"), // GBK("gbk", "gbk_chinese_ci"), // LATIN5("latin5", "latin5_turkish_ci"), // ARMSCII8("armscii8", "armscii8_general_ci"), // UCS2("ucs2", "ucs2_general_ci"), // CP866("cp866", "cp866_general_ci"), // KEYBCS2("keybcs2", "keybcs2_general_ci"), // MACCE("macce", "macce_general_ci"), // MACROMAN("macroman", "macroman_general_ci"), // CP852("cp852", "cp852_general_ci"), // LATIN7("latin7", "latin7_general_ci"), // UTF8MB4("utf8mb4", "utf8mb4_general_ci"), // CP1251("cp1251", "cp1251_general_ci"), // UTF16("utf16", "utf16_general_ci"), // UTF16LE("utf16le", "utf16le_general_ci"), // CP1256("cp1256", "cp1256_general_ci"), // CP1257("cp1257", "cp1257_general_ci"), // UTF32("utf32", "utf32_general_ci"), // BINARY("binary", "binary"), // GEOSTD8("geostd8", "geostd8_general_ci"), // CP932("cp932", "cp932_japanese_ci"), // EUCJPMS("eucjpms", "eucjpms_japanese_ci"), // GB18030("gb18030", "gb18030_chinese_ci"); // private Charset charset; // // SqliteCharsetEnum(String charsetName, String defaultCollationName) { // this.charset = new Charset(charsetName, defaultCollationName); // } // // // public Charset getCharset() { // return charset; // } // // public static List getCharsets() { // return Arrays.stream(SqliteCharsetEnum.values()).map(SqliteCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); // } // //} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCollationEnum.java ================================================ package ai.chat2db.plugin.sqlite.type; import ai.chat2db.spi.model.Collation; import java.util.Arrays; import java.util.List; public enum SqliteCollationEnum { BINARY("BINARY"), NOCASE("NOCASE"), RTRIM("RTRIM"), ; private Collation collation; SqliteCollationEnum(String collationName) { this.collation = new Collation(collationName); } public Collation getCollation() { return collation; } public static List getCollations() { return Arrays.asList(SqliteCollationEnum.values()).stream().map(SqliteCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java ================================================ package ai.chat2db.plugin.sqlite.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; public enum SqliteColumnTypeEnum implements ColumnBuilder { INTEGER("INTEGER", true, false, true, false, false, true, false, false, false, false), REAL("REAL", true, false, true, false, false, true, false, false, false, false), BLOB("BLOB", true, false, true, false, false, true, false, false, false, false), TEXT("TEXT", true, false, true, false, false, true, false, false, false, false), ; private ColumnType columnType; public static SqliteColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); } public ColumnType getColumnType() { return columnType; } SqliteColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (SqliteColumnTypeEnum value : SqliteColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } @Override public String buildCreateColumnSql(TableColumn column) { SqliteColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildCharset(column, type)).append(" "); script.append(buildCollation(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildExt(column, type)).append(" "); script.append(buildAutoIncrement(column, type)).append(" "); // script.append(buildComment(column, type)).append(" "); return script.toString(); } private String buildCharset(TableColumn column, SqliteColumnTypeEnum type) { if (!type.getColumnType().isSupportCharset() || StringUtils.isEmpty(column.getCharSetName())) { return ""; } return StringUtils.join("CHARACTER SET ", column.getCharSetName()); } private String buildCollation(TableColumn column, SqliteColumnTypeEnum type) { if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { return ""; } return StringUtils.join("COLLATE ", column.getCollationName()); } @Override public String buildModifyColumn(TableColumn tableColumn) { // if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { // return StringUtils.join("DROP COLUMN \"", tableColumn.getName() + "\""); // } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("ADD ", buildCreateColumnSql(tableColumn)); } // if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { // if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { // return StringUtils.join("CHANGE COLUMN \"", tableColumn.getOldName(), "\" ", buildCreateColumnSql(tableColumn)); // } else { // return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); // } // } return ""; } private String buildAutoIncrement(TableColumn column, SqliteColumnTypeEnum type) { if (!type.getColumnType().isSupportAutoIncrement()) { return ""; } if (column.getAutoIncrement() != null && column.getAutoIncrement()) { return "AUTO_INCREMENT"; } return ""; } private String buildExt(TableColumn column, SqliteColumnTypeEnum type) { if (!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())) { return ""; } return column.getComment(); } private String buildDefaultValue(TableColumn column, SqliteColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildNullable(TableColumn column, SqliteColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDataType(TableColumn column, SqliteColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (column.getColumnSize() == null || column.getDecimalDigits() == null) { return columnType; } if (column.getColumnSize() != null && column.getDecimalDigits() == null) { return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); } if (column.getColumnSize() != null && column.getDecimalDigits() != null) { return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); } return columnType; } public static List getTypes() { return Arrays.stream(SqliteColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteDefaultValueEnum.java ================================================ package ai.chat2db.plugin.sqlite.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum SqliteDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; SqliteDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(SqliteDefaultValueEnum.values()).map(SqliteDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java ================================================ package ai.chat2db.plugin.sqlite.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.Collation; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum SqliteIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), NORMAL("Normal", "INDEX"), UNIQUE("Unique", "UNIQUE INDEX"); public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; SqliteIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static SqliteIndexTypeEnum getByType(String type) { for (SqliteIndexTypeEnum value : SqliteIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public String buildIndexScript(TableIndex tableIndex) { if (this.equals(PRIMARY_KEY)) { return buildPrimaryKeyScript(tableIndex); } else { StringBuilder script = new StringBuilder(); script.append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append(" ON ").append(tableIndex.getTableName()).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); return script.toString(); } } private String buildPrimaryKeyScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("CONSTRAINT ").append(buildIndexName(tableIndex)).append(" ").append(keyword).append(" ").append(buildIndexColumn(tableIndex)); return script.toString(); } private String buildIndexComment(TableIndex tableIndex) { if (StringUtils.isBlank(tableIndex.getComment())) { return ""; } else { return StringUtils.join("COMMENT '", tableIndex.getComment(), "'"); } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { if (this.equals(PRIMARY_KEY)) { return tableIndex.getTableName()+"_pk"; } else { return "\"" + tableIndex.getName() + "\""; } } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), ",\n", "ADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("CREATE ", buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (SqliteIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\""); } public static List getIndexTypes() { return Arrays.asList(SqliteIndexTypeEnum.values()).stream().map(SqliteIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.sqlite.SqlitePlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml chat2db-sqlserver ai.chat2db chat2db-spi src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerCommandExecutor.java ================================================ package ai.chat2db.plugin.sqlserver; import ai.chat2db.spi.model.Command; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.SQLExecutor; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public class SqlServerCommandExecutor extends SQLExecutor { /** * Execute command */ @Override public List execute(Command command) { String sql = command.getScript(); command.setScript(removeSpecialGO(sql)); return super.execute(command); } private String removeSpecialGO(String sql) { if (StringUtils.isBlank(sql)) { return null; } sql = sql.replaceAll("(?mi)^[ \\t]*go[ \\t]*$", ";"); return sql; } /** * Execute command */ @Override public ExecuteResult executeUpdate(String sql, Connection connection, int n) throws SQLException { sql = removeSpecialGO(sql); return super.executeUpdate(sql, connection, n); } /** * */ public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count) throws SQLException { return super.execute(removeSpecialGO(sql), connection, limitRowSize, offset, count); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java ================================================ package ai.chat2db.plugin.sqlserver; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.ResultSetUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class SqlServerDBManage extends DefaultDBManage implements DBManage { private String tableDDLFunction = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; private static String TRIGGER_SQL_LIST = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " + "WHERE xtype = 'TR' "; private static String PROCEDURE_SQL = "SELECT COUNT(*) AS ProcedureCount\n" + "FROM sys.procedures\n" + "WHERE [name] = N'%s'\n" + "AND schema_id = SCHEMA_ID(N'%s');"; @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { exportTables(connection, databaseName, schemaName, asyncContext); exportViews(connection, databaseName, schemaName, asyncContext); exportFunctions(connection, schemaName, asyncContext); exportProcedures(connection, schemaName, asyncContext); exportTriggers(connection, asyncContext); } private void exportTables(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = "SELECT name FROM SysObjects Where XType='U'"; try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String tableName = resultSet.getString("name"); exportTable(connection, databaseName, schemaName, tableName, asyncContext); } } } public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { try { SQLExecutor.getInstance().execute(connection, tableDDLFunction.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { //log.error("Failed to create function", e); } String sql = String.format("SELECT %s.ufn_GetCreateTableScript('%s', '%s') as ddl", schemaName, schemaName, tableName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP TABLE IF EXISTS ").append(tableName).append(";").append("\n") .append(resultSet.getString("ddl")).append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData()) { exportTableData(connection, databaseName, schemaName, tableName, asyncContext); } else { asyncContext.write("go \n"); } } } } public void exportTableData(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String tableQuerySql = sqlBuilder.buildTableQuerySql(databaseName, schemaName, tableName); SQLExecutor.getInstance().execute(connection, tableQuerySql, 1000, resultSet -> { ResultSetMetaData metaData = resultSet.getMetaData(); List columnList = ResultSetUtils.getRsHeader(resultSet); List valueList = new ArrayList<>(); while (resultSet.next()) { for (int i = 1; i <= metaData.getColumnCount(); i++) { ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); JDBCDataValue jdbcDataValue = new JDBCDataValue(resultSet, metaData, i, false); String valueString = valueProcessor.getJdbcSqlValueString(jdbcDataValue); valueList.add(valueString); } String insertSql = sqlBuilder.buildSingleInsertSql(databaseName, schemaName, tableName, columnList, valueList); asyncContext.write(insertSql+";"); valueList.clear(); } asyncContext.write("go \n"); }); } private void exportViews(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS " + "WHERE TABLE_SCHEMA = '%s' AND TABLE_CATALOG = '%s'; ", schemaName, databaseName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("DROP VIEW IF EXISTS ").append(resultSet.getString("TABLE_NAME")).append(";\n").append("go").append("\n") .append(resultSet.getString("VIEW_DEFINITION")).append(";").append("\n") .append("go").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportFunctions(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT name FROM sys.objects WHERE type = 'FN' and SCHEMA_ID = SCHEMA_ID('%s')", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String functionName = resultSet.getString("name"); exportFunction(connection, functionName, schemaName, asyncContext); } } } private void exportFunction(Connection connection, String functionName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT OBJECT_DEFINITION(OBJECT_ID('%s.%s')) as ddl", schemaName, functionName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("ddl") .replace("CREATE FUNCTION", "CREATE OR ALTER FUNCTION")) .append("\n").append("go").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportProcedures(Connection connection, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT name FROM sys.procedures WHERE SCHEMA_ID = SCHEMA_ID('%s')", schemaName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { while (resultSet.next()) { String procedureName = resultSet.getString("name"); exportProcedure(connection, procedureName, schemaName, asyncContext); } } } private void exportProcedure(Connection connection, String procedureName, String schemaName, AsyncContext asyncContext) throws SQLException { String sql = String.format("SELECT definition FROM sys.sql_modules WHERE object_id = (OBJECT_ID('%s.%s'));", schemaName, procedureName); try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) { if (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("definition") .replace("CREATE PROCEDURE", "CREATE OR ALTER PROCEDURE")) .append("\n").append("go").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTriggers(Connection connection, AsyncContext asyncContext) throws SQLException { try (ResultSet resultSet = connection.createStatement().executeQuery(TRIGGER_SQL_LIST)) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append(resultSet.getString("triggerDefinition") .replace("CREATE TRIGGER", "CREATE OR ALTER TRIGGER")) .append("\n").append("go").append("\n"); asyncContext.write(sqlBuilder.toString()); } } } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { try { connection.setAutoCommit(false); String procedureBody = procedure.getProcedureBody(); boolean isCreateOrAlter = procedureBody.trim().toUpperCase().startsWith("CREATE OR ALTER "); if (procedureBody == null || !procedureBody.trim().toUpperCase().startsWith("CREATE")) { throw new IllegalArgumentException("No CREATE statement found."); } String procedureNewName = getSchemaOrProcedureName(procedureBody, schemaName, procedure); if (!procedureNewName.equals(procedure.getProcedureName())) { procedureBody = procedureBody.replace(procedure.getProcedureName(), procedureNewName); } String checkProcedureSQL = String.format(PROCEDURE_SQL, procedure.getProcedureName().toUpperCase(), schemaName.toUpperCase()); String finalProcedureBody = procedureBody; SQLExecutor.getInstance().execute(connection, checkProcedureSQL, resultSet -> { if (resultSet.next()) { int count = resultSet.getInt(1); if (count >= 1) { if (isCreateOrAlter) { SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet2 -> {}); } else { throw new SQLException("Procedure with the same name already exists."); } } } }); SQLExecutor.getInstance().execute(connection, finalProcedureBody, resultSet -> {}); } catch (Exception e) { connection.rollback(); throw new RuntimeException(e); } finally { connection.setAutoCommit(true); } } @Override public void connectDatabase(Connection connection, String database) { try { SQLExecutor.getInstance().execute(connection, "use [" + database + "];"); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException { String sql = ""; if(copyData){ sql = "SELECT * INTO " + newTableName + " FROM " + tableName; }else { sql = "SELECT * INTO " + newTableName + " FROM " + tableName + " WHERE 1=0"; } SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java ================================================ package ai.chat2db.plugin.sqlserver; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; import ai.chat2db.plugin.sqlserver.builder.SqlServerSqlBuilder; import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerDefaultValueEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class SqlServerMetaData extends DefaultMetaService implements MetaData { private List systemDatabases = Arrays.asList("master", "model", "msdb", "tempdb"); @Override public List databases(Connection connection) { List databases = SQLExecutor.getInstance().databases(connection); return sortDatabase(databases, systemDatabases, connection); } private List systemSchemas = Arrays.asList("guest", "INFORMATION_SCHEMA", "sys", "db_owner", "db_accessadmin", "db_securityadmin", "db_ddladmin", "db_backupoperator", "db_datareader", "db_datawriter", "db_denydatareader", "db_denydatawriter"); @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); return SortUtils.sortSchema(schemas, systemSchemas); } private String functionSQL = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { SQLExecutor.getInstance().execute(connection, functionSQL.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { //log.error("Failed to create function", e); } String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName + "') AS sql"; return SQLExecutor.getInstance().execute(connection, ddlSql, resultSet -> { if (resultSet.next()) { return resultSet.getString("sql"); } return null; }); } private static String SELECT_TABLES_SQL = "SELECT t.name AS TableName, mm.value as comment FROM sys.tables t LEFT JOIN(SELECT * from sys.extended_properties ep where ep.minor_id = 0 AND ep.name = 'MS_Description') mm ON t.object_id = mm.major_id WHERE t.schema_id= SCHEMA_ID('%s')"; @Override public List
tables(Connection connection, String databaseName, String schemaName, String tableName) { List
tables = new ArrayList<>(); String sql = String.format(SELECT_TABLES_SQL, schemaName); if (StringUtils.isNotBlank(tableName)) { sql += " AND t.name = '" + tableName + "'"; }else { sql += " ORDER BY t.name"; } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(resultSet.getString("TableName")); table.setComment(resultSet.getString("comment")); tables.add(table); } return tables; }); } private static final String SELECT_TABLE_COLUMNS = "SELECT c.name as COLUMN_NAME , c.is_nullable as IS_NULLABLE ,c.column_id as ORDINAL_POSITION,c.max_length as COLUMN_SIZE, c.scale as NUMERIC_SCALE, c.collation_name as COLLATION_NAME, ty.name as DATA_TYPE ,t.name, def.definition as COLUMN_DEFAULT, ep.value as COLUMN_COMMENT from sys.columns c LEFT JOIN sys.tables t on c.object_id=t.object_id LEFT JOIN sys.types ty ON c.user_type_id = ty.user_type_id LEFT JOIN sys.default_constraints def ON c.default_object_id = def.object_id LEFT JOIN sys.extended_properties ep ON t.object_id = ep.major_id AND c.column_id = ep.minor_id WHERE t.name ='%s' and t.schema_id=SCHEMA_ID('%s');"; @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TABLE_COLUMNS, tableName, schemaName); List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn column = new TableColumn(); column.setDatabaseName(databaseName); column.setTableName(tableName); column.setSchemaName(schemaName); column.setOldName(resultSet.getString("COLUMN_NAME")); column.setName(resultSet.getString("COLUMN_NAME")); //column.setColumnType(resultSet.getString("COLUMN_TYPE")); String dataType = resultSet.getString("DATA_TYPE").toUpperCase(); column.setColumnType(SqlUtils.removeDigits(dataType)); //column.setDataType(resultSet.getInt("DATA_TYPE")); column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT")); //column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); column.setComment(resultSet.getString("COLUMN_COMMENT")); // column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY"))); column.setNullable(resultSet.getInt("IS_NULLABLE")); column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE")); // column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); column.setCollationName(resultSet.getString("COLLATION_NAME")); column.setColumnSize(resultSet.getInt("COLUMN_SIZE")); //setColumnSize(column, resultSet.getString("COLUMN_TYPE")); tableColumns.add(column); } return tableColumns; }); } private static String ROUTINES_SQL = "SELECT type_desc, OBJECT_NAME(object_id) AS FunctionName, OBJECT_DEFINITION(object_id) AS " + "definition FROM sys.objects WHERE type_desc IN(%s) and name = '%s' ;"; private static String OBJECT_SQL = "SELECT name FROM sys.objects WHERE type = '%s' and SCHEMA_ID = SCHEMA_ID('%s') order by name;"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { String sql = String.format(ROUTINES_SQL, "'SQL_SCALAR_FUNCTION', 'SQL_TABLE_VALUED_FUNCTION'", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { function.setFunctionBody(resultSet.getString("definition")); } return function; }); } @Override public List functions(Connection connection, String databaseName, String schemaName) { List functions = new ArrayList<>(); String sql = String.format(OBJECT_SQL, "FN", schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(resultSet.getString("name")); functions.add(function); } return functions; }); } private Function removeVersion(Function function) { String fullFunctionName = function.getFunctionName(); if (!StringUtils.isEmpty(fullFunctionName)) { String[] parts = fullFunctionName.split(";"); String functionName = parts[0]; function.setFunctionName(functionName); } return function; } @Override public List procedures(Connection connection, String databaseName, String schemaName) { List procedures = new ArrayList<>(); String sql = String.format(OBJECT_SQL, "P", schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(resultSet.getString("name")); procedures.add(procedure); } return procedures; }); } private Procedure removeVersion(Procedure procedure) { String fullProcedureName = procedure.getProcedureName(); if (!StringUtils.isEmpty(fullProcedureName)) { String[] parts = fullProcedureName.split(";"); String procedureName = parts[0]; procedure.setProcedureName(procedureName); } return procedure; } private static String TRIGGER_SQL = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " + "WHERE xtype = 'TR' and name = '%s';"; private static String TRIGGER_SQL_LIST = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " + "WHERE xtype = 'TR' order by name"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, TRIGGER_SQL_LIST, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); trigger.setTriggerName(resultSet.getString("triggerName")); trigger.setSchemaName(schemaName); trigger.setDatabaseName(databaseName); triggers.add(trigger); } return triggers; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName) { String sql = String.format(TRIGGER_SQL, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody(resultSet.getString("triggerDefinition")); } return trigger; }); } @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName) { String sql = String.format(ROUTINES_SQL, "'SQL_STORED_PROCEDURE'", procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setProcedureBody(resultSet.getString("definition")); } return procedure; } ); } private static String VIEW_SQL = "SELECT TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' " + "AND TABLE_NAME = '%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { String sql = String.format(VIEW_SQL, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(databaseName); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("VIEW_DEFINITION")); } return table; }); } private static final String INDEX_SQL = "SELECT ic.key_ordinal AS COLUMN_POSITION, ic.is_descending_key as DESCEND , ind.name AS INDEX_NAME, ind.is_unique AS IS_UNIQUE, col.name AS COLUMN_NAME, ind.type_desc AS INDEX_TYPE, ind.is_primary_key AS IS_PRIMARY FROM sys.indexes ind INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id and ic.key_ordinal>0 INNER JOIN sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id INNER JOIN sys.tables t ON ind.object_id = t.object_id LEFT JOIN sys.key_constraints kc ON ind.object_id = kc.parent_object_id AND ind.index_id = kc.unique_index_id WHERE t.name = '%s' and t.schema_id= SCHEMA_ID('%s') ORDER BY t.name, ind.name, ind.index_id, ic.index_column_id"; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(INDEX_SQL, tableName,schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { LinkedHashMap map = new LinkedHashMap(); while (resultSet.next()) { String keyName = resultSet.getString("INDEX_NAME"); TableIndex tableIndex = map.get(keyName); if (tableIndex != null) { List columnList = tableIndex.getColumnList(); columnList.add(getTableIndexColumn(resultSet)); columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) .collect(Collectors.toList()); tableIndex.setColumnList(columnList); } else { TableIndex index = new TableIndex(); index.setDatabaseName(databaseName); index.setSchemaName(schemaName); index.setTableName(tableName); index.setName(keyName); int isunique = resultSet.getInt("IS_UNIQUE"); if(isunique ==1){ index.setUnique(true); }else { index.setUnique(false); } List tableIndexColumns = new ArrayList<>(); tableIndexColumns.add(getTableIndexColumn(resultSet)); index.setColumnList(tableIndexColumns); String indexType = resultSet.getString("INDEX_TYPE"); if (resultSet.getBoolean("IS_PRIMARY")) { index.setType(SqlServerIndexTypeEnum.PRIMARY_KEY.getName()); }else if("CLUSTERED".equalsIgnoreCase(indexType)){ if(index.getUnique()){ index.setType(SqlServerIndexTypeEnum.UNIQUE_CLUSTERED.getName()); }else { index.setType(SqlServerIndexTypeEnum.CLUSTERED.getName()); } }else if("NONCLUSTERED".equalsIgnoreCase(indexType)){ if(index.getUnique()){ index.setType(SqlServerIndexTypeEnum.UNIQUE_NONCLUSTERED.getName()); }else { index.setType(SqlServerIndexTypeEnum.NONCLUSTERED.getName()); } }else { index.setType(indexType); } map.put(keyName, index); } } return map.values().stream().collect(Collectors.toList()); }); } private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("COLUMN_NAME")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("COLUMN_POSITION")); int collation = resultSet.getInt("DESCEND"); if (collation == 1) { tableIndexColumn.setAscOrDesc("ASC"); } else { tableIndexColumn.setAscOrDesc("DESC"); } return tableIndexColumn; } @Override public SqlBuilder getSqlBuilder() { return new SqlServerSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() .columnTypes(SqlServerColumnTypeEnum.getTypes()) .charsets(null) .collations(null) .indexTypes(SqlServerIndexTypeEnum.getIndexTypes()) .defaultValues(SqlServerDefaultValueEnum.getDefaultValues()) .build(); } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); } @Override public CommandExecutor getCommandExecutor() { return new SqlServerCommandExecutor(); } @Override public List getSystemDatabases() { return systemDatabases; } @Override public List getSystemSchemas() { return systemSchemas; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerPlugin.java ================================================ package ai.chat2db.plugin.sqlserver; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class SqlServerPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue(this.getClass(),"sqlserver.json", DBConfig.class); } @Override public MetaData getMetaData() { return new SqlServerMetaData(); } @Override public DBManage getDBManage() { return new SqlServerDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java ================================================ package ai.chat2db.plugin.sqlserver.builder; import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; public class SqlServerSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE ").append("[").append(table.getSchemaName()).append("].[").append(table.getName()).append("] (").append("\n"); for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(column.getColumnType()); if (typeEnum == null) { continue; } script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)\ngo\n"); for (TableIndex tableIndex : table.getIndexList()) { if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } SqlServerIndexTypeEnum sqlServerIndexTypeEnum = SqlServerIndexTypeEnum.getByType(tableIndex.getType()); if (sqlServerIndexTypeEnum == null) { continue; } script.append("\n").append(sqlServerIndexTypeEnum.buildIndexScript(tableIndex)); if (StringUtils.isNotBlank(tableIndex.getComment())) { script.append("\n").append(buildIndexComment(tableIndex)); } } for (TableColumn column : table.getColumnList()) { if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildColumnComment(column)); } if (StringUtils.isNotBlank(table.getComment())) { script.append("\n").append(buildTableComment(table)); } return script.toString(); } private static String INDEX_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s','INDEX','%s' \ngo"; private String buildIndexComment(TableIndex tableIndex) { return String.format(INDEX_COMMENT_SCRIPT, tableIndex.getComment(), tableIndex.getSchemaName(), tableIndex.getTableName(), tableIndex.getName()); } private static String TABLE_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s' \ngo"; private String buildTableComment(Table table) { return String.format(TABLE_COMMENT_SCRIPT, table.getComment(), table.getSchemaName(), table.getName()); } private static String COLUMN_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s','COLUMN','%s' \ngo"; private String buildColumnComment(TableColumn column) { return String.format(COLUMN_COMMENT_SCRIPT, column.getComment(), column.getSchemaName(), column.getTableName(), column.getName()); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { script.append(buildRenameTable(oldTable, newTable)); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { if (oldTable.getComment() == null) { script.append("\n").append(buildTableComment(newTable)); } else { script.append("\n").append(buildUpdateTableComment(newTable)); } } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(tableColumn.getColumnType()); if (typeEnum == null) { continue; } script.append(typeEnum.buildModifyColumn(tableColumn)).append("\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { SqlServerIndexTypeEnum mysqlIndexTypeEnum = SqlServerIndexTypeEnum.getByType(tableIndex.getType()); if (mysqlIndexTypeEnum == null) { continue; } script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append("\n"); if (StringUtils.isNotBlank(tableIndex.getComment())) { script.append("\n").append(buildIndexComment(tableIndex)).append("\ngo"); } } } return script.toString(); } private static String UPDATE_TABLE_COMMENT_SCRIPT = "exec sp_updateextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s' \ngo"; private String buildUpdateTableComment(Table newTable) { return String.format(UPDATE_TABLE_COMMENT_SCRIPT, newTable.getComment(), newTable.getSchemaName(), newTable.getName()); } private static String RENAME_TABLE_SCRIPT = "exec sp_rename '%s','%s','OBJECT' \ngo"; private String buildRenameTable(Table oldTable, Table newTable) { return String.format(RENAME_TABLE_SCRIPT, oldTable.getName(), newTable.getName()); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { String version = Chat2DBContext.getDbVersion(); if (StringUtils.isNotBlank(version)) { String[] versions = version.split("\\."); if (versions.length > 0 && Integer.parseInt(versions[0]) >= 11) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if(!sql.toLowerCase().contains("order by")){ sqlBuilder.append("\n ORDER BY (SELECT NULL)"); } sqlBuilder.append("\n OFFSET "); sqlBuilder.append(offset); sqlBuilder.append(" ROWS "); sqlBuilder.append(" FETCH NEXT "); sqlBuilder.append(pageSize); sqlBuilder.append(" ROWS ONLY"); return sqlBuilder.toString(); } } return ""; } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE [" + database.getName() + "]"); if (StringUtils.isNotBlank(database.getCollation())) { sqlBuilder.append(" COLLATE ").append(database.getCollation()); } sqlBuilder.append("\ngo\n"); if (StringUtils.isNotBlank(database.getComment())) { sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','") .append(database.getComment()).append("'").append("\ngo\n"); } return sqlBuilder.toString(); } @Override public String buildCreateSchemaSql(Schema schema) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE SCHEMA [" + schema.getName() + "] \ngo\n"); if (StringUtils.isNotBlank(schema.getComment())) { sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','") .append(schema.getComment()).append("'").append(",'SCHEMA'") .append(",'").append(schema.getName()).append("'").append("\ngo\n"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json ================================================ { "dbType": "SQLSERVER", "supportDatabase": true, "supportSchema": true, "driverConfigList": [ { "url": "jdbc:sqlserver://localhost:1433;database=master", "custom": false, "defaultDriver": true, "downloadJdbcDriverUrls": [ "https://cdn.chat2db-ai.com/lib/mssql-jdbc-11.2.1.jre17.jar" ], "jdbcDriver": "mssql-jdbc-11.2.1.jre17.jar", "jdbcDriverClass": "com.microsoft.sqlserver.jdbc.SQLServerDriver", "extendInfo": [ { "key": "encrypt", "value": "false", "required": false }, { "key": "trustServerCertificate", "value": "true", "required": false }, { "key": "integratedSecurity", "value": "false", "required": false }, { "key": "Trusted_Connection", "value": "yes", "required": false } ] } ], "name": "SQLServer" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java ================================================ package ai.chat2db.plugin.sqlserver.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; public enum SqlServerColumnTypeEnum implements ColumnBuilder { //JSON("JSON", false, false, true, false, false, false, true, false, false, false) BIGINT("BIGINT", false, false, true, false, false, false, true, true), BINARY("BINARY", false, false, true, false, false, false, true, true), BIT("BIT", false, false, true, false, false, false, true, true), CHAR("CHAR", true, false, true, false, false, true, true, true), DATE("DATE", false, false, true, false, false, false, true, true), DATETIME("DATETIME", false, false, true, false, false, false, true, true), DATETIME2("DATETIME2", true, false, true, false, false, false, true, true), DATETIMEOFFSET("DATETIMEOFFSET", true, false, true, false, false, false, true, true), DECIMAL("DECIMAL", true, true, true, false, false, false, true, true), FLOAT("FLOAT", true, false, true, false, false, false, true, true), GEOGRAPHY("GEOGRAPHY", false, false, true, false, false, false, true, true), GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, true), HIERARCHYID("HIERARCHYID", false, false, true, false, false, false, true, true), IMAGE("IMAGE", false, false, true, false, false, false, true, true), INT("INT", false, false, true, false, false, false, true, true), MONEY("MONEY", false, false, true, false, false, false, true, true), NCHAR("NCHAR", true, false, true, false, false, true, true, true), NTEXT("NTEXT", false, false, true, false, false, false, true, true), NUMERIC("NUMERIC", true, true, true, false, false, false, true, true), NVARCHAR("NVARCHAR", true, false, true, false, false, true, true, true), NVARCHAR_MAX("NVARCHAR(MAX)", false, false, true, false, false, true, true, true), REAL("REAL", false, false, true, false, false, false, true, true), SMALLDATETIME("SMALLDATETIME", false, false, true, false, false, false, true, true), SMALLINT("SMALLINT", false, false, true, false, false, false, true, true), SMALLMONEY("SMALLMONEY", false, false, true, false, false, false, true, true), SQL_VARIANT("SQL_VARIANT", false, false, true, false, false, false, true, true), SYSNAME("SYSNAME", false, false, true, false, false, false, true, true), TEXT("TEXT", false, false, true, false, false, true, true, true), TIME("TIME", true, false, true, false, false, false, true, true), TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, true, true), TINYINT("TINYINT", false, false, true, false, false, false, true, true), UNIQUEIDENTIFIER("UNIQUEIDENTIFIER", false, false, true, false, false, false, true, true), VARBINARY("VARBINARY", true, false, true, false, false, false, true, true), VARBINARY_MAX("VARBINARY(MAX)", false, false, true, false, false, false, true, true), VARCHAR("VARCHAR", true, false, true, false, false, true, true, true), VARCHAR_MAX("VARCHAR(MAX)", false, false, true, false, false, true, true, true), XML("XML", false, false, true, false, false, false, true, true), OTHER("OTHER", false, false, true, false, false, false, true, true), ; private ColumnType columnType; public static SqlServerColumnTypeEnum getByType(String dataType) { SqlServerColumnTypeEnum typeEnum = COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase())); if (typeEnum == null) { return OTHER; } return typeEnum; } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (SqlServerColumnTypeEnum value : SqlServerColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } public ColumnType getColumnType() { return columnType; } SqlServerColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, false, false, false); } @Override public String buildCreateColumnSql(TableColumn column) { SqlServerColumnTypeEnum type = this; StringBuilder script = new StringBuilder(); script.append("[").append(column.getName()).append("]").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildSparse(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildNullable(column, type)).append(" "); script.append(buildCollation(column, type)).append(" "); return script.toString(); } public String buildUpdateColumnSql(TableColumn column) { SqlServerColumnTypeEnum type = this; StringBuilder script = new StringBuilder(); script.append("[").append(column.getName()).append("]").append(" "); script.append(buildDataType(column, type)).append(" "); script.append(buildNullable(column, type)).append(" \ngo\n"); if (StringUtils.isNotBlank(column.getDefaultValue()) && column.getOldColumn().getDefaultValue() != null && !StringUtils.equalsIgnoreCase(column.getDefaultValue(), column.getOldColumn().getDefaultValue())) { script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); script.append(" ").append("DROP CONSTRAINT ").append("[").append(column.getDefaultConstraintName()).append("]"); script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); script.append(" ").append("ADD ").append(buildDefaultValue(column, type)).append(" for ").append(column.getName()).append(" \ngo\n"); } if (StringUtils.isNotBlank(column.getDefaultValue()) && column.getOldColumn().getDefaultValue() == null) { script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); script.append(" ").append("ADD ").append(buildDefaultValue(column, type)).append(" for ").append(column.getName()).append(" \ngo\n"); } if (!Objects.equals(column.getSparse(), column.getOldColumn().getSparse())) { script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); script.append(" ").append("ALTER COLUMN ").append("[").append(column.getName()).append("]").append(" add ").append("SPARSE").append(" \ngo\n"); } if (!Objects.equals(column.getCollationName(), column.getOldColumn().getCollationName())) { script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); script.append(" ").append("ALTER COLUMN ").append("[").append(column.getName()).append("]").append(" ").append("COLLATE ").append(column.getCollationName()).append(" \ngo\n"); } return script.toString(); } private String buildSparse(TableColumn column, SqlServerColumnTypeEnum type) { if (Boolean.TRUE.equals(column.getSparse())) { return "SPARSE"; } else { return ""; } } private String buildCollation(TableColumn column, SqlServerColumnTypeEnum type) { if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { return ""; } return StringUtils.join("COLLATE ", column.getCollationName()); } private String buildNullable(TableColumn column, SqlServerColumnTypeEnum type) { if (!type.getColumnType().isSupportNullable()) { return ""; } if (column.getNullable() != null && 1 == column.getNullable()) { return "NULL"; } else { return "NOT NULL"; } } private String buildDefaultValue(TableColumn column, SqlServerColumnTypeEnum type) { if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildDataType(TableColumn column, SqlServerColumnTypeEnum type) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(CHAR, NCHAR, NVARCHAR, VARBINARY, VARCHAR).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null) { script.append("(").append(column.getColumnSize()).append(")"); } return script.toString(); } if (Arrays.asList(DECIMAL, FLOAT, TIMESTAMP, TIME, DATETIME2, DATETIMEOFFSET, FLOAT, NUMERIC).contains(type)) { StringBuilder script = new StringBuilder(); script.append(columnType); if (column.getColumnSize() != null && column.getDecimalDigits() == null) { script.append("(").append(column.getColumnSize()).append(")"); } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); } return script.toString(); } if (Arrays.asList().contains(type)) { StringBuilder script = new StringBuilder(); if (column.getColumnSize() == null) { script.append(columnType); } else { String[] split = columnType.split("TIMESTAMP"); script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]); } return script.toString(); } if(OTHER.equals(columnType)){ return column.getColumnType(); } return columnType; } private static String RENAME_COLUMN_SCRIPT = "exec sp_rename '%s.%s','%s','COLUMN' \ngo"; private String renameColumn(TableColumn tableColumn) { return String.format(RENAME_COLUMN_SCRIPT, tableColumn.getTableName(), tableColumn.getOldName(), tableColumn.getName()); } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); if (StringUtils.isNotBlank(tableColumn.getDefaultConstraintName())) { script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); script.append(" ").append("DROP CONSTRAINT ").append("[").append(tableColumn.getDefaultConstraintName()).append("]"); script.append("\ngo\n"); } script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); script.append(" ").append("DROP COLUMN ").append("[").append(tableColumn.getName()).append("]"); script.append("\ngo\n"); return script.toString(); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); script.append(" ").append("ADD ").append(buildCreateColumnSql(tableColumn)).append(" \ngo\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildModifyColumnComment(tableColumn)); } return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { script.append(renameColumn(tableColumn)); script.append("\n"); } script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); script.append(" ").append("ALTER COLUMN ").append(buildUpdateColumnSql(tableColumn)).append(" \n"); if (!Objects.equals(tableColumn.getComment(), tableColumn.getOldColumn().getComment())) { script.append("\n").append(buildModifyColumnComment(tableColumn)); } return script.toString(); } return ""; } private static String COLUMN_MODIFY_COMMENT_SCRIPT = "IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',\n" + "'SCHEMA', N'%s',\n" + "'TABLE', N'%s',\n" + "'COLUMN', N'%s')) > 0)\n" + " EXEC sp_updateextendedproperty\n" + "'MS_Description', N'%s',\n" + "'SCHEMA', N'%s',\n" + "'TABLE', N'%s',\n" + "'COLUMN', N'%s'\n" + "ELSE\n" + " EXEC sp_addextendedproperty\n" + "'MS_Description', N'%s',\n" + "'SCHEMA', N'%s',\n" + "'TABLE', N'%s',\n" + "'COLUMN', N'%s'\n go"; private String buildModifyColumnComment(TableColumn tableColumn) { return String.format(COLUMN_MODIFY_COMMENT_SCRIPT, tableColumn.getSchemaName(), tableColumn.getTableName(), tableColumn.getName(), tableColumn.getComment(), tableColumn.getSchemaName(), tableColumn.getTableName(), tableColumn.getName(), tableColumn.getComment(), tableColumn.getSchemaName(), tableColumn.getTableName(), tableColumn.getName()); } public static List getTypes() { return Arrays.stream(SqlServerColumnTypeEnum.values()).map(columnTypeEnum -> columnTypeEnum.getColumnType() ).toList(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerDefaultValueEnum.java ================================================ package ai.chat2db.plugin.sqlserver.type; import ai.chat2db.spi.model.DefaultValue; import java.util.Arrays; import java.util.List; public enum SqlServerDefaultValueEnum { EMPTY_STRING("EMPTY_STRING"), NULL("NULL"), ; private DefaultValue defaultValue; SqlServerDefaultValueEnum(String defaultValue) { this.defaultValue = new DefaultValue(defaultValue); } public DefaultValue getDefaultValue() { return defaultValue; } public static List getDefaultValues() { return Arrays.stream(SqlServerDefaultValueEnum.values()).map(SqlServerDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java ================================================ package ai.chat2db.plugin.sqlserver.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.List; public enum SqlServerIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), // NORMAL("Normal", "INDEX"), // // UNIQUE("Unique", "UNIQUE INDEX"), UNIQUE_CLUSTERED("UNIQUE CLUSTERED", "UNIQUE CLUSTERED INDEX"), CLUSTERED("CLUSTERED", "CLUSTERED INDEX"), NONCLUSTERED("NONCLUSTERED", "NONCLUSTERED INDEX"), UNIQUE_NONCLUSTERED("UNIQUE NONCLUSTERED", "UNIQUE NONCLUSTERED INDEX"), SPATIAL("SPATIAL", "SPATIAL INDEX"), XML("XML", "XML INDEX"); public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } private IndexType indexType; public String getName() { return name; } private String name; public String getKeyword() { return keyword; } private String keyword; SqlServerIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static SqlServerIndexTypeEnum getByType(String type) { for (SqlServerIndexTypeEnum value : SqlServerIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } //ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [PK__Employee__7AD04FF164ABF7C7] PRIMARY KEY CLUSTERED ([FirstName]) public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (PRIMARY_KEY.equals(this)) { script.append("ALTER TABLE [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] ADD CONSTRAINT ").append(buildIndexName(tableIndex)).append(" ").append(keyword).append(" ").append(buildIndexColumn(tableIndex)); } else { script.append("CREATE ").append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append("\n ON [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] ").append(buildIndexColumn(tableIndex)); } script.append("\ngo"); return script.toString(); } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("[").append(column.getColumnName()).append("]"); if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { script.append(" ").append(column.getAscOrDesc()); } script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { return "[" + tableIndex.getName() + "]"; } public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex), "\n", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildIndexScript(tableIndex)); } return ""; } private String buildDropIndex(TableIndex tableIndex) { if (SqlServerIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("ALTER TABLE [", tableIndex.getSchemaName(), "].[", tableIndex.getTableName(), "] DROP CONSTRAINT ", buildIndexName(tableIndex),"\ngo"); } StringBuilder script = new StringBuilder(); script.append("DROP INDEX "); script.append(buildIndexName(tableIndex)); script.append(" ON [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] \ngo"); return script.toString(); } public static List getIndexTypes() { return Arrays.asList(SqlServerIndexTypeEnum.values()).stream().map(SqlServerIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); }} ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.sqlserver.SqlServerPlugin ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/pom.xml ================================================ 4.0.0 ai.chat2db chat2db-plugins ${revision} ../pom.xml ai.chat2db chat2db-spi chat2db-timeplus src/main/java **/*.json src/main/resources ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/TimeplusDBManage.java ================================================ package ai.chat2db.plugin.timeplus; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import java.sql.*; import java.util.Objects; import org.apache.commons.lang3.StringUtils; public class TimeplusDBManage extends DefaultDBManage implements DBManage { @Override public void exportDatabase( Connection connection, String databaseName, String schemaName, AsyncContext asyncContext ) throws SQLException { exportTablesOrViewsOrDictionaries( connection, databaseName, schemaName, asyncContext ); exportFunctions(connection, asyncContext); } private void exportFunctions( Connection connection, AsyncContext asyncContext ) throws SQLException { String sql = "SELECT name,create_query from system.functions where origin='ExecutableUserDefined'"; try ( ResultSet resultSet = connection.createStatement().executeQuery(sql) ) { while (resultSet.next()) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder .append("DROP FUNCTION IF EXISTS ") .append(resultSet.getString("name")) .append(";") .append("\n") .append(resultSet.getString("create_query")) .append(";") .append("\n"); asyncContext.write(sqlBuilder.toString()); } } } private void exportTablesOrViewsOrDictionaries( Connection connection, String databaseName, String schemaName, AsyncContext asyncContext ) throws SQLException { String sql = String.format( "SELECT create_table_query, has_own_data,engine,name from system.`tables` WHERE `database`='%s'", databaseName ); try ( Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql) ) { while (resultSet.next()) { String ddl = resultSet.getString("create_table_query"); boolean dataFlag = resultSet.getInt("has_own_data") == 1; String tableType = resultSet.getString("engine"); String tableOrViewName = resultSet.getString("name"); if (Objects.equals("View", tableType)) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder .append("DROP VIEW IF EXISTS ") .append(databaseName) .append(".") .append(tableOrViewName) .append(";") .append("\n") .append(ddl) .append(";") .append("\n"); asyncContext.write(sqlBuilder.toString()); } else if (Objects.equals("Dictionary", tableType)) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder .append("DROP DICTIONARY IF EXISTS ") .append(databaseName) .append(".") .append(tableOrViewName) .append(";") .append("\n") .append(ddl) .append(";") .append("\n"); asyncContext.write(sqlBuilder.toString()); } else { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder .append("DROP STREAM IF EXISTS ") .append(databaseName) .append(".") .append(tableOrViewName) .append(";") .append("\n") .append(ddl) .append(";") .append("\n"); asyncContext.write(sqlBuilder.toString()); if (asyncContext.isContainsData() && dataFlag) { exportTableData( connection, databaseName, schemaName, tableOrViewName, asyncContext ); } } } } } @Override public Connection getConnection(ConnectInfo connectInfo) { String url = setDatabaseInJdbcUrl(connectInfo); connectInfo.setUrl(url); return super.getConnection(connectInfo); } private String setDatabaseInJdbcUrl(ConnectInfo connectInfo) { String databaseName; //sometimes it's null, sometimes not. Need special handling String url = connectInfo.getUrl(); if ( StringUtils.isBlank( (databaseName = connectInfo.getDatabaseName()) ) && StringUtils.isBlank((databaseName = connectInfo.getSchemaName())) ) { return url; } String connectAddress = connectInfo.getHost() + ":" + connectInfo.getPort(); String[] addressSplit = url.split(connectAddress); String connectParams = addressSplit[1]; if (connectParams.startsWith("/")) { // Remove / from connection parameters connectParams = connectParams.substring(1); } if (connectParams.equals(databaseName)) { return url; } // Add database name String rv = addressSplit[0] + connectAddress + "/" + databaseName + connectParams; return rv; } @Override public void dropTable( Connection connection, String databaseName, String schemaName, String tableName ) { String sql = "DROP STREAM IF EXISTS " + databaseName + "." + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void copyTable( Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData ) throws SQLException { String sql = "CREATE STREAM " + newTableName + " AS " + tableName + ""; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); if (copyData) { sql = "INSERT INTO " + newTableName + " SELECT * FROM table(" + tableName + ")"; SQLExecutor.getInstance() .execute(connection, sql, resultSet -> null); } } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/TimeplusMetaData.java ================================================ package ai.chat2db.plugin.timeplus; import static ai.chat2db.spi.util.SortUtils.sortDatabase; import ai.chat2db.plugin.timeplus.builder.TimeplusSqlBuilder; import ai.chat2db.plugin.timeplus.type.TimeplusColumnTypeEnum; import ai.chat2db.plugin.timeplus.type.TimeplusEngineTypeEnum; import ai.chat2db.plugin.timeplus.type.TimeplusIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; public class TimeplusMetaData extends DefaultMetaService implements MetaData { private static String ROUTINES_SQL = ""; private static String TRIGGER_SQL = ""; private static String TRIGGER_SQL_LIST = ""; private static String SELECT_TABLE_COLUMNS = "select * from `system`.columns where table ='%s' and database='%s';"; private static String VIEW_SQL = "SELECT create_table_query from system.`tables` WHERE `database`='%s' and name='%s'"; private List systemDatabases = Arrays.asList( "information_schema", "system" ); public static final String FUNCTION_SQL = "SELECT name,create_query as ddl from system.functions where origin='ExecutableUserDefined'"; public static String format(String tableName) { return "`" + tableName + "`"; } @Override public List functions( Connection connection, String databaseName, String schemaName ) { return SQLExecutor.getInstance() .execute(connection, FUNCTION_SQL, resultSet -> { List functions = new ArrayList<>(); while (resultSet.next()) { Function function = new Function(); function.setFunctionName(resultSet.getString("name")); functions.add(function); } return functions; }); } @Override public List databases(Connection connection) { List list = SQLExecutor.getInstance() .execute( connection, "SELECT name FROM system.databases;", resultSet -> { List databases = new ArrayList<>(); try { while (resultSet.next()) { String dbName = resultSet.getString("name"); Database database = new Database(); database.setName(dbName); databases.add(database); } } catch (SQLException e) { throw new RuntimeException(e); } return databases; } ); return sortDatabase(list, systemDatabases, connection); } @Override public String tableDDL( Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName ) { String sql = "SHOW CREATE " + format(schemaName) + "." + format(tableName); return SQLExecutor.getInstance() .execute(connection, sql, resultSet -> { if (resultSet.next()) { return resultSet.getString("statement"); } return null; }); } @Override public Function function( Connection connection, @NotEmpty String databaseName, String schemaName, String functionName ) { return SQLExecutor.getInstance() .execute(connection, FUNCTION_SQL, resultSet -> { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); function.setFunctionName(functionName); if (resultSet.next()) { /* function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); function.setRemarks(resultSet.getString("ROUTINE_COMMENT"));*/ function.setFunctionBody(resultSet.getString("ddl")); } return function; }); } @Override public List triggers( Connection connection, String databaseName, String schemaName ) { List triggers = new ArrayList<>(); return triggers; } @Override public Trigger trigger( Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName ) { String sql = String.format(TRIGGER_SQL, databaseName, triggerName); return SQLExecutor.getInstance() .execute(connection, sql, resultSet -> { Trigger trigger = new Trigger(); trigger.setDatabaseName(databaseName); trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { trigger.setTriggerBody( resultSet.getString("ACTION_STATEMENT") ); } return trigger; }); } @Override public Procedure procedure( Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName ) { String sql = String.format( ROUTINES_SQL, "PROCEDURE", schemaName, procedureName ); return SQLExecutor.getInstance() .execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setSpecificName( resultSet.getString("SPECIFIC_NAME") ); procedure.setRemarks( resultSet.getString("ROUTINE_COMMENT") ); procedure.setProcedureBody( resultSet.getString("ROUTINE_DEFINITION") ); } return procedure; }); } @Override public List columns( Connection connection, String databaseName, String schemaName, String tableName ) { final String db = "default"; String sql = String.format(SELECT_TABLE_COLUMNS, tableName, db); List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance() .execute(connection, sql, resultSet -> { while (resultSet.next()) { TableColumn column = new TableColumn(); column.setDatabaseName(db); column.setTableName(tableName); column.setOldName(resultSet.getString("name")); column.setName(resultSet.getString("name")); String dataType = resultSet.getString("type"); if (dataType.startsWith("nullable(")) { dataType = dataType.substring(9, dataType.length() - 1); column.setNullable(1); } column.setColumnType(dataType); column.setDefaultValue( resultSet.getString("default_expression") ); // column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); column.setComment(resultSet.getString("comment")); column.setOrdinalPosition(resultSet.getInt("position")); column.setDecimalDigits(resultSet.getInt("numeric_scale")); /*column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); column.setCollationName(resultSet.getString("COLLATION_NAME"));*/ setColumnSize(column, dataType); tableColumns.add(column); } return tableColumns; }); } private void setColumnSize(TableColumn column, String columnType) { try { if (columnType.contains("(")) { String size = columnType.substring( columnType.indexOf("(") + 1, columnType.indexOf(")") ); //"size" can be a number or "3, 'UTC'" with timezone for datetime objects if ( "SET".equalsIgnoreCase(column.getColumnType()) || "ENUM".equalsIgnoreCase(column.getColumnType()) ) { column.setValue(size); } else { if (size.contains(",")) { String[] sizes = size.split(","); if (StringUtils.isNotBlank(sizes[0])) { column.setColumnSize(Integer.parseInt(sizes[0])); } if (StringUtils.isNotBlank(sizes[1])) { //can be " 'UTC'" if (sizes[1].contains("'") == false) { column.setDecimalDigits( Integer.parseInt(sizes[1]) ); } } } else { column.setColumnSize(Integer.parseInt(size)); } } } } catch (Exception e) { e.printStackTrace(); } } @Override public Table view( Connection connection, String databaseName, String schemaName, String viewName ) { final String db = "default"; String sql = String.format(VIEW_SQL, db, viewName); return SQLExecutor.getInstance() .execute(connection, sql, resultSet -> { Table table = new Table(); table.setDatabaseName(db); table.setSchemaName(schemaName); table.setName(viewName); if (resultSet.next()) { table.setDdl(resultSet.getString("create_table_query")); } return table; }); } @Override public List indexes( Connection connection, String databaseName, String schemaName, String tableName ) { List rv = new ArrayList<>(); return rv; } private List getTableIndexColumn(ResultSet resultSet) throws SQLException { List tableIndexColumns = new ArrayList<>(); return tableIndexColumns; } @Override public SqlBuilder getSqlBuilder() { return new TimeplusSqlBuilder(); } @Override public TableMeta getTableMeta( String databaseName, String schemaName, String tableName ) { return TableMeta.builder() .columnTypes(TimeplusColumnTypeEnum.getTypes()) .engineTypes(TimeplusEngineTypeEnum.getTypes()) //.indexTypes(TimeplusIndexTypeEnum.getIndexTypes()) .build(); } @Override public String getMetaDataName(String... names) { //avoid default.default.abc String rv = Arrays.stream(names) .filter(name -> StringUtils.isNotBlank(name)) .map(name -> "`" + name + "`") .collect(Collectors.joining(".")); rv = rv.replaceFirst("`default`.`default`", "`default`"); return rv; } @Override public List getSystemDatabases() { return systemDatabases; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/TimeplusPlugin.java ================================================ package ai.chat2db.plugin.timeplus; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.FileUtils; public class TimeplusPlugin implements Plugin { @Override public DBConfig getDBConfig() { return FileUtils.readJsonValue( this.getClass(), "timeplus.json", DBConfig.class ); } @Override public MetaData getMetaData() { return new TimeplusMetaData(); } @Override public DBManage getDBManage() { return new TimeplusDBManage(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/builder/TimeplusSqlBuilder.java ================================================ package ai.chat2db.plugin.timeplus.builder; import ai.chat2db.plugin.timeplus.type.TimeplusColumnTypeEnum; import ai.chat2db.plugin.timeplus.type.TimeplusIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; public class TimeplusSqlBuilder extends DefaultSqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE STREAM "); if (StringUtils.isNotBlank(table.getDatabaseName())) { script .append("`") .append(table.getDatabaseName()) .append("`") .append("."); } script .append("`") .append(table.getName()) .append("`") .append(" (") .append("\n"); // append column for (TableColumn column : table.getColumnList()) { if ( StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) ) { continue; } TimeplusColumnTypeEnum typeEnum = TimeplusColumnTypeEnum.getByType( column.getColumnType() ); if (typeEnum != null) { continue; } script .append("\t") .append(typeEnum.buildCreateColumnSql(column)) .append(",\n"); } // append index for (TableIndex tableIndex : table.getIndexList()) { if ( StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType()) ) { continue; } TimeplusIndexTypeEnum mysqlIndexTypeEnum = TimeplusIndexTypeEnum.getByType(tableIndex.getType()); if (!TimeplusIndexTypeEnum.PRIMARY.equals(mysqlIndexTypeEnum)) { script .append("\t") .append("") .append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)) .append(",\n"); } } script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); if (StringUtils.isNotBlank(table.getEngine())) { script.append(" ENGINE=").append(table.getEngine()).append("\n"); } // append primary key for (TableIndex tableIndex : table.getIndexList()) { if ( StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType()) ) { continue; } TimeplusIndexTypeEnum mysqlIndexTypeEnum = TimeplusIndexTypeEnum.getByType(tableIndex.getType()); if (TimeplusIndexTypeEnum.PRIMARY.equals(mysqlIndexTypeEnum)) { script .append("\t") .append("") .append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)) .append("\n"); } } if (StringUtils.isNotBlank(table.getComment())) { script.append(" COMMENT '").append(table.getComment()).append("'"); } script.append(";"); return script.toString(); } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); script.append("ALTER STREAM "); if (StringUtils.isNotBlank(oldTable.getDatabaseName())) { script .append("`") .append(oldTable.getDatabaseName()) .append("`") .append("."); } script.append("`").append(oldTable.getName()).append("`").append("\n"); if ( !StringUtils.equalsIgnoreCase( oldTable.getComment(), newTable.getComment() ) ) { script .append("\t") .append("MODIFY COMMENT") .append("'") .append(newTable.getComment()) .append("'") .append(",\n"); } // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if ( StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName()) ) { TimeplusColumnTypeEnum typeEnum = TimeplusColumnTypeEnum.getByType( tableColumn.getColumnType() ); if (typeEnum == null) { continue; } script .append("\t") .append(typeEnum.buildModifyColumn(tableColumn)) .append(",\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if ( StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType()) ) { TimeplusIndexTypeEnum clickHouseIndexTypeEnum = TimeplusIndexTypeEnum.getByType(tableIndex.getType()); if (clickHouseIndexTypeEnum == null) { continue; } script .append("\t") .append( clickHouseIndexTypeEnum.buildModifyIndex(tableIndex) ) .append(",\n"); } } if (script.length() > 2) { script = new StringBuilder( script.substring(0, script.length() - 2) ); script.append(";"); } return script.toString(); } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if (offset == 0) { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(pageSize); } else { sqlBuilder.append("\n LIMIT "); sqlBuilder.append(offset); sqlBuilder.append(","); sqlBuilder.append(pageSize); } return sqlBuilder.toString(); } @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE DATABASE `" + database.getName() + "`"); if (StringUtils.isNotBlank(database.getComment())) { sqlBuilder .append(";ALTER DATABASE ") .append(database.getName()) .append(" COMMENT '") .append(database.getComment()) .append("';"); } return sqlBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/timeplus.json ================================================ { "dbType": "TIMEPLUS", "supportDatabase": true, "supportSchema": false, "driverConfigList": [ { "url": "jdbc:timeplus://localhost:7587", "defaultDriver": true, "custom": false, "downloadJdbcDriverUrls": [ "https://timeplus.io/downloads/timeplus-native-jdbc-shaded-2.0.5.jar" ], "jdbcDriver": "timeplus-native-jdbc-shaded-2.0.5.jar", "jdbcDriverClass": "com.timeplus.jdbc.TimeplusDriver" } ], "name": "Timeplus" } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/type/TimeplusColumnTypeEnum.java ================================================ package ai.chat2db.plugin.timeplus.type; import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Maps; import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; public enum TimeplusColumnTypeEnum implements ColumnBuilder { String( "string", false, false, true, false, false, false, true, true, false, false ), Int8( "int8", false, false, true, false, false, false, true, true, false, false ), Int16( "int16", false, false, true, false, false, false, true, true, false, false ), Int32( "int32", false, false, true, false, false, false, true, true, false, false ), Int64( "int64", false, false, true, false, false, false, true, true, false, false ), Int128( "int128", false, false, true, false, false, false, true, true, false, false ), Int256( "int256", false, false, true, false, false, false, true, true, false, false ), UInt8( "uint8", false, false, true, false, false, false, true, true, false, false ), UInt16( "uint16", false, false, true, false, false, false, true, true, false, false ), UInt32( "uint32", false, false, true, false, false, false, true, true, false, false ), UInt64( "uint64", false, false, true, false, false, false, true, true, false, false ), UInt128( "uint128", false, false, true, false, false, false, true, true, false, false ), UInt256( "uint256", false, false, true, false, false, false, true, true, false, false ), Float32( "float32", false, false, true, false, false, false, true, true, false, false ), Float64( "float64", false, false, true, false, false, false, true, true, false, false ), Decimal( "decimal", true, true, true, false, false, false, true, true, false, false ), Boolean( "bool", false, false, true, false, false, false, true, true, false, false ), FixedString( "fixed_string", false, false, true, false, false, false, true, true, false, false ), UUID( "uuid", false, false, true, false, false, false, true, true, false, false ), Date( "date", false, false, true, false, false, false, true, true, false, false ), DATE32( "date32", false, false, true, false, false, false, true, true, false, false ), DateTime( "datetime", false, false, true, false, false, false, true, true, false, false ), DateTime64( "datetime64", false, false, true, false, false, false, true, true, false, false ), Enum8( "enum8", false, false, true, false, false, false, true, true, false, false ), Enum16( "enum16", false, false, true, false, false, false, true, true, false, false ), Array( "array", false, false, false, false, false, false, true, true, false, false ), JSON( "json", false, false, true, false, false, false, true, true, false, false ), Nested( "nested", false, false, true, false, false, false, true, true, false, false ), Map("map", true, true, true, false, false, false, true, true, false, false), IPv4( "ipv4", false, false, true, false, false, false, true, true, false, false ), IPv6( "ipv6", false, false, true, false, false, false, true, true, false, false ), Point( "point", false, false, true, false, false, false, true, true, false, false ), Ring( "ring", false, false, true, false, false, false, true, true, false, false ), Polygon( "polygon", false, false, true, false, false, false, true, true, false, false ), MultiPolygon( "multi_polygon", false, false, true, false, false, false, true, true, false, false ), AggregateFunction( "aggregate_function", true, true, true, false, false, false, true, true, false, false ), SimpleAggregateFunction( "simple_aggregate_function", true, true, true, false, false, false, true, true, false, false ); private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); static { for (TimeplusColumnTypeEnum value : TimeplusColumnTypeEnum.values()) { COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } private ColumnType columnType; TimeplusColumnTypeEnum( String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue ) { this.columnType = new ColumnType( dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false ); } public static TimeplusColumnTypeEnum getByType(String dataType) { return COLUMN_TYPE_MAP.get( SqlUtils.removeDigits(dataType.toUpperCase()) ); } public static List getTypes() { return Arrays.stream(TimeplusColumnTypeEnum.values()) .map(columnTypeEnum -> columnTypeEnum.getColumnType()) .toList(); } public ColumnType getColumnType() { return columnType; } @Override public String buildCreateColumnSql(TableColumn column) { TimeplusColumnTypeEnum type = COLUMN_TYPE_MAP.get( column.getColumnType() ); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildNullableAndDataType(column, type)).append(" "); script.append(buildDefaultValue(column, type)).append(" "); script.append(buildComment(column, type)).append(" "); return script.toString(); } @Override public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join( "DROP COLUMN `", tableColumn.getName() + "`" ); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { return StringUtils.join( "ADD COLUMN ", buildCreateColumnSql(tableColumn) ); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { String modifyColumn = ""; if ( !StringUtils.equalsIgnoreCase( tableColumn.getOldName(), tableColumn.getName() ) ) { modifyColumn = StringUtils.join( "RENAME COLUMN `", tableColumn.getOldName(), "` TO `", tableColumn.getName(), "`, ", buildCreateColumnSql(tableColumn) ); } return StringUtils.join( modifyColumn, "MODIFY COLUMN ", buildCreateColumnSql(tableColumn) ); } return ""; } private String buildComment( TableColumn column, TimeplusColumnTypeEnum type ) { if ( !type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment()) ) { return ""; } return StringUtils.join("COMMENT '", column.getComment(), "'"); } private String buildDefaultValue( TableColumn column, TimeplusColumnTypeEnum type ) { if ( !type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue()) ) { return ""; } if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT ''"); } if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { return StringUtils.join("DEFAULT NULL"); } if (Arrays.asList(Enum8, Enum16).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(Date).contains(type)) { return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } if (Arrays.asList(DateTime).contains(type)) { if ( "CURRENT_TIMESTAMP".equalsIgnoreCase( column.getDefaultValue().trim() ) ) { return StringUtils.join("DEFAULT ", column.getDefaultValue()); } return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); } return StringUtils.join("DEFAULT ", column.getDefaultValue()); } private String buildNullableAndDataType( TableColumn column, TimeplusColumnTypeEnum type ) { StringBuilder script = new StringBuilder(); script.append(buildDataType(column, type)); if (!type.getColumnType().isSupportNullable()) { return script.toString(); } if (column.getNullable() != null && 1 == column.getNullable()) { return "Nullable(" + script.append(")").toString(); } else { return script.toString(); } } private String buildDataType( TableColumn column, TimeplusColumnTypeEnum type ) { String columnType = type.columnType.getTypeName(); if (Arrays.asList(FixedString).contains(type)) { return StringUtils.join( columnType, "(", column.getColumnSize(), ")" ); } if (Arrays.asList(Decimal).contains(type)) { if ( column.getColumnSize() == null || column.getDecimalDigits() == null ) { return columnType; } if ( column.getColumnSize() != null && column.getDecimalDigits() == null ) { return StringUtils.join( columnType, "(", column.getColumnSize() + ")" ); } if ( column.getColumnSize() != null && column.getDecimalDigits() != null ) { return StringUtils.join( columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")" ); } } return columnType; } public String buildColumn(TableColumn column) { TimeplusColumnTypeEnum type = COLUMN_TYPE_MAP.get( column.getColumnType() ); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); script.append("`").append(column.getName()).append("`").append(" "); script.append(buildDataType(column, type)).append(" "); if (StringUtils.isNoneBlank(column.getComment())) { script .append("COMMENT") .append(" ") .append("'") .append(column.getComment()) .append("'") .append(" "); } return script.toString(); } private String unsignedDataType(String dataTypeName, String middle) { String[] split = dataTypeName.split(" "); if (split.length == 2) { return StringUtils.join(split[0], middle, split[1]); } return StringUtils.join(dataTypeName, middle); } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/type/TimeplusEngineTypeEnum.java ================================================ package ai.chat2db.plugin.timeplus.type; import ai.chat2db.spi.model.EngineType; import com.google.common.collect.Maps; import java.util.Arrays; import java.util.List; import java.util.Map; public enum TimeplusEngineTypeEnum { Stream("Stream", true, true, true, false, true, true, true, true), ExternalStream( "ExternalStream", false, false, false, false, true, false, false, false ), ExternalTable( "ExternalTable", false, false, false, false, true, false, false, false ), MutableStream( "MutableStream", true, true, true, true, true, true, true, true ), View("View", false, false, false, false, false, false, false, false), MaterializedView( "MaterializedView", true, false, false, false, true, false, false, false ), Random("Random", false, false, false, false, true, false, false, false), Dictionary( "Dictionary", false, false, false, false, false, false, false, false ), S3("S3", false, true, false, false, true, false, false, false), MergeTree("MergeTree", true, true, true, false, true, true, true, false), ReplicatedReplacingMergeTree( "ReplicatedReplacingMergeTree", true, true, true, true, true, true, true, true ), Memory("Memory", false, false, false, false, true, true, false, false), ReplacingMergeTree( "ReplacingMergeTree", true, true, true, false, true, true, true, false ), ReplicatedAggregatingMergeTree( "ReplicatedAggregatingMergeTree", true, true, true, true, true, true, true, true ), ReplicatedMergeTree( "ReplicatedMergeTree", true, true, true, true, true, true, true, true ), ReplicatedCollapsingMergeTree( "ReplicatedCollapsingMergeTree", true, true, true, true, true, true, true, true ), File("File", false, false, false, false, true, false, false, false), SummingMergeTree( "SummingMergeTree", true, true, true, false, true, true, true, false ), CollapsingMergeTree( "CollapsingMergeTree", true, true, true, false, true, true, true, false ), Merge("Merge", false, false, false, false, false, false, false, false), AggregatingMergeTree( "AggregatingMergeTree", true, true, true, false, true, true, true, false ), Null("Null", false, false, false, false, false, true, false, false), Log("Log", false, false, false, false, true, false, false, false); private static Map ENGINE_TYPE_MAP = Maps.newHashMap(); static { for (TimeplusEngineTypeEnum value : TimeplusEngineTypeEnum.values()) { ENGINE_TYPE_MAP.put(value.getEngineType().getName(), value); } } private EngineType engineType; TimeplusEngineTypeEnum( String name, boolean supportTTL, boolean supportSortOrder, boolean supportSkippingIndices, boolean supportDeduplication, boolean supportSettings, boolean supportParallelInsert, boolean supportProjections, boolean supportReplication ) { this.engineType = new EngineType( name, supportTTL, supportSortOrder, supportSkippingIndices, supportDeduplication, supportSettings, supportParallelInsert, supportProjections, supportReplication ); } public static TimeplusEngineTypeEnum getByType(String dataType) { return ENGINE_TYPE_MAP.get(dataType.toUpperCase()); } public static List getTypes() { return Arrays.stream(TimeplusEngineTypeEnum.values()) .map(engineTypeEnum -> engineTypeEnum.getEngineType()) .toList(); } public EngineType getEngineType() { return engineType; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/java/ai/chat2db/plugin/timeplus/type/TimeplusIndexTypeEnum.java ================================================ package ai.chat2db.plugin.timeplus.type; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.StringUtils; public enum TimeplusIndexTypeEnum { PRIMARY("Primary", "PRIMARY KEY"), MINMAX("MINMAX", "INDEX"), SET("SET", "INDEX"), BLOOM_FILTER("BLOOM_FILTER", "INDEX"), TOKENBF_V1("TOKENBF_V1", "INDEX"), NGRAMBF_V1("NGRAMBF_V1", "INDEX"), INVERTED("INVERTED", "INDEX"), HYPOTHESIS("HYPOTHESIS", "INDEX"), ANNOY("ANNOY", "INDEX"), USEARCH("USEARCH", "INDEX"); private String name; private String keyword; private IndexType indexType; TimeplusIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; this.indexType = new IndexType(name); } public static TimeplusIndexTypeEnum getByType(String type) { for (TimeplusIndexTypeEnum value : TimeplusIndexTypeEnum.values()) { if (value.name.equalsIgnoreCase(type)) { return value; } } return null; } public static List getIndexTypes() { return Arrays.asList(TimeplusIndexTypeEnum.values()) .stream() .map(TimeplusIndexTypeEnum::getIndexType) .collect(java.util.stream.Collectors.toList()); } public String getName() { return name; } public String getKeyword() { return keyword; } public IndexType getIndexType() { return indexType; } public void setIndexType(IndexType indexType) { this.indexType = indexType; } public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append(keyword).append(" "); script.append(buildIndexName(tableIndex)).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); script.append(buildIndexType(tableIndex)).append(" "); return script.toString(); } private String buildIndexType(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } else { return "TYPE " + name; } } private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("`").append(column.getColumnName()).append("`"); script.append(","); } } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String buildIndexName(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } else { return "`" + tableIndex.getName() + "`"; } } public String buildModifyIndex(TableIndex tableIndex) { if (this.equals(PRIMARY)) { return ""; } if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return StringUtils.join( "DROP INDEX `", tableIndex.getOldName(), "`" ); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join( "DROP INDEX `", tableIndex.getOldName(), "`,\n ADD ", buildIndexScript(tableIndex) ); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join("ADD ", buildIndexScript(tableIndex)); } return ""; } } ================================================ FILE: chat2db-server/chat2db-plugins/chat2db-timeplus/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin ================================================ ai.chat2db.plugin.timeplus.TimeplusPlugin ================================================ FILE: chat2db-server/chat2db-plugins/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-plugins pom chat2db-mysql chat2db-sqlserver chat2db-postgresql chat2db-oracle chat2db-sqlite chat2db-h2 chat2db-clickhouse chat2db-oceanbase chat2db-db2 chat2db-mariadb chat2db-dm chat2db-mongodb chat2db-presto chat2db-hive chat2db-kingbase chat2db-timeplus ================================================ FILE: chat2db-server/chat2db-server-domain/README.md ================================================ ## ali-dbhub-server-domain-core 写核心处理逻辑 ## ali-dbhub-server-domain-data 连接各种花里胡哨数据库连接 ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/pom.xml ================================================ ai.chat2db chat2db-server-domain ${revision} ../pom.xml 4.0.0 chat2db-server-domain-api ai.chat2db chat2db-server-tools-base org.projectlombok lombok provided org.hibernate.validator hibernate-validator provided ai.chat2db chat2db-spi ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartCreateParam.java ================================================ package ai.chat2db.server.domain.api.chart; import java.time.LocalDateTime; import lombok.Data; /** * @author moji * @version ChartCreateParam.java, v 0.1 June 9, 2023 15:38 moji Exp $ * @date 2023/06/09 */ @Data public class ChartCreateParam { /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * chart name */ private String name; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * schemaName */ private String schemaName; /** * ddl content */ private String ddl; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartListQueryParam.java ================================================ package ai.chat2db.server.domain.api.chart; import java.util.List; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; /** * query * * @author Jiaju Zhuang */ @Data @NoArgsConstructor public class ChartListQueryParam { /** * primary key */ @NonNull private List idList; /** * user id */ @NonNull private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.chart; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * @author moji * @version UserSavedDdlPageQueryParam.java, v 0.1 September 25, 2022 14:05 moji Exp $ * @date 2022/09/25 */ @Data public class ChartPageQueryParam extends PageQueryParam { /** * Report ID */ private Long dashboardId; /** * search keyword */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartQueryParam.java ================================================ package ai.chat2db.server.domain.api.chart; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; /** * query * * @author Jiaju Zhuang */ @Data @NoArgsConstructor public class ChartQueryParam { /** * primary key */ @NonNull private Long id; /** * user id */ @NonNull private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartUpdateParam.java ================================================ package ai.chat2db.server.domain.api.chart; import java.time.LocalDateTime; import lombok.Data; /** * @author moji * @version ChartUpdateParam.java, v 0.1 June 9, 2023 15:39 moji Exp $ * @date 2023/06/09 */ @Data public class ChartUpdateParam { /** * primary key */ private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * chart name */ private String name; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * ddl content */ private String ddl; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AccessObjectTypeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Access Object Type * * @author Jiaju Zhuang */ @Getter public enum AccessObjectTypeEnum implements BaseEnum { /** * TEAM */ TEAM("TEAM"), /** * USER */ USER("USER"), ; final String description; AccessObjectTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * AI model type selected by AI SQL * * @author moji */ @Getter public enum AiSqlSourceEnum implements BaseEnum { /** * OPENAI */ OPENAI( "OPENAI"), /** * RESTAI */ RESTAI("RESTAI"), /** * AZURE OPENAI */ AZUREAI("AZURE OPENAI"), /** * CHAT2DB OPENAI */ CHAT2DBAI("CHAT2DB OPENAI"), /** * CLAUDE AI */ CLAUDEAI("CLAUDE AI"), /** * WENXIN AI */ WENXINAI("WENXIN AI"), /** * BAICHUAN AI */ BAICHUANAI("BAICHUAN AI"), /** * ZHIPU AI */ ZHIPUAI("ZHIPU AI"), /** * TONGYIQIANWEN AI */ TONGYIQIANWENAI("TONGYIQIANWEN AI"), /** * FAST CHAT AI */ FASTCHATAI("FAST CHAT AI"), ; final String description; AiSqlSourceEnum(String description) { this.description = description; } /** * Get enum by name * * @param name * @return */ public static AiSqlSourceEnum getByName(String name) { for (AiSqlSourceEnum dbTypeEnum : AiSqlSourceEnum.values()) { if (dbTypeEnum.name().equals(name)) { return dbTypeEnum; } } return null; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/DataSourceKindEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Data Source Kind * * @author Jiaju Zhuang */ @Getter public enum DataSourceKindEnum implements BaseEnum { /** * PRIVATE */ PRIVATE("PRIVATE"), /** * SHARED */ SHARED("SHARED"), ; final String description; DataSourceKindEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/DeletedTypeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; public enum DeletedTypeEnum { Y,N } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Environment * * @author Jiaju Zhuang */ @Getter public enum EnvironmentEnum implements BaseEnum { /** * RELEASE */ RELEASE(1L, "RELEASE"), /** * TEST */ TEST(2L, "TEST"), ; final Long code; final String description; EnvironmentEnum(Long code, String description) { this.code = code; this.description = description; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java ================================================ package ai.chat2db.server.domain.api.enums; import lombok.Getter; /** * ExportFileType * * @author lzy **/ @Getter public enum ExportFileSuffix { //word WORD(".docx"), //excel EXCEL(".xlsx"), XLS(".xls"), //markdown MARKDOWN(".md"), //html HTML(".html"), //pdf PDF(".pdf"), SQL(".sql"), JSON(".json"), CSV(".csv"), ZIP(".zip"); private String suffix; ExportFileSuffix(String suffix) { this.suffix = suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportSizeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * How much data is currently needed at the beginning * * @author Jiaju Zhuang */ @Getter public enum ExportSizeEnum implements BaseEnum { /** * CURRENT_PAGE */ CURRENT_PAGE("CURRENT_PAGE"), /** * ALL */ ALL("ALL"), ; final String description; ExportSizeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportTypeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; import javax.swing.text.html.HTML; /** * export type * * @author Jiaju Zhuang */ @Getter public enum ExportTypeEnum implements BaseEnum { /** * CSV */ CSV("CSV"), /** * INSERT */ INSERT("INSERT"), /** * WORD */ WORD("WORD"), /** * EXCEL */ EXCEL("EXCEL"), /** * HTML */ HTML("HTML"), /** * MARKDOWN */ MARKDOWN("MARKDOWN"), /** * PDF */ PDF("PDF"), JSON("JSON"), SQL("SQL"); final String description; ExportTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/OperationStatusEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * state * * @author Shi Yi */ @Getter public enum OperationStatusEnum implements BaseEnum { /** * draft */ DRAFT("草稿"), /** * Published */ RELEASE("已发布"), ; final String description; OperationStatusEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/RoleCodeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * role code * * @author Jiaju Zhuang */ @Getter public enum RoleCodeEnum implements BaseEnum { /** * DESKTOP */ DESKTOP("DESKTOP", 1L, "_desktop_default_user_name", "_desktop_default_user_name"), /** * ADMIN */ ADMIN("ADMIN", 2L, System.getenv().getOrDefault("ADMIN_NAME","chat2db"), System.getenv().getOrDefault("ADMIN_PASSWORD","chat2db")), /** * USER */ USER("USER", null, null, null), ; final String description; final Long defaultUserId; final String userName; final String password; RoleCodeEnum(String description, Long defaultUserId, String userName, String password) { this.description = description; this.defaultUserId = defaultUserId; this.userName = userName; this.password = password; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TableVectorEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * table vector status * * @author moji */ @Getter public enum TableVectorEnum implements BaseEnum { /** * SAVED */ SAVED( "SAVED"), ; final String description; TableVectorEnum(String description) { this.description = description; } /** * Get enum by name * * @param name * @return */ public static TableVectorEnum getByName(String name) { for (TableVectorEnum dbTypeEnum : TableVectorEnum.values()) { if (dbTypeEnum.name().equals(name)) { return dbTypeEnum; } } return null; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskStatusEnum.java ================================================ package ai.chat2db.server.domain.api.enums; public enum TaskStatusEnum { INIT, PROCESSING, FINISH, ERROR } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/TaskTypeEnum.java ================================================ package ai.chat2db.server.domain.api.enums; public enum TaskTypeEnum { /** * download table data */ DOWNLOAD_TABLE_DATA, /** * upload table data */ UPLOAD_TABLE_DATA, /** * download table structure */ DOWNLOAD_TABLE_STRUCTURE, /** * upload table structure */ UPLOAD_TABLE_STRUCTURE, } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ValidStatusEnum.java ================================================ package ai.chat2db.server.domain.api.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Is it a valid enumeration * * @author Jiaju Zhuang */ @Getter public enum ValidStatusEnum implements BaseEnum { /** * VALID */ VALID("VALID"), /** * INVALID */ INVALID("INVALID"), ; final String description; ValidStatusEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/AIConfig.java ================================================ package ai.chat2db.server.domain.api.model; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author jipengfei * @version : SystemConfigRequest.java */ @Data public class AIConfig { /** * APIKEY */ private String apiKey = ""; /** * SECRETKEY */ private String secretKey = ""; /** * APIHOST */ private String apiHost = ""; /** * api http proxy host */ private String httpProxyHost = ""; /** * api http proxy port */ private String httpProxyPort = ""; /** * @see AiSqlSourceEnum */ @NotNull private String aiSqlSource = ""; /** * return data stream * Optional, default value is TRUE */ private Boolean stream = Boolean.TRUE; /** * deployed model, default gpt-3.5-turbo */ private String model = ""; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Chart.java ================================================ package ai.chat2db.server.domain.api.model; import java.util.Date; import lombok.Data; /** * @author moji * @version Chart.java, v 0.1 June 9, 2023 15:37 moji Exp $ * @date 2023/06/09 */ @Data public class Chart { /** * primary key */ private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Chart name */ private String name; /** * Chart description */ private String description; /** * schema */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Data source name */ private String dataSourceName; /** * schema name */ private String schemaName; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * ddl content */ private String ddl; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ChatGptConfig.java ================================================ package ai.chat2db.server.domain.api.model; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import lombok.Data; /** * @author moji * @version ChatGptConfig.java, v 0.1 May 9, 2023 13:47 moji Exp $ * @date 2023/05/09 */ @Data public class ChatGptConfig { /** * chat2db APIKEY */ private String chat2dbApiKey; /** * chat2db APIHOST */ private String chat2dbApiHost; /** * OpenAi APIKEY */ private String apiKey; /** * OpenAi APIHOST */ private String apiHost; /** * HTTP proxy host */ private String httpProxyHost; /** * HTTP proxy Port */ private String httpProxyPort; /** * AI type * @see AiSqlSourceEnum */ private String aiSqlSource; /** * Custom AI interface */ private String restAiUrl; /** * Whether the Rest interface streams output * Optional, default value is TRUE */ private Boolean restAiStream = Boolean.TRUE; /** * Get Azure OpenAI key credential from the Azure Portal */ private String azureApiKey; /** * Get Azure OpenAI endpoint from the Azure Portal */ private String azureEndpoint; /** * deploymentId of the deployed model, default gpt-3.5-turbo */ private String azureDeploymentId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Config.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; import lombok.Data; /** * @author jipengfei * @version : Config.java */ @Data public class Config implements Serializable { @Serial private static final long serialVersionUID = 8377899386569086415L; private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Configuration item code */ private String code; /** * Configuration item content */ private String content; /** * Configuration summary */ private String summary; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Dashboard.java ================================================ package ai.chat2db.server.domain.api.model; import java.util.Date; import java.util.List; import lombok.Data; /** * @author moji * @version Dashboard.java, v 0.1 June 9, 2023 15:32 moji Exp $ * @date 2023/06/09 */ @Data public class Dashboard { /** * primary key */ private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Dashboard name */ private String name; /** * Dashboard description */ private String description; /** * Dashboard layout information */ private String schema; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSource.java ================================================ package ai.chat2db.server.domain.api.model; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import lombok.Data; import org.springframework.util.ObjectUtils; /** * @author moji * @version DataSourceDTO.java, v 0.1 September 23, 2022 15:39 moji Exp $ * @date 2022/09/23 */ @Data public class DataSource { /** * primary key */ private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Alias */ private String alias; /** * connection address */ private String url; /** * user name */ private String userName; /** * password */ private String password; /** * Database type */ private String type; /** * environment type */ private String envType; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * environment id */ private Long environmentId; /** * environment */ private Environment environment; /** * user id */ private Long userId; /** * Connection Type * * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; /** * Service name */ private String serviceName; /** * Service type */ private String serviceType; private boolean supportDatabase; private boolean supportSchema; public LinkedHashMap getExtendMap() { if (ObjectUtils.isEmpty(extendInfo)) { return new LinkedHashMap<>(); } LinkedHashMap map = new LinkedHashMap<>(); for (KeyValue keyValue : extendInfo) { map.put(keyValue.getKey(), keyValue.getValue()); } return map; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccess implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ @NotNull private Long id; /** * creation time */ @NotNull private LocalDateTime gmtCreate; /** * modified time */ @NotNull private LocalDateTime gmtModified; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; /** * Data source id */ @NotNull private Long dataSourceId; /** * data source */ @NotNull private DataSource dataSource; /** * Authorization type * * @see AccessObjectTypeEnum */ @NotNull private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ @NotNull private Long accessObjectId; /** * Authorization object * @see DataSourceAccessSelector#setAccessObject(Boolean) */ @NotNull private DataSourceAccessObject accessObject; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccessObject.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access Object * It could be a user or a team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessObject implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; /** * The name of the code that belongs to the authorization type, such as user account, team code */ private String code; /** * Code that belongs to the authorization type, such as user name, team name */ private String name; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Environment * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Environment implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ private Long id; /** * environment name */ private String name; /** * environment abbreviation */ private String shortName; /** * color */ private String color; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/IndexInfo.java ================================================ package ai.chat2db.server.domain.api.model; import lombok.Data; import lombok.experimental.Accessors; /** * Index export information * * @author lzy */ @Data @Accessors(chain = true) public class IndexInfo { /** * Index name */ private String name; /** * Field */ private String columnName; /** * Index type */ private String indexType; /** * Index method */ private String indexMethod; /** * Comment */ private String comment; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java ================================================ package ai.chat2db.server.domain.api.model; import java.time.LocalDateTime; import lombok.Data; /** *

* My save list *

* * @author ali-dbhub * @since 2022-09-18 */ @Data public class Operation { /** * primary key */ private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * Data source name */ private String dataSourceName; /** * DB name */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * save name */ private String name; /** * Database type */ private String type; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * ddl content */ private String ddl; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/OperationLog.java ================================================ package ai.chat2db.server.domain.api.model; import java.time.LocalDateTime; import lombok.Data; /** *

* My execution record *

* * @author ali-dbhub * @since 2022-09-18 */ @Data public class OperationLog { /** * primary key */ private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * data source */ private String dataSourceName; /** * DB name */ private String databaseName; /** * Database type */ private String type; /** * ddl content */ private String ddl; /** * status */ private String status; /** * Number of operation lines */ private Long operationRows; /** * Length of use */ private Long useTime; /** * Extended Information */ private String extendInfo; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TableParameter.java ================================================ package ai.chat2db.server.domain.api.model; import lombok.Data; import lombok.experimental.Accessors; /** * TableParameter * * @author lzy **/ @Data @Accessors(chain = true) public class TableParameter { /** * serial number **/ private String no; /** * Field name **/ private String fieldName; /** * type of data **/ private String columnType; /** * length **/ private String length; /** * not null **/ private String isNullAble; /** * default value **/ private String columnDefault; /** * Decimal places **/ private String decimalPlaces; /** * Remark **/ private String columnComment; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Task.java ================================================ package ai.chat2db.server.domain.api.model; import lombok.Data; import java.io.Serializable; import java.util.Date; @Data public class Task implements Serializable { /** * primary key */ private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * table_name */ private String tableName; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; /** * task type, such as: DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE, */ private String taskType; /** * task status */ private String taskStatus; /** * task progress */ private String taskProgress; /** * task name */ private String taskName; /** * download url */ private String downloadUrl; /** * task content */ private byte[] content; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import java.util.Date; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Team implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ @NotNull private Long id; /** * team coding */ @NotNull private String code; /** * Team Name */ @NotNull private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ @NotNull private String status; /** * Team description */ private String description; /** * modified time */ private Date gmtModified; /** * Modifier user id */ private Long modifiedUserId; /** * Modifier user */ private User modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TeamUser.java ================================================ package ai.chat2db.server.domain.api.model; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Team user * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamUser implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ @NotNull private Long id; /** * team id */ @NotNull private Long teamId; /** * team */ @NotNull private Team team; /** * user id */ @NotNull private Long userId; /** * user */ @NotNull private User user; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java ================================================ package ai.chat2db.server.domain.api.model; import java.util.Date; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * User Info * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class User { /** * primary key */ @NotNull private Long id; /** * username */ @NotNull private String userName; /** * password */ @NotNull private String password; /** * Nick name */ @NotNull private String nickName; /** * email */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ private String roleCode; /** * user status * * @see ValidStatusEnum */ @NotNull private String status; /** * modified time */ private Date gmtModified; /** * Modifier user id */ private Long modifiedUserId; /** * Modifier user */ private User modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ConsoleCloseParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Console shutdown parameters * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ConsoleCloseParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * The id of the console, ensuring global uniqueness */ @NotNull private Long consoleId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ConsoleConnectParam.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.Data; /** * @author moji * @version ConsoleConnectParam.java, v 0.1 October 30, 2022 15:53 moji Exp $ * @date 2022/10/30 */ @Data public class ConsoleConnectParam { /** * Data source id */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * console id */ private Long consoleId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ConsoleCreateParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Console creation parameters * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ConsoleCreateParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * The id of the console, ensuring global uniqueness * Make sure not to duplicate it, in which case the previous connection will be discarded and recreated */ @NotNull private Long consoleId; /** * Corresponding connection database name * Databases that support multiple databases will call use xx; to switch to the database. */ @NotNull private String databaseName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlCountParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * total number * * @author Shi Yi */ @Data public class DlCountParam { /** * sql statement */ @NotNull private String sql; /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * databaseName */ @NotNull private String databaseName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version DataSourceExecuteParam.java, v 0.1 October 14, 2022 13:53 moji Exp $ * @date 2022/10/14 */ @Data public class DlExecuteParam { /** * sql statement */ @NotNull private String sql; /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * databaseName */ @NotNull private String databaseName; private String tableName; /** * schema name */ private String schemaName; /** * Page coding * Only the select statement has */ private Integer pageNo; /** * Paging Size * Only the select statement has */ private Integer pageSize; /** * Return all data * Only the select statement has */ private Boolean pageSizeAll; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DmlSqlCopyParam.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DmlSqlCopyParam extends TableQueryParam{ private String type; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DropParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Delete table structure * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DropParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Name */ private String name; /** * schema */ private String schema; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/EnvironmentPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * environment * * @author Jiaju Zhuang */ @Data public class EnvironmentPageQueryParam extends PageQueryParam { /** * search keyword */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/GroupByParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @Data public class GroupByParam { /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * databaseName */ private String databaseName; /** * schema name */ private String schemaName; /** * origin sql */ private String originSql; /** * sort field */ private List groupByList; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/MetaDataQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class MetaDataQueryParam { @NotNull private Long dataSourceId; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OrderByParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.spi.model.OrderBy; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @Data public class OrderByParam { /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * databaseName */ private String databaseName; /** * schema name */ private String schemaName; /** * origin sql */ private String originSql; /** * sort field */ private List orderByList; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/PinTableParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class PinTableParam { @NotNull private Long dataSourceId; /** * databaseName */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * tableName */ private String tableName; /** * pin userId */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaOperationParam.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author jipengfei * @version : SchemaOperationParam.java */ @Data @AllArgsConstructor @Builder @NoArgsConstructor public class SchemaOperationParam { String databaseName; String schemaName; String newSchemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import java.sql.Connection; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : SchemaQueryParam.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SchemaQueryParam { @NotNull private Long dataSourceId; private String dataBaseName; /** * if true, refresh the cache */ private boolean refresh; /** * Can be null, if null, use the default connection */ private Connection connection; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SelectResultOperation.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.Data; import java.util.List; @Data public class SelectResultOperation { private String type; private List dataList; private List oldDataList; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SequencePageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Pagination query sequence information * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SequencePageQueryParam extends PageQueryParam { private static final long serialVersionUID = 1364512325486354343L; /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Sequence Name */ private String sequenceName; /** * schema */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SequenceQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.QueryParam; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serial; /** * Sequence query param * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SequenceQueryParam extends QueryParam { @Serial private static final long serialVersionUID = -6918238998725081254L; /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Sequence Name */ private String sequenceName; /** * Space name */ private String schemaName; private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ShowCreateSequenceParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Query Sequence creation statement * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ShowCreateSequenceParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Sequence Name */ private String sequenceName; /** * The schema to which the sequence belongs */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ShowCreateTableParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Query table creation statement * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ShowCreateTableParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Table Name */ private String tableName; /** * The schema to which the table belongs */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SqlAnalyseParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Sql parsing parameters * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SqlAnalyseParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * The SQL that needs to be parsed may be a complex SQL */ private String sql; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SystemConfigParam.java ================================================ package ai.chat2db.server.domain.api.param; import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : SystemConfigParam.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SystemConfigParam implements Serializable { @Serial private static final long serialVersionUID = 7969235263543844658L; /** * Configuration item code */ private String code; /** * Configuration item content */ private String content; /** * Configuration summary */ private String summary; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Pagination query table information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TablePageQueryParam extends PageQueryParam { private static final long serialVersionUID = 8054519332890887747L; /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Table Name */ private String tableName; /** * schema */ private String schemaName; /** * if true, refresh the cache */ private boolean refresh; private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import java.io.Serial; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.tools.base.wrapper.param.QueryParam; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Query table information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableQueryParam extends QueryParam { @Serial private static final long serialVersionUID = -8918610899872508804L; /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * Corresponding connection database name */ @NotNull private String databaseName; /** * Table Name */ private String tableName; /** * Space name */ private String schemaName; private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableSelector.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * table structure selector * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableSelector { /** * column list */ private Boolean columnList; /** * index list */ private Boolean indexList; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableVectorParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableVectorParam { /** * api key */ @NotNull private String apiKey; /** * Data source connection ID */ private Long dataSourceId; /** * database name */ private String database; /** * schema name */ private String schema; /** * Vector saved state */ private String status; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskCreateParam.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.Data; import java.io.Serializable; @Data public class TaskCreateParam implements Serializable { private static final long serialVersionUID = 1L; /** * Data source connection ID */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * schema name */ private String schemaName; /** * table_name */ private String tableName; /** * user id */ private Long userId; /** * task progress */ private String taskProgress; /** * task name */ private String taskName; /** * task type, such as: DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE, */ private String taskType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskPageParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; import java.io.Serializable; import java.util.List; @Data public class TaskPageParam extends PageQueryParam implements Serializable { private Long userId; private List taskType; private String taskStatus; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TaskUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param; import lombok.Data; import java.io.Serializable; @Data public class TaskUpdateParam implements Serializable { private static final long serialVersionUID = 1L; /** * task id */ private Long id; /** * user id */ private Long userId; /** * task type, such as: DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE, */ private String taskStatus; /** * task progress */ private String taskProgress; /** * task name */ private String taskName; /** * task description */ private String downloadUrl; /** * task content */ private byte[] content; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TypeQueryParam.java ================================================ package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TypeQueryParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java ================================================ package ai.chat2db.server.domain.api.param; import ai.chat2db.spi.model.Header; import ai.chat2db.spi.model.ResultOperation; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @Data public class UpdateSelectResultParam { /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * databaseName */ private String databaseName; /** * schema name */ private String schemaName; /** * List of display headers */ @NotEmpty private List
headerList; /** * List of modified data */ @NotEmpty private List operations; /** * Table Name */ @NotEmpty private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.dashboard; import java.time.LocalDateTime; import java.util.List; import lombok.Data; /** * @author moji * @version DashboardSaveParam.java, v 0.1 June 9, 2023 15:29 moji Exp $ * @date 2023/06/09 */ @Data public class DashboardCreateParam { /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Report name */ private String name; /** * description */ private String description; /** * Report layout information */ private String schema; /** * Whether it has been deleted, 'Y' means deleted, 'N' means not deleted */ private String deleted; /** * user id */ private Long userId; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.dashboard; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * @author moji * @version UserSavedDdlPageQueryParam.java, v 0.1 September 25, 2022 14:05 moji Exp $ * @date 2022/09/25 */ @Data public class DashboardPageQueryParam extends PageQueryParam { /** * search keyword */ private String searchKey; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.dashboard; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; /** * query * * @author Jiaju Zhuang */ @Data @NoArgsConstructor public class DashboardQueryParam { /** * primary key */ @NonNull private Long id; /** * user id */ @NonNull private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardSelector.java ================================================ package ai.chat2db.server.domain.api.param.dashboard; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * selectro * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DashboardSelector { /** * Chart ID list */ private Boolean chartIds; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param.dashboard; import java.time.LocalDateTime; import java.util.List; import lombok.Data; /** * @author moji * @version DashboardSaveParam.java, v 0.1 June 9, 2023 15:29 moji Exp $ * @date 2023/06/09 */ @Data public class DashboardUpdateParam { /** * primary key */ private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Report name */ private String name; /** * description */ private String description; /** * Report layout information */ private String schema; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCloseParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * data source closed * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceCloseParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version DataSourceCreateParam.java, v 0.1 September 23, 2022 15:23 moji Exp $ * @date 2022/09/23 */ @Data public class DataSourceCreateParam { /** * Alias */ private String alias; /** * connection address */ private String url; /** * userName */ private String userName; /** * password */ private String password; /** * Database type */ private String type; /** * environment type */ private String envType; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * Connection Type * * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; /** * environment id */ @NotNull private Long environmentId; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; import lombok.Getter; /** * @author moji * @version DataSourcePageQueryParam.java, v 0.1 September 23, 2022 15:27 moji Exp $ * @date 2022/09/23 */ @Data public class DataSourcePageQueryParam extends PageQueryParam { /** * search keyword */ private String searchKey; /** * Connection Type * * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; @Getter public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { ID_DESC(OrderBy.desc("id")), ; final OrderBy orderBy; OrderCondition(OrderBy orderBy) { this.orderBy = orderBy; } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePreConnectParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourcePreConnectParam { /** * Connection alias */ private String alias; /** * connection address */ @NotNull private String url; /** * Connect users */ private String user; /** * password */ @NotNull private String password; /** * Connection Type */ @NotNull private String type; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceSelector.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author moji * @version DataSourceSelector.java, v 0.1 September 23, 2022 15:28 moji Exp $ * @date 2022/09/23 */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceSelector { /** * environment id */ private Boolean environment; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceTestParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Data source test parameters * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceTestParam { /** * Database type * * @see DbTypeEnum */ @NotNull private String dbType; /** * Request connection */ @NotNull private String url; /** * userName */ private String username; /** * password */ private String password; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version DataSourceCreateParam.java, v 0.1 September 23, 2022 15:23 moji Exp $ * @date 2022/09/23 */ @Data public class DataSourceUpdateParam { /** * primary key */ @NotNull private Long id; /** * Alias */ private String alias; /** * connection address */ private String url; /** * userName */ private String userName; /** * password */ private String password; /** * Database type */ private String type; /** * environment type */ private String envType; /** * environment id */ private Integer environmentId; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author jipengfei * @version : DatabaseOperationParam.java */ @Data @AllArgsConstructor @Builder @NoArgsConstructor public class DatabaseCreateParam { private Long dataSourceId; private String name; private String newName; private String comment; private String charset; private String collation; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseExportDataParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * @author: zgq * @date: 2024年03月24日 13:17 */ @Data @AllArgsConstructor @NoArgsConstructor public class DatabaseExportDataParam { private Long dataSourceId; private String databaseName; private String schemaName; private String exportType; private List tableNames; private String sqyType; private Boolean containsHeader; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseExportParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author: zgq * @date: 2024年02月27日 22:08 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class DatabaseExportParam { /** * DB name */ private String databaseName; private String schemaName; private Boolean containData; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseQueryAllParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource; import java.sql.Connection; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Display database information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DatabaseQueryAllParam { /** * Corresponding source id stored in the database */ @NotNull private Long dataSourceId; /** * if true, refresh the cache */ private boolean refresh; /** * Can be null, if null, use the default connection */ private Connection connection; /** * Can be null, if null, use the default dbType */ private String dbType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessBatchCreatParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import java.util.List; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Data Source Access * * @author Jiaju Zhuang */ @Data public class DataSourceAccessBatchCreatParam extends PageQueryParam { /** * Data source id */ @NotNull private Long dataSourceId; /** * DataSource Access Object */ @NotNull @NotEmpty private List accessObjectList; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * Data Source Access * * @author Jiaju Zhuang */ @Data public class DataSourceAccessComprehensivePageQueryParam extends PageQueryParam { /** * Data source id */ private Long dataSourceId; /** * Authorization type * * @see AccessObjectTypeEnum */ private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long accessObjectId; /** * Query keywords for users or teams */ private String userOrTeamSearchKey; /** * Query keywords for data source */ private String dataSourceSearchKey; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessCreatParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Data Source Access * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessCreatParam { /** * Data source id */ @NotNull private Long dataSourceId; /** * Authorization type * * @see AccessObjectTypeEnum */ @NotNull private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ @NotNull private Long accessObjectId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessObjectParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access Object * It could be a user or a team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessObjectParam implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Data Source Access * * @author Jiaju Zhuang */ @Data public class DataSourceAccessPageQueryParam extends PageQueryParam { /** * Data source id */ @NotNull private Long dataSourceId; /** * Authorization type * * @see AccessObjectTypeEnum */ @NotNull private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ @NotNull private Long accessObjectId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java ================================================ package ai.chat2db.server.domain.api.param.datasource.access; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * slecetor * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessSelector { /** * Authorization object */ private Boolean accessObject; /** * data source */ private Boolean dataSource; /** * data source */ private DataSourceSelector dataSourceSelector; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/message/MessageCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.message; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author Juechen * @version : MessageCreateParam.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class MessageCreateParam { /** * 平台类型 * @see ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum */ private String platformType; /** * 服务URL */ private String serviceUrl; /** * 密钥 */ private String secretKey; /** * 消息模版 */ private String textTemplate; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import lombok.Data; /** * @author moji * @version UserExecutedDdlCreateParam.java, v 0.1 September 25, 2022 11:08 moji Exp $ * @date 2022/09/25 */ @Data public class OperationLogCreateParam { /** * primary key */ private Long id; /** * Data source connection ID */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * Database type */ private String type; /** * ddl content */ private String ddl; /** * state */ private String status; /** * Number of operation lines */ private Long operationRows; /** * Length of use */ private Long useTime; /** * Extended Information */ private String extendInfo; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * @author moji * @version UserExecutedDdlPageQueryParam.java, v 0.1 September 25, 2022 14:05 moji Exp $ * @date 2022/09/25 */ @Data public class OperationLogPageQueryParam extends PageQueryParam { /** * user id */ private Long userId; /** * search keyword */ private String searchKey; /** * Data source id */ private Long dataSourceId; /** * database name */ private String databaseName; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * @author moji * @version UserSavedDdlPageQueryParam.java, v 0.1 September 25, 2022 14:05 moji Exp $ * @date 2022/09/25 */ @Data public class OperationPageQueryParam extends PageQueryParam { /** * Data source connection ID */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * search keyword */ private String searchKey; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * orderBy modify time desc */ private Boolean orderByDesc; /** * orderBy create time desc */ private Boolean orderByCreateDesc; /** * operation type */ private String operationType; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; /** * query * * @author Jiaju Zhuang */ @Data @NoArgsConstructor public class OperationQueryParam { /** * primary key */ @NonNull private Long id; /** * user id */ @NonNull private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationSavedParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import lombok.Data; /** * @author moji * @version UserSavedDdlCreateParam.java, v 0.1 September 25, 2022 15:40 moji Exp $ * @date 2022/09/25 */ @Data public class OperationSavedParam { /** * Data source connection ID */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * save name */ private String name; /** * Database type */ private String type; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * ddl content */ private String ddl; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param.operation; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version UserSavedDdlCreateParam.java, v 0.1 September 25, 2022 15:40 moji Exp $ * @date 2022/09/25 */ @Data public class OperationUpdateParam { /** * primary key */ @NotNull private Long id; /** * Data source connection ID */ private Long dataSourceId; /** * databaseName */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * save name */ private String name; /** * Database type */ private String type; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * ddl content */ private String ddl; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.team; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create * * @author Jiaju Zhuang */ @Data public class TeamCreateParam { /** * team coding */ @NotNull private String code; /** * Team Name */ @NotNull private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ @NotNull private String status; /** * role coding * * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum */ @NotNull private String roleCode; /** * Team description */ private String description; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.team; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; import lombok.Getter; /** * page query * * @author Jiaju Zhuang */ @Data public class TeamPageQueryParam extends PageQueryParam { /** * searchKey */ private String searchKey; @Getter public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { ID_DESC(OrderBy.desc("id")), ; final OrderBy orderBy; OrderCondition(OrderBy orderBy) { this.orderBy = orderBy; } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java ================================================ package ai.chat2db.server.domain.api.param.team; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * select * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamSelector { /** * Modifier user */ private Boolean modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param.team; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * update * * @author Jiaju Zhuang */ @Data public class TeamUpdateParam { /** * primary key */ @NotNull private Long id; /** * Team Name */ private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ private String status; /** * role coding * * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum */ private String roleCode; /** * Team description */ private String description; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.team.user; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; /** * Team User * * @author Jiaju Zhuang */ @Data public class TeamUserComprehensivePageQueryParam extends PageQueryParam { /** * team id */ private Long teamId; /** * user id */ private Long userId; /** * Query keywords for team */ private String teamSearchKey; /** * Query keywords for user */ private String userSearchKey; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserCreatParam.java ================================================ package ai.chat2db.server.domain.api.param.team.user; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Team User * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamUserCreatParam { /** * team id */ @NotNull private Long teamId; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.team.user; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Team User * * @author Jiaju Zhuang */ @Data public class TeamUserPageQueryParam extends PageQueryParam { /** * team id */ @NotNull private Long teamId; /** * user id */ @NotNull private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserSelector.java ================================================ package ai.chat2db.server.domain.api.param.team.user; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * select * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamUserSelector { /** * Team */ private Boolean team; /** * User */ private Boolean user; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserCreateParam.java ================================================ package ai.chat2db.server.domain.api.param.user; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create * * @author Jiaju Zhuang */ @Data public class UserCreateParam { /** * userName */ @NotNull private String userName; /** * password */ @NotNull private String password; /** * Nick name */ @NotNull private String nickName; /** * Mail */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ @NotNull private String roleCode; /** * user status * * @see ValidStatusEnum */ @NotNull private String status; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java ================================================ package ai.chat2db.server.domain.api.param.user; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * * page query * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserPageQueryParam extends PageQueryParam { /** * searchKey */ private String searchKey; @Getter public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { ID_DESC(OrderBy.desc("id")), ; final OrderBy orderBy; OrderCondition(OrderBy orderBy) { this.orderBy = orderBy; } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java ================================================ package ai.chat2db.server.domain.api.param.user; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * select * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserSelector { /** * Modifier user */ private Boolean modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserUpdateParam.java ================================================ package ai.chat2db.server.domain.api.param.user; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create * * @author Jiaju Zhuang */ @Data public class UserUpdateParam { /** * primary key */ @NotNull private Long id; /** * password */ @NotNull private String password; /** * Nick name */ @NotNull private String nickName; /** * Mail */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ private String roleCode; /** * user status * * @see ValidStatusEnum */ @NotNull private String status; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartListQueryParam; import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; /** * @author moji * @version ChartService.java, v 0.1 June 9, 2023 15:28 moji Exp $ * @date 2023/06/09 */ public interface ChartService { /** * Create report * * @param param * @return */ DataResult createWithPermission(ChartCreateParam param); /** * Update report * * @param param * @return */ ActionResult updateWithPermission(ChartUpdateParam param); /** * Query based on id * * @param id * @return */ DataResult find(@NotNull Long id); /** * Query a piece of data * * @param param * @return */ DataResult queryExistent(@NotNull ChartQueryParam param); /** * Query a piece of data * * @param id * @return */ DataResult queryExistent(@NotNull Long id); /** * Query multiple pieces of data * * @param param * @return */ ListResult listQuery(@NotNull ChartListQueryParam param); /** * Query chart list by ID * * @param ids * @return */ ListResult queryByIds(@NotEmpty List ids); /** * delete * * @param id * @return */ ActionResult deleteWithPermission(@NotNull Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java ================================================ package ai.chat2db.server.domain.api.service; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; /** * @author jipengfei * @version : SystemConfigService.java */ public interface ConfigService { /** * Create configuration * * @param param * @return */ ActionResult create(SystemConfigParam param); /** * Change setting * * @param param * @return */ ActionResult update(SystemConfigParam param); /** * insert or update * @param param * @return */ ActionResult createOrUpdate(SystemConfigParam param); /** * Query based on code * * @param code * @return */ DataResult find(@NotNull String code); /** * delete * * @param code * @return */ ActionResult delete(@NotNull String code); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConsoleService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; /** * Data source management services * * @author moji * @version DataSourceCoreService.java, v 0.1 September 23, 2022 15:22 moji Exp $ * @date 2022/09/23 */ public interface ConsoleService { /** * Create console link * * @param param * @return */ ActionResult createConsole(ConsoleConnectParam param); /** * close connection * * @param param * @return */ ActionResult closeConsole(ConsoleCloseParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; /** * @author moji * @version DashboardService.java, v 0.1 June 9, 2023 15:28 moji Exp $ * @date 2023/06/09 */ public interface DashboardService { /** * Save report * * @param param * @return */ DataResult createWithPermission(DashboardCreateParam param); /** * Update report * * @param param * @return */ ActionResult updateWithPermission(DashboardUpdateParam param); /** * Query based on id * * @param id * @return */ DataResult find(@NotNull Long id); /** * Query a piece of data * * @param param * @param selector * @return */ DataResult queryExistent(@NotNull DashboardQueryParam param); /** * Query a piece of data * * @param id * @return */ DataResult queryExistent(@NotNull Long id); /** * delete * * @param id * @return */ ActionResult deleteWithPermission(@NotNull Long id); /** * Query report list * * @param param * @return */ PageResult queryPage(DashboardPageQueryParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import jakarta.validation.constraints.NotNull; /** * Data Source Access * * @author Jiaju Zhuang */ public interface DataSourceAccessBusinessService { /** * delete * * @param dataSource * @return */ ActionResult checkPermission(@NotNull DataSource dataSource); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; /** * Data Source Access * * @author Jiaju Zhuang */ public interface DataSourceAccessService { /** * Comprehensive Paging Query Data * * @param param * @param selector * @return */ PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector); /** * Paging Query Data * * @param param * @param selector * @return */ PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector); /** * Batch Create * * @param param * @return */ DataResult create(DataSourceAccessCreatParam param); /** * delete * * @param id * @return */ ActionResult delete(@NotNull Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.spi.model.Database; import jakarta.validation.constraints.NotNull; /** * Data source management services * * @author moji * @version DataSourceCoreService.java, v 0.1 September 23, 2022 15:22 moji Exp $ * @date 2022/09/23 */ public interface DataSourceService { /** * Create data source connection * * @param param * @return */ DataResult createWithPermission(DataSourceCreateParam param); /** * Update data source connection * * @param param * @return */ DataResult updateWithPermission(DataSourceUpdateParam param); /** * Delete data source connection * * @param id * @return */ ActionResult deleteWithPermission(@NotNull Long id); /** * Query data source connection details based on id * * @param id * @return */ DataResult queryById(@NotNull Long id); /** * Query data source connection details based on id * * @param id * @return * @throws ai.chat2db.server.tools.common.exception.DataNotFoundException */ DataResult queryExistent(@NotNull Long id, DataSourceSelector selector); /** * clone connection * * @param id * @return */ DataResult copyByIdWithPermission(@NotNull Long id); /** * Paginated query data source list * * @param param * @param selector * @return */ PageResult queryPage(DataSourcePageQueryParam param, DataSourceSelector selector); /** * Paginated query data source list * Need to determine permissions * * @param param * @param selector * @return * @throws PermissionDeniedBusinessException */ PageResult queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector); /** * Query data source by ID list * * @param ids * @return * @deprecated Use {@link #listQuery(List, DataSourceSelector)} */ ListResult queryByIds(List ids); /** * Query data source by ID list * * @param idList * @return */ ListResult listQuery(List idList, DataSourceSelector selector); /** * Data source connection test * * @param param * @return */ ActionResult preConnect(DataSourcePreConnectParam param); /** * Connect to data source * * @param id * @return */ ListResult connect(Long id); /** * Close data source connection * * @param id * @return */ ActionResult close(Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.model.*; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import java.sql.SQLException; /** * Data source management services * * @author moji * @version DataSourceCoreService.java, v 0.1 September 23, 2022 15:22 moji Exp $ * @date 2022/09/23 */ public interface DatabaseService { /** * Query all databases under the data source * * @param param * @return */ ListResult queryAll(DatabaseQueryAllParam param); /** * Query the schema under a database * @param param * @return */ ListResult querySchema(SchemaQueryParam param); /** * query Database and Schema * @param param * @return */ DataResult queryDatabaseSchema(MetaDataQueryParam param); /** * Delete database * * @param param * @return */ ActionResult deleteDatabase(DatabaseCreateParam param); /** * create database * * @param param * @return */ DataResult createDatabase(Database param); /** * Modify database * * @return */ ActionResult modifyDatabase( DatabaseCreateParam param) ; /** * Delete schema * * @param param * @return */ ActionResult deleteSchema(SchemaOperationParam param) ; /** * Create schema * * @param schema * @return */ DataResult createSchema(Schema schema); /** * Modify schema * * @param request * @return */ ActionResult modifySchema( SchemaOperationParam request); /** * Export database * * @param param * @return */ String exportDatabase(DatabaseExportParam param) throws SQLException; /** * Query the user under a database * * @return User list */ ListResult getUsernameList(); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.domain.api.param.GroupByParam; import ai.chat2db.server.tools.base.wrapper.result.ListResult; /** * Data source management services * * @author moji * @version DataSourceCoreService.java, v 0.1 September 23, 2022 15:22 moji Exp $ * @date 2022/09/23 */ public interface DlTemplateService { /** * data source execution dl * * @param param * @return */ ListResult execute(DlExecuteParam param); /** * * @param param * @return */ ListResult executeSelectTable(DlExecuteParam param); /** * Data source execution update * * @param param * @return */ DataResult executeUpdate(DlExecuteParam param); /** * Execute statistics sql * * @param param * @return */ DataResult count(DlCountParam param); /** * Update query results * @param param * @return */ DataResult updateSelectResult(UpdateSelectResultParam param); /** * * @param param * @return */ DataResult getGroupBySql(GroupByParam param); /** * * @param param * @return */ DataResult getOrderBySql(OrderByParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/EnvironmentService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; /** * environment * * @author Jiaju Zhuang */ public interface EnvironmentService { /** * List Query Data * * @param idList * @return */ ListResult listQuery(List idList); /** * Paging Query Data * * @param param * @return */ PageResult pageQuery(EnvironmentPageQueryParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Function; import jakarta.validation.constraints.NotEmpty; /** * author jipengfei * date 2021/9/23 15:22 */ public interface FunctionService { /** * Querying all functions under a schema. * * @param databaseName * @return */ ListResult functions(@NotEmpty String databaseName, String schemaName); /** * Querying function information. * * @param databaseName * @param schemaName * @param functionName * @return */ DataResult detail(String databaseName, String schemaName, String functionName); /** * Delete function. * * @param databaseName * @param schemaName * @param function * @return */ ActionResult delete(String databaseName, String schemaName, Function function); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.config.DBConfig; public interface JdbcDriverService { /** * Query the driver list of the current DB * * @param dbType * @return */ DataResult getDrivers(String dbType); /** * Upload the driver * * @param dbType * @param jdbcDriverClass * @param jdbcDriver * @return */ ActionResult upload(String dbType, String jdbcDriverClass, String jdbcDriver); /** * Upload the driver * * @param dbType * @return */ ActionResult download(String dbType); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; /** * User executes ddl * * @author moji * @version UserExecutedDdlCoreService.java, v 0.1 September 23, 2022 17:35 moji Exp $ * @date 2022/09/23 */ public interface OperationLogService { /** * Create ddl record executed by user * * @param param * @return */ DataResult create(OperationLogCreateParam param); /** * Query the ddl records executed by the user * * @param param * @return */ PageResult queryPage(OperationLogPageQueryParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; /** * user save ddl * * @author moji * @version UserSavedDdlCoreService.java, v 0.1 September 23, 2022 17:35 moji Exp $ * @date 2022/09/23 */ public interface OperationService { /** * Save user's ddl * * @param param * @return */ DataResult createWithPermission(OperationSavedParam param); /** * Update user's ddl * * @param param * @return */ ActionResult updateWithPermission(OperationUpdateParam param); /** * Query based on id * * @param id * @return */ DataResult find(@NotNull Long id); /** * Query based on id * * @param id * @return */ DataResult queryExistent(@NotNull Long id); /** * Query a piece of data * * @param param * @return */ DataResult queryExistent(@NotNull OperationQueryParam param); /** * delete * * @param id * @return */ ActionResult deleteWithPermission(@NotNull Long id); /** * Query the ddl records executed by the user * * @param param * @return */ PageResult queryPage(OperationPageQueryParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/PinService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import java.util.List; public interface PinService { /** * User pin table * @param param * @return */ ActionResult pinTable(PinTableParam param); /** * Delete pin table * @param param * @return */ ActionResult deletePinTable(PinTableParam param); /** * Query user pin tables * @param param * @return */ ListResult queryPinTables(PinTableParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Procedure; import jakarta.validation.constraints.NotEmpty; import java.sql.SQLException; public interface ProcedureService { /** * Querying all procedures under a schema. * * @param databaseName * @return */ ListResult procedures(@NotEmpty String databaseName, String schemaName); /** * Querying procedure information. * * @param databaseName * @param schemaName * @param procedureName * @return */ DataResult detail(String databaseName, String schemaName, String procedureName); /** * Update procedure. * * @param databaseName * @param schemaName * @param procedure * @return */ ActionResult update(String databaseName, String schemaName, Procedure procedure) throws SQLException; /** * Delete procedure. * * @param databaseName * @param schemaName * @param procedure * @return */ ActionResult delete(String databaseName, String schemaName, Procedure procedure); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/SequenceService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.DropParam; import ai.chat2db.server.domain.api.param.SequencePageQueryParam; import ai.chat2db.server.domain.api.param.SequenceQueryParam; import ai.chat2db.server.domain.api.param.ShowCreateSequenceParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Sequence; import ai.chat2db.spi.model.SimpleSequence; import ai.chat2db.spi.model.Sql; /** * Sequence source management services * * @author Sylphy */ public interface SequenceService { DataResult showCreateSequence(ShowCreateSequenceParam request); ListResult pageQuery(SequencePageQueryParam request); ListResult buildSql(Sequence oldSequence, Sequence newSequence); ActionResult drop(DropParam dropParam); DataResult query(SequenceQueryParam queryParam); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.spi.model.*; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; /** * Data source management services * * @author moji * @version DataSourceCoreService.java, v 0.1 September 23, 2022 15:22 moji Exp $ * @date 2022/09/23 */ public interface TableService { /** * Query table information * * @param param * @return */ DataResult showCreateTable(ShowCreateTableParam param); /** * Delete table * * @param param * @return */ ActionResult drop(DropParam param); /** * Example of creating a table structure * * @param dbType * @return */ DataResult createTableExample(String dbType); /** * Example of modifying table structure * * @param dbType * @return */ DataResult alterTableExample(String dbType); /** * Query table information * * @param param * @return */ DataResult
query(TableQueryParam param, TableSelector selector); /** * build sql * * @param oldTable * @param newTable * @return */ ListResult buildSql(Table oldTable, Table newTable); /** * Pagination query table information * * @param param * @return */ PageResult
pageQuery(TablePageQueryParam param, TableSelector selector); /** * Query table information * @param param * @return */ ListResult queryTables(TablePageQueryParam param); /** * Fields included in the query table * * @param param * @return */ List queryColumns(TableQueryParam param); /** * Query table index * * @param param * @return */ List queryIndexes(TableQueryParam param); /** * * @param param * * @return */ List queryTypes(TypeQueryParam param); /** * * @param param * @return */ TableMeta queryTableMeta(TypeQueryParam param); /** * save table vector * * @param param * @return */ ActionResult saveTableVector(TableVectorParam param); /** * check if table vector saved status * * @param param * @return */ DataResult checkTableVector(TableVectorParam param); /** * Get dml template sql * @param param table query param * @return sql */ DataResult copyDmlSql(DmlSqlCopyParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TaskService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.param.TaskUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; public interface TaskService { /** * create task * * @param param task param * @return task id */ DataResult create(TaskCreateParam param); /** * update task status * * @param param task param * @return action result */ ActionResult updateStatus(TaskUpdateParam param); /** * get task list * * @param param task id * @return task */ PageResult page(TaskPageParam param); /** * get task * * @param id task id * @return task */ DataResult get(Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; /** * team * * @author Jiaju Zhuang */ public interface TeamService { /** * Pagination query * * @param param * @param selector * @return */ PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector); /** * List Query Data * * @param idList * @return */ ListResult listQuery(List idList); /** * Create * * @param param * @return */ DataResult create(TeamCreateParam param); /** * update * * @param param * @return */ DataResult update(TeamUpdateParam param); /** * delete * * @param id * @return */ ActionResult delete(@NotNull Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; /** * team user * * @author Jiaju Zhuang */ public interface TeamUserService { /** * Comprehensive Paging Query Data * * @param param * @param selector * @return */ PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector); /** * Comprehensive Paging Query Data * * @param param * @param selector * @return */ PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector); /** * Create * * @param param * @return */ DataResult create(TeamUserCreatParam param); /** * delete * * @param id * @return */ ActionResult delete(@NotNull Long id); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Trigger; import jakarta.validation.constraints.NotEmpty; public interface TriggerService { /** * Querying all triggers under a schema. * * @param databaseName * @return */ ListResult triggers(@NotEmpty String databaseName, String schemaName); /** * Querying trigger information. * @param databaseName * @param schemaName * @param triggerName * @return */ DataResult detail(String databaseName, String schemaName, String triggerName); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java ================================================ package ai.chat2db.server.domain.api.service; import java.util.List; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; /** * User service * * @author Jiaju Zhuang */ public interface UserService { /** * Query user information * * @param id * @return */ DataResult query(Long id); /** * gen * @param userName * @return */ DataResult query(String userName); /** * List Query Data * * @param idList * @return */ ListResult listQuery(List idList); /** * Query user information * * @param param * @return */ PageResult pageQuery(UserPageQueryParam param, UserSelector selector); /** * Update user information * @param user * @return */ DataResult update(UserUpdateParam user); /** * delete users * @param id * @return */ ActionResult delete(Long id); /** * Create a user * @param user * @return */ DataResult create(UserCreateParam user); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Table; import jakarta.validation.constraints.NotEmpty; /** * author jipengfei * date 2021/9/23 15:22 */ public interface ViewService { /** * Querying all views under a schema. * * @param databaseName * @return */ ListResult
views(@NotEmpty String databaseName, String schemaName); /** * Querying the details of a view. * * @param databaseName * @return */ DataResult
detail(@NotEmpty String databaseName, String schemaName,String tableName); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/WebhookSender.java ================================================ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; /** * @author Juechen * @version : WebhookSender.java */ public interface WebhookSender { void sendMessage(MessageCreateParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml ================================================ ai.chat2db chat2db-server-domain ${revision} ../pom.xml 4.0.0 chat2db-server-domain-core ai.chat2db chat2db-server-domain-api ai.chat2db chat2db-server-domain-repository com.baomidou mybatis-plus ai.chat2db chat2db-server-tools-common org.ehcache ehcache javax.cache cache-api ai.chat2db chat2db-spi ai.chat2db chat2db-mysql ${revision} ai.chat2db chat2db-clickhouse ${revision} ai.chat2db chat2db-db2 ${revision} ai.chat2db chat2db-dm ${revision} ai.chat2db chat2db-h2 ${revision} ai.chat2db chat2db-hive ${revision} ai.chat2db chat2db-kingbase ${revision} ai.chat2db chat2db-mariadb ${revision} ai.chat2db chat2db-mongodb ${revision} ai.chat2db chat2db-oceanbase ${revision} ai.chat2db chat2db-oracle ${revision} ai.chat2db chat2db-postgresql ${revision} ai.chat2db chat2db-presto ${revision} ai.chat2db chat2db-sqlite ${revision} ai.chat2db chat2db-sqlserver ${revision} ai.chat2db chat2db-timeplus ${revision} commons-codec commons-codec 1.11 com.squareup.okhttp3 okhttp 4.9.1 ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java ================================================ package ai.chat2db.server.domain.core.cache; import org.springframework.util.StringUtils; public class CacheKey { public static String getLoginUserKey(Long userId) { return "login_user_" + userId; } public static String getDataSourceKey(Long dataSourceId) { return "schemas_datasourceId_" + dataSourceId; } public static String getDataBasesKey(Long dataSourceId) { return "databases_datasourceId_" + dataSourceId; } public static String getSchemasKey(Long dataSourceId, String databaseName) { return "databases_datasourceId_" + dataSourceId + "_databaseName_" + databaseName; } public static String getTableKey(Long dataSourceId, String databaseName, String schemaName) { StringBuffer stringBuffer = new StringBuffer("tables_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { stringBuffer.append("_databaseName_" + databaseName); } if (!StringUtils.isEmpty(schemaName)) { stringBuffer.append("_schemaName_" + schemaName); } return stringBuffer.toString(); } public static String getColumnKey(Long dataSourceId, String databaseName, String schemaName,String tableName) { StringBuffer stringBuffer = new StringBuffer("columns_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { stringBuffer.append("_databaseName_" + databaseName); } if (!StringUtils.isEmpty(schemaName)) { stringBuffer.append("_schemaName_" + schemaName); } stringBuffer.append("_tableName_"+tableName); return stringBuffer.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java ================================================ package ai.chat2db.server.domain.core.cache; import java.io.File; import java.util.List; import java.util.function.Function; import com.alibaba.fastjson2.JSON; import org.ehcache.Cache; import org.ehcache.CacheManager; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.CacheManagerBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.config.units.EntryUnit; import org.ehcache.config.units.MemoryUnit; import org.springframework.util.StringUtils; public class CacheManage { private static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + File.separator + "cache" + File.separator + "chat2db-ehcache-data_" +System.getProperty("spring.profiles.active"); private static final String CACHE = "meta_cache"; private static CacheManager cacheManager; static { cacheManager = CacheManagerBuilder.newCacheManagerBuilder() .with(CacheManagerBuilder.persistence(PATH)) // Make sure this path exists and has write permissions .withCache(CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(1000, EntryUnit.ENTRIES) .disk(20, MemoryUnit.GB, true))) // Disk persistence is set to true .build(true); } public static T get(String key, Class clazz) { Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); String value = myCache.get(key); if (!StringUtils.isEmpty(value)) { return JSON.parseObject(value, clazz); } return null; } public static List getList(String key, Class clazz) { Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); String value = myCache.get(key); if (!StringUtils.isEmpty(value)) { return JSON.parseArray(value, clazz); } return null; } public static T get(String key, Class clazz, Function refresh, Function function) { T t; if (refresh.apply(key)) { t = function.apply(key); put(key, t); } else { t = get(key, clazz); if (t == null) { t = function.apply(key); put(key, t); } } return t; } public static List getList(String key, Class clazz, Function refresh, Function> function) { List t; if (refresh.apply(key)) { t = function.apply(key); put(key, t); } else { t = getList(key, clazz); if (t == null) { t = function.apply(key); put(key, t); } } return t; } public static void put(String s, Object value) { Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); myCache.put(s, JSON.toJSONString(value)); } public static void close() { cacheManager.close(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/MemoryCacheManage.java ================================================ package ai.chat2db.server.domain.core.cache; import java.io.Serializable; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.Weigher; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.cache.support.NullValue; /** * It will only be stored in memory * * @author Jiaju Zhuang */ public class MemoryCacheManage { private static final byte[] NULL_BYTES = SerializationUtils.serialize((NullValue)NullValue.INSTANCE); private static final String SYNCHRONIZED_PREFIX = "MemoryCache:"; private static final Cache CACHE = CacheBuilder.newBuilder() // 5M .maximumWeight(5 * 1024 * 1024) .weigher((Weigher)(key, value) -> value.length) .expireAfterAccess(10, TimeUnit.MINUTES) .build(); /** * Retrieve a value from the cache, and if not, query it * The timeout is fixed at 10 minutes * * @param key * @param queryData * @param * @return */ public static T computeIfAbsent(String key, Supplier queryData) { if (key == null) { return null; } T data = get(key); if (data != null) { return data; } String lockKey = SYNCHRONIZED_PREFIX + key; synchronized (lockKey.intern()) { data = get(key); if (data != null) { return data; } T value = queryData.get(); put(key, value); return value; } } /** * Get a data from cache * * @param key * @param * @return */ public static T get(String key) { if (StringUtils.isBlank(key)) { return null; } byte[] bytes = CACHE.getIfPresent(key); if (bytes == null) { return null; } T data = SerializationUtils.deserialize(bytes); if (NullValue.INSTANCE.equals(data)) { return null; } return data; } /** * Put a data from cache * The timeout is fixed at 10 minutes * * @param key * @param value */ public static void put(String key, Serializable value) { if (key == null) { return; } if (value == null) { CACHE.put(key, NULL_BYTES); } else { CACHE.put(key, SerializationUtils.serialize(value)); } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ChartConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.repository.entity.ChartDO; import org.mapstruct.Mapper; /** * @author moji * @version ChartConverter.java, v 0.1 June 9, 2023 17:13 moji Exp $ * @date 2023/06/09 */ @Mapper(componentModel = "spring") public abstract class ChartConverter { /** * Parameter conversion * * @param param * @return */ public abstract ChartDO param2do(ChartCreateParam param); /** * Parameter conversion * * @param param * @return */ public abstract ChartDO updateParam2do(ChartUpdateParam param); /** * Model conversion * * @param chartDO * @return */ public abstract Chart do2model(ChartDO chartDO); /** * Model conversion * * @param chartDOS * @return */ public abstract List do2model(List chartDOS); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/CommandConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.spi.model.Command; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @Mapper(componentModel = "spring") public abstract class CommandConverter { @Mappings({ @Mapping(target = "script", source = "sql") }) public abstract Command param2model(DlExecuteParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ConfigConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.repository.entity.SystemConfigDO; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; /** * @author jipengfei * @version : ConfigConverter.java */ @Slf4j @Mapper(componentModel = "spring") public abstract class ConfigConverter { public abstract SystemConfigDO param2do(SystemConfigParam param); public abstract Config do2model(SystemConfigDO systemConfigDO); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DashboardConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.repository.entity.DashboardDO; import org.mapstruct.Mapper; /** * @author moji * @version ChartConverter.java, v 0.1 June 9, 2023 17:13 moji Exp $ * @date 2023/06/09 */ @Mapper(componentModel = "spring") public abstract class DashboardConverter { /** * Parameter conversion * * @param param * @return */ public abstract DashboardDO param2do(DashboardCreateParam param); /** * Parameter conversion * * @param param * @return */ public abstract DashboardDO updateParam2do(DashboardUpdateParam param); /** * Model conversion * * @param chartDO * @return */ public abstract Dashboard do2model(DashboardDO chartDO); /** * Model conversion * * @param chartDOS * @return */ public abstract List do2model(List chartDOS); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Slf4j @Mapper(componentModel = "spring") public abstract class DataSourceAccessConverter { /** * convert * * @param data * @return */ @Mappings({ @Mapping(target = "accessObject.id", source = "accessObjectId"), @Mapping(target = "accessObject.type", source = "accessObjectType"), @Mapping(target = "dataSource.id", source = "dataSourceId"), }) public abstract DataSourceAccess do2dto(DataSourceAccessDO data); /** * convert * * @param dataSourceId * @param accessObjectId * @param accessObjectType * @param userId * @return */ @Mappings({ @Mapping(target = "createUserId", source = "userId"), @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract DataSourceAccessDO param2do(Long dataSourceId, Long accessObjectId, String accessObjectType, Long userId); /** * convert * * @param param * @return */ @Mappings({ @Mapping(target = "createUserId", source = "userId"), @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract DataSourceAccessDO param2do(DataSourceAccessCreatParam param, Long userId); /** * convert * * @param list * @return */ public abstract List do2dto(List list); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.ConsoleCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.core.util.DesUtil; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.springframework.context.annotation.Lazy; /** * @author moji * @version DataSourceCoreConverter.java, v 0.1 September 23, 2022 15:53 moji Exp $ * @date 2022/09/23 */ @Slf4j @Mapper(componentModel = "spring") public abstract class DataSourceConverter { @Resource @Lazy private DataSourceService dataSourceService; /** * Parameter conversion * * @param param * @return */ @Mapping(target = "password", expression = "java(encryptString(param))") @Mapping(target = "ssh", expression = "java(param.getSsh()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param.getSsh()))") @Mapping(target = "ssl", expression = "java(param.getSsl()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param.getSsl()))") @Mapping(target = "extendInfo", expression = "java(param.getExtendInfo()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + ".getExtendInfo()))") @Mapping(target = "driverConfig", expression = "java(param.getDriverConfig()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + ".getDriverConfig()))") public abstract DataSourceDO param2do(DataSourceCreateParam param); /** * encrypt * * @param param * @return */ protected String encryptString(DataSourceCreateParam param) { String encryptStr = param.getPassword(); if (StringUtils.isNotBlank(encryptStr)) { try { DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); encryptStr = desUtil.encrypt(param.getPassword(), "CBC"); } catch (Exception exception) { // do nothing log.error("encrypt error", exception); } } return encryptStr; } /** * encrypt * * @param param * @return */ protected String encryptString(DataSourceUpdateParam param) { String encryptStr = param.getPassword(); try { DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); encryptStr = desUtil.encrypt(param.getPassword(), "CBC"); } catch (Exception exception) { // do nothing log.error("encrypt error", exception); } return encryptStr; } /** * decrypt * * @param param * @return */ protected String decryptString(DataSourceDO param) { String decryptStr = param.getPassword(); try { DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); decryptStr = desUtil.decrypt(param.getPassword(), "CBC"); } catch (Exception exception) { // do nothing log.error("encrypt error", exception); } return decryptStr; } /** * Parameter conversion * * @param param * @return */ @Mappings({ @Mapping(target = "password", expression = "java(encryptString(param))"), @Mapping(target = "ssh", expression = "java(param.getSsh()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param.getSsh()))"), @Mapping(target = "ssl", expression = "java(param.getSsl()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param.getSsl()))"), @Mapping(target = "extendInfo", expression = "java(param.getExtendInfo()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + ".getExtendInfo()))"), @Mapping(target = "driverConfig", expression = "java(param.getDriverConfig()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + ".getDriverConfig()))") }) public abstract DataSourceDO param2do(DataSourceUpdateParam param); /** * Parameter conversion * * @param param * @return */ public abstract ConsoleCreateParam param2consoleParam(ConsoleConnectParam param); /** * Parameter conversion * * @param dataSourcePreConnectParam * @return */ @Mappings({ @Mapping(source = "type", target = "dbType"), @Mapping(source = "user", target = "username") }) public abstract DataSourceTestParam param2param( DataSourcePreConnectParam dataSourcePreConnectParam); /** * Model conversion * * @param dataSourceDO * @return */ @Mapping(target = "password", expression = "java(decryptString(dataSourceDO))") @Mapping(target = "ssh", expression = "java(com.alibaba.fastjson2.JSON.parseObject(dataSourceDO.getSsh(),ai.chat2db.spi" + ".model.SSHInfo.class))") @Mapping(target = "ssl", expression = "java(com.alibaba.fastjson2.JSON.parseObject(dataSourceDO.getSsl(),ai.chat2db.spi" + ".model.SSLInfo" + ".class))") @Mapping(target = "driverConfig", expression = "java(com.alibaba.fastjson2.JSON.parseObject(dataSourceDO.getDriverConfig(),ai.chat2db.spi.config" + ".DriverConfig" + ".class))") @Mapping(target = "extendInfo", expression = "java(com.alibaba.fastjson2.JSON.parseArray(dataSourceDO.getExtendInfo(),ai.chat2db.spi.model" + ".KeyValue.class))") @Mapping(target = "environment.id", source = "environmentId") public abstract DataSource do2dto(DataSourceDO dataSourceDO); /** * Model conversion * * @param dataSourceDOList * @return */ public abstract List do2dto(List dataSourceDOList); /** * Fill in detailed information * * @param list */ public void fillDetail(List list) { fillDetail(list, null); } /** * Fill in detailed information * * @param list */ public void fillDetail(List list, DataSourceSelector selector) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, DataSource::getId); List queryList = dataSourceService.listQuery(idList, selector).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, DataSource::getId); for (DataSource data : list) { if (data == null || data.getId() == null) { continue; } DataSource query = queryMap.get(data.getId()); add(data, query); } } @Mappings({ @Mapping(target = "id", ignore = true), }) public abstract void add(@MappingTarget DataSource target, DataSource source); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DriverConfigConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; import ai.chat2db.spi.config.DriverConfig; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; @Slf4j @Mapper(componentModel = "spring") public abstract class DriverConfigConverter { public abstract DriverConfig do2Config(JdbcDriverDO driverDO); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/EnvironmentConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.domain.repository.entity.EnvironmentDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.springframework.context.annotation.Lazy; /** * converter * * @author Jiaju Zhuang */ @Slf4j @Mapper(componentModel = "spring") public abstract class EnvironmentConverter { @Resource @Lazy private EnvironmentService environmentService; /** * convert * * @param list * @return */ public abstract List do2dto(List list); /** * Fill in detailed information * * @param list */ public void fillDetail(List list) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, Environment::getId); List queryList = environmentService.listQuery(idList).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Environment::getId); for (Environment data : list) { if (data == null || data.getId() == null) { continue; } Environment query = queryMap.get(data.getId()); add(data, query); } } @Mappings({ @Mapping(target = "id", ignore = true), }) public abstract void add(@MappingTarget Environment target, Environment source); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.repository.entity.OperationSavedDO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version UserSavedDdlCoreConverter.java, v 0.1 September 25, 2022 15:50 moji Exp $ * @date 2022/09/25 */ @Mapper(componentModel = "spring") public abstract class OperationConverter { /** * Parameter conversion * * @param param * @return */ @Mappings({ @Mapping(source = "schemaName", target = "dbSchemaName") }) public abstract OperationSavedDO param2do(OperationSavedParam param); /** * Parameter conversion * * @param param * @return */ @Mappings({ @Mapping(source = "schemaName", target = "dbSchemaName") }) public abstract OperationSavedDO param2do(OperationUpdateParam param); /** * Model conversion * * @param userSavedDdlDO * @return */ @Mappings({ @Mapping(source = "dbSchemaName", target = "schemaName") }) public abstract Operation do2dto(OperationSavedDO userSavedDdlDO); /** * Model conversion * * @param userSavedDdlDOS * @return */ public abstract List do2dto(List userSavedDdlDOS); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationLogConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.repository.entity.OperationLogDO; import org.mapstruct.Mapper; /** * @author moji * @version UserExecutedDdlCoreConverter.java, v 0.1 September 25, 2022 14:08 moji Exp $ * @date 2022/09/25 */ @Mapper(componentModel = "spring") public abstract class OperationLogConverter { /** * Parameter conversion * * @param param * @return */ public abstract OperationLogDO param2do(OperationLogCreateParam param); /** * Model conversion * * @param userExecutedDdlDO * @return */ public abstract OperationLog do2dto(OperationLogDO userExecutedDdlDO); /** * Model conversion * * @param userExecutedDdlDOS * @return */ public abstract List do2dto(List userExecutedDdlDOS); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/PinTableConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.repository.entity.PinTableDO; import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public abstract class PinTableConverter { /** * * @param param * @return */ public abstract PinTableDO param2do(PinTableParam param); public abstract PinTableParam toPinTableParam (TablePageQueryParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TableConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.param.TableVectorParam; import ai.chat2db.server.domain.repository.entity.TableVectorMappingDO; import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public abstract class TableConverter { /** * TableVectorParam to TableVectorMappingDO * * @param param * @return */ public abstract TableVectorMappingDO toTableVectorMappingDO(TableVectorParam param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TaskConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.repository.entity.TaskDO; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; import java.util.List; @Slf4j @Mapper(componentModel = "spring") public abstract class TaskConverter { public abstract TaskDO todo(TaskCreateParam param); public abstract Task toModel(TaskDO param); public abstract List toModel(List param); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.repository.entity.TeamDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.springframework.context.annotation.Lazy; /** * converter * * @author Jiaju Zhuang */ @Slf4j @Mapper(componentModel = "spring") public abstract class TeamConverter { @Resource @Lazy private TeamService teamService; /** * convert * * @param list * @return */ public abstract List do2dto(List list); /** * convert * * @param data * @return */ @Mappings({ @Mapping(target = "modifiedUser.id", source = "modifiedUserId"), }) public abstract Team do2dto(TeamDO data); /** * convert * * @param param * @return */ @Mappings({ @Mapping(target = "createUserId", source = "userId"), @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract TeamDO param2do(TeamCreateParam param, Long userId); /** * convert * * @param param * @return */ @Mappings({ @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract TeamDO param2do(TeamUpdateParam param, Long userId); /** * Fill in detailed information * * @param list */ public void fillDetail(List list) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, Team::getId); List queryList = teamService.listQuery(idList).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Team::getId); for (Team data : list) { if (data == null || data.getId() == null) { continue; } Team query = queryMap.get(data.getId()); add(data, query); } } @Mappings({ @Mapping(target = "id", ignore = true), }) public abstract void add(@MappingTarget Team target, Team source); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.springframework.context.annotation.Lazy; /** * converter * * @author Jiaju Zhuang */ @Slf4j @Mapper(componentModel = "spring") public abstract class TeamUserConverter { @Resource @Lazy private EnvironmentService environmentService; /** * convert * * @param data * @return */ @Mappings({ @Mapping(target = "team.id", source = "teamId"), @Mapping(target = "user.id", source = "userId"), }) public abstract TeamUser do2dto(TeamUserDO data); /** * convert * * @param list * @return */ public abstract List do2dto(List list); /** * convert * * @param param * @return */ @Mappings({ @Mapping(target = "createUserId", source = "userId"), @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract TeamUserDO param2do(TeamUserCreatParam param, Long userId); /** * Fill in detailed information * * @param list */ public void fillDetail(List list) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, Environment::getId); List queryList = environmentService.listQuery(idList).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, Environment::getId); for (Environment data : list) { if (data == null || data.getId() == null) { continue; } Environment query = queryMap.get(data.getId()); add(data, query); } } @Mappings({ @Mapping(target = "id", ignore = true), }) public abstract void add(@MappingTarget Environment target, Environment source); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java ================================================ package ai.chat2db.server.domain.core.converter; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.springframework.context.annotation.Lazy; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring") public abstract class UserConverter { @Resource @Lazy private UserService userService; /** * Convert * * @param data * @return */ @Mappings({ @Mapping(target = "modifiedUser.id", source = "modifiedUserId"), }) public abstract User do2dto(DbhubUserDO data); /** * Convert * * @param datas * @return */ public abstract List do2dto(List datas); /** * * @param user * @return */ public abstract DbhubUserDO dto2do(User user); /** * * @param user * @return */ @Mappings({ @Mapping(target = "createUserId", source = "userId"), @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract DbhubUserDO param2do(UserCreateParam user, Long userId); /** * * @param user * @return */ @Mappings({ @Mapping(target = "modifiedUserId", source = "userId"), }) public abstract DbhubUserDO param2do(UserUpdateParam user, Long userId); /** * Fill in detailed information * * @param list */ public void fillDetail(List list) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, User::getId); List queryList = userService.listQuery(idList).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, User::getId); for (User data : list) { if (data == null || data.getId() == null) { continue; } User query = queryMap.get(data.getId()); add(data, query); } } @Mappings({ @Mapping(target = "id", ignore = true), }) public abstract void add(@MappingTarget User target, User source); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/enums/ExternalNotificationTypeEnum.java ================================================ package ai.chat2db.server.domain.core.enums; import ai.chat2db.server.domain.api.service.WebhookSender; import ai.chat2db.server.domain.core.notification.DingTalkWebhookSender; import ai.chat2db.server.domain.core.notification.LarkWebhookSender; import ai.chat2db.server.domain.core.notification.WeComWebhookSender; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * @author Juechen * @version : ExternalNotificationTypeEnum.java */ @Getter public enum ExternalNotificationTypeEnum implements BaseEnum { /** * 企业微信 */ WECOM("WeCom", WeComWebhookSender.class), /** * 钉钉 */ DINGTALK("DingTalk", DingTalkWebhookSender.class), /** * 飞书 */ LARK("Lark", LarkWebhookSender.class), ; final String description; final Class webhookSender; @Override public String getCode() { return this.name(); } public static WebhookSender getWebhookSender(String platformType) { String lowerCasePlatformType = platformType.toLowerCase(); switch (lowerCasePlatformType) { case "wecom": return new WeComWebhookSender(); case "dingtalk": return new DingTalkWebhookSender(); case "lark": return new LarkWebhookSender(); default: return null; } } /** * Get enum by name * * @param name * @return */ public static ExternalNotificationTypeEnum getByName(String name) { for (ExternalNotificationTypeEnum dbTypeEnum : ExternalNotificationTypeEnum.values()) { if (dbTypeEnum.name().equalsIgnoreCase(name)) { return dbTypeEnum; } } return null; } ExternalNotificationTypeEnum(String description, Class webhookSender) { this.description = description; this.webhookSender = webhookSender; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartListQueryParam; import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.core.converter.ChartConverter; import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.ChartDO; import ai.chat2db.server.domain.repository.entity.DashboardChartRelationDO; import ai.chat2db.server.domain.repository.mapper.ChartMapper; import ai.chat2db.server.domain.repository.mapper.DashboardChartRelationMapper; import ai.chat2db.server.tools.base.enums.YesOrNoEnum; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** * @author moji * @version ChartServiceImpl.java, v 0.1 June 9, 2023 16:06 moji Exp $ * @date 2023/06/09 */ @Service public class ChartServiceImpl implements ChartService { @Autowired private DataSourceService dataSourceService; private DashboardChartRelationMapper getDashboardMapper() { return Dbutils.getMapper(DashboardChartRelationMapper.class); } @Autowired private ChartConverter chartConverter; @Override public DataResult createWithPermission(ChartCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); param.setUserId(ContextUtils.getUserId()); ChartDO chartDO = chartConverter.param2do(param); getMapper().insert(chartDO); return DataResult.of(chartDO.getId()); } @Override public ActionResult updateWithPermission(ChartUpdateParam param) { Chart data = queryExistent(param.getId()).getData(); PermissionUtils.checkOperationPermission(data.getUserId()); param.setGmtModified(LocalDateTime.now()); ChartDO chartDO = chartConverter.updateParam2do(param); getMapper().updateById(chartDO); return ActionResult.isSuccess(); } @Override public DataResult find(Long id) { ChartDO chartDO = getMapper().selectById(id); if (YesOrNoEnum.YES.getLetter().equals(chartDO.getDeleted())) { return DataResult.empty(); } Chart chart = chartConverter.do2model(chartDO); setDataSourceInfo(Lists.newArrayList(chart)); return DataResult.of(chart); } @Override public DataResult queryExistent(ChartQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) .eqWhenPresent(ChartDO::getId, param.getId()) .eqWhenPresent(ChartDO::getUserId, param.getUserId()); IPage page = getMapper().selectPage(new Page<>(1, 1), queryWrapper); if (CollectionUtils.isEmpty(page.getRecords())) { throw new DataNotFoundException(); } Chart data = chartConverter.do2model(page.getRecords().get(0)); setDataSourceInfo(Lists.newArrayList(data)); return DataResult.of(data); } @Override public DataResult queryExistent(Long id) { DataResult dataResult = find(id); if (dataResult.getData() == null) { throw new DataNotFoundException(); } return dataResult; } @Override public ListResult listQuery(ChartListQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) .inWhenPresent(ChartDO::getId, param.getIdList()) .eqWhenPresent(ChartDO::getUserId, param.getUserId()); List queryList = getMapper().selectList(queryWrapper); List list = chartConverter.do2model(queryList); setDataSourceInfo(list); return ListResult.of(list); } @Override public ActionResult deleteWithPermission(Long id) { Chart data = queryExistent(id).getData(); PermissionUtils.checkOperationPermission(data.getUserId()); ChartDO chartDO = new ChartDO(); chartDO.setId(id); chartDO.setDeleted(YesOrNoEnum.YES.getLetter()); getMapper().updateById(chartDO); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DashboardChartRelationDO::getChartId, id); List relationDO = getDashboardMapper().selectList(queryWrapper); List relationIds = relationDO.stream().map(DashboardChartRelationDO::getId).toList(); if (CollectionUtils.isNotEmpty(relationIds)) { getDashboardMapper().deleteBatchIds(relationIds); } return ActionResult.isSuccess(); } @Override public ListResult queryByIds(List ids) { if (CollectionUtils.isEmpty(ids)) { return ListResult.empty(); } List chartDOS = getMapper().selectBatchIds(ids); List charts = chartConverter.do2model(chartDOS); List result = charts.stream().filter(o -> YesOrNoEnum.NO.getLetter().equals(o.getDeleted())).toList(); setDataSourceInfo(result); return ListResult.of(result); } /** * Backfill data source information * * @param result */ private void setDataSourceInfo(List result) { List dataSourceIds = result.stream().map(Chart::getDataSourceId).toList(); ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); Map dataSourceMap = dataSourceListResult.getData().stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); result.forEach(o -> { if (dataSourceMap.containsKey(o.getDataSourceId())) { o.setDataSourceName(dataSourceMap.get(o.getDataSourceId()).getAlias()); } }); } private ChartMapper getMapper() { return Dbutils.getMapper(ChartMapper.class); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.time.LocalDateTime; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.core.converter.ConfigConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.SystemConfigDO; import ai.chat2db.server.domain.repository.mapper.SystemConfigMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author jipengfei * @version : ConfigServiceImpl.java */ @Service public class ConfigServiceImpl implements ConfigService { private SystemConfigMapper getMapper() { return Dbutils.getMapper(SystemConfigMapper.class); } @Autowired private ConfigConverter configConverter; @Override public ActionResult create(SystemConfigParam param) { SystemConfigDO systemConfigDO = configConverter.param2do(param); systemConfigDO.setGmtCreate(LocalDateTime.now()); systemConfigDO.setGmtModified(LocalDateTime.now()); getMapper().insert(systemConfigDO); return ActionResult.isSuccess(); } @Override public ActionResult update(SystemConfigParam param) { SystemConfigDO systemConfigDO = configConverter.param2do(param); UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("code", param.getCode()); getMapper().update(systemConfigDO, updateWrapper); return ActionResult.isSuccess(); } @Override public ActionResult createOrUpdate(SystemConfigParam param) { SystemConfigDO systemConfigDO = getMapper().selectOne( new UpdateWrapper().eq("code", param.getCode())); if (systemConfigDO == null) { return create(param); } else { return update(param); } } @Override public DataResult find(String code) { SystemConfigDO systemConfigDO = getMapper().selectOne( new UpdateWrapper().eq("code", code)); return DataResult.of(configConverter.do2model(systemConfigDO)); } @Override public ActionResult delete(String code) { getMapper().delete(new UpdateWrapper().eq("code", code)); return ActionResult.isSuccess(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import org.springframework.stereotype.Service; /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 September 23, 2022 15:51 moji Exp $ * @date 2022/09/23 */ @Service public class ConsoleServiceImpl implements ConsoleService { @Override public ActionResult createConsole(ConsoleConnectParam param) { Chat2DBContext.getDBManage().connectDatabase(Chat2DBContext.getConnection(),param.getDatabaseName()); return ActionResult.isSuccess(); } @Override public ActionResult closeConsole(ConsoleCloseParam param) { return ActionResult.isSuccess(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.time.LocalDateTime; import java.util.List; import java.util.Objects; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; import ai.chat2db.server.domain.core.converter.DashboardConverter; import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DashboardChartRelationDO; import ai.chat2db.server.domain.repository.entity.DashboardDO; import ai.chat2db.server.domain.repository.mapper.DashboardChartRelationMapper; import ai.chat2db.server.domain.repository.mapper.DashboardMapper; import ai.chat2db.server.tools.base.enums.YesOrNoEnum; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasySqlUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author moji * @version DashboardServiceImpl.java, v 0.1 June 9, 2023 16:06 moji Exp $ * @date 2023/06/09 */ @Service public class DashboardServiceImpl implements DashboardService { private DashboardMapper getMapper() { return Dbutils.getMapper(DashboardMapper.class); } private DashboardChartRelationMapper getMapper1() { return Dbutils.getMapper(DashboardChartRelationMapper.class); } @Autowired private DashboardConverter dashboardConverter; @Override public DataResult createWithPermission(DashboardCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); param.setUserId(ContextUtils.getUserId()); DashboardDO dashboardDO = dashboardConverter.param2do(param); getMapper().insert(dashboardDO); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); return DataResult.of(dashboardDO.getId()); } @Override public ActionResult updateWithPermission(DashboardUpdateParam param) { Dashboard dashboardData = queryExistent(param.getId()).getData(); PermissionUtils.checkOperationPermission(dashboardData.getUserId()); param.setGmtModified(LocalDateTime.now()); DashboardDO dashboardDO = dashboardConverter.updateParam2do(param); getMapper().updateById(dashboardDO); if (CollectionUtils.isEmpty(param.getChartIds())) { return ActionResult.isSuccess(); } deleteDashboardRelation(dashboardDO.getId()); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); return ActionResult.isSuccess(); } @Override public DataResult find(Long id) { DashboardDO dashboardDO = getMapper().selectById(id); if (YesOrNoEnum.YES.getLetter().equals(dashboardDO.getDeleted())) { return DataResult.empty(); } Dashboard dashboard = dashboardConverter.do2model(dashboardDO); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DashboardChartRelationDO::getDashboardId, id); List relationDO = getMapper1().selectList(queryWrapper); List chartIds = relationDO.stream().map(DashboardChartRelationDO::getChartId).toList(); dashboard.setChartIds(chartIds); return DataResult.of(dashboard); } @Override public DataResult queryExistent(DashboardQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) .eqWhenPresent(DashboardDO::getId, param.getId()) .eqWhenPresent(DashboardDO::getUserId, param.getUserId()); IPage page = getMapper().selectPage(new Page<>(1, 1), queryWrapper); if (CollectionUtils.isEmpty(page.getRecords())) { throw new DataNotFoundException(); } Dashboard dashboardData = dashboardConverter.do2model(page.getRecords().get(0)); LambdaQueryWrapper dashboardChartRelationQueryWrapper = new LambdaQueryWrapper<>(); dashboardChartRelationQueryWrapper.eq(DashboardChartRelationDO::getDashboardId, param.getId()); List relationDO = getMapper1().selectList( dashboardChartRelationQueryWrapper); List chartIds = relationDO.stream().map(DashboardChartRelationDO::getChartId).toList(); dashboardData.setChartIds(chartIds); return DataResult.of(dashboardData); } @Override public DataResult queryExistent(Long id) { DataResult dataResult = find(id); if (dataResult.getData() == null) { throw new DataNotFoundException(); } return dataResult; } @Override public ActionResult deleteWithPermission(Long id) { Dashboard data = queryExistent(id).getData(); PermissionUtils.checkOperationPermission(data.getUserId()); DashboardDO dashboardDO = new DashboardDO(); dashboardDO.setId(id); dashboardDO.setDeleted(YesOrNoEnum.YES.getLetter()); getMapper().updateById(dashboardDO); deleteDashboardRelation(id); return ActionResult.isSuccess(); } /** * delete dashboard relation * * @param id */ private void deleteDashboardRelation(Long id) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DashboardChartRelationDO::getDashboardId, id); List relationDO = getMapper1().selectList(queryWrapper); List relationIds = relationDO.stream().map(DashboardChartRelationDO::getId).toList(); if (CollectionUtils.isNotEmpty(relationIds)) { getMapper1().deleteBatchIds(relationIds); } } /** * insert dashboard relation * * @param dashboardId * @param chartIds */ private void insertDashboardRelation(Long dashboardId, List chartIds) { if (Objects.isNull(dashboardId) || CollectionUtils.isEmpty(chartIds)) { return; } chartIds.forEach(chartId -> { DashboardChartRelationDO relationDO = new DashboardChartRelationDO(); relationDO.setGmtCreate(LocalDateTime.now()); relationDO.setGmtModified(LocalDateTime.now()); relationDO.setDashboardId(dashboardId); relationDO.setChartId(chartId); getMapper1().insert(relationDO); }); } @Override public PageResult queryPage(DashboardPageQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) .likeWhenPresent(DashboardDO::getName, EasySqlUtils.buildLikeRightFuzzy(param.getSearchKey())) .eqWhenPresent(DashboardDO::getUserId, param.getUserId()); Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); IPage iPage = getMapper().selectPage(page, queryWrapper); List dashboards = dashboardConverter.do2model(iPage.getRecords()); return PageResult.of(dashboards, iPage.getTotal(), param); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * Data Source Access * * @author Jiaju Zhuang */ @Slf4j @Service public class DataSourceAccessBusinessServiceImpl implements DataSourceAccessBusinessService { @Resource private DataSourceAccessService dataSourceAccessService; private DataSourceAccessCustomMapper getMapper() { return Dbutils.getMapper(DataSourceAccessCustomMapper.class); } @Override public ActionResult checkPermission(@NotNull DataSource dataSource) { LoginUser loginUser = ContextUtils.getLoginUser(); // private if (DataSourceKindEnum.PRIVATE.getCode().equals(dataSource.getKind())) { if (loginUser.getId().equals(dataSource.getUserId())) { return ActionResult.isSuccess(); } else { throw new PermissionDeniedBusinessException(); } } // Administrators can edit anything if (loginUser.getAdmin()) { return ActionResult.isSuccess(); } // Verify if user have permission DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); dataSourceAccessPageQueryParam.setDataSourceId(dataSource.getId()); dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(loginUser.getId()); dataSourceAccessPageQueryParam.queryOne(); if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { return ActionResult.isSuccess(); } // Verify if the team has permission if (getMapper().checkTeamPermission(dataSource.getId(), loginUser.getId()) != null) { return ActionResult.isSuccess(); } throw new PermissionDeniedBusinessException(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.util.List; import java.util.Map; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.model.DataSourceAccessObject; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.DataSourceAccessConverter; import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.springframework.stereotype.Service; /** * Data Source Access * * @author Jiaju Zhuang */ @Slf4j @Service public class DataSourceAccessServiceImpl implements DataSourceAccessService { private DataSourceAccessCustomMapper getMapper() { return Dbutils.getMapper(DataSourceAccessCustomMapper.class); } private DataSourceAccessMapper getAccessMapper() { return Dbutils.getMapper(DataSourceAccessMapper.class); } @Resource private DataSourceAccessConverter dataSourceAccessConverter; @Resource private DataSourceConverter dataSourceConverter; @Resource private UserService userService; @Resource private TeamService teamService; @Override public PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DataSourceAccessDO::getDataSourceId, param.getDataSourceId()) .eq(DataSourceAccessDO::getAccessObjectType, param.getAccessObjectType()) .eq(DataSourceAccessDO::getAccessObjectId, param.getAccessObjectId()) ; Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getAccessMapper().selectPage(page, queryWrapper); List list = dataSourceAccessConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector) { Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getMapper().comprehensivePageQuery(page, param.getDataSourceId(), param.getAccessObjectType(), param.getAccessObjectId(), param.getUserOrTeamSearchKey(), param.getDataSourceSearchKey()); List list = dataSourceAccessConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public DataResult create(DataSourceAccessCreatParam param) { DataSourceAccessDO data = dataSourceAccessConverter.param2do(param, ContextUtils.getUserId()); getAccessMapper().insert(data); return DataResult.of(data.getId()); } @Override public ActionResult delete(Long id) { getAccessMapper().deleteById(id); return ActionResult.isSuccess(); } private void fillData(List list, DataSourceAccessSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } fillAccessObject(list, selector); fillDataSource(list, selector); } private void fillDataSource(List list, DataSourceAccessSelector selector) { if (BooleanUtils.isNotTrue(selector.getDataSource())) { return; } dataSourceConverter.fillDetail(EasyCollectionUtils.toList(list, DataSourceAccess::getDataSource), selector.getDataSourceSelector()); } private void fillAccessObject(List list, DataSourceAccessSelector selector) { if (BooleanUtils.isNotTrue(selector.getAccessObject())) { return; } List userIdList = Lists.newArrayList(); List teamIdList = Lists.newArrayList(); for (DataSourceAccess data : list) { if (AccessObjectTypeEnum.TEAM.getCode().equals(data.getAccessObjectType())) { teamIdList.add(data.getAccessObjectId()); } else if (AccessObjectTypeEnum.USER.getCode().equals(data.getAccessObjectType())) { userIdList.add(data.getAccessObjectId()); } } List userList = userService.listQuery(userIdList).getData(); Map userMap = EasyCollectionUtils.toIdentityMap(userList, User::getId); List teamList = teamService.listQuery(teamIdList).getData(); Map teamMap = EasyCollectionUtils.toIdentityMap(teamList, Team::getId); for (DataSourceAccess data : list) { DataSourceAccessObject dataSourceAccessObject = data.getAccessObject(); if (dataSourceAccessObject == null) { continue; } if (AccessObjectTypeEnum.TEAM.getCode().equals(data.getAccessObjectType())) { Team team = teamMap.get(data.getAccessObjectId()); if (team == null) { continue; } dataSourceAccessObject.setCode(team.getCode()); dataSourceAccessObject.setName(team.getName()); } else if (AccessObjectTypeEnum.USER.getCode().equals(data.getAccessObjectType())) { User user = userMap.get(data.getAccessObjectId()); if (user == null) { continue; } dataSourceAccessObject.setCode(user.getUserName()); dataSourceAccessObject.setName(user.getNickName()); } } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; import java.util.List; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.DataSourceCloseParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.core.converter.EnvironmentConverter; import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.tools.common.util.EasySqlUtils; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 September 23, 2022 15:51 moji Exp $ * @date 2022/09/23 */ @Slf4j @Service public class DataSourceServiceImpl implements DataSourceService { private DataSourceMapper getMapper() { return Dbutils.getMapper(DataSourceMapper.class); } @Autowired private DataSourceConverter dataSourceConverter; @Autowired private DatabaseService databaseService; private DataSourceCustomMapper getCustomMapper() { return Dbutils.getMapper(DataSourceCustomMapper.class); } @Resource private EnvironmentConverter environmentConverter; private DataSourceAccessMapper getAccessMapper() { return Dbutils.getMapper(DataSourceAccessMapper.class); } @Override public DataResult createWithPermission(DataSourceCreateParam param) { DataSourceKindEnum dataSourceKind = EasyEnumUtils.getEnum(DataSourceKindEnum.class, param.getKind()); if (dataSourceKind == null) { throw new ParamBusinessException("kind"); } if (dataSourceKind == DataSourceKindEnum.SHARED && !ContextUtils.getLoginUser().getAdmin()) { throw new PermissionDeniedBusinessException(); } JdbcUtils.removePropertySameAsDefault(param.getDriverConfig()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtCreate(DateUtil.date()); dataSourceDO.setGmtModified(DateUtil.date()); dataSourceDO.setUserId(ContextUtils.getUserId()); //dataSourceDO.setExtendInfo(null); getMapper().insert(dataSourceDO); preWarmingData(dataSourceDO.getId()); return DataResult.of(dataSourceDO.getId()); } private void preWarmingData(Long dataSourceId) { DataResult dataResult = queryById(dataSourceId); if (dataResult.success() && dataResult.getData() != null) { DataSource dataSource = dataResult.getData(); DriverConfig driverConfig = dataSource.getDriverConfig(); if (driverConfig == null || StringUtils.isBlank(driverConfig.getJdbcDriver())) { return; } try (Connection connection = IDriverManager.getConnection(dataSource.getUrl(), dataSource.getUserName(), dataSource.getPassword(), dataSource.getDriverConfig(), dataSource.getExtendMap())) { DatabaseQueryAllParam databaseQueryAllParam = new DatabaseQueryAllParam(); databaseQueryAllParam.setDataSourceId(dataSourceId); databaseQueryAllParam.setConnection(connection); databaseQueryAllParam.setDbType(dataSource.getType()); databaseQueryAllParam.setRefresh(true); databaseService.queryAll(databaseQueryAllParam); } catch (Exception e) { log.error("preWarmingData error", e); } } } @Override public DataResult updateWithPermission(DataSourceUpdateParam param) { DataSource dataSource = queryExistent(param.getId(), null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); JdbcUtils.removePropertySameAsDefault(param.getDriverConfig()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtModified(DateUtil.date()); getMapper().updateById(dataSourceDO); return DataResult.of(dataSourceDO.getId()); } @Override public ActionResult deleteWithPermission(Long id) { DataSource dataSource = queryExistent(id, null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); getMapper().deleteById(id); LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getDataSourceId, id) ; getAccessMapper().delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } @Override public DataResult queryById(Long id) { DataSourceDO dataSourceDO = getMapper().selectById(id); return DataResult.of(dataSourceConverter.do2dto(dataSourceDO)); } @Override public DataResult queryExistent(Long id, DataSourceSelector selector) { DataResult dataResult = queryById(id); if (dataResult.getData() == null) { throw new DataNotFoundException(); } fillData(Lists.newArrayList(dataResult.getData()), selector); return dataResult; } @Override public DataResult copyByIdWithPermission(Long id) { DataSource dataSource = queryExistent(id, null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = getMapper().selectById(id); dataSourceDO.setId(null); String alias = dataSourceDO.getAlias() + "Copy"; dataSourceDO.setAlias(alias); dataSourceDO.setGmtCreate(DateUtil.date()); dataSourceDO.setGmtModified(DateUtil.date()); getMapper().insert(dataSourceDO); return DataResult.of(dataSourceDO.getId()); } @Override public PageResult queryPage(DataSourcePageQueryParam param, DataSourceSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(DataSourceDO::getAlias, "%" + param.getSearchKey() + "%") .or() .like(DataSourceDO::getUrl, "%" + param.getSearchKey() + "%")); } Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); IPage iPage = getMapper().selectPage(page, queryWrapper); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); fillData(dataSources, selector); return PageResult.of(dataSources, iPage.getTotal(), param); } @Override public PageResult queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector) { LoginUser loginUser = ContextUtils.getLoginUser(); IPage iPage = getCustomMapper().selectPageWithPermission( new Page<>(param.getPageNo(), param.getPageSize()), BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(), param.getKind(), EasySqlUtils.orderBy(param.getOrderByList())); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); fillData(dataSources, selector); return PageResult.of(dataSources, iPage.getTotal(), param); } @Override public ListResult queryByIds(List ids) { return listQuery(ids, null); } @Override public ListResult listQuery(List idList, DataSourceSelector selector) { if (CollectionUtils.isEmpty(idList)) { return ListResult.empty(); } List dataList = getMapper().selectBatchIds(idList); List list = dataSourceConverter.do2dto(dataList); fillData(list, selector); return ListResult.of(list); } @Override public ActionResult preConnect(DataSourcePreConnectParam param) { DataSourceTestParam testParam = dataSourceConverter.param2param(param); DriverConfig driverConfig = testParam.getDriverConfig(); if (driverConfig == null || !driverConfig.notEmpty()) { driverConfig = Chat2DBContext.getDefaultDriverConfig(param.getType()); } DataSourceConnect dataSourceConnect = JdbcUtils.testConnect(testParam.getUrl(), testParam.getHost(), testParam.getPort(), testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); if (BooleanUtils.isNotTrue(dataSourceConnect.getSuccess())) { return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription(), dataSourceConnect.getErrorDetail()); } return ActionResult.isSuccess(); } @Override public ListResult connect(Long id) { DatabaseQueryAllParam queryAllParam = new DatabaseQueryAllParam(); queryAllParam.setDataSourceId(id); List databases = Chat2DBContext.getMetaData().databases(Chat2DBContext.getConnection()); return ListResult.of(databases); } @Override public ActionResult close(Long id) { DataSourceCloseParam closeParam = new DataSourceCloseParam(); closeParam.setDataSourceId(id); return ActionResult.isSuccess(); } private void fillData(List list, DataSourceSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } fillEnvironment(list, selector); fillSupportDatabase(list); } private void fillSupportDatabase(List list) { if(CollectionUtils.isEmpty(list)) { return; } for (DataSource dataSource:list) { String type = dataSource.getType(); if(StringUtils.isNotBlank(type)) { DBConfig config = Chat2DBContext.getDBConfig(type); if(config != null) { dataSource.setSupportDatabase(config.isSupportDatabase()); dataSource.setSupportSchema(config.isSupportSchema()); } } } } private void fillEnvironment(List list, DataSourceSelector selector) { if (BooleanUtils.isNotTrue(selector.getEnvironment())) { return; } environmentConverter.fillDetail(EasyCollectionUtils.toList(list, DataSource::getEnvironment)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.SchemaOperationParam; import ai.chat2db.server.domain.api.param.SchemaQueryParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import static ai.chat2db.server.domain.core.cache.CacheKey.getDataBasesKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getDataSourceKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getSchemasKey; /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 September 23, 2022 15:51 moji Exp $ * @date 2022/09/23 */ @Slf4j @Service public class DatabaseServiceImpl implements DatabaseService { @Override public ListResult queryAll(DatabaseQueryAllParam param) { List databases = CacheManage.getList(getDataBasesKey(param.getDataSourceId()), Database.class, (key) -> param.isRefresh(), (key) -> getDatabases(param.getDbType(), param.getConnection() == null ? Chat2DBContext.getConnection() : param.getConnection()) ); return ListResult.of(databases); } private List getDatabases(String dbType, Connection connection) { return Chat2DBContext.getMetaData(dbType).databases(connection); } @Override public ListResult querySchema(SchemaQueryParam param) { List schemas = CacheManage.getList(getSchemasKey(param.getDataSourceId(), param.getDataBaseName()), Schema.class, (key) -> param.isRefresh(), (key) -> { Connection connection = param.getConnection() == null ? Chat2DBContext.getConnection() : param.getConnection(); return getSchemaList(param.getDataBaseName(), connection); }); return ListResult.of(schemas); } private List getSchemaList(String databaseName, Connection connection) { MetaData metaData = Chat2DBContext.getMetaData(); List schemas = metaData.schemas(connection, databaseName); sortSchema(schemas, connection); return schemas; } private void sortSchema(List schemas, Connection connection) { if (CollectionUtils.isEmpty(schemas)) { return; } String ulr = null; try { ulr = connection.getMetaData().getURL(); } catch (SQLException e) { log.error("get url error", e); } // If the database name contains the name of the current database, the current database is placed in the first place int targetIndex = -1; for (int i = 0; i < schemas.size(); i++) { String schema = schemas.get(i).getName(); if (StringUtils.isNotBlank(ulr) && schema!=null && ulr.contains(schema)) { targetIndex = i; break; } } if (targetIndex != -1 && targetIndex != 0) { Collections.swap(schemas, targetIndex, 0); } } @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); MetaData metaData = Chat2DBContext.getMetaData(); MetaSchema ms = CacheManage.get(getDataSourceKey(param.getDataSourceId()), MetaSchema.class, (key) -> param.isRefresh(), (key) -> { Connection connection = Chat2DBContext.getConnection(); List databases = metaData.databases(connection); if (!CollectionUtils.isEmpty(databases)) { CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); for (Database database : databases) { ThreadUtil.execute(() -> { try { database.setSchemas(metaData.schemas(connection, database.getName())); } catch (Exception e) { log.error("queryDatabaseSchema error", e); } finally{ countDownLatch.countDown(); } }); } try { countDownLatch.await(); } catch (InterruptedException e) { log.error("queryDatabaseSchema error", e); } metaSchema.setDatabases(databases); } else { List schemas = metaData.schemas(connection, null); metaSchema.setSchemas(schemas); } return metaSchema; }); return DataResult.of(ms); } @Override public ActionResult deleteDatabase(DatabaseCreateParam param) { Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(), param.getName()); return ActionResult.isSuccess(); } @Override public DataResult createDatabase(Database database) { String sql = Chat2DBContext.getSqlBuilder().buildCreateDatabaseSql(database); return DataResult.of(Sql.builder().sql(sql).build()); } @Override public ActionResult modifyDatabase(DatabaseCreateParam param) { Chat2DBContext.getDBManage().modifyDatabase(Chat2DBContext.getConnection(), param.getName(), param.getNewName()); return ActionResult.isSuccess(); } @Override public ActionResult deleteSchema(SchemaOperationParam param) { Chat2DBContext.getDBManage().dropSchema(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName()); return ActionResult.isSuccess(); } @Override public DataResult createSchema(Schema schema) { String sql = Chat2DBContext.getSqlBuilder().buildCreateSchemaSql(schema); return DataResult.of(Sql.builder().sql(sql).build()); } @Override public ActionResult modifySchema(SchemaOperationParam param) { Chat2DBContext.getDBManage().modifySchema(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getNewSchemaName()); return ActionResult.isSuccess(); } @Override public String exportDatabase(DatabaseExportParam param) throws SQLException { AsyncCall call = new AsyncCall() { @Override public void update(Map map) { } }; AsyncContext asyncContext = new AsyncContext(call, ContextUtils.queryContext(), null, param.getContainData()); Chat2DBContext.getDBManage().exportDatabase(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), asyncContext); return "exportDatabase success"; } @Override public ListResult getUsernameList(){ MetaData metaSchema = Chat2DBContext.getMetaData(); List usernames = metaSchema.usernames(Chat2DBContext.getConnection()); return ListResult.of(usernames); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.core.converter.CommandConverter; import ai.chat2db.server.domain.core.util.MetaNameUtils; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.SqlUtils; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.sql.Connection; import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 September 23, 2022 15:51 moji Exp $ * @date 2022/09/23 */ @Slf4j @Service public class DlTemplateServiceImpl implements DlTemplateService { @Autowired private OperationLogService operationLogService; @Autowired private TableService tableService; @Autowired private CommandConverter commandConverter; @Override public ListResult execute(DlExecuteParam param) { CommandExecutor executor = Chat2DBContext.getMetaData().getCommandExecutor(); Command command = commandConverter.param2model(param); List results = executor.execute(command); return reBuildHeader(results,param.getSchemaName(),param.getDatabaseName()); } private ListResult reBuildHeader(List results,String schemaName,String databaseName){ ListResult listResult = ListResult.of(results); for (ExecuteResult executeResult : results) { List
headers = executeResult.getHeaderList(); if (executeResult.getSuccess() && executeResult.isCanEdit() && CollectionUtils.isNotEmpty(headers)) { headers = setColumnInfo(headers, executeResult.getTableName(), schemaName, databaseName); executeResult.setHeaderList(headers); } if (!executeResult.getSuccess()) { listResult.setSuccess(false); listResult.errorCode(executeResult.getDescription()); listResult.setErrorMessage(executeResult.getMessage()); } addOperationLog(executeResult); } return listResult; } @Override public ListResult executeSelectTable(DlExecuteParam param) { Command command = commandConverter.param2model(param); List results = Chat2DBContext.getMetaData().getCommandExecutor().executeSelectTable(command); return reBuildHeader(results,param.getSchemaName(),param.getDatabaseName()); } @Override public DataResult executeUpdate(DlExecuteParam param) { CommandExecutor executor = Chat2DBContext.getMetaData().getCommandExecutor(); DataResult dataResult = new DataResult<>(); dataResult.setSuccess(true); //RemoveSpecialGO(param); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); List sqlList = SqlUtils.parse(param.getSql(), dbType,true); Connection connection = Chat2DBContext.getConnection(); try { // connection.setAutoCommit(false); for (String originalSql : sqlList) { ExecuteResult executeResult = executor.executeUpdate(originalSql, connection, 1); dataResult.setData(executeResult); addOperationLog(executeResult); } // connection.commit(); } catch (Exception e) { log.error("executeUpdate error", e); dataResult.setSuccess(false); dataResult.setErrorCode("connection error"); dataResult.setErrorMessage(e.getMessage()); } return dataResult; } @Override public DataResult count(DlCountParam param) { if (StringUtils.isBlank(param.getSql())) { return DataResult.of(0L); } DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); String sql = param.getSql(); // Parse sql pagination SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if (!(sqlStatement instanceof SQLSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } sql = PagerUtils.count(sql, dbType); ExecuteResult executeResult; try { executeResult = Chat2DBContext.getMetaData().getCommandExecutor().execute(sql, Chat2DBContext.getConnection(), true, null, null); } catch (SQLException e) { log.warn("Execute sql: {} exception", sql, e); executeResult = ExecuteResult.builder() .sql(sql) .success(Boolean.FALSE) .message(e.getMessage()) .build(); } List> dataList = executeResult.getDataList(); if (CollectionUtils.isEmpty(dataList)) { return DataResult.of(0L); } String count = EasyCollectionUtils.stream(executeResult.getDataList()) .findFirst() .orElse(Collections.emptyList()) .stream() .findFirst() .orElse("0"); return DataResult.of(Long.valueOf(count)); } @Override public DataResult updateSelectResult(UpdateSelectResultParam param) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); QueryResult queryResult = new QueryResult(); BeanUtils.copyProperties(param, queryResult); String sql = sqlBuilder.buildSqlByQuery(queryResult); return DataResult.of(sql); } @Override public DataResult getOrderBySql(OrderByParam param) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String orderSql = sqlBuilder.buildOrderBySql(param.getOriginSql(), param.getOrderByList()); return DataResult.of(orderSql); } /** * The method getGroupBySql constructs a GROUP BY SQL query string from the provided parameters. * * @param param - a GroupByParam object containing the original SQL query and the list of columns to group by * @return DataResult - a DataResult object containing the constructed GROUP BY SQL query string */ @Override public DataResult getGroupBySql(GroupByParam param) { // - Retrieve the SqlBuilder instance from Chat2DBContext SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); // Build the GROUP BY SQL using the provided parameters String groupSql = sqlBuilder.buildGroupBySql(param.getOriginSql(), param.getGroupByList()); // Return the built SQL as a DataResult return DataResult.of(groupSql); } private List
setColumnInfo(List
headers, String tableName, String schemaName, String databaseName) { try { TableQueryParam tableQueryParam = new TableQueryParam(); tableQueryParam.setTableName(MetaNameUtils.getMetaName(tableName)); tableQueryParam.setSchemaName(schemaName); tableQueryParam.setDatabaseName(databaseName); tableQueryParam.setRefresh(true); List columns = tableService.queryColumns(tableQueryParam); if (CollectionUtils.isEmpty(columns)) { return headers; } Map columnMap = columns.stream().collect( Collectors.toMap(TableColumn::getName, tableColumn -> tableColumn)); List tableIndices = tableService.queryIndexes(tableQueryParam); if (!CollectionUtils.isEmpty(tableIndices)) { for (TableIndex tableIndex : tableIndices) { if ("PRIMARY".equalsIgnoreCase(tableIndex.getType())) { List columnList = tableIndex.getColumnList(); if (!CollectionUtils.isEmpty(columnList)) { for (TableIndexColumn tableIndexColumn : columnList) { TableColumn tableColumn = columnMap.get(tableIndexColumn.getColumnName()); if (tableColumn != null) { tableColumn.setPrimaryKey(true); } } } } } } for (Header header : headers) { TableColumn tableColumn = columnMap.get(header.getName()); if (tableColumn != null) { header.setPrimaryKey(tableColumn.getPrimaryKey()); header.setComment(tableColumn.getComment()); header.setDefaultValue(tableColumn.getDefaultValue()); header.setNullable(tableColumn.getNullable()); header.setColumnSize(tableColumn.getColumnSize()); header.setDecimalDigits(tableColumn.getDecimalDigits()); } } } catch (Exception e) { log.error("setColumnInfo error:", e); } return headers; } private void addOperationLog(ExecuteResult executeResult) { if (executeResult == null) { return; } try { ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); OperationLogCreateParam createParam = new OperationLogCreateParam(); createParam.setDdl(executeResult.getSql()); createParam.setStatus(executeResult.getSuccess() ? "success" : "fail"); createParam.setDatabaseName(connectInfo.getDatabaseName()); createParam.setDataSourceId(connectInfo.getDataSourceId()); createParam.setSchemaName(connectInfo.getSchemaName()); createParam.setUseTime(executeResult.getDuration()); createParam.setType(connectInfo.getDbType()); createParam.setOperationRows( executeResult.getUpdateCount() != null ? Long.valueOf(executeResult.getUpdateCount()) : null); operationLogService.create(createParam); } catch (Exception e) { log.error("addOperationLog error:", e); } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/EnvironmentServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.util.List; import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.domain.core.converter.EnvironmentConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.EnvironmentDO; import ai.chat2db.server.domain.repository.mapper.EnvironmentMapper; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * environment * * @author Jiaju Zhuang */ @Slf4j @Service public class EnvironmentServiceImpl implements EnvironmentService { private EnvironmentMapper getMapper() { return Dbutils.getMapper(EnvironmentMapper.class); } @Resource private EnvironmentConverter environmentConverter; @Override public ListResult listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { return ListResult.empty(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(EnvironmentDO::getId, idList); List dataList = getMapper().selectList(queryWrapper); List list = environmentConverter.do2dto(dataList); return ListResult.of(list); } @Override public PageResult pageQuery(EnvironmentPageQueryParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(EnvironmentDO::getName, "%" + param.getSearchKey() + "%") .or() .like(EnvironmentDO::getShortName, "%" + param.getSearchKey() + "%")); } IPage iPage = getMapper().selectPage(new Page<>(param.getPageNo(), param.getPageSize()), queryWrapper); List dataList = environmentConverter.do2dto(iPage.getRecords()); return PageResult.of(dataList, iPage.getTotal(), param); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.FunctionService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; @Service public class FunctionServiceImpl implements FunctionService { @Override public ListResult functions(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().functions(Chat2DBContext.getConnection(),databaseName, schemaName)); } @Override public DataResult detail(String databaseName, String schemaName, String functionName) { return DataResult.of(Chat2DBContext.getMetaData().function(Chat2DBContext.getConnection(), databaseName, schemaName, functionName)); } @Override public ActionResult delete(String databaseName, String schemaName, Function function) { Chat2DBContext.getDBManage().deleteFunction(Chat2DBContext.getConnection(), databaseName, schemaName, function); return ActionResult.isSuccess(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.JdbcDriverService; import ai.chat2db.server.domain.core.converter.DriverConfigConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; import ai.chat2db.server.domain.repository.mapper.EnvironmentMapper; import ai.chat2db.server.domain.repository.mapper.JdbcDriverMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.util.JdbcJarUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static ai.chat2db.spi.util.JdbcUtils.setDriverDefaultProperty; @Slf4j @Service public class JdbcDriverServiceImpl implements JdbcDriverService { @Autowired private DriverConfigConverter driverConfigConverter; private JdbcDriverMapper getMapper() { return Dbutils.getMapper(JdbcDriverMapper.class); } @Override public DataResult getDrivers(String dbType) { Map driverConfigMap = new LinkedHashMap<>(); LambdaQueryWrapper query = new LambdaQueryWrapper(); query.eq(JdbcDriverDO::getDbType, dbType); List driverDOS = getMapper().selectList(query); List driverConfigs = Lists.newArrayList(); if (!CollectionUtils.isEmpty(driverDOS)) { driverConfigs = driverDOS.stream().map(driverConfigConverter::do2Config).collect(Collectors.toList()); } DBConfig dbConfig = Chat2DBContext.PLUGIN_MAP.get(dbType).getDBConfig(); List driverConfigList = dbConfig.getDriverConfigList(); if (CollectionUtils.isNotEmpty(driverConfigList)) { driverConfigs.addAll(driverConfigList); } for (DriverConfig driverConfig : driverConfigs) { boolean flag = driverExists(driverConfig); if (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { driverConfigMap.put(driverConfig.getJdbcDriver(), driverConfig); //TODO :Temporary solution, need to be optimized later //setDriverDefaultProperty(driverConfig); } else { log.warn("Driver file not found: {}", driverConfig.getJdbcDriver()); } } dbConfig.setDriverConfigList(driverConfigMap.isEmpty() ? null : Lists.newArrayList(driverConfigMap.values())); return DataResult.of(dbConfig); } private boolean driverExists(DriverConfig driverConfig) { boolean flag = true; String[] jarPaths = driverConfig.getJdbcDriver().split(","); for (String jarPath : jarPaths) { File file = new File(JdbcJarUtils.PATH + jarPath); if (!file.exists()) { flag = false; break; } } return flag; } @Override public ActionResult upload(String dbType, String jdbcDriverClass, String localPath) { JdbcDriverDO driverDO = new JdbcDriverDO(); driverDO.setJdbcDriverClass(jdbcDriverClass); driverDO.setDbType(dbType); driverDO.setJdbcDriver(localPath); DriverConfig driverConfig = driverConfigConverter.do2Config(driverDO); try { IDriverManager.getClassLoader(driverConfig); } catch (Exception e) { throw new RuntimeException("Driver error,please check the driver file", e); } getMapper().insert(driverDO); return ActionResult.isSuccess(); } @Override public ActionResult download(String dbType) { DBConfig dbConfig = Chat2DBContext.PLUGIN_MAP.get(dbType).getDBConfig(); List driverConfigList = dbConfig.getDriverConfigList(); for (DriverConfig driverConfig : driverConfigList) { List downloadJdbcDriverUrls = driverConfig.getDownloadJdbcDriverUrls(); for (String downloadJdbcDriverUrl : downloadJdbcDriverUrls) { try { JdbcJarUtils.download(downloadJdbcDriverUrl); } catch (IOException e) { throw new RuntimeException(e); } } } return ActionResult.isSuccess(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.domain.core.converter.OperationLogConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.OperationLogDO; import ai.chat2db.server.domain.repository.mapper.OperationLogMapper; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasySqlUtils; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author moji * @version UserExecutedDdlCoreServiceImpl.java, v 0.1 September 25, 2022 14:07 moji Exp $ * @date 2022/09/25 */ @Service public class OperationLogServiceImpl implements OperationLogService { private OperationLogMapper getMapper() { return Dbutils.getMapper(OperationLogMapper.class); } @Autowired private OperationLogConverter operationLogConverter; @Autowired private DataSourceService dataSourceService; @Override public DataResult create(OperationLogCreateParam param) { OperationLogDO userExecutedDdlDO = operationLogConverter.param2do(param); userExecutedDdlDO.setGmtCreate(LocalDateTime.now()); userExecutedDdlDO.setGmtModified(LocalDateTime.now()); userExecutedDdlDO.setUserId(ContextUtils.getUserId()); getMapper().insert(userExecutedDdlDO); return DataResult.of(userExecutedDdlDO.getId()); } @Override public PageResult queryPage(OperationLogPageQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper.likeWhenPresent(OperationLogDO::getDdl, EasySqlUtils.buildLikeRightFuzzy(param.getSearchKey())) .eqWhenPresent(OperationLogDO::getUserId, param.getUserId()) .eqWhenPresent(OperationLogDO::getDataSourceId, param.getDataSourceId()) .eqWhenPresent(OperationLogDO::getDatabaseName, param.getDatabaseName()) .eqWhenPresent(OperationLogDO::getSchemaName, param.getSchemaName()) ; Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); page.setOptimizeCountSql(false); page.setOrders(Arrays.asList(OrderItem.desc("gmt_create"))); IPage executedDdlDOIPage = getMapper().selectPage(page, queryWrapper); List executedDdlDTOS = operationLogConverter.do2dto(executedDdlDOIPage.getRecords()); if (CollectionUtils.isEmpty(executedDdlDTOS)) { return PageResult.empty(param.getPageNo(), param.getPageSize()); } List dataSourceIds = executedDdlDTOS.stream().map(OperationLog::getDataSourceId).toList(); ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); Map dataSourceMap = dataSourceListResult.getData().stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); executedDdlDTOS.stream().forEach(executeDdl -> { if (dataSourceMap.containsKey(executeDdl.getDataSourceId())) { executeDdl.setDataSourceName(dataSourceMap.get(executeDdl.getDataSourceId()).getAlias()); } }); return PageResult.of(executedDdlDTOS, executedDdlDOIPage.getTotal(), param); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.OperationService; import ai.chat2db.server.domain.core.converter.OperationConverter; import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.OperationSavedDO; import ai.chat2db.server.domain.repository.mapper.OperationLogMapper; import ai.chat2db.server.domain.repository.mapper.OperationSavedMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author moji * @version UserSavedDdlCoreServiceImpl.java, v 0.1 September 25, 2022 15:50 moji Exp $ * @date 2022/09/25 */ @Service public class OperationServiceImpl implements OperationService { private OperationSavedMapper getMapper() { return Dbutils.getMapper(OperationSavedMapper.class); } @Autowired private OperationConverter operationConverter; @Autowired private DataSourceService dataSourceService; @Override public DataResult createWithPermission(OperationSavedParam param) { OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtCreate(LocalDateTime.now()); userSavedDdlDO.setGmtModified(LocalDateTime.now()); userSavedDdlDO.setUserId(ContextUtils.getUserId()); getMapper().insert(userSavedDdlDO); return DataResult.of(userSavedDdlDO.getId()); } @Override public ActionResult updateWithPermission(OperationUpdateParam param) { Operation data = queryExistent(param.getId()).getData(); PermissionUtils.checkOperationPermission(data.getUserId()); OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtModified(LocalDateTime.now()); getMapper().updateById(userSavedDdlDO); return ActionResult.isSuccess(); } @Override public DataResult find(Long id) { OperationSavedDO operationSavedDO = getMapper().selectById(id); List dataSourceIds = Lists.newArrayList(operationSavedDO.getDataSourceId()); Map dataSourceMap = getDataSourceInfo(dataSourceIds); Operation operation = operationConverter.do2dto(operationSavedDO); operation.setDataSourceName(dataSourceMap.containsKey(operation.getDataSourceId()) ? dataSourceMap.get( operation.getDataSourceId()).getAlias() : null); return DataResult.of(operation); } @Override public DataResult queryExistent(Long id) { DataResult dataResult = find(id); if (dataResult.getData() == null) { throw new DataNotFoundException(); } return dataResult; } @Override public DataResult queryExistent(OperationQueryParam param) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); queryWrapper.eqWhenPresent(OperationSavedDO::getId, param.getId()) .eqWhenPresent(OperationSavedDO::getUserId, param.getUserId()); IPage page = getMapper().selectPage(new Page<>(1, 1), queryWrapper); if (CollectionUtils.isEmpty(page.getRecords())) { throw new DataNotFoundException(); } return DataResult.of(operationConverter.do2dto(page.getRecords().get(0))); } @Override public ActionResult deleteWithPermission(Long id) { Operation data = queryExistent(id).getData(); PermissionUtils.checkOperationPermission(data.getUserId()); getMapper().deleteById(id); return ActionResult.isSuccess(); } @Override public PageResult queryPage(OperationPageQueryParam param) { QueryWrapper queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.like("name", param.getSearchKey()); } if (Objects.nonNull(param.getDataSourceId())) { queryWrapper.eq("data_source_id", param.getDataSourceId()); } if (StringUtils.isNotBlank(param.getDatabaseName())) { queryWrapper.eq("database_name", param.getDatabaseName()); } if (StringUtils.isNotBlank(param.getStatus())) { queryWrapper.eq("status", param.getStatus()); } if (StringUtils.isNotBlank(param.getTabOpened())) { queryWrapper.eq("tab_opened", param.getTabOpened()); } if (StringUtils.isNotBlank(param.getOperationType())) { queryWrapper.eq("operation_type", param.getOperationType()); } if (param.getUserId() != null) { queryWrapper.eq("user_id", param.getUserId()); } Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); page.setOptimizeCountSql(false); if (Objects.nonNull(param.getOrderByDesc()) && param.getOrderByDesc()) { queryWrapper.orderByDesc("gmt_modified"); } if (Objects.nonNull(param.getOrderByCreateDesc()) && param.getOrderByCreateDesc()) { queryWrapper.orderByDesc("gmt_create"); } IPage iPage = getMapper().selectPage(page, queryWrapper); List userSavedDdlDOS = operationConverter.do2dto(iPage.getRecords()); if (CollectionUtils.isEmpty(userSavedDdlDOS)) { return PageResult.empty(param.getPageNo(), param.getPageSize()); } List dataSourceIds = userSavedDdlDOS.stream().map(Operation::getDataSourceId).toList(); Map dataSourceMap = getDataSourceInfo(dataSourceIds); userSavedDdlDOS.forEach(userSavedDdl -> userSavedDdl.setDataSourceName( dataSourceMap.containsKey(userSavedDdl.getDataSourceId()) ? dataSourceMap.get( userSavedDdl.getDataSourceId()).getAlias() : null)); return PageResult.of(userSavedDdlDOS, iPage.getTotal(), param); } /** * Query data source information * * @param dataSourceIds * @return */ private Map getDataSourceInfo(List dataSourceIds) { if (CollectionUtils.isEmpty(dataSourceIds)) { return Maps.newHashMap(); } ListResult dataSourceListResult = dataSourceService.queryByIds(dataSourceIds); Map dataSourceMap = dataSourceListResult.getData().stream().collect( Collectors.toMap(DataSource::getId, Function.identity(), (a, b) -> a)); return dataSourceMap; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.core.converter.PinTableConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.PinTableDO; import ai.chat2db.server.domain.repository.mapper.PinTableMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service public class PinServiceImpl implements PinService { @Autowired private PinTableConverter pinTableConverter; private PinTableMapper getMapper() { return Dbutils.getMapper(PinTableMapper.class); } @Override public ActionResult pinTable(PinTableParam param) { PinTableDO entity = pinTableConverter.param2do(param); entity.setUserId(ContextUtils.getUserId()); getMapper().insert(entity); return ActionResult.isSuccess(); } @Override public ActionResult deletePinTable(PinTableParam param) { param.setUserId(ContextUtils.getUserId()); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(PinTableDO::getUserId, param.getUserId()); updateWrapper.eq(PinTableDO::getDataSourceId, param.getDataSourceId()); if (StringUtils.isNotBlank(param.getDatabaseName())) { updateWrapper.eq(PinTableDO::getDatabaseName, param.getDatabaseName()); } if (StringUtils.isNotBlank(param.getSchemaName())) { updateWrapper.eq(PinTableDO::getSchemaName, param.getSchemaName()); } if (StringUtils.isNotBlank(param.getTableName())) { updateWrapper.eq(PinTableDO::getTableName, param.getTableName()); } getMapper().delete(updateWrapper); return ActionResult.isSuccess(); } @Override public ListResult queryPinTables(PinTableParam param) { List result = new ArrayList<>(); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(PinTableDO::getUserId, param.getUserId()); queryWrapper.eq(PinTableDO::getDataSourceId, param.getDataSourceId()); if (StringUtils.isNotBlank(param.getDatabaseName())) { queryWrapper.eq(PinTableDO::getDatabaseName, param.getDatabaseName()); } if (StringUtils.isNotBlank(param.getSchemaName())) { queryWrapper.eq(PinTableDO::getSchemaName, param.getSchemaName()); } if (StringUtils.isNotBlank(param.getTableName())) { queryWrapper.eq(PinTableDO::getTableName, param.getTableName()); } queryWrapper.orderByDesc(PinTableDO::getGmtModified); List list = getMapper().selectList(queryWrapper); if (!CollectionUtils.isEmpty(list)) { result = list.stream().map(pinTableDO -> pinTableDO.getTableName()).collect(Collectors.toList()); } return ListResult.of(result); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.ProcedureService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; import java.sql.SQLException; @Service public class ProcedureServiceImpl implements ProcedureService { @Override public ListResult procedures(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().procedures(Chat2DBContext.getConnection(),databaseName, schemaName)); } @Override public DataResult detail(String databaseName, String schemaName, String procedureName) { return DataResult.of(Chat2DBContext.getMetaData().procedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedureName)); } @Override public ActionResult update(String databaseName, String schemaName, Procedure procedure) throws SQLException { Chat2DBContext.getDBManage().updateProcedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedure); return ActionResult.isSuccess(); } @Override public ActionResult delete(String databaseName, String schemaName, Procedure procedure) { Chat2DBContext.getDBManage().deleteProcedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedure); return ActionResult.isSuccess(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/SequenceServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.param.DropParam; import ai.chat2db.server.domain.api.param.SequencePageQueryParam; import ai.chat2db.server.domain.api.param.SequenceQueryParam; import ai.chat2db.server.domain.api.param.ShowCreateSequenceParam; import ai.chat2db.server.domain.api.service.SequenceService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import cn.hutool.core.util.ObjectUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * Sequence source management serviceImpl * * @author Sylphy */ @Slf4j @Service public class SequenceServiceImpl implements SequenceService { @Override public DataResult showCreateSequence(ShowCreateSequenceParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); String ddl = metaSchema.sequenceDDL(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getSequenceName()); return DataResult.of(ddl); } @Override public ListResult pageQuery(SequencePageQueryParam request) { MetaData metaSchema = Chat2DBContext.getMetaData(); List sequences = metaSchema.sequences(Chat2DBContext.getConnection(), request.getDatabaseName(), request.getSchemaName()); return ListResult.of(sequences); } @Override public ListResult buildSql(Sequence oldSequence, Sequence newSequence) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); List sqls = new ArrayList<>(); if (ObjectUtil.isEmpty(oldSequence)) { sqls.add(Sql.builder().sql(sqlBuilder.buildCreateSequenceSql(newSequence)).build()); } else { sqls.add(Sql.builder().sql(sqlBuilder.buildModifySequenceSql(oldSequence, newSequence)).build()); } return ListResult.of(sqls); } @Override public ActionResult drop(DropParam param) { DBManage metaSchema = Chat2DBContext.getDBManage(); metaSchema.dropSequence(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchema(), param.getName()); return ActionResult.isSuccess(); } @Override public DataResult query(SequenceQueryParam param){ MetaData metaSchema = Chat2DBContext.getMetaData(); Sequence sequences = metaSchema.sequences(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getSequenceName()); return DataResult.of(sequences); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.enums.TableVectorEnum; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.domain.core.converter.PinTableConverter; import ai.chat2db.server.domain.core.converter.TableConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.TableCacheDO; import ai.chat2db.server.domain.repository.entity.TableCacheVersionDO; import ai.chat2db.server.domain.repository.entity.TableVectorMappingDO; import ai.chat2db.server.domain.repository.mapper.TableCacheMapper; import ai.chat2db.server.domain.repository.mapper.TableCacheVersionMapper; import ai.chat2db.server.domain.repository.mapper.TableVectorMappingMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import static ai.chat2db.server.domain.core.cache.CacheKey.getColumnKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getTableKey; /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 September 23, 2022 15:51 moji Exp $ * @date 2022/09/23 */ @Service @Slf4j public class TableServiceImpl implements TableService { @Autowired private PinService pinService; @Autowired private PinTableConverter pinTableConverter; private TableCacheMapper getTableCacheMapper() { return Dbutils.getMapper(TableCacheMapper.class); } @Autowired private TableConverter tableConverter; private TableCacheVersionMapper getVersionMapper() { return Dbutils.getMapper(TableCacheVersionMapper.class); } private TableVectorMappingMapper getTableVectorMapper() { return Dbutils.getMapper(TableVectorMappingMapper.class); } @Override public DataResult showCreateTable(ShowCreateTableParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); String ddl = metaSchema.tableDDL(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); return DataResult.of(ddl); } @Override public ActionResult drop(DropParam param) { DBManage metaSchema = Chat2DBContext.getDBManage(); metaSchema.dropTable(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchema(), param.getName()); return ActionResult.isSuccess(); } @Override public DataResult createTableExample(String dbType) { String sql = Chat2DBContext.getDBConfig().getSimpleCreateTable(); return DataResult.of(sql); } @Override public DataResult alterTableExample(String dbType) { String sql = Chat2DBContext.getDBConfig().getSimpleAlterTable(); return DataResult.of(sql); } @Override public DataResult
query(TableQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); List
tables = metaSchema.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); if (!CollectionUtils.isEmpty(tables)) { Table table = tables.get(0); table.setIndexList( metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); table.setColumnList( metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); setPrimaryKey(table); return DataResult.of(table); } return DataResult.of(null); } private void setPrimaryKey(Table table) { if (table == null) { return; } List tableIndices = table.getIndexList(); if (CollectionUtils.isEmpty(tableIndices)) { return; } List columns = table.getColumnList(); if (CollectionUtils.isEmpty(columns)) { return; } Map columnMap = columns.stream() .collect(Collectors.toMap(TableColumn::getName, Function.identity())); List indexes = new ArrayList<>(); for (TableIndex tableIndex : tableIndices) { if ("Primary".equalsIgnoreCase(tableIndex.getType())) { List indexColumns = tableIndex.getColumnList(); if (CollectionUtils.isNotEmpty(indexColumns)) { for (TableIndexColumn indexColumn : indexColumns) { TableColumn column = columnMap.get(indexColumn.getColumnName()); if (column != null) { column.setPrimaryKey(true); column.setPrimaryKeyOrder(indexColumn.getOrdinalPosition()); column.setPrimaryKeyName(tableIndex.getName()); } } } } else { indexes.add(tableIndex); } } table.setIndexList(indexes); } @Override public ListResult buildSql(Table oldTable, Table newTable) { initOldTable(oldTable, newTable); SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); List sqls = new ArrayList<>(); if (oldTable == null) { initPrimaryKey(newTable); sqls.add(Sql.builder().sql(sqlBuilder.buildCreateTableSql(newTable)).build()); } else { initUpdatePrimaryKey(oldTable, newTable); sqls.add(Sql.builder().sql(sqlBuilder.buildModifyTaleSql(oldTable, newTable)).build()); } return ListResult.of(sqls); } private void initUpdatePrimaryKey(Table oldTable, Table newTable) { if (newTable == null || oldTable == null) { return; } List newColumns = getPrimaryKeyColumn(newTable); List oldColumns = getPrimaryKeyColumn(oldTable); if (CollectionUtils.isEmpty(newColumns) && CollectionUtils.isEmpty(oldColumns)) { return; } if (!CollectionUtils.isEmpty(newColumns) && CollectionUtils.isEmpty(oldColumns)) { initPrimaryKey(newTable); return; } if (CollectionUtils.isEmpty(newColumns) && CollectionUtils.isNotEmpty(oldColumns)) { addPrimaryKey(newTable, oldColumns.get(0), EditStatus.DELETE.name()); return; } if (newColumns.size() != oldColumns.size()) { for (TableColumn column : newColumns) { if (column.getPrimaryKey() != null && column.getPrimaryKey()) { addPrimaryKey(newTable, column, EditStatus.MODIFY.name()); } } return; } boolean flag = false; Map oldColumnMap = oldColumns.stream().collect(Collectors.toMap(TableColumn::getName, Function.identity())); for (TableColumn column : newColumns) { TableColumn oldColumn = oldColumnMap.get(column.getName()); if (oldColumn == null) { flag = true; } } if (flag) { for (TableColumn column : newColumns) { if (column.getPrimaryKey() != null && column.getPrimaryKey()) { addPrimaryKey(newTable, column, EditStatus.MODIFY.name()); } } } } private List getPrimaryKeyColumn(Table table) { if (table == null || CollectionUtils.isEmpty(table.getColumnList())) { return null; } return table.getColumnList().stream().filter(tableColumn -> tableColumn.getPrimaryKey() != null && tableColumn.getPrimaryKey()) .collect(Collectors.toList()); } private void initPrimaryKey(Table newTable) { if (newTable == null) { return; } List columns = newTable.getColumnList(); if (CollectionUtils.isEmpty(columns)) { return; } for (TableColumn column : columns) { if (column.getPrimaryKey() != null && column.getPrimaryKey()) { addPrimaryKey(newTable, column, EditStatus.ADD.name()); } } } private void addPrimaryKey(Table newTable, TableColumn column, String status) { List indexes = newTable.getIndexList(); if (indexes == null) { indexes = new ArrayList<>(); } TableIndex keyIndex = indexes.stream().filter(index -> "Primary".equalsIgnoreCase(index.getType())).findFirst().orElse(null); if (keyIndex == null) { keyIndex = new TableIndex(); keyIndex.setType("Primary"); keyIndex.setName(StringUtils.isBlank(column.getPrimaryKeyName()) ? "PRIMARY_KEY" : column.getPrimaryKeyName()); keyIndex.setTableName(newTable.getName()); keyIndex.setSchemaName(newTable.getSchemaName()); keyIndex.setDatabaseName(newTable.getDatabaseName()); keyIndex.setEditStatus(status); if (!EditStatus.ADD.name().equals(status)) { keyIndex.setOldName(keyIndex.getName()); } indexes.add(keyIndex); } List tableIndexColumns = keyIndex.getColumnList(); if (tableIndexColumns == null) { tableIndexColumns = new ArrayList<>(); } TableIndexColumn indexColumn = new TableIndexColumn(); indexColumn.setColumnName(column.getName()); indexColumn.setTableName(newTable.getName()); indexColumn.setSchemaName(newTable.getSchemaName()); indexColumn.setDatabaseName(newTable.getDatabaseName()); indexColumn.setOrdinalPosition(Short.valueOf(column.getPrimaryKeyOrder() + "")); indexColumn.setEditStatus(status); tableIndexColumns.add(indexColumn); List sortTableIndexColumns = tableIndexColumns.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)).collect(Collectors.toList()); Set statusList = sortTableIndexColumns.stream().map(TableIndexColumn::getEditStatus).collect(Collectors.toSet()); if (statusList.size() == 1) { //only one status ,set index status keyIndex.setEditStatus(statusList.iterator().next()); } else { //more status ,set index status modify keyIndex.setEditStatus(EditStatus.MODIFY.name()); } keyIndex.setColumnList(sortTableIndexColumns); newTable.setIndexList(indexes); } private void initOldTable(Table oldTable, Table newTable) { if (oldTable == null || newTable == null) { return; } Map columnMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(oldTable.getColumnList())) { for (TableColumn column : oldTable.getColumnList()) { columnMap.put(column.getName(), column); } } if (CollectionUtils.isNotEmpty(newTable.getColumnList())) { for (TableColumn newColumn : newTable.getColumnList()) { if (EditStatus.ADD.name().equals(newColumn.getEditStatus())) { continue; } String name = newColumn.getOldName() == null ? newColumn.getName() : newColumn.getOldName(); TableColumn oldColumn = columnMap.get(name); if (oldColumn != null) { if (oldColumn.equals(newColumn) && EditStatus.MODIFY.name().equals(newColumn.getEditStatus())) { newColumn.setEditStatus(null); } else { newColumn.setOldColumn(oldColumn); } } } } } @Override public PageResult
pageQuery(TablePageQueryParam param, TableSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); queryWrapper.eq(TableCacheVersionDO::getKey, key); TableCacheVersionDO versionDO = getVersionMapper().selectOne(queryWrapper); long total = 0; long version = 0L; if (param.isRefresh() || versionDO == null) { total = addCache(param, versionDO); } else { if ("2".equals(versionDO.getStatus())) { version = versionDO.getVersion() - 1; } else { version = versionDO.getVersion(); } total = versionDO.getTableCount(); } Page page = new Page<>(param.getPageNo(), param.getPageSize()); // page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getTableCacheMapper().pageQuery(page, param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), param.getSearchKey()); List
tables = new ArrayList<>(); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { for (TableCacheDO tableCacheDO : iPage.getRecords()) { Table t = new Table(); t.setName(tableCacheDO.getTableName()); t.setComment(tableCacheDO.getExtendInfo()); t.setSchemaName(tableCacheDO.getSchemaName()); t.setDatabaseName(tableCacheDO.getDatabaseName()); tables.add(t); } } if (param.getPageNo() <= 1) { tables = pinTable(tables, param); } return PageResult.of(tables, total, param); } private long addCache(TablePageQueryParam param, TableCacheVersionDO versionDO) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); queryWrapper.eq(TableCacheVersionDO::getKey, key); long total = 0; long version = getLock(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), versionDO); if (version == -1) { int n = 0; while (n < 100) { try { Thread.sleep(200); } catch (InterruptedException e) { } versionDO = getVersionMapper().selectOne(queryWrapper); if (versionDO != null && "1".equals(versionDO.getStatus())) { version = versionDO.getVersion(); total = versionDO.getTableCount(); break; } n++; } } else { total = addDBCache(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), version); TableCacheVersionDO versionDO1 = new TableCacheVersionDO(); versionDO1.setStatus("1"); versionDO1.setTableCount(total); getVersionMapper().update(versionDO1, queryWrapper); } return total; } @Override public ListResult queryTables(TablePageQueryParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); queryWrapper.eq(TableCacheVersionDO::getKey, key); TableCacheVersionDO versionDO = getVersionMapper().selectOne(queryWrapper); if (versionDO == null) { addCache(param, versionDO); versionDO = getVersionMapper().selectOne(queryWrapper); } long version = "2".equals(versionDO.getStatus()) ? versionDO.getVersion() - 1 : versionDO.getVersion(); LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.eq(TableCacheDO::getVersion, version); query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); if (StringUtils.isNotBlank(param.getDatabaseName())) { query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); } if (StringUtils.isNotBlank(param.getSchemaName())) { query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); } List tables = new ArrayList<>(); for (int i = 0; i < versionDO.getTableCount() / 500 + 1; i++) { Page page = new Page<>(i + 1, 500); IPage iPage = getTableCacheMapper().selectPage(page, query); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { for (TableCacheDO tableCacheDO : iPage.getRecords()) { SimpleTable t = new SimpleTable(); t.setName(tableCacheDO.getTableName()); t.setComment(tableCacheDO.getExtendInfo()); tables.add(t); } } } return ListResult.of(tables); } private long addDBCache(Long dataSourceId, String databaseName, String schemaName, long version) { String key = getTableKey(dataSourceId, databaseName, schemaName); Connection connection = Chat2DBContext.getConnection(); long n = 0; try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null, new String[]{"TABLE", "SYSTEM TABLE"})) { List cacheDOS = new ArrayList<>(); while (resultSet.next()) { TableCacheDO tableCacheDO = new TableCacheDO(); tableCacheDO.setDatabaseName(databaseName); tableCacheDO.setSchemaName(schemaName); tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); tableCacheDO.setDataSourceId(dataSourceId); tableCacheDO.setVersion(version); tableCacheDO.setKey(key); cacheDOS.add(tableCacheDO); if (cacheDOS.size() >= 500) { getTableCacheMapper().batchInsert(cacheDOS); cacheDOS = new ArrayList<>(); } n++; } if (!CollectionUtils.isEmpty(cacheDOS)) { getTableCacheMapper().batchInsert(cacheDOS); } LambdaQueryWrapper q = new LambdaQueryWrapper(); q.eq(TableCacheDO::getDataSourceId, dataSourceId); q.lt(TableCacheDO::getVersion, version); if (StringUtils.isNotBlank(databaseName)) { q.eq(TableCacheDO::getDatabaseName, databaseName); } if (StringUtils.isNotBlank(schemaName)) { q.eq(TableCacheDO::getSchemaName, schemaName); } getTableCacheMapper().delete(q); } catch (SQLException e) { throw new RuntimeException(e); } return n; } private Long getLock(Long dataSourceId, String databaseName, String schemaName, TableCacheVersionDO versionDO) { String key = getTableKey(dataSourceId, databaseName, schemaName); if (versionDO == null) { versionDO = new TableCacheVersionDO(); versionDO.setDatabaseName(databaseName); versionDO.setSchemaName(schemaName); versionDO.setDataSourceId(dataSourceId); versionDO.setStatus("2"); versionDO.setKey(key); versionDO.setVersion(0L); versionDO.setTableCount(0L); try { getVersionMapper().insert(versionDO); return 0L; } catch (Exception e) { log.error("getLock error", e); return -1L; } } else { long version = versionDO.getVersion() + 1; LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); queryWrapper.eq(TableCacheVersionDO::getId, versionDO.getId()); queryWrapper.eq(TableCacheVersionDO::getVersion, versionDO.getVersion()); versionDO.setVersion(version); versionDO.setStatus("2"); int n = getVersionMapper().update(versionDO, queryWrapper); if (n == 1) { return version; } else { return -1L; } } } // private String buildKey(Long dataSourceId, String databaseName, String schemaName) { // StringBuilder stringBuilder = new StringBuilder(dataSourceId.toString()); // if (StringUtils.isNotBlank(databaseName)) { // stringBuilder.append("_").append(databaseName); // } // if (StringUtils.isNotBlank(schemaName)) { // stringBuilder.append("_").append(schemaName); // } // return stringBuilder.toString(); // } private List
pinTable(List
list, TablePageQueryParam param) { if (CollectionUtils.isEmpty(list)) { return Lists.newArrayList(); } PinTableParam pinTableParam = pinTableConverter.toPinTableParam(param); pinTableParam.setUserId(ContextUtils.getUserId()); ListResult listResult = pinService.queryPinTables(pinTableParam); if (!listResult.success() || CollectionUtils.isEmpty(listResult.getData())) { return list; } List
tables = new ArrayList<>(); Map tableMap = list.stream().collect(Collectors.toMap(Table::getName, Function.identity())); for (String tableName : listResult.getData()) { Table table = tableMap.get(tableName); if (table != null) { table.setPinned(true); tables.add(table); } } for (Table table : list) { if (table != null && !tables.contains(table)) { tables.add(table); } } return tables; } @Override public List queryColumns(TableQueryParam param) { String tableColumnKey = getColumnKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); MetaData metaSchema = Chat2DBContext.getMetaData(); return CacheManage.getList(tableColumnKey, TableColumn.class, (key) -> param.isRefresh(), (key) -> metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); } @Override public List queryIndexes(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); return metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); } @Override public List queryTypes(TypeQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); return metaSchema.types(Chat2DBContext.getConnection()); } @Override public TableMeta queryTableMeta(TypeQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); TableMeta tableMeta = metaSchema.getTableMeta(null, null, null); if (tableMeta != null) { //filter primary key List indexTypes = tableMeta.getIndexTypes(); if (CollectionUtils.isNotEmpty(indexTypes)) { List types = indexTypes.stream().filter(indexType -> !"Primary".equalsIgnoreCase(indexType.getTypeName())).collect(Collectors.toList()); tableMeta.setIndexTypes(types); } } return tableMeta; } @Override public ActionResult saveTableVector(TableVectorParam param) { if (checkTableVector(param).getData()) { return ActionResult.isSuccess(); } TableVectorMappingDO mappingDO = tableConverter.toTableVectorMappingDO(param); mappingDO.setStatus(TableVectorEnum.SAVED.getCode()); getTableVectorMapper().insert(mappingDO); return ActionResult.isSuccess(); } @Override public DataResult checkTableVector(TableVectorParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); queryWrapper.eq(TableVectorMappingDO::getApiKey, param.getApiKey()); queryWrapper.eq(TableVectorMappingDO::getDataSourceId, param.getDataSourceId()); queryWrapper.eq(TableVectorMappingDO::getDatabase, param.getDatabase()); queryWrapper.eq(TableVectorMappingDO::getSchema, param.getSchema()); TableVectorMappingDO mappingDO = getTableVectorMapper().selectOne(queryWrapper); if (Objects.nonNull(mappingDO) && TableVectorEnum.SAVED.getCode().equals(mappingDO.getStatus())) { return DataResult.of(true); } return DataResult.of(false); } @Override public DataResult copyDmlSql(DmlSqlCopyParam param) { List columns = queryColumns(param); SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); Table table = Table.builder().name(param.getTableName()).columnList(columns).build(); String sql = sqlBuilder.getTableDmlSql(table, param.getType()); return DataResult.of(sql); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TaskServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.enums.DeletedTypeEnum; import ai.chat2db.server.domain.api.enums.TaskStatusEnum; import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.param.TaskUpdateParam; import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.domain.core.converter.TaskConverter; import ai.chat2db.server.domain.repository.MapperUtils; import ai.chat2db.server.domain.repository.entity.TaskDO; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class TaskServiceImpl implements TaskService { /** * task converter */ @Autowired private TaskConverter taskConverter; @Override public DataResult create(TaskCreateParam param) { TaskDO taskDO = taskConverter.todo(param); taskDO.setDeleted(DeletedTypeEnum.N.name()); taskDO.setTaskStatus(TaskStatusEnum.INIT.name()); MapperUtils.getTaskMapper().insert(taskDO); return DataResult.of(taskDO.getId()); } @Override public ActionResult updateStatus(TaskUpdateParam param) { TaskDO taskDO = new TaskDO(); taskDO.setId(param.getId()); taskDO.setTaskStatus(param.getTaskStatus()); taskDO.setContent(param.getContent()); taskDO.setDownloadUrl(param.getDownloadUrl()); MapperUtils.getTaskMapper().updateById(taskDO); return ActionResult.isSuccess(); } @Override public PageResult page(TaskPageParam param) { Page page = new Page<>(); page.setCurrent(param.getPageNo()); page.setSize(param.getPageSize()); page.setOrders(Lists.newArrayList(OrderItem.desc("gmt_create"))); IPage iPage = MapperUtils.getTaskMapper().pageQuery(page, param.getUserId(), DeletedTypeEnum.N.name()); if (iPage != null) { return PageResult.of(taskConverter.toModel(iPage.getRecords()), param); } return PageResult.empty(param.getPageNo(), param.getPageSize()); } @Override public DataResult get(Long id) { TaskDO task = MapperUtils.getTaskMapper().selectById(id); return DataResult.of(taskConverter.toModel(task)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.util.List; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.core.converter.TeamConverter; import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.TeamDO; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.TeamMapper; import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * team * * @author Jiaju Zhuang */ @Slf4j @Service public class TeamServiceImpl implements TeamService { private TeamMapper getTeamMapper() { return Dbutils.getMapper(TeamMapper.class); } private TeamUserMapper getTeamUserMapper() { return Dbutils.getMapper(TeamUserMapper.class); } private DataSourceAccessMapper getDataSourceAccessMapper() { return Dbutils.getMapper(DataSourceAccessMapper.class); } @Resource private TeamConverter teamConverter; @Resource private UserConverter userConverter; @Override public ListResult listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { return ListResult.empty(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(TeamDO::getId, idList); List dataList = getTeamMapper().selectList(queryWrapper); List list = teamConverter.do2dto(dataList); return ListResult.of(list); } @Override public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(TeamDO::getCode, "%" + param.getSearchKey() + "%") .or() .like(TeamDO::getName, "%" + param.getSearchKey() + "%")); } Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); queryWrapper.orderBy(param.getOrderByList()); IPage iPage = getTeamMapper().selectPage(page, queryWrapper); List list = teamConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public DataResult create(TeamCreateParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TeamDO::getCode, param.getCode()); Page page = new Page<>(1, 1); page.setSearchCount(false); IPage iPage = getTeamMapper().selectPage(page, queryWrapper); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { throw new DataAlreadyExistsBusinessException("code", param.getCode()); } if (RoleCodeEnum.DESKTOP.getCode().equals(param.getRoleCode())) { throw new ParamBusinessException("roleCode"); } TeamDO data = teamConverter.param2do(param, ContextUtils.getUserId()); getTeamMapper().insert(data); return DataResult.of(data.getId()); } @Override public DataResult update(TeamUpdateParam param) { TeamDO data = teamConverter.param2do(param, ContextUtils.getUserId()); getTeamMapper().updateById(data); return DataResult.of(data.getId()); } @Override public ActionResult delete(Long id) { getTeamMapper().deleteById(id); LambdaQueryWrapper teamUserQueryWrapper = new LambdaQueryWrapper<>(); teamUserQueryWrapper.eq(TeamUserDO::getTeamId, id); getTeamUserMapper().delete(teamUserQueryWrapper); LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getAccessObjectId, id) .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.TEAM.getCode()) ; getDataSourceAccessMapper().delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } private void fillData(List list, TeamSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } fillUser(list, selector); } private void fillUser(List list, TeamSelector selector) { if (BooleanUtils.isNotTrue(selector.getModifiedUser())) { return; } userConverter.fillDetail(EasyCollectionUtils.toList(list, Team::getModifiedUser)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.util.List; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.core.converter.TeamConverter; import ai.chat2db.server.domain.core.converter.TeamUserConverter; import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.domain.repository.mapper.TeamUserCustomMapper; import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.springframework.stereotype.Service; /** * Team User * * @author Jiaju Zhuang */ @Slf4j @Service public class TeamUserServiceImpl implements TeamUserService { @Resource private TeamUserConverter teamUserConverter; private TeamUserCustomMapper getTeamUserCustomMapper() { return Dbutils.getMapper(TeamUserCustomMapper.class); } private TeamUserMapper getTeamUserMapper() { return Dbutils.getMapper(TeamUserMapper.class); } @Resource private UserConverter userConverter; @Resource private TeamConverter teamConverter; @Override public PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TeamUserDO::getTeamId, param.getTeamId()) .eq(TeamUserDO::getUserId, param.getUserId()) ; Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getTeamUserMapper().selectPage(page, queryWrapper); List list = teamUserConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector) { Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getTeamUserCustomMapper().comprehensivePageQuery(page, param.getTeamId(), param.getUserId(), param.getTeamSearchKey(), param.getUserSearchKey()); List list = teamUserConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public DataResult create(TeamUserCreatParam param) { TeamUserDO data = teamUserConverter.param2do(param, ContextUtils.getUserId()); getTeamUserMapper().insert(data); return DataResult.of(data.getId()); } @Override public ActionResult delete(Long id) { getTeamUserMapper().deleteById(id); return ActionResult.isSuccess(); } private void fillData(List list, TeamUserSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } fillUser(list, selector); fillTeam(list, selector); } private void fillUser(List list, TeamUserSelector selector) { if (BooleanUtils.isNotTrue(selector.getUser())) { return; } userConverter.fillDetail(EasyCollectionUtils.toList(list, TeamUser::getUser)); } private void fillTeam(List list, TeamUserSelector selector) { if (BooleanUtils.isNotTrue(selector.getTeam())) { return; } teamConverter.fillDetail(EasyCollectionUtils.toList(list, TeamUser::getTeam)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.TriggerService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; @Service public class TriggerServiceImpl implements TriggerService { @Override public ListResult triggers(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().triggers(Chat2DBContext.getConnection(),databaseName, schemaName)); } @Override public DataResult detail(String databaseName, String schemaName, String triggerName) { return DataResult.of(Chat2DBContext.getMetaData().trigger(Chat2DBContext.getConnection(), databaseName, schemaName, triggerName)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import java.util.List; import java.util.Objects; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.DbhubUserMapper; import ai.chat2db.server.domain.repository.mapper.TeamUserCustomMapper; import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import cn.hutool.crypto.digest.DigestUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * User service * * @author Shi Yi */ @Service public class UserServiceImpl implements UserService { private DbhubUserMapper getDbhubUserMapper() { return Dbutils.getMapper(DbhubUserMapper.class); } @Resource private UserConverter userConverter; private TeamUserMapper getTeamUserMapper() { return Dbutils.getMapper(TeamUserMapper.class); } private DataSourceAccessMapper getDataSourceAccessMapper() { return Dbutils.getMapper(DataSourceAccessMapper.class); } @Override public DataResult query(Long id) { return DataResult.of(userConverter.do2dto(getDbhubUserMapper().selectById(id))); } @Override public DataResult query(String userName) { LambdaQueryWrapper query = new LambdaQueryWrapper<>(); if (Objects.nonNull(userName)) { query.eq(DbhubUserDO::getUserName, userName); } DbhubUserDO dbhubUserDO = getDbhubUserMapper().selectOne(query); return DataResult.of(userConverter.do2dto(dbhubUserDO)); } @Override public ListResult listQuery(List idList) { if (CollectionUtils.isEmpty(idList)) { return ListResult.empty(); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(DbhubUserDO::getId, idList); List dataList = getDbhubUserMapper().selectList(queryWrapper); List list = userConverter.do2dto(dataList); return ListResult.of(list); } @Override public PageResult pageQuery(UserPageQueryParam param, UserSelector selector) { EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(DbhubUserDO::getUserName, "%" + param.getSearchKey() + "%") .or() .like(DbhubUserDO::getNickName, "%" + param.getSearchKey() + "%") .or() .like(DbhubUserDO::getEmail, "%" + param.getSearchKey() + "%")); } // Default not to query desktop accounts queryWrapper.ne(DbhubUserDO::getId, RoleCodeEnum.DESKTOP.getDefaultUserId()); queryWrapper.orderBy(param.getOrderByList()); Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = getDbhubUserMapper().selectPage(page, queryWrapper); List list = userConverter.do2dto(iPage.getRecords()); fillData(list, selector); return PageResult.of(list, iPage.getTotal(), param); } @Override public DataResult update(UserUpdateParam param) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(param.getId())) { throw new BusinessException("user.canNotOperateSystemAccount"); } if (RoleCodeEnum.DESKTOP.getCode().equals(param.getRoleCode())) { throw new ParamBusinessException("roleCode"); } DbhubUserDO data = userConverter.param2do(param, ContextUtils.getUserId()); if (Objects.nonNull(data.getPassword())) { String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); } if (RoleCodeEnum.ADMIN.getDefaultUserId().equals(param.getId())) { data.setStatus(null); data.setEmail(null); data.setUserName(null); data.setRoleCode(null); } getDbhubUserMapper().updateById(data); return DataResult.of(data.getId()); } @Override public ActionResult delete(Long id) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(id) || RoleCodeEnum.ADMIN.getDefaultUserId().equals(id)) { throw new BusinessException("user.canNotOperateSystemAccount"); } getDbhubUserMapper().deleteById(id); LambdaQueryWrapper teamUserQueryWrapper = new LambdaQueryWrapper<>(); teamUserQueryWrapper.eq(TeamUserDO::getUserId, id); getTeamUserMapper().delete(teamUserQueryWrapper); LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getAccessObjectId, id) .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.USER.getCode()) ; getDataSourceAccessMapper().delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } @Override public DataResult create(UserCreateParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.and(wrapper -> wrapper.eq(DbhubUserDO::getUserName, param.getUserName()) .or() .eq(DbhubUserDO::getEmail, param.getEmail())); Page page = new Page<>(1, 1); page.setSearchCount(false); IPage iPage = getDbhubUserMapper().selectPage(page, queryWrapper); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { throw new DataAlreadyExistsBusinessException("userName or email", param.getUserName() + " or " + param.getEmail()); } if (RoleCodeEnum.DESKTOP.getCode().equals(param.getRoleCode())) { throw new ParamBusinessException("roleCode"); } DbhubUserDO data = userConverter.param2do(param, ContextUtils.getUserId()); String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); getDbhubUserMapper().insert(data); return DataResult.of(data.getId()); } private void fillData(List list, UserSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } fillUser(list, selector); } private void fillUser(List list, UserSelector selector) { if (BooleanUtils.isNotTrue(selector.getModifiedUser())) { return; } userConverter.fillDetail(EasyCollectionUtils.toList(list, User::getModifiedUser)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java ================================================ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.ViewService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; @Service public class ViewServiceImpl implements ViewService { @Override public ListResult
views(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().views(Chat2DBContext.getConnection(),databaseName, schemaName)); } @Override public DataResult
detail(String databaseName, String schemaName, String tableName) { MetaData metaSchema = Chat2DBContext.getMetaData(); Table table = metaSchema.view(Chat2DBContext.getConnection(), databaseName, schemaName, tableName); return DataResult.of(table); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/BaseWebhookSender.java ================================================ package ai.chat2db.server.domain.core.notification; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; import ai.chat2db.server.domain.api.service.WebhookSender; import ai.chat2db.server.domain.core.enums.ExternalNotificationTypeEnum; import org.springframework.stereotype.Service; @Service public class BaseWebhookSender implements WebhookSender { /** * Sends a message through the specified webhook platform. * * @param param The parameter object containing message details and platform type. * @throws IllegalArgumentException if the provided param is null or invalid. * @throws RuntimeException if an error occurs while attempting to send the message. */ public void sendMessage(MessageCreateParam param) throws IllegalArgumentException { // Validate the input parameter to ensure it's not null and meets the necessary criteria. if (param == null || param.getPlatformType() == null) { throw new IllegalArgumentException("MessageCreateParam or its platform type cannot be null."); } try { // Attempt to retrieve the appropriate WebhookSender based on the platform type. ExternalNotificationTypeEnum extern = ExternalNotificationTypeEnum.getByName(param.getPlatformType()); WebhookSender sender = extern.getWebhookSender(param.getPlatformType()); // Guard clause for null sender. Ideally, getWebhookSender should prevent this, but it's good to be cautious. if (sender == null) { throw new RuntimeException("Failed to retrieve WebhookSender for platform type: " + param.getPlatformType()); } // Send the message. Any exceptions thrown by sendMessage should be caught and handled here. sender.sendMessage(param); } catch (Exception e) { // Wrap and re-throw any runtime exceptions as a checked exception specific to webhook sending. throw new RuntimeException("An error occurred while sending the message.", e); } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/DingTalkWebhookSender.java ================================================ package ai.chat2db.server.domain.core.notification; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; import okhttp3.*; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; /** * @author Juechen * @version : DingTalkWebhookSender.java */ @Service public class DingTalkWebhookSender extends BaseWebhookSender { private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; @Override public void sendMessage(MessageCreateParam param) { try { OkHttpClient client = new OkHttpClient(); String secret = param.getSecretKey(); Long timestamp = System.currentTimeMillis(); String sign = generateSign(secret, timestamp); String webhookUrl = param.getServiceUrl() + "&sign=" + sign + "×tamp=" + timestamp; String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + param.getTextTemplate() + "\"}}"; RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder() .url(webhookUrl) .post(requestBody) .header("Content-Type", "application/json") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new RuntimeException("Failed to send message: " + response.code()); } System.out.println(response.body().string()); } catch (IOException e) { throw new RuntimeException(e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } } private static String generateSign(String secret, Long timestamp) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException { String stringToSign = timestamp + "\n" + secret; Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256")); byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8")); return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8"); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/LarkWebhookSender.java ================================================ package ai.chat2db.server.domain.core.notification; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; import okhttp3.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Service; /** * @author Juechen * @version : LarkWebhookSender.java */ @Service public class LarkWebhookSender extends BaseWebhookSender { private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; @Override public void sendMessage(MessageCreateParam param) { try { OkHttpClient client = new OkHttpClient(); String webhookUrl = param.getServiceUrl(); String secret = param.getSecretKey(); int timestamp = (int) (System.currentTimeMillis() / 1000); String signature = GenSign(secret, timestamp); String payload = "{\"timestamp\": \"" + timestamp + "\",\"sign\": \"" + signature + "\",\"msg_type\":\"text\",\"content\":{\"text\":\""+ param.getTextTemplate() +"\"}}"; RequestBody body = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder() .url(webhookUrl) .post(body) .addHeader("Content-Type", "application/json") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new RuntimeException("Failed to send message: " + response.code()); } System.out.println(response.body().string()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } private static String GenSign(String secret, int timestamp) throws NoSuchAlgorithmException, InvalidKeyException { String stringToSign = timestamp + "\n" + secret; Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM)); byte[] signData = mac.doFinal(new byte[]{}); return new String(Base64.encodeBase64(signData)); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/notification/WeComWebhookSender.java ================================================ package ai.chat2db.server.domain.core.notification; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; import okhttp3.*; import org.springframework.stereotype.Service; import java.io.IOException; /** * @author Juechen * @version : WeComWebhookSender.java */ @Service public class WeComWebhookSender extends BaseWebhookSender { @Override public void sendMessage(MessageCreateParam param) { try { OkHttpClient client = new OkHttpClient(); String webhookUrl = param.getServiceUrl(); String text = param.getTextTemplate(); String payload = "{\"msgtype\": \"text\",\"text\": {\"content\": \"" + text + "\"}}"; RequestBody requestBody = RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder() .url(webhookUrl) .post(requestBody) .addHeader("Content-Type", "application/json") .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new RuntimeException("Failed to send message: " + response.code()); } System.out.println(response.body().string()); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/DesUtil.java ================================================ package ai.chat2db.server.domain.core.util; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.KeyGenerator; import javax.crypto.spec.IvParameterSpec; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; /** * @author moji * @version DESUtil.java, v 0.1 December 26, 2022 19:54 moji Exp $ * @date 2022/12/26 */ public class DesUtil { /** * CFB */ public static final String CFB = "CFB"; /** * OFB */ public static final String OFB = "OFB"; /** * CBC */ public static final String CBC = "CBC"; /** * iv vector */ private static final byte[] DESIV = { (byte) 0xCE, (byte) 0x35, (byte) 0x5, (byte) 0xD, (byte) 0x98, (byte) 0x91, (byte) 0x8, (byte) 0xA }; /** * AlgorithmParameterSpec */ private static AlgorithmParameterSpec IV = null; /** * SHA1PRNG */ private static final String SHA1PRNG = "SHA1PRNG"; /** * DES default mode */ private static final String DES = "DES"; /** * CBC encryption mode */ private static final String DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding"; /** * OFB encryption mode */ private static final String DES_OFB_PKCS5PADDING = "DES/OFB/PKCS5Padding"; /** * CFB encryption mode */ private static final String DES_CFB_PKCS5_PADDING = "DES/CFB/PKCS5Padding"; /** * encryption mode */ private static final int ENCRYPT_MODE = 1; /** * des key */ public static final String DES_KEY = "dbhub"; /** * Decryption mode */ private static final int DECRYPT_MODE = 2; /** * private key */ private Key key; public DesUtil(String str) { getKey(str); } public Key getKey() { return key; } public void setKey(Key key) { this.key = key; } /** * Get key through private key * @param secretKey private key * @author sucb * @date 2017年2月28日下午1:17:58 */ public void getKey(String secretKey) { try { SecureRandom secureRandom = SecureRandom.getInstance(SHA1PRNG); secureRandom.setSeed(secretKey.getBytes()); KeyGenerator generator = null; try { generator = KeyGenerator.getInstance(DES); } catch (NoSuchAlgorithmException e) { } generator.init(secureRandom); IV = new IvParameterSpec(DESIV); this.key = generator.generateKey(); generator = null; } catch (Exception e) { throw new RuntimeException("Error in getKey(String secretKey), Cause: " + e); } } /** * String des encryption * @param data String that needs to be encrypted * @param encryptType encryption mode (ECB/CBC/OFB/CFB) * @return Encryption result * @throws Exception exception * @author sucb * @date March 2, 2017 7:47:37 pm */ public String encrypt(String data, String encryptType) throws Exception { Cipher cipher = getPattern(encryptType, ENCRYPT_MODE); byte[] pasByte = cipher.doFinal(data.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(pasByte); } /** * String decryption * @param data The string that needs to be decrypted * @param decryptType Decryption mode (ECB/CBC/OFB/CFB) * @return Decryption result * @throws Exception exception * @author sucb * @date March 2, 2017 7:48:21 pm */ public String decrypt(String data, String decryptType) throws Exception { if (StringUtils.isBlank(data)) { return Strings.EMPTY; } Cipher cipher = getPattern(decryptType, DECRYPT_MODE); byte[] pasByte = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(pasByte, "UTF-8"); } /** * Initialize cipher * @param type Encryption/decryption mode (ECB/CBC/OFB/CFB) * @param cipherMode cipher working mode 1: encryption; 2: decryption * @return cipher * @throws Exception exception * @author sucb * @date March 2, 2017 7:49:16 pm */ private Cipher getPattern(String type, int cipherMode) throws Exception { Cipher cipher; switch (type){ case CBC : cipher = Cipher.getInstance(DES_CBC_PKCS5PADDING); cipher.init(cipherMode, key, IV); break; case OFB : cipher = Cipher.getInstance(DES_OFB_PKCS5PADDING); cipher.init(cipherMode, key, IV); break; case CFB : cipher = Cipher.getInstance(DES_CFB_PKCS5_PADDING); cipher.init(cipherMode, key, IV); break; default : cipher = Cipher.getInstance(DES); cipher.init(cipherMode, key); break; } return cipher; } /** * The file file is encrypted and saved in the target file destFile. * @param file The file to be encrypted such as c:/test/file.txt * @param destFile The name of the file stored after encryption, such as c:/ encrypted file .txt * @param encryptType encryption mode (ECB/CBC/OFB/CFB) * @return Encryption result 0: Abnormal 1: Encryption successful; 5: File to be encrypted not found * @author sucb * @date March 2, 2017 7:56:08 pm */ public int encryptFile(String file, String destFile, String encryptType) { int result = 0; try { Cipher cipher = getPattern(encryptType, ENCRYPT_MODE); InputStream is = new FileInputStream(file); OutputStream out = new FileOutputStream(destFile); CipherInputStream cis = new CipherInputStream(is, cipher); byte[] buffer = new byte[1024]; int r; while ((r = cis.read(buffer)) > 0) { out.write(buffer, 0, r); } cis.close(); is.close(); out.close(); result = 1; } catch (FileNotFoundException e) { result = 5; } catch (Exception e) { throw new RuntimeException(e); } return result; } /** * The file file is decrypted and saved in the target file destFile. * @param file The file to be decrypted such as c:/test/file.txt * @param destFile The name of the file stored after decryption, such as c:/ decrypted file .txt * @param decryptType Decryption mode (ECB/CBC/OFB/CFB) * @return Decryption result 0: decryption abnormal; 1: decryption normal; 5: file to be decrypted not found * @author sucb * @date March 2, 2017 7:58:56 pm */ public int decryptFile(String file, String destFile, String decryptType) { int result = 0; try { Cipher cipher = getPattern(decryptType, DECRYPT_MODE); InputStream is = new FileInputStream(file); OutputStream out = new FileOutputStream(destFile); CipherOutputStream cos = new CipherOutputStream(out, cipher); byte[] buffer = new byte[1024]; int r; while ((r = is.read(buffer)) >= 0) { cos.write(buffer, 0, r); } cos.close(); out.close(); is.close(); result = 1; }catch (FileNotFoundException e) { result = 5; } catch (Exception e) { throw new RuntimeException(e); } return result; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Functions.java ================================================ package ai.chat2db.server.domain.core.util; public class H2Functions { public static String keyGeneratorFunction(String tableName) { return null; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Triggers.java ================================================ package ai.chat2db.server.domain.core.util; import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; import org.h2.api.Trigger; public class H2Triggers implements Trigger { @Override public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException { // Initialization logic, if needed. } @Override public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { // This method is called when the trigger is executed. // In this example, let's simply print the new values when a row is inserted. if (newRow != null) { // System.out.println("New Row Inserted: " + Arrays.toString(newRow)); } } @Override public void close() throws SQLException { // Cleanup logic, if needed. } @Override public void remove() throws SQLException { // Logic to execute when the trigger is dropped/removed. } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/MetaNameUtils.java ================================================ package ai.chat2db.server.domain.core.util; import org.apache.commons.lang3.StringUtils; public class MetaNameUtils { public static String getMetaName(String tableName) { if(StringUtils.isBlank(tableName)){ return tableName; } if(tableName.startsWith("`") && tableName.endsWith("`")){ return tableName.substring(1,tableName.length()-1); } if(tableName.startsWith("\"") && tableName.endsWith("\"")){ return tableName.substring(1,tableName.length()-1); } if(tableName.startsWith("'") && tableName.endsWith("'")){ return tableName.substring(1,tableName.length()-1); } if(tableName.startsWith("[") && tableName.endsWith("]")){ return tableName.substring(1,tableName.length()-1); } return tableName; } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java ================================================ package ai.chat2db.server.domain.core.util; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; /** * Permission Utils * * @author Jiaju Zhuang */ public class PermissionUtils { /** * Verify whether the currently logged-in user has permission to operate on the current content * * @param createUserId The creator of the current content */ public static void checkOperationPermission(Long createUserId) { LoginUser loginUser = ContextUtils.getLoginUser(); // Representative is desktop mode if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(createUserId)) { return; } else { throw new PermissionDeniedBusinessException(); } } // Administrators can edit anything if (loginUser.getAdmin()) { return; } // Not that administrators can only edit their own things if (!loginUser.getId().equals(createUserId)) { throw new PermissionDeniedBusinessException(); } } /** * Verify whether you have query permission * * @param createUserId * @return */ public static boolean checkBaseQueryPermission(Long createUserId) { LoginUser loginUser = ContextUtils.getLoginUser(); // Representative is desktop mode if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(createUserId)) { return true; } else { throw new PermissionDeniedBusinessException(); } } // Administrators can edit anything return loginUser.getAdmin(); } /** * Verify if it is an administrator * * @return */ public static void checkDeskTopOrAdmin() { LoginUser loginUser = ContextUtils.getLoginUser(); // Representative is desktop mode if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { return; } if (loginUser.getAdmin()) { return; } throw new PermissionDeniedBusinessException(); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/pom.xml ================================================ ai.chat2db chat2db-server-domain ${revision} ../pom.xml 4.0.0 chat2db-server-domain-repository com.baomidou mybatis-plus ai.chat2db chat2db-server-tools-common com.h2database h2 com.zaxxer HikariCP org.flywaydb flyway-core org.flywaydb flyway-mysql ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/Dbutils.java ================================================ package ai.chat2db.server.domain.repository; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.logging.slf4j.Slf4jImpl; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.flywaydb.core.Flyway; import javax.sql.DataSource; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; @Slf4j public class Dbutils { private static final ThreadLocal SQL_SESSION_THREAD_LOCAL = new ThreadLocal<>(); public static void init() { } public static void setSession() { SqlSession session = sqlSessionFactory.openSession(true); SQL_SESSION_THREAD_LOCAL.set(session); } public static void removeSession() { SqlSession session = SQL_SESSION_THREAD_LOCAL.get(); if (session != null) { session.close(); } SQL_SESSION_THREAD_LOCAL.remove(); } private static SqlSessionFactory sqlSessionFactory; static { try { before(); } catch (IOException e) { log.error("Dbutils error", e); } } private static void before() throws IOException { SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //This is the configuration object of mybatis-plus, which enhances the configuration of mybatis. MybatisConfiguration configuration = new MybatisConfiguration(); //This is the initial configuration, this part of the code will be added later initConfiguration(configuration); //This is the initialization connector, such as the paging plug-in of mybatis-plus configuration.addInterceptor(initInterceptor()); //Configuration log implementation configuration.setLogImpl(Slf4jImpl.class); //Scan the package where the mapper interface is located configuration.addMappers("ai.chat2db.server.domain.repository.mapper"); //Globalconfig required to build mybatis-plus GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration); //This parameter will automatically generate the basic method mapping that implements baseMapper. globalConfig.setSqlInjector(new DefaultSqlInjector()); //Set up id generator globalConfig.setIdentifierGenerator(new DefaultIdentifierGenerator()); //Set super class mapper globalConfig.setSuperMapperClass(BaseMapper.class); DataSource dataSource = initDataSource(); Environment environment = new Environment("1", new JdbcTransactionFactory(), dataSource); configuration.setEnvironment(environment); //Set data source registryMapperXml(configuration, "mapper"); //Build sqlSessionFactory sqlSessionFactory = builder.build(configuration); initFlyway(dataSource); //create session } private static void initFlyway(DataSource dataSource) { String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); // Represents that the current version has been successfully launched if (StringUtils.isNotBlank(currentVersion) && configJson != null && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { return; }else { Flyway flyway = Flyway.configure() .dataSource(dataSource) .locations("classpath:db/migration") .load(); flyway.migrate(); configJson.setLatestStartupSuccessVersion(currentVersion); ConfigUtils.setConfig(configJson); } } /** * Initial configuration * * @param configuration */ private static void initConfiguration(MybatisConfiguration configuration) { //Turn on camel case conversion configuration.setMapUnderscoreToCamelCase(true); //Configure adding data to automatically return the data primary key configuration.setUseGeneratedKeys(true); } /** * Initialize data source * * @return */ private static DataSource initDataSource() { HikariDataSource dataSource = new HikariDataSource(); String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); if ("dev".equalsIgnoreCase(environment)) { dataSource.setJdbcUrl("jdbc:h2:file:~/.chat2db/db/chat2db_dev;MODE=MYSQL"); }else if ("test".equalsIgnoreCase(environment)) { dataSource.setJdbcUrl("jdbc:h2:file:~/.chat2db/db/chat2db_test;MODE=MYSQL"); }else { dataSource.setJdbcUrl("jdbc:h2:~/.chat2db/db/chat2db;MODE=MYSQL;FILE_LOCK=NO"); } dataSource.setDriverClassName("org.h2.Driver"); dataSource.setIdleTimeout(60000); dataSource.setAutoCommit(true); dataSource.setMaximumPoolSize(500); dataSource.setMinimumIdle(1); dataSource.setMaxLifetime(60000 * 10); dataSource.setConnectionTestQuery("SELECT 1"); return dataSource; } /** * Initialize interceptor * * @return */ private static Interceptor initInterceptor() { //Create mybatis-plus plug-in object MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //Build a pagination plugin PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); paginationInnerInterceptor.setDbType(DbType.H2); paginationInnerInterceptor.setOverflow(true); paginationInnerInterceptor.setMaxLimit(2000L); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } /** * Parse mapper.xml file * * @param configuration * @param classPath * @throws IOException */ private static void registryMapperXml(MybatisConfiguration configuration, String classPath) throws IOException { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); Enumeration mapper = contextClassLoader.getResources(classPath); while (mapper.hasMoreElements()) { URL url = mapper.nextElement(); if (url.getProtocol().equals("file")) { String path = url.getPath(); File file = new File(path); File[] files = file.listFiles(); for (File f : files) { FileInputStream in = new FileInputStream(f); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in, configuration, f.getPath(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); in.close(); } } else { JarURLConnection urlConnection = (JarURLConnection) url.openConnection(); JarFile jarFile = urlConnection.getJarFile(); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); if (jarEntry.getName().endsWith("Mapper.xml")) { InputStream in = jarFile.getInputStream(jarEntry); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in, configuration, jarEntry.getName(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); in.close(); } } } } } public static T getMapper(Class clazz) { SqlSession session = SQL_SESSION_THREAD_LOCAL.get(); return session.getMapper(clazz); } // public static void main(String[] args) { // // ExecutorService e = Executors.newCachedThreadPool(); // for (int i = 0; i < 20; i++) { // e.execute(() -> { // SqlSession session = sqlSessionFactory.openSession(); // DataSourceMapper mapper = session.getMapper(DataSourceMapper.class); // DataSourceDO dataSourceDO = mapper.selectById(1); // session.close(); // System.out.println(JSON.toJSONString(dataSourceDO)); // }); // } // // } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/MapperUtils.java ================================================ package ai.chat2db.server.domain.repository; import ai.chat2db.server.domain.repository.mapper.TaskMapper; public class MapperUtils { public static TaskMapper getTaskMapper() { return Dbutils.getMapper(TaskMapper.class); } } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ChartDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Custom dashboard *

* * @author chat2db * @since 2023-09-09 */ @Getter @Setter @TableName("CHART") public class ChartDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Chart name */ private String name; /** * Chart description */ private String description; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Database type */ private String type; /** * DB name */ private String databaseName; private String schemaName; /** * DDL content */ private String ddl; /** * Whether it has been deleted, 'Y' means deleted, 'N' means not deleted */ private String deleted; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardChartRelationDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; /** *

* Custom dashboard *

* * @author ali-dbhub * @since 2023-06-09 */ @Getter @Setter @TableName("DASHBOARD_CHART_RELATION") public class DashboardChartRelationDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * report id */ private Long dashboardId; /** * chart id */ private Long chartId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Custom dashboard *

* * @author chat2db * @since 2023-09-02 */ @Getter @Setter @TableName("DASHBOARD") public class DashboardDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Dashboard name */ private String name; /** * Dashboard description */ private String description; /** * Dashboard layout information */ private String schema; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Data source authorization *

* * @author chat2db * @since 2023-08-26 */ @Getter @Setter @TableName("DATA_SOURCE_ACCESS") public class DataSourceAccessDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; /** * Data source id */ private Long dataSourceId; /** * Authorization type */ private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long accessObjectId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Data source connection table *

* * @author chat2db * @since 2023-08-26 */ @Getter @Setter @TableName("DATA_SOURCE") public class DataSourceDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Alias */ private String alias; /** * connection address */ private String url; /** * userName */ private String userName; /** * password */ private String password; /** * Database type */ private String type; /** * environment type */ private String envType; /** * user id */ private Long userId; /** * host address */ private String host; /** * port */ private String port; /** * ssh configuration information json */ private String ssh; /** * ssl configuration information json */ private String ssl; /** * sid */ private String sid; /** * driver information */ private String driver; /** * jdbc version */ private String jdbc; /** * Custom extension field json */ private String extendInfo; /** * driver_config configuration */ private String driverConfig; /** * environment id */ private Long environmentId; /** * Connection Type */ private String kind; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Data source connection table *

* * @author chat2db * @since 2023-08-26 */ @Getter @Setter @TableName("DBHUB_USER") public class DbhubUserDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * userName */ private String userName; /** * password */ private String password; /** * Nick name */ private String nickName; /** * email */ private String email; /** * role coding */ private String roleCode; /** * user status */ private String status; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Database connection environment *

* * @author chat2db * @since 2023-09-09 */ @Getter @Setter @TableName("ENVIRONMENT") public class EnvironmentDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; /** * environment name */ private String name; /** * environment abbreviation */ private String shortName; /** * color */ private String color; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/JdbcDriverDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import java.io.Serializable; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; @Getter @Setter @TableName("JDBC_DRIVER") public class JdbcDriverDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * dbType */ private String dbType; /** * jdbcDriver */ private String jdbcDriver; /** * jdbcDriverClass */ private String jdbcDriverClass; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationLogDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.time.LocalDateTime; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* My execution record table *

* * @author chat2db * @since 2023-10-14 */ @Getter @Setter @TableName("OPERATION_LOG") public class OperationLogDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * Database type */ private String type; /** * ddl content */ private String ddl; /** * user id */ private Long userId; /** * status */ private String status; /** * Number of operation lines */ private Long operationRows; /** * Length of use */ private Long useTime; /** * Extended Information */ private String extendInfo; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationSavedDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; /** *

* My save list *

* * @author ali-dbhub * @since 2023-04-22 */ @Getter @Setter @TableName("OPERATION_SAVED") public class OperationSavedDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * save name */ private String name; /** * Database type */ private String type; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * ddl content */ private String ddl; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * user id */ private Long userId; /** * schema name */ private String dbSchemaName; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/PinTableDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.time.LocalDateTime; @Getter @Setter @TableName("PIN_TABLE") public class PinTableDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * save name */ private String schemaName; /** * userId */ private Long userId; /** * tableName */ private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/SystemConfigDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import java.io.Serializable; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; /** * @author jipengfei * @version : SystemConfigDO.java */ @Getter @Setter @TableName("SYSTEM_CONFIG") public class SystemConfigDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Configuration item code */ private String code; /** * Configuration item content */ private String content; /** * Configuration summary */ private String summary; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* table cache *

* * @author chat2db * @since 2023-10-11 */ @Getter @Setter @TableName("TABLE_CACHE") public class TableCacheDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * table name */ private String tableName; /** * unique index */ @TableField(value = "`key`") private String key; /** * version */ private Long version; /** * table fields */ private String columns; /** * Custom extension field json */ private String extendInfo; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* table cache version *

* * @author chat2db * @since 2023-10-11 */ @Getter @Setter @TableName("TABLE_CACHE_VERSION") public class TableCacheVersionDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * unique index */ @TableField(value = "`key`") private String key; /** * version */ private Long version; /** * Number of tables */ private Long tableCount; /** * status */ private String status; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableVectorMappingDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import lombok.Getter; import lombok.Setter; /** *

* Milvus mapping table saves records *

* * @author chat2db * @since 2023-10-14 */ @Getter @Setter @TableName("TABLE_VECTOR_MAPPING") public class TableVectorMappingDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * api key */ private String apiKey; /** * Data source connection ID */ private Long dataSourceId; /** * Name database */ private String database; /** * schema name */ private String schema; /** * Vector saved state */ private String status; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TaskDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* TASK TABLE *

* * @author chat2db * @since 2024-01-25 */ @Getter @Setter @TableName("TASK") public class TaskDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * table_name */ private String tableName; /** * Whether it has been deleted, y means deleted, n means not deleted */ private String deleted; /** * user id */ private Long userId; /** * task type, such as: DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE, */ private String taskType; /** * task status */ private String taskStatus; /** * task progress */ private String taskProgress; /** * task name */ private String taskName; /** * download url */ private String downloadUrl; /** * task content */ private byte[] content; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.util.Date; import lombok.Getter; import lombok.Setter; /** *

* Team *

* * @author chat2db * @since 2023-08-26 */ @Getter @Setter @TableName("TEAM") public class TeamDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; /** * Team Coding */ private String code; /** * Team Name */ private String name; /** * Team Status */ private String status; /** * Team Description */ private String description; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamUserDO.java ================================================ package ai.chat2db.server.domain.repository.entity; import java.io.Serializable; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; /** *

* User team table *

* * @author chat2db * @since 2023-08-05 */ @Getter @Setter @TableName("TEAM_USER") public class TeamUserDO implements Serializable { private static final long serialVersionUID = 1L; /** * primary key */ @TableId(value = "ID", type = IdType.AUTO) private Long id; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * Creator user id */ private Long createUserId; /** * Modifier user id */ private Long modifiedUserId; /** * team id */ private Long teamId; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ChartMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.ChartDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Custom dashboard Mapper interface *

* * @author chat2db * @since 2023-09-09 */ public interface ChartMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardChartRelationMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DashboardChartRelationDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Custom dashboard Mapper interface *

* * @author ali-dbhub * @since 2023-06-09 */ public interface DashboardChartRelationMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DashboardDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Custom dashboard Mapper interface *

* * @author chat2db * @since 2023-09-02 */ public interface DashboardMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; /** * Data Source Access Mapper * * @author Jiaju Zhuang */ public interface DataSourceAccessCustomMapper extends Mapper { IPage comprehensivePageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, @Param("accessObjectType") String accessObjectType, @Param("accessObjectId") Long accessObjectId, @Param("userOrTeamSearchKey") String userOrTeamSearchKey, @Param("dataSourceSearchKey") String dataSourceSearchKey); DataSourceAccessDO checkTeamPermission( @Param("dataSourceId") Long dataSourceId, @Param("userId") Long userId); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Data source authorization Mapper interface *

* * @author chat2db * @since 2023-08-26 */ public interface DataSourceAccessMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; /** * Data Source Custom Mapper * * @author Jiaju Zhuang */ public interface DataSourceCustomMapper extends Mapper { IPage selectPageWithPermission(IPage page, @Param("admin") Boolean admin, @Param("userId") Long userId, @Param("searchKey") String searchKey, @Param("kind") String kind, @Param("orderBy") String orderBy); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Data source connection table Mapper interface *

* * @author chat2db * @since 2023-08-26 */ public interface DataSourceMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Data source connection table Mapper interface *

* * @author chat2db * @since 2023-08-26 */ public interface DbhubUserMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.EnvironmentDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Database connection environment Mapper interface *

* * @author chat2db * @since 2023-09-09 */ public interface EnvironmentMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/JdbcDriverMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * @author jipengfei * @version : SystemConfigMapper.java */ public interface JdbcDriverMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/OperationLogMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.OperationLogDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* My execution record table Mapper interface *

* * @author chat2db * @since 2023-10-14 */ public interface OperationLogMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/OperationSavedMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.OperationSavedDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* My save table Mapper interface *

* * @author ali-dbhub * @since 2022-12-28 */ public interface OperationSavedMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/PinTableMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.PinTableDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface PinTableMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/SystemConfigMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.SystemConfigDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * @author jipengfei * @version : SystemConfigMapper.java */ public interface SystemConfigMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TableCacheDO; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; import java.util.List; /** *

* table cache Mapper interface *

* * @author chat2db * @since 2023-10-11 */ public interface TableCacheMapper extends BaseMapper { void batchInsert(List list); IPage pageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, @Param("databaseName") String databaseName, @Param("schemaName") String schemaName, @Param("searchKey") String searchKey); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TableCacheVersionDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* table cache version Mapper interface *

* * @author chat2db * @since 2023-10-11 */ public interface TableCacheVersionMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableVectorMappingMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TableVectorMappingDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Milvus mapping table saves records Mapper interface *

* * @author chat2db * @since 2023-10-14 */ public interface TableVectorMappingMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TaskMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TaskDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; /** *

* TASK TABLE Mapper interface *

* * @author chat2db * @since 2024-01-25 */ public interface TaskMapper extends BaseMapper { IPage pageQuery(IPage page, @Param("userId") Long userId,@Param("deleted") String deleted); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TeamDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* Team Mapper interface *

* * @author chat2db * @since 2023-08-26 */ public interface TeamMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; /** * Team User Custom Mapper * * @author Jiaju Zhuang */ public interface TeamUserCustomMapper extends Mapper { IPage comprehensivePageQuery(IPage page, @Param("teamId") Long teamId, @Param("userId") Long userId, @Param("teamSearchKey") String teamSearchKey, @Param("userSearchKey") String userSearchKey); } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserMapper.java ================================================ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* User team table Mapper interface *

* * @author chat2db * @since 2023-08-05 */ public interface TeamUserMapper extends BaseMapper { } ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_0__初始化信息.sql ================================================ CREATE TABLE IF NOT EXISTS `data_source` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `alias` varchar(128) DEFAULT NULL COMMENT '别名', `url` varchar(1024) DEFAULT NULL COMMENT '连接地址', `user_name` varchar(128) DEFAULT NULL COMMENT '用户名', `password` varchar(256) DEFAULT NULL COMMENT '密码', `type` varchar(32) DEFAULT NULL COMMENT '数据库类型', `env_type` varchar(32) DEFAULT NULL COMMENT '环境类型', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' ; CREATE TABLE IF NOT EXISTS `operation_log` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', `type` varchar(32) NOT NULL COMMENT '数据库类型', `ddl` text DEFAULT NULL COMMENT 'ddl内容', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的执行记录表' ; CREATE TABLE IF NOT EXISTS `operation_saved` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', `name` varchar(128) DEFAULT NULL COMMENT '保存名称', `type` varchar(32) NOT NULL COMMENT '数据库类型', `status` varchar(32) NOT NULL COMMENT 'ddl语句状态:DRAFT/RELEASE', `ddl` text DEFAULT NULL COMMENT 'ddl内容', `tab_opened` text DEFAULT NULL COMMENT '是否在tab中被打开,y表示打开,n表示未打开', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的保存表' ; CREATE TABLE IF NOT EXISTS `dbhub_user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `user_name` varchar(32) NOT NULL COMMENT '用户名', `password` varchar(256) DEFAULT NULL COMMENT '密码', `nick_name` varchar(256) DEFAULT NULL COMMENT '昵称', `email` varchar(256) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' ; CREATE TABLE IF NOT EXISTS `system_config` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `code` varchar(32) NOT NULL COMMENT '配置项编码', `content` varchar(256) DEFAULT NULL COMMENT '配置项内容', `summary` varchar(256) DEFAULT NULL COMMENT '配置项说明', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置中心表' ; create UNIQUE INDEX uk_code on system_config(code) ; INSERT INTO `dbhub_user` (`user_name`,`password`,`nick_name`) VALUES ('dbhub','$2a$10$yElafjDHPoPHSaCo6cjJGuWmtXWNVz/cOOOtDg99eNfvUfalzfane','管理员'); ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_2__修改Console.sql ================================================ ALTER TABLE `operation_saved` ADD COLUMN `db_schema_name` varchar(128) NULL COMMENT 'schema名称'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_3__增加SSH.sql ================================================ ALTER TABLE `data_source` ADD COLUMN `host` varchar(128) NULL COMMENT 'host地址'; ALTER TABLE `data_source` ADD COLUMN `port` varchar(128) NULL COMMENT '端口'; ALTER TABLE `data_source` ADD COLUMN `ssh` varchar(1024) NULL COMMENT 'ssh配置信息json'; ALTER TABLE `data_source` ADD COLUMN `ssl` varchar(1024) NULL COMMENT 'ssl配置信息json'; ALTER TABLE `data_source` ADD COLUMN `sid` varchar(32) NULL COMMENT 'sid'; ALTER TABLE `data_source` ADD COLUMN `driver` varchar(128) NULL COMMENT '驱动信息'; ALTER TABLE `data_source` ADD COLUMN `jdbc` varchar(128) NULL COMMENT 'jdbc版本'; ALTER TABLE `data_source` ADD COLUMN `extend_info` varchar(4096) NULL COMMENT '自定义扩展字段json'; create INDEX idx_user_id on data_source(user_id) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_4__增加报表.sql ================================================ CREATE TABLE IF NOT EXISTS `dashboard` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `name` varchar(128) DEFAULT NULL COMMENT '报表名称', `description` varchar(128) DEFAULT NULL COMMENT '报表描述', `schema` text DEFAULT NULL COMMENT '报表布局信息', `deleted` text DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='自定义报表表' ; CREATE TABLE IF NOT EXISTS `chart` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `name` varchar(128) DEFAULT NULL COMMENT '图表名称', `description` varchar(128) DEFAULT NULL COMMENT '图表描述', `schema` text DEFAULT NULL COMMENT '图表信息', `data_source_id` bigint(20) unsigned DEFAULT NULL COMMENT '数据源连接ID', `type` varchar(32) DEFAULT NULL COMMENT '数据库类型', `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', `ddl` text DEFAULT NULL COMMENT 'ddl内容', `deleted` text DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='自定义报表表' ; CREATE TABLE IF NOT EXISTS `dashboard_chart_relation` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `dashboard_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '报表id', `chart_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '图表id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='自定义报表表' ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_5__增加置顶表.sql ================================================ CREATE TABLE IF NOT EXISTS `pin_table` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名称', `table_name` varchar(128) DEFAULT NULL COMMENT 'table_name', `deleted` text DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='PIN TABLES' ; create INDEX idx_user_id_data_source_id on pin_table(user_id,data_source_id) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_6__初始化demo信息.sql ================================================ INSERT INTO DATA_SOURCE (GMT_CREATE, GMT_MODIFIED, ALIAS, URL, USER_NAME, PASSWORD, TYPE, USER_ID, HOST, PORT, SSH,JDBC) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'DEMO@db.sqlgpt.cn', 'jdbc:mysql://db.sqlgpt.cn:3306/DEMO', 'demo', 'kok39AYoOSM=', 'MYSQL', 0, 'db.sqlgpt.cn', '3306', '{"use":false}', '8.0'); INSERT INTO DASHBOARD (ID, GMT_CREATE, GMT_MODIFIED, NAME, DESCRIPTION, SCHEMA, DELETED, USER_ID) VALUES (1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '学生成绩分析', '学生成绩分析', '[[1],[2],[3]]', 'N', 0); INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) VALUES (1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Column","xAxis":"name","yAxis":"total_score"}', 1, 'DEMO', 'SELECT s.name, sc.chinese_score, sc.math_score, sc.english_score, sc.science_score, sc.humanities_score, (sc.chinese_score + sc.math_score + sc.english_score + sc.science_score + sc.humanities_score) AS total_score FROM student s JOIN score sc ON s.id = sc.student_id', 'N', 0); INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) VALUES (2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Pie","xAxis":"grade"}', 1, 'DEMO', 'SELECT s.name, score.chinese_score, score.math_score, score.english_score, score.science_score, score.humanities_score, (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) AS total_score, CASE WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) < 630 THEN "D" WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) >= 630 AND (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) <= 735 THEN "C" WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) > 840 THEN "A" ELSE "B" END AS grade FROM score JOIN student s ON score.student_id = s.id', 'N', 0); INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) VALUES (3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Line","xAxis":"name","yAxis":"chinese_score"}', 1, 'DEMO', 'SELECT s.name, sc.chinese_score, sc.math_score, sc.english_score, sc.science_score, sc.humanities_score, (sc.chinese_score + sc.math_score + sc.english_score + sc.science_score + sc.humanities_score) AS total_score FROM student s JOIN score sc ON s.id = sc.student_id', 'N', 0); INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 1); INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 2); INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 3); ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_7__自定义驱动.sql ================================================ CREATE TABLE IF NOT EXISTS `jdbc_driver` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `db_type` varchar(32) NOT NULL COMMENT 'db类型', `jdbc_driver` varchar(512) DEFAULT NULL COMMENT 'jar包', `jdbc_driver_class` varchar(512) DEFAULT NULL COMMENT 'driver class类', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自定义驱动表' ; create INDEX idx_db_type on jdbc_driver(db_type) ; ALTER TABLE `data_source` ADD COLUMN `driver_config` varchar(1024) NULL COMMENT 'driver_config配置'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V1_0_8__操作保存类型.sql ================================================ ALTER TABLE `operation_saved` ADD COLUMN `operation_type` varchar(1024) NULL COMMENT '操作类型'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_0__支持环境、用户权限.sql ================================================ CREATE TABLE IF NOT EXISTS `environment` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `name` varchar(128) DEFAULT NOT NULL COMMENT '环境名称', `short_name` varchar(128) DEFAULT NULL COMMENT '环境缩写', `color` varchar(32) DEFAULT NULL COMMENT '颜色', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='数据库连接环境' ; INSERT INTO `environment` (`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `color`) VALUES (1, 1, 1, 'Release Environment', 'RELEASE', 'RED'); INSERT INTO `environment` (`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `color`) VALUES (2, 1, 1, 'Test Environment', 'TEST', 'GREEN'); ALTER TABLE `data_source` ADD COLUMN `environment_id` bigint(20) unsigned NOT NULL DEFAULT 2 COMMENT '环境id'; ALTER TABLE `data_source` modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; ALTER TABLE `data_source` ADD COLUMN `kind` varchar(32) NOT NULL DEFAULT 'PRIVATE' COMMENT '连接类型'; update data_source set user_id= 1; ALTER TABLE `dbhub_user` ADD COLUMN `role_code` varchar(32) DEFAULT NULL COMMENT '角色编码'; ALTER TABLE `dbhub_user` ADD `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '用户状态'; ALTER TABLE `dbhub_user` ADD `create_user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '创建人用户id'; ALTER TABLE `dbhub_user` ADD `modified_user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '修改人用户id'; update dbhub_user set role_code= 'DESKTOP',user_name='_desktop_default_user_name',password='_desktop_default_user_name',nick_name='Desktop User' where id = 1; INSERT INTO DBHUB_USER (USER_NAME, PASSWORD, NICK_NAME, EMAIL, ROLE_CODE) VALUES ('chat2db', 'chat2db', 'Administrator', null, 'ADMIN'); create UNIQUE INDEX uk_user_user_name on dbhub_user (user_name); CREATE TABLE IF NOT EXISTS `team` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `code` varchar(128) DEFAULT NOT NULL COMMENT '团队编码', `name` varchar(512) DEFAULT NULL COMMENT '团队名称', `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '团队状态', `description` text DEFAULT NULL COMMENT '团队描述', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='团队' ; create UNIQUE INDEX uk_team_code on team (code); CREATE TABLE IF NOT EXISTS `team_user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `team_id` bigint(20) unsigned NOT NULL COMMENT '团队id', `user_id` bigint(20) unsigned NOT NULL COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='用户团队表' ; create INDEX idx_team_user_team_id on team_user (`team_id`); create INDEX idx_team_user_user_id on team_user (`user_id`); create UNIQUE INDEX uk_team_user on team_user (`team_id`,`user_id`); CREATE TABLE IF NOT EXISTS `data_source_access` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源id', `access_object_type` varchar(32) NOT NULL COMMENT '授权类型', `access_object_id` bigint(20) unsigned NOT NULL COMMENT '授权id,根据类型区分是用户还是团队', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='数据源授权' ; create INDEX idx_data_source_access_data_source_id on data_source_access (`data_source_id`); create INDEX idx_data_source_access_access_object_id on data_source_access (`access_object_type`, `access_object_id`); create UNIQUE INDEX uk_data_source_access on data_source_access (`data_source_id`,`access_object_type`, `access_object_id`); ALTER TABLE `operation_saved` modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update operation_saved set user_id= 1; ALTER TABLE `operation_log` modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update operation_log set user_id= 1; ALTER TABLE `dashboard` modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update dashboard set user_id= 1; ALTER TABLE `chart` modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update chart set user_id= 1; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_10__REMOVEdEMO.sql ================================================ delete from DATA_SOURCE where ALIAS ='DEMO@db.sqlgpt.cn'; delete from DASHBOARD where id =ID; delete from CHART where id<=3; delete from DASHBOARD_CHART_RELATION where CHART_ID<=3; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_1__TableCache.sql ================================================ CREATE TABLE IF NOT EXISTS `table_cache_version` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', `database_name` varchar(256) DEFAULT NULL COMMENT 'db名称', `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', `version` bigint(20) unsigned NOT NULL COMMENT '版本', `table_count` bigint(20) unsigned NOT NULL COMMENT '表数量', `status` varchar(256) DEFAULT NULL COMMENT '状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache version' ; create INDEX idx_table_cache_version_data_source_id on table_cache_version(`data_source_id`) ; create UNIQUE INDEX uk_table_cache_version_key on table_cache_version(`key`) ; CREATE TABLE IF NOT EXISTS `table_cache` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', `database_name` varchar(256) DEFAULT NULL COMMENT 'db名称', `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', `table_name` varchar(256) DEFAULT NULL COMMENT 'table名称', `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', `version` bigint(20) unsigned NOT NULL COMMENT '版本', `columns` varchar(2048) DEFAULT NULL COMMENT '表字段', `extend_info` varchar(2048) NULL COMMENT '自定义扩展字段json', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache' ; create INDEX idx_table_cache_data_source_id on table_cache(`data_source_id`) ; create INDEX idx_table_cache_key_version on table_cache(`key`,`version`) ; create INDEX idx_table_cache_key_table_name on table_cache(`key`,`table_name`) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_2__OPERATION.sql ================================================ ALTER TABLE `operation_log` ADD COLUMN `status` varchar(20) DEFAULT 'success' COMMENT '状态'; ALTER TABLE `operation_log` ADD COLUMN `operation_rows` bigint(20) unsigned COMMENT '操作行数'; ALTER TABLE `operation_log` ADD COLUMN `use_time` bigint(20) unsigned COMMENT '使用时长'; ALTER TABLE `operation_log` ADD COLUMN `extend_info` varchar(1024) COMMENT '扩展信息'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_4__OPERATION.sql ================================================ ALTER TABLE `operation_log` ADD COLUMN `schema_name` varchar(256) COMMENT 'schema名称'; create INDEX idx_op_data_source_id on operation_log(data_source_id) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_5__TableVector.sql ================================================ CREATE TABLE IF NOT EXISTS `table_vector_mapping` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `api_key` varchar(128) DEFAULT NULL COMMENT 'api key', `data_source_id` bigint(20) unsigned DEFAULT NULL COMMENT '数据源连接ID', `database` text DEFAULT NULL COMMENT '数据库名称', `schema` text DEFAULT NULL COMMENT 'schema名称', `status` varchar(4) DEFAULT NULL COMMENT '向量保存状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='milvus映射表保存记录' ; create INDEX idx_api_key on table_vector_mapping(api_key) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_6__TableVectorUpdate.sql ================================================ ALTER TABLE table_vector_mapping ALTER COLUMN status VARCHAR(64); ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_7__DATASOURCE.sql ================================================ ALTER TABLE `data_source` ADD COLUMN `service_name` varchar(128) NULL COMMENT '服务名'; ALTER TABLE `data_source` ADD COLUMN `service_type` varchar(128) NULL COMMENT '服务类型'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_8__Chart.sql ================================================ ALTER TABLE `chart` ADD COLUMN `schema_name` varchar(128) NULL COMMENT 'schemaName'; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/migration/V2_1_9__task.sql ================================================ CREATE TABLE IF NOT EXISTS `task` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `data_source_id` bigint(20) unsigned NULL COMMENT '数据源连接ID', `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名称', `table_name` varchar(128) DEFAULT NULL COMMENT 'table_name', `deleted` varchar(10) DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', `task_type` varchar(128) DEFAULT NULL COMMENT 'task type, such as: DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE,', `task_status` varchar(128) DEFAULT NULL COMMENT 'task status', `task_progress` varchar(128) DEFAULT NULL COMMENT 'task progress', `task_name` varchar(128) DEFAULT NULL COMMENT 'task name', `content` blob DEFAULT NULL COMMENT 'task content', `download_url` varchar(512) DEFAULT NULL COMMENT 'down load url', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='TASK TABLE' ; create INDEX idx_task_user_id on task(user_id) ; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/db/temp/V2_1_0__补充.sql ================================================ # ALTER TABLE `operation_saved` # modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; # # update operation_saved # set user_id= 1; # # ALTER TABLE `dashboard` # modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; # update dashboard # set user_id= 1; # # # ALTER TABLE `chart` # modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; # update chart # set user_id= 1; ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/ChartMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DashboardChartRelationMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DashboardMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DbhubUserMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/EnvironmentMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/JdbcDriverMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/OperationLogMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/OperationSavedMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/PinTableMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/SystemConfigMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml ================================================ insert into TABLE_CACHE (data_source_id,database_name,schema_name,table_name,`key`,version,columns,extend_info) values (#{item.dataSourceId},#{item.databaseName},#{item.schemaName},#{item.tableName},#{item.key},#{item.version},#{item.columns},#{item.extendInfo}) ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheVersionMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableVectorMappingMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TaskMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserMapper.xml ================================================ ================================================ FILE: chat2db-server/chat2db-server-domain/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-domain pom chat2db-server-domain-api chat2db-server-domain-core chat2db-server-domain-repository ================================================ FILE: chat2db-server/chat2db-server-start/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-start jar chat2db-server-start org.springframework.boot spring-boot-starter-web log4j-api org.apache.logging.log4j ai.chat2db chat2db-server-web-api ai.chat2db chat2db-server-domain-core org.slf4j jcl-over-slf4j org.slf4j log4j-over-slf4j ch.qos.logback logback-classic com.h2database h2 org.flywaydb flyway-core org.flywaydb flyway-mysql org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-test test org.freemarker freemarker test com.baomidou mybatis-plus-generator test com.dtflys.forest forest-spring com.dtflys.forest forest-core org.zalando logbook-spring-boot-starter chat2db-server-start org.springframework.boot spring-boot-maven-plugin ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java ================================================ package ai.chat2db.server.start; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.common.util.ConfigUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Indexed; /** * Startup class * * @author Jiaju Zhuang */ @SpringBootApplication @ComponentScan(value = {"ai.chat2db.server"}) @Indexed @EnableCaching @EnableScheduling @EnableAsync @Slf4j public class Application { public static void main(String[] args) { ConfigUtils.initProcess(); new Thread(() -> { Dbutils.init(); }).start(); SpringApplication.run(Application.class, args); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java ================================================ //package ai.chat2db.server.start.config.config; // //import ai.chat2db.server.tools.common.model.ConfigJson; //import ai.chat2db.server.tools.common.util.ConfigUtils; //import lombok.extern.slf4j.Slf4j; //import org.apache.commons.lang3.StringUtils; //import org.springframework.context.ApplicationListener; //import org.springframework.context.event.ContextRefreshedEvent; //import org.springframework.scheduling.annotation.Async; //import org.springframework.stereotype.Component; // ///** // * Execute tasks after startup is completed // * // * @author Jiaju Zhuang // */ //@Component //@Slf4j //public class AsyncContextRefreshedListener implements ApplicationListener { // @Override // @Async // public void onApplicationEvent(ContextRefreshedEvent event) { // // Successfully set up startup // String currentVersion = ConfigUtils.getLocalVersion(); // ConfigJson configJson = ConfigUtils.getConfig(); // if (StringUtils.isNotBlank(currentVersion) && !StringUtils.equals(currentVersion, // configJson.getLatestStartupSuccessVersion())) { // configJson.setLatestStartupSuccessVersion(currentVersion); // ConfigUtils.setConfig(configJson); // } // } //} ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbForestConfiguration.java ================================================ package ai.chat2db.server.start.config.config; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import com.dtflys.forest.Forest; import jakarta.annotation.Resource; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Configuration; /** * forest config * * @author Jiaju Zhuang */ @Configuration public class Chat2dbForestConfiguration implements InitializingBean { @Resource private Chat2dbProperties chat2dbProperties; @Override public void afterPropertiesSet() throws Exception { Forest.config() .setVariableValue("gatewayBaseUrl", chat2dbProperties.getGateway().getBaseUrl()); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java ================================================ package ai.chat2db.server.start.config.config; import java.io.IOException; import java.util.Enumeration; import ai.chat2db.server.domain.repository.Dbutils; import com.alibaba.fastjson2.JSON; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.cache.CacheKey; import ai.chat2db.server.domain.core.cache.MemoryCacheManage; import ai.chat2db.server.tools.base.constant.SymbolConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.tools.common.enums.ModeEnum; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.exception.RedirectBusinessException; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.I18nUtils; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * web project configuration * * @author Shi Yi */ @Configuration @Slf4j public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer { /** * api prefix */ private static final String API_PREFIX = "/api/"; /** * Globally released url */ private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**", "/api/system", "/login"}; @Resource private UserService userService; @Override public void addInterceptors(InterceptorRegistry registry) { // All requests try to add user information registry.addInterceptor(new AsyncHandlerInterceptor() { @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { Dbutils.setSession(); Long userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); Long finalUserId = userId; LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { User user = userService.query(finalUserId).getData(); if (user == null) { return null; } boolean iaAdmin = RoleCodeEnum.ADMIN.getCode().equals(user.getRoleCode()); return LoginUser.builder() .id(user.getId()) .nickName(user.getNickName()) .admin(iaAdmin) .roleCode(user.getRoleCode()) .build(); }); if (loginUser == null) { // Indicates that the user may have been deleted return true; } loginUser.setToken(userId.toString()); ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Remove login information ContextUtils.removeContext(); Dbutils.removeSession(); } }) .order(1) .addPathPatterns("/**") .excludePathPatterns(FRONT_PERMIT_ALL); // Verify login information } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/JarDownloadTask.java ================================================ package ai.chat2db.server.start.config.config; import java.util.ArrayList; import java.util.List; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.JdbcJarUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; /** * @author jipengfei * @version : JarDownloadTask.java */ @Component @Slf4j public class JarDownloadTask implements CommandLineRunner { @Override public void run(String... args) throws Exception { List urls = new ArrayList<>(); Chat2DBContext.PLUGIN_MAP.forEach((k, v) -> { v.getDBConfig().getDriverConfigList().forEach(driverConfig -> { if (driverConfig != null && !CollectionUtils.isEmpty(driverConfig.getDownloadJdbcDriverUrls()) && ( "MYSQL".equals(driverConfig.getDbType()))) { urls.addAll(driverConfig.getDownloadJdbcDriverUrls()); } }); }); JdbcJarUtils.asyncDownload(urls); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/WebLogConfiguration.java ================================================ package ai.chat2db.server.start.config.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.zalando.logbook.BodyFilter; /** * log config * * @author Jiaju Zhuang */ @Configuration public class WebLogConfiguration { @Bean public BodyFilter bodyFilter() { return BodyFilter.none(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/i18n/I18nConfig.java ================================================ package ai.chat2db.server.start.config.i18n; import java.util.Locale; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.i18n.CookieLocaleResolver; /** * Internationalized configuration * * @author Jiaju Zhuang */ @Configuration public class I18nConfig { @Bean public CookieLocaleResolver localeResolver() { CookieLocaleResolver resolver = new CookieLocaleResolver("CHAT2DB.LOCALE"); resolver.setDefaultLocale(Locale.US); return resolver; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/interceptor/CorsFilter.java ================================================ package ai.chat2db.server.start.config.interceptor; import java.io.IOException; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; /** * Cors cross-domain interceptor, allowing cross-domain under any circumstances * * There will be problems when logging in across domains through the CorsRegistry policy, but it does not occur locally. The possible reason is: the loading order of the beans. * Temporarily solved through CorsFilter, you can study it later: CorsRegistry * * @author Shi Yi */ @Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse)res; HttpServletRequest request = (HttpServletRequest)req; response.setHeader("Access-Control-Allow-Origin", request.getHeader(HttpHeaders.ORIGIN)); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, DBHUB, uid"); chain.doFilter(req, res); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/DbhubTomcatConnectorCustomizer.java ================================================ //package ai.chat2db.server.start.config.listener; // //import ai.chat2db.server.web.api.controller.system.util.SystemUtils; //import lombok.extern.slf4j.Slf4j; //import org.apache.catalina.LifecycleState; //import org.apache.catalina.connector.Connector; //import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; //import org.springframework.stereotype.Component; // ///** // * Custom tomcat parameters // * // * @author Jiaju Zhuang // */ //@Component //@Slf4j //public class DbhubTomcatConnectorCustomizer implements TomcatConnectorCustomizer { // @Override // public void customize(Connector connector) { // connector.addLifecycleListener(event -> { // // Exit the system directly after receiving the shutdown event, because sometimes the system will not exit. // if (LifecycleState.STOPPING == event.getLifecycle().getState()) { // SystemUtils.stop(); // } // }); // } //} ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/FailedEventApplicationListener.java ================================================ package ai.chat2db.server.start.config.listener; import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.context.ApplicationListener; /** * Listener that failed to start application * The application failed to start. It just stopped tomcat and did not stop the application. Stop xia here. * * @author Jiaju Zhuang */ @Slf4j public class FailedEventApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationFailedEvent event) { log.error("Failed to start, stop application", event.getException()); SystemUtils.stop(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java ================================================ //package ai.chat2db.server.start.config.listener; // //import com.alibaba.fastjson2.JSON; //import com.alibaba.fastjson2.TypeReference; // //import ai.chat2db.server.tools.base.enums.SystemEnvironmentEnum; //import ai.chat2db.server.tools.base.wrapper.result.DataResult; //import cn.hutool.http.HttpUtil; //import lombok.extern.slf4j.Slf4j; //import org.apache.commons.lang3.BooleanUtils; //import org.apache.commons.lang3.StringUtils; //import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; //import org.springframework.context.ApplicationListener; //import org.springframework.util.Assert; // ///** // * Used to manage startup // * Prevent starting multiple // * // * @author zhuangjiaju // * @date 2023/05/08 // */ //@Slf4j //public class ManageApplicationListener implements ApplicationListener { // // @Override // public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { // Integer serverPort = event.getEnvironment().getProperty("server.port", Integer.class); // Assert.notNull(serverPort, "server.port configuration information"); // log.info("The startup port is: {}", serverPort); // String environment = event.getEnvironment().getProperty("spring.profiles.active", String.class); // // // Try to access to confirm whether the application has been started // DataResult dataResult; // try { // String body = HttpUtil.get("http://127.0.0.1:" + serverPort + "/api/system/get-version-a", 10); // dataResult = JSON.parseObject(body, new TypeReference<>() {}); // } catch (Exception e) { // // Throwing an exception means that there is no old startup or the old one is unreliable. // log.info("Attempts to access old applications failed. This exception is not important. It will be output during normal startup, so please ignore it." + e.getMessage()); // // // Try killing the old process // killOldIfNecessary(environment); // return; // } // // if (dataResult == null || BooleanUtils.isNotTrue(dataResult.getSuccess())) { // // Try killing the old process // killOldIfNecessary(environment); // return; // } // // // Indicates that the old process is available // log.info("There is already a started application on the current interface, and this application is no longer started."); // System.exit(0); // } // // private void killOldIfNecessary(String environment) { // try { // ProcessHandle.allProcesses().forEach(process -> { // String command = process.info().command().orElse(null); // // Not a java application // boolean isJava = StringUtils.endsWithIgnoreCase(command, "java") || StringUtils.endsWithIgnoreCase( // command, // "java.exe"); // if (!isJava) { // return; // } // String[] arguments = process.info().arguments().orElse(null); // // no parameters // if (arguments == null) { // return; // } // // Is it dbhub? // boolean isDbhub = false; // String environmentArgument = null; // for (String argument : arguments) { // if (StringUtils.equals("chat2db-server-start.jar", argument)) { // isDbhub = true; // } // if (StringUtils.startsWith(argument, "-Dspring.profiles.active=")) { // environmentArgument = StringUtils.substringAfter(argument, "-Dspring.profiles.active="); // } // } // // Not dbhub // if (!isDbhub) { // return; // } // // Determine whether it is a formal environment // if (StringUtils.equals(SystemEnvironmentEnum.RELEASE.getCode(), environment) && StringUtils.equals( // SystemEnvironmentEnum.RELEASE.getCode(), environmentArgument)) { // log.info("The formal environment requires closing the process"); // destroyProcess(process, command, arguments); // return; // } // // // Determine whether it is a test environment // if (StringUtils.equals(SystemEnvironmentEnum.TEST.getCode(), environment) && StringUtils.equals( // SystemEnvironmentEnum.TEST.getCode(), environmentArgument)) { // log.info("The test environment needs to shut down the process"); // destroyProcess(process, command, arguments); // return; // } // // // Determine whether it is a local environment // boolean devDestroy = StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environment) && ( // environmentArgument == null // || StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environmentArgument)); // if (devDestroy) { // log.info("The local environment needs to close the process"); // destroyProcess(process, command, arguments); // } // }); // } catch (Throwable t) { // log.warn("Attempts to close redundant processes failed and did not affect normal startup.", t); // } // // } // // private void destroyProcess(ProcessHandle process, String command, String[] arguments) { // log.info("Checked that there are processes that need to be shut down:{},{}", JSON.toJSONString(command), JSON.toJSONString(arguments)); // try { // process.destroy(); // } catch (Exception e) { // log.error("Failed to end process", e); // } // } //} ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/manage/ManageMessage.java ================================================ package ai.chat2db.server.start.config.listener.manage; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serial; import java.io.Serializable; /** * Administrative messages * * @author Jiaju Zhuang */ @Data @SuperBuilder @AllArgsConstructor @NoArgsConstructor public class ManageMessage implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Message type * * @see MessageTypeEnum */ private MessageTypeEnum messageTypeEnum; } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/manage/MessageTypeEnum.java ================================================ package ai.chat2db.server.start.config.listener.manage; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Message type enum * * @author Jiaju Zhuang */ @Getter public enum MessageTypeEnum implements BaseEnum { /** * Check if it works properly */ HEARTBEAT, ; @Override public String getCode() { return this.name(); } @Override public String getDescription() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/mybatis/MyBatisPlusConfig.java ================================================ package ai.chat2db.server.start.config.mybatis; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author moji * @version MyBatisPlusConfig.java, v 0.1 September 29, 2022 17:38 moji Exp $ * @date 2022/09/29 */ @Configuration public class MyBatisPlusConfig { /** * myBatisPlus paging plug-in */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mybatisPlusInterceptor; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java ================================================ package ai.chat2db.server.start.controller.oauth; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.start.controller.oauth.request.LoginRequest; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import cn.hutool.crypto.digest.DigestUtil; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Objects; /** * Login authorization service * * @author Jiaju Zhuang */ @RestController @RequestMapping("/api/oauth") @Slf4j public class OauthController { @Resource private UserService userService; /** * Login with username and password * * @param request * @return */ @PostMapping("login_a") public DataResult login(@Validated @RequestBody LoginRequest request) { // Query user return DataResult.of(null); } private boolean validateAdmin(final @NotNull User user) { return RoleCodeEnum.ADMIN.getDefaultUserId().equals(user.getId()) && RoleCodeEnum.ADMIN.getPassword().equals( user.getPassword()); } private void validateUser(final User user) { if (Objects.isNull(user)) { throw new BusinessException("oauth.userNameNotExits"); } if (!ValidStatusEnum.VALID.getCode().equals(user.getStatus())) { throw new BusinessException("oauth.invalidUserName"); } if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(user.getId())) { throw new BusinessException("oauth.IllegalUserName"); } } /** * user * * @return */ @GetMapping("user") public DataResult user() { return DataResult.of(getLoginUser()); } /** * user * * @return */ @GetMapping("user_a") public DataResult usera() { return DataResult.of(getLoginUser()); } private LoginUser getLoginUser() { return ContextUtils.queryLoginUser(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/request/LoginRequest.java ================================================ package ai.chat2db.server.start.controller.oauth.request; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Log in * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class LoginRequest { /** * userName */ @NotNull(message = "用户名不能为空") private String userName; /** * password */ @NotNull(message = "密码不能为空") private String password; } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java ================================================ package ai.chat2db.server.start.controller.thymeleaf; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Template engine configuration * * @author Jiaju Zhuang */ @Controller @Slf4j @Order(Integer.MIN_VALUE) public class ThymeleafController { /** * Front-end template settings * * @return */ @GetMapping(value = {"/", "/web/", "/web/**","/login","/workspace","/dashboard","/connections","/team"}) public String index() { return "index"; } @RequestMapping(value = "/chat.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") public String chat(){ return "chat"; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/EasyControllerExceptionHandler.java ================================================ package ai.chat2db.server.start.exception; import java.util.Map; import com.alibaba.fastjson2.JSON; import ai.chat2db.server.start.exception.convertor.BindExceptionConvertor; import ai.chat2db.server.start.exception.convertor.BusinessExceptionConvertor; import ai.chat2db.server.start.exception.convertor.DefaultExceptionConvertor; import ai.chat2db.server.start.exception.convertor.ExceptionConvertor; import ai.chat2db.server.start.exception.convertor.MaxUploadSizeExceededExceptionConvertor; import ai.chat2db.server.start.exception.convertor.MethodArgumentNotValidExceptionConvertor; import ai.chat2db.server.start.exception.convertor.MethodArgumentTypeMismatchExceptionConvertor; import ai.chat2db.server.start.exception.convertor.ParamExceptionConvertor; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.excption.SystemException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.exception.NeedLoggedInBusinessException; import ai.chat2db.server.tools.common.exception.RedirectBusinessException; import com.google.common.collect.Maps; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.ModelAndView; /** * Intercepting Controller exceptions * * @author Shi Yi */ @ControllerAdvice @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) public class EasyControllerExceptionHandler { /** * All exception handling converters */ public static final Map, ExceptionConvertor> EXCEPTION_CONVERTOR_MAP = Maps.newHashMap(); static { EXCEPTION_CONVERTOR_MAP.put(MethodArgumentNotValidException.class, new MethodArgumentNotValidExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(BindException.class, new BindExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(BusinessException.class, new BusinessExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(NeedLoggedInBusinessException.class, new BusinessExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MissingServletRequestParameterException.class, new ParamExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(IllegalArgumentException.class, new ParamExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MethodArgumentTypeMismatchException.class, new MethodArgumentTypeMismatchExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MaxUploadSizeExceededException.class, new MaxUploadSizeExceededExceptionConvertor()); } /** * Default converter */ public static ExceptionConvertor DEFAULT_EXCEPTION_CONVERTOR = new DefaultExceptionConvertor(); /** * Business abnormality * * @param request request * @param exception exception * @return return */ @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, IllegalArgumentException.class, MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class, BusinessException.class, MaxUploadSizeExceededException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MultipartException.class, MissingRequestHeaderException.class, HttpMediaTypeNotSupportedException.class, NeedLoggedInBusinessException.class}) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handleBusinessException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.info("Business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } /** * Business abnormality * * @param request request * @param exception exception * @return return */ @ExceptionHandler({RedirectBusinessException.class}) public ModelAndView handleModelAndViewBizException(HttpServletRequest request, Exception exception) { ModelAndView result = translateModelAndView(exception); log.info("ModelAndView business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } public ModelAndView translateModelAndView(Throwable exception) { // Parameter exception if (exception instanceof RedirectBusinessException) { RedirectBusinessException e = (RedirectBusinessException)exception; return dealResponseModelAndView(null, e.getMessage(), e.getRedirect(), null, null); } // Jump to homepage by default return new ModelAndView("redirect:/"); } private ModelAndView dealResponseModelAndView(String title, String errorMessage, String redirect, String href, String buttonText) { // If there is redirection information, jump if (StringUtils.isNotBlank(redirect)) { return new ModelAndView("redirect:" + redirect); } // Jump to homepage by default return new ModelAndView("redirect:/"); // synchronous request //return ModelAndViewUtils.error(title, errorMessage,href,buttonText); } /** * System exception * * @param request request * @param exception exception * @return return */ @ExceptionHandler({SystemException.class}) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handleSystemException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.error("Business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } /** * Unknown exception requires manual intervention to view logs * * @param request request * @param exception exception * @return return */ @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handledException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.error("An unknown exception occurred {}:{}, request parameters:{}", request.getRequestURI(), result, JSON.toJSONString(request.getParameterMap()), exception); return result; } public ActionResult convert(Throwable exception) { ExceptionConvertor exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(exception.getClass()); if (exceptionConvertor == null) { if (exception instanceof BusinessException) { exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(BusinessException.class); } else if (exception instanceof SystemException) { exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(SystemException.class); } else { exceptionConvertor = DEFAULT_EXCEPTION_CONVERTOR; } } return exceptionConvertor.convert(exception); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BindExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.validation.BindException; /** * BindException * * @author Shi Yi */ public class BindExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(BindException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BusinessExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; /** * BusinessException * * @author Shi Yi */ public class BusinessExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(BusinessException exception) { return ActionResult.fail(exception.getCode(), I18nUtils.getMessage(exception.getCode(), exception.getArgs()), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/DefaultExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; /** * Default exception handling * Throw system exception directly * * @author Shi Yi */ public class DefaultExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(Throwable exception) { return ActionResult.fail("common.systemError", I18nUtils.getMessage("common.systemError"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; /** * exception converter * * @author Shi Yi */ public interface ExceptionConvertor { /** * Conversion exception * * @param exception * @return */ ActionResult convert(T exception); } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertorUtils.java ================================================ package ai.chat2db.server.start.exception.convertor; import java.util.List; import ai.chat2db.server.tools.base.constant.SymbolConstant; import ai.chat2db.server.tools.common.util.I18nUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; /** * Conversion tool class * * @author Shi Yi */ public class ExceptionConvertorUtils { /** * Extract error message from BindingResult * * @param result * @return */ public static String buildMessage(BindingResult result) { List errors = result.getAllErrors(); if (CollectionUtils.isEmpty(errors)) { return null; } int index = 1; StringBuilder msg = new StringBuilder(); msg.append(I18nUtils.getMessage("common.paramCheckError")); for (ObjectError e : errors) { msg.append(index++); // got error message msg.append(SymbolConstant.DOT); if (e instanceof FieldError fieldError) { msg.append(fieldError.getField()); msg.append(" : "); } msg.append(e.getDefaultMessage()); msg.append(SymbolConstant.SEMICOLON); } return msg.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.multipart.MaxUploadSizeExceededException; /** * MaxUploadSizeExceededException * * @author Shi Yi */ public class MaxUploadSizeExceededExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MaxUploadSizeExceededException exception) { return ActionResult.fail("common.maxUploadSize", I18nUtils.getMessage("common.maxUploadSize"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.bind.MethodArgumentNotValidException; /** * MethodArgumentNotValidException * * @author Shi Yi */ public class MethodArgumentNotValidExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MethodArgumentNotValidException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; /** * MethodArgumentTypeMismatchException * * @author Shi Yi */ public class MethodArgumentTypeMismatchExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MethodArgumentTypeMismatchException exception) { return ActionResult.fail("common.paramError", I18nUtils.getMessage("common.paramError"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ParamExceptionConvertor.java ================================================ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; /** * Parameter exceptions currently include: * ConstraintViolationException * MissingServletRequestParameterException * IllegalArgumentException * * @author Shi Yi */ public class ParamExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(Throwable exception) { return ActionResult.fail("common.paramError", exception.getMessage(), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java ================================================ package ai.chat2db.server.start.log; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.ZoneId; import ai.chat2db.server.tools.common.util.LogUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.thymeleaf.util.ContentTypeUtils; import org.zalando.logbook.Correlation; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Precorrelation; import org.zalando.logbook.Sink; /** * log * * @author Jiaju Zhuang */ @Slf4j @Component public class EasyLogSink implements Sink { @Override public void write(final Precorrelation precorrelation, final HttpRequest request) { } @Override public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response) { try { printLog(correlation, request, response); } catch (Exception e) { log.error("Log exceptions", e); } } public void printLog(final Correlation correlation, final HttpRequest request, final HttpResponse response) throws IOException { // Encapsulate log object WebLog webLog = new WebLog(); String method = request.getMethod(); // Path String path = request.getPath(); webLog.setMethod(method); webLog.setPath(LogUtils.cutLog(path)); webLog.setQuery(LogUtils.cutLog(request.getQuery())); webLog.setDuration(correlation.getDuration().toMillis()); webLog.setStartTime(LocalDateTime.ofInstant(correlation.getStart(), ZoneId.systemDefault())); webLog.setEndTime(LocalDateTime.ofInstant(correlation.getEnd(), ZoneId.systemDefault())); try { webLog.setRequest(LogUtils.maskString(LogUtils.cutLog(new String(request.getBody(), StandardCharsets.UTF_8)))); if (ContentTypeUtils.isContentTypeJSON(response.getContentType()) || ContentTypeUtils.isContentTypeHTML( response.getContentType())) { webLog.setResponse(LogUtils.maskString(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8)))); } else { webLog.setResponse(response.getContentType() + ":[" + response.getBody().length + "]"); } } catch (IOException e) { log.warn("The request to obtain the log & returns an exception. Most likely, the user has closed the stream.", e); } webLog.setIp(LogUtils.getClientIp(request)); String pathAndQuery = path; if (StringUtils.isNotBlank(webLog.getQuery())) { pathAndQuery += "?" + webLog.getQuery(); } log.info("http : {}|{}|{}|{}|{}", webLog.getMethod(), pathAndQuery, webLog.getDuration(), webLog.getRequest(), webLog.getResponse()); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/LogOncePerRequestFilter.java ================================================ package ai.chat2db.server.start.log; import java.io.IOException; import ai.chat2db.server.tools.common.util.LogUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; /** * Intercept the log and put in the trace id * * @author Jiaju Zhuang */ @Order(Ordered.HIGHEST_PRECEDENCE) @Component @Slf4j public class LogOncePerRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { MDC.put(LogUtils.TRACE_ID, LogUtils.generateTraceId()); filterChain.doFilter(request, response); } finally { MDC.remove(LogUtils.TRACE_ID); } } } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/WebLog.java ================================================ package ai.chat2db.server.start.log; import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * log object * * @author Shi Yi */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class WebLog { /** * call method */ private String method; /** * Path */ private String path; /** * Query conditions */ private String query; /** * Time consuming (ms) */ private Long duration; /** * Time consuming (ms) */ private LocalDateTime startTime; /** * Time consuming (ms) */ private LocalDateTime endTime; /** * request */ private String request; /** * response */ private String response; /** * IP address */ private String ip; } ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/META-INF/spring.factories ================================================ ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/application-dev.yml ================================================ # port server: port: 10821 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway management: endpoints: web: exposure: include: startup ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/application-release.yml ================================================ #spring: # datasource: # # Configure the relative path of the built-in database # url: jdbc:h2:~/.chat2db/db/chat2db;MODE=MYSQL;FILE_LOCK=NO # driver-class-name: org.h2.Driver # port server: port: 10824 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/application-test.yml ================================================ # port server: port: 10822 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/application.yml ================================================ spring: # Default development environment profiles: active: dev main: allow-bean-definition-overriding: true lazy-initialization: true messages: basename: i18n/messages encoding: UTF-8 fallbackToSystemLocale: true jmx: enabled: false # thymeleaf thymeleaf: prefix: classpath:/thymeleaf/ check-template-location: true suffix: .html servlet: content-type: text/html mode: HTML5 # static files mvc: static-path-pattern: /static/** web: resources: static-locations[0]: classpath:/static/ # Used for database table structure version management servlet: multipart: max-file-size: -1 max-request-size: -1 jackson: serialization: write-dates-as-timestamps: true chat2db: version: 1.0.0 # flywaydb outputs the log of executing sql logging: ai: chat2db: server: domain: repository: mapper: debug chatgpt: apiKey: sk-xxxx apiHost: https://api.openai.com/ # You can choose GPT3 or GPT35 version: GPT35 context: length: 1 # Print the HTTP log logbook: include: - /api/** #server: # undertow: # io-threads: 8 # worker-threads: 200 # buffer-size: 1024 # direct-buffers: true # max-http-post-size: 0 ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties ================================================ # common common.businessError=Please try resubmitting or refreshing the page later common.systemError=An exception occurs, you can view the exception details in the log in the help menu. common.needLoggedIn=Login required common.redirect=Redirect common.paramError=The parameter is incorrect common.paramDetailError=The parameter: {0} is incorrect common.paramCheckError=The following parameters are not valid: common.maxUploadSize=The file exceeds the maximum limit common.permissionDenied=Permission denied common.dataNotFound=Data not found common.dataAlreadyExists=The data already exists in the database common.dataAlreadyExistsWithParam=The data already exists in the database,{0}:{1} oauth.userNameNotExits=The current account does not exist oauth.IllegalUserName=The current account cannot be logged in. Please change your account oauth.passwordIncorrect=The password you entered is incorrect oauth.invalidUserName=The current account is invalid # dataSource dataSource.sqlAnalysisError=Invalid statements # connection connection.error=Connection failed, please check the connection information connection.ssh.error=SSH connection failed, please check the connection information connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties ================================================ common.businessError=Please try resubmitting or refreshing the page later common.systemError=An exception occurs, you can view the exception details in the log in the help menu. common.needLoggedIn=Login required common.redirect=Redirect common.paramError=The parameter is incorrect common.paramDetailError=The parameter: {0} is incorrect common.paramCheckError=The following parameters are not valid common.maxUploadSize=The file exceeds the maximum limit common.permissionDenied=Permission denied common.dataNotFound=Data not found common.dataAlreadyExists=The data already exists in the database common.dataAlreadyExistsWithParam=The data already exists in the database,{0}:{1} oauth.userNameNotExits=The current account does not exist oauth.IllegalUserName=The current account cannot be logged in. Please change your account oauth.passwordError=The password you entered is incorrect oauth.invalidUserName=The current account is invalid dataSource.sqlAnalysisError=Invalid statements connection.error=Connection failed, please check the connection information connection.ssh.error=SSH connection failed, please check the connection information connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" main.indexName=Name main.indexFieldName=Column Name main.indexType=Index Type main.indexMethod=Index Method main.indexNote=Index Comment main.fieldNo=No main.fieldName=Field Name main.fieldType=Column Type main.fieldLength=Length main.fieldIfEmpty=Nullable main.fieldDefault=Column Default main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment main.databaseText=Database: main.sheetName=Table Structure ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties ================================================ common.businessError=请尝试重新提交或者刷新页面 common.systemError=系统发生异常,可在帮助中点击日志查看异常详情。 common.needLoggedIn=需要登陆页面 common.redirect=重定向页面 common.paramError=您输入的参数异常 common.paramDetailError=您输入的参数:{0},存在异常 common.paramCheckError=请检查以下参数: common.maxUploadSize=您输入的文件超过最大限制 common.permissionDenied=您没有权限访问该页面 common.dataNotFound=您访问的数据不存在 common.dataAlreadyExists=数据库总已经存在该数据 common.dataAlreadyExistsWithParam=数据库总已经存在该数据,{0}:{1} oauth.userNameNotExits=当前账号不存在 oauth.IllegalUserName=当前账号无法登录,请换一个账号 oauth.passwordIncorrect=您输入的密码有误 oauth.invalidUserName=您输入的账号已经失效 dataSource.sqlAnalysisError=不合法的执行语句 connection.error=数据库链接异常,请检查数据库配置 connection.ssh.error=SSH 链接异常,请检查SSH配置 connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 # sqlResult sqlResult.rowNumber=行号 sqlResult.success=执行成功 user.canNotOperateSystemAccount=不能操作系统账号 execute.exportCsv=更多数据请点击导出csv settings.permissionDeniedForAiConfig=请联系管理员在 “设置->自定义AI” 里面设置ApiKey main.indexName=名称 main.indexFieldName=字段 main.indexType=索引类型 main.indexMethod=索引方法 main.indexNote=注释 main.fieldNo=序号 main.fieldName=字段名 main.fieldType=数据类型 main.fieldLength=长度 main.fieldIfEmpty=不是null main.fieldDefault=默认值 main.fieldDecimalPlaces=小数位 main.fieldNote=备注 main.databaseText=数据库: main.sheetName=表结构 ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/logback-spring.xml ================================================ ${LOG_FILE} ${EASY_FILE_LOG_PATTERN} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 1GB 10GB ${EASY_CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/template.html ================================================ Chat2DB ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java ================================================ package ai.chat2db.server.start.test; import ai.chat2db.server.start.Application; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.stereotype.Indexed; /** * Startup of the local environment. * Mainly to read some local configurations. For example, log output is different from other environments. * * @author Shi Yi */ @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j @Indexed public class TestApplication { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/common/BaseTest.java ================================================ package ai.chat2db.server.start.test.common; import ai.chat2db.server.start.Application; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.test.context.SpringBootTest; /** * Basic test class * * @author Jiaju Zhuang **/ @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j public abstract class BaseTest { } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/ChartServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartListQueryParam; import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.Arrays; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; public class ChartServiceTest extends TestApplication { @Autowired private ChartService chartService; @Test public void testCreateWithPermission() { userLoginIdentity(false, 4L); // userLoginIdentity(true, 3L); ChartCreateParam createParam = new ChartCreateParam(); Optional.of(createParam).ifPresent(param -> { param.setName("chat2db"); param.setSchema("test"); param.setDataSourceId(1L); param.setType("MYSQL"); param.setDatabaseName("chat2db"); param.setSchemaName("ali_dbhub"); param.setDdl("test"); }); DataResult withPermission = chartService.createWithPermission(createParam); assertNotNull(withPermission); Long id = withPermission.getData(); chartService.find(id); } @Test public void testUpdateWithPermission() { userLoginIdentity(false, 1L); // userLoginIdentity(true, 4L); ChartUpdateParam chartUpdateParam = new ChartUpdateParam(); Optional.of(chartUpdateParam).ifPresent(param -> { param.setId(1L); param.setName("chat2db"); param.setSchema("test"); param.setDataSourceId(1L); param.setType("DM"); param.setDatabaseName("chat2db"); param.setSchemaName("ali_dbhub"); param.setDdl("test"); }); ActionResult actionResult = chartService.updateWithPermission(chartUpdateParam); assertNotNull(actionResult); } @Test public void testFind() { userLoginIdentity(false, 6L); // userLoginIdentity(true, 8L); DataResult result = chartService.find(2L); assertNotNull(result.getData()); } @Test public void testQueryExistent() { userLoginIdentity(false, 7L); // userLoginIdentity(true, 9L); ChartQueryParam chartQueryParam = new ChartQueryParam(); chartQueryParam.setId(1L); chartQueryParam.setUserId(1L); DataResult chartDataResult = chartService.queryExistent(chartQueryParam); DataResult queryExistent = chartService.queryExistent(chartDataResult.getData().getId()); assertNotNull(chartDataResult); assertNotNull(queryExistent); assertEquals(chartDataResult, queryExistent); } @Test public void testListQuery() { userLoginIdentity(false, 8L); // userLoginIdentity(true, 10L); ChartListQueryParam param = new ChartListQueryParam(); param.setIdList(Arrays.asList(4L, 5L, 6L)); param.setUserId(1L); ListResult listQuery = chartService.listQuery(param); assertNotNull(listQuery); } @Test public void testQueryByIds() { userLoginIdentity(false, 9L); // userLoginIdentity(true, 11L); ListResult chartListResult = chartService.queryByIds(Arrays.asList(1L, 2L, 3L)); assertNotNull(chartListResult.getData()); } @Test public void testDeleteWithPermission() { userLoginIdentity(false, 10L); // userLoginIdentity(true, 12L); ActionResult actionResult = chartService.deleteWithPermission(3L); assertNotNull(actionResult); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/ConfigServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.security.SecureRandom; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertNotNull; public class ConfigServiceTest extends TestApplication { @Autowired private ConfigService configService; @Test public void testCreate() { userLoginIdentity(true, 1L); // userLoginIdentity(false, 2L); SystemConfigParam systemConfigParam = new SystemConfigParam(); Optional.ofNullable(systemConfigParam).ifPresent(param -> { param.setCode(RandomCodeGenerator.generateRandomCode(6)); param.setContent(RandomCodeGenerator.generateRandomCode(6)); param.setSummary(RandomCodeGenerator.generateRandomCode(6)); }); ActionResult actionResult = configService.create(systemConfigParam); assertNotNull(actionResult); } @Test public void testUpdate() { userLoginIdentity(true, 4L); // userLoginIdentity(false, 5L); SystemConfigParam systemConfigParam = new SystemConfigParam(); systemConfigParam.setCode(RandomCodeGenerator.generateRandomCode(6)); systemConfigParam.setContent(RandomCodeGenerator.generateRandomCode(6)); systemConfigParam.setSummary(RandomCodeGenerator.generateRandomCode(6)); ActionResult update = configService.update(systemConfigParam); assertNotNull(update); } @Test public void testCreateOrUpdate() { userLoginIdentity(true, 3L); // userLoginIdentity(false, 6L); SystemConfigParam systemConfigParam = new SystemConfigParam(); systemConfigParam.setCode(RandomCodeGenerator.generateRandomCode(6)); systemConfigParam.setContent(RandomCodeGenerator.generateRandomCode(6)); systemConfigParam.setSummary(RandomCodeGenerator.generateRandomCode(6)); ActionResult orUpdate = configService.createOrUpdate(systemConfigParam); assertNotNull(orUpdate); } @Test public void testFind() { userLoginIdentity(true, 9L); // userLoginIdentity(false, 4L); DataResult configDataResult = configService.find("4TxfzW"); assertNotNull(configDataResult.getData()); } @Test public void testDelete() { userLoginIdentity(true, 11L); // userLoginIdentity(false, 12L); ActionResult result = configService.delete("4TxfzW"); assertNotNull(result); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } public class RandomCodeGenerator { private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private static final SecureRandom RANDOM = new SecureRandom(); public static String generateRandomCode(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { sb.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); } return sb.toString(); } } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/ConsoleServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.sql.Chat2DBContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; import static org.junit.jupiter.api.Assertions.assertNotNull; public class ConsoleServiceTest extends TestApplication { @Autowired private ConsoleService consoleService; @Autowired private List dialectPropertiesList; @Test public void testCreateAndCloseConsole() { // MYSQL ORACLE POSTGRESQL for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); // creat ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); ActionResult console = consoleService.createConsole(consoleCreateParam); assertNotNull(console); // close ConsoleCloseParam consoleCloseParam = new ConsoleCloseParam(); consoleCloseParam.setDataSourceId(dataSourceId); consoleCloseParam.setConsoleId(consoleId); consoleService.closeConsole(consoleCloseParam); Chat2DBContext.removeContext(); } } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DashboardServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.enums.YesOrNoEnum; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; public class DashboardServiceTest extends TestApplication { @Autowired private DashboardService dashboardService; @Test public void testCreateWithPermission() { userLoginIdentity(false, 9L); // userLoginIdentity(true, 11L); DashboardCreateParam dashboardCreateParam = new DashboardCreateParam(); dashboardCreateParam.setName("chat2db"); dashboardCreateParam.setSchema("test"); dashboardCreateParam.setDescription("This is a test!"); dashboardCreateParam.setDeleted(YesOrNoEnum.NO.getLetter()); dashboardCreateParam.setUserId(5L); dashboardCreateParam.setChartIds(new ArrayList()); DataResult withPermission = dashboardService.createWithPermission(dashboardCreateParam); assertNotNull(withPermission); } @Test public void testUpdateWithPermission() { // Note: Only administrators can edit this. userLoginIdentity(true, 9L); DashboardUpdateParam dashboardUpdateParam = new DashboardUpdateParam(); dashboardUpdateParam.setId(1L); dashboardUpdateParam.setName("chat2db"); dashboardUpdateParam.setSchema("test"); dashboardUpdateParam.setDescription("This is a test!"); dashboardUpdateParam.setDeleted(YesOrNoEnum.NO.getLetter()); dashboardUpdateParam.setUserId(5L); dashboardUpdateParam.setChartIds(new ArrayList()); ActionResult actionResult = dashboardService.updateWithPermission(dashboardUpdateParam); assertNotNull(actionResult); } @Test public void testFind() { userLoginIdentity(false, 4L); // userLoginIdentity(true, 2L); DataResult find = dashboardService.find(2L); assertNotNull(find.getData()); } @Test public void testQueryExistent() { userLoginIdentity(false, 8L); DashboardQueryParam param = new DashboardQueryParam(); param.setId(5L); param.setUserId(9L); DataResult existent = dashboardService.queryExistent(param); DataResult dashboardDataResult = dashboardService.queryExistent(5L); assertNotNull(existent.getData()); assertNotNull(dashboardDataResult.getData()); assertEquals(existent, dashboardDataResult); } @Test public void testDeleteWithPermission() { userLoginIdentity(false, 7L); // userLoginIdentity(true, 4L); DataResult dashboardDataResult = dashboardService.find(4L); if (dashboardDataResult.getData() != null) { ActionResult actionResult = dashboardService.deleteWithPermission(dashboardDataResult.getData().getId()); assertNotNull(actionResult); } } @Test public void testQueryPage() { userLoginIdentity(false, 12L); DashboardPageQueryParam param = new DashboardPageQueryParam(); param.setUserId(5L); param.setSearchKey("chat"); PageResult queryPage = dashboardService.queryPage(param); assertNotNull(queryPage.getData()); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DataSourceAccessBusinessServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static org.junit.jupiter.api.Assertions.assertNotNull; public class DataSourceAccessBusinessServiceTest extends TestApplication { @Autowired private DataSourceAccessBusinessService dataSourceAccessBusinessService; /** * 1. First, determine whether it is a private data source (PRIVATE) based on the type of the data source. * If it is a private data source, determine whether the currently logged-in user is the owner of the data source. * If so, allow the operation, otherwise throw a permission exception. *

* 2. If the currently logged-in user is an administrator userLoginIdentity(true, **), the operation is allowed. * If the currently logged-in user is a common user, determine whether the user has permission to access the data source. * If so, the operation is allowed. *

* 3. If the team to which the currently logged-in user belongs has permission to access the data source, the operation is allowed. *

* 4. If none of the above conditions are met, a permission exception is thrown. */ @Test public void testCheckPermission() { // userLoginIdentity(false, 3L); userLoginIdentity(true, 2L); DataSource source = new DataSource(); // source.setKind("PRIVATE"); source.setKind("SHARED"); source.setUserId(5L); source.setId(3L); ActionResult actionResult = dataSourceAccessBusinessService.checkPermission(source); assertNotNull(actionResult); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DataSourceAccessServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static org.junit.jupiter.api.Assertions.assertNotNull; public class DataSourceAccessServiceTest extends TestApplication { @Autowired private DataSourceAccessService dataSourceAccessService; @Test public void testPageQuery() { // userLoginIdentity(true,5L); userLoginIdentity(false, 2L); DataSourceAccessPageQueryParam queryParam = new DataSourceAccessPageQueryParam(); queryParam.setDataSourceId(TestUtils.nextLong()); // queryParam.setAccessObjectType("TEAM"); queryParam.setAccessObjectType("USER"); queryParam.setAccessObjectId(TestUtils.nextLong()); queryParam.setPageNo(3); queryParam.setPageSize(5); // Returns false by default queryParam.setEnableReturnCount(true); DataSourceAccessSelector accessSelector = new DataSourceAccessSelector(); accessSelector.setAccessObject(true); accessSelector.setDataSource(true); accessSelector.setDataSourceSelector(new DataSourceSelector(true)); PageResult result = dataSourceAccessService.pageQuery(queryParam, accessSelector); assertNotNull(result); } @Test public void testComprehensivePageQuery() { userLoginIdentity(false, 2L); // userLoginIdentity(true,5L); DataSourceAccessComprehensivePageQueryParam param = new DataSourceAccessComprehensivePageQueryParam(); param.setPageNo(1); param.setPageSize(10); param.setEnableReturnCount(true); param.setDataSourceId(TestUtils.nextLong()); param.setAccessObjectType("USER"); // param.setAccessObjectType("TEAM"); param.setAccessObjectId(TestUtils.nextLong()); param.setUserOrTeamSearchKey("test"); param.setDataSourceSearchKey("m"); DataSourceAccessSelector selector = new DataSourceAccessSelector(); selector.setAccessObject(true); selector.setDataSource(true); selector.setDataSourceSelector(new DataSourceSelector(true)); PageResult result = dataSourceAccessService.comprehensivePageQuery(param, selector); assertNotNull(result); } @Test public void testCreateAndDelete() { userLoginIdentity(false, 8L); // userLoginIdentity(true,6L); DataSourceAccessCreatParam creatParam = new DataSourceAccessCreatParam(); creatParam.setDataSourceId(TestUtils.nextLong()); creatParam.setAccessObjectId(TestUtils.nextLong()); creatParam.setAccessObjectType("USER"); // creatParam.setAccessObjectType("TEAM"); DataResult result = dataSourceAccessService.create(creatParam); assertNotNull(result); ActionResult delete = dataSourceAccessService.delete(result.getData()); assertNotNull(delete); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DataSourceServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.*; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertNotNull; public class DataSourceServiceTest extends TestApplication { @Autowired private DataSourceService dataSourceService; @Autowired private List dialectPropertiesList; @Test public void testCreateWithPermission() { // userLoginIdentity(true, 1L); userLoginIdentity(false, 2L); DataSourceCreateParam createParam = new DataSourceCreateParam(); createParam.setKind("PRIVATE"); // createParam.setKind("SHARED"); createParam.setDriverConfig(new DriverConfig()); DataResult withPermission = dataSourceService.createWithPermission(createParam); assertNotNull(withPermission.getData()); } @Test public void testUpdateWithPermission() { // userLoginIdentity(true, 7L); userLoginIdentity(false, 2L); DataSourceUpdateParam updateParam = new DataSourceUpdateParam(); updateParam.setId(4L); updateParam.setDriverConfig(new DriverConfig()); updateParam.setPassword("123456"); DataResult result = dataSourceService.updateWithPermission(updateParam); ActionResult delete = dataSourceService.deleteWithPermission(4L); assertNotNull(result.getData()); assertNotNull(delete); } @Test public void testQueryById() { userLoginIdentity(false, 2L); // userLoginIdentity(true, 7L); DataResult result = dataSourceService.queryById(3L); ListResult dataSourceListResult = dataSourceService.listQuery(new ArrayList<>(), null); assertNotNull(result.getData()); assertNotNull(dataSourceListResult.getData()); } @Test public void testQueryExistent() { userLoginIdentity(false, 2L); // userLoginIdentity(true, 7L); DataSourceSelector selector = new DataSourceSelector(); selector.setEnvironment(true); // selector.setEnvironment(false); DataResult result = dataSourceService.queryExistent(3L, null); assertNotNull( result.getData(),"Data should not be null"); } @Test public void testCopyByIdWithPermission() { userLoginIdentity(false, 2L); // userLoginIdentity(true, 7L); DataResult longDataResult = dataSourceService.copyByIdWithPermission(3L); assertNotNull(longDataResult.getData()); } @Test public void testQueryPage() { userLoginIdentity(false,6L); // userLoginIdentity(true,9L); DataSourcePageQueryParam queryParam = new DataSourcePageQueryParam(); queryParam.setSearchKey("test"); queryParam.setPageNo(1); queryParam.setPageSize(10); DataSourceSelector selector = new DataSourceSelector(); selector.setEnvironment(true); // selector.setEnvironment(false); PageResult result = dataSourceService.queryPage(queryParam, selector); assertNotNull(result.getData()); } @Test public void testQueryPageWithPermission() { // userLoginIdentity(false,3L); userLoginIdentity(true,9L); DataSourcePageQueryParam queryParam = new DataSourcePageQueryParam(); queryParam.setSearchKey("test"); queryParam.setKind("PRIVATE"); // queryParam.setKind("SHARED"); queryParam.setPageNo(1); queryParam.setPageSize(10); queryParam.setOrderByList(new ArrayList()); DataSourceSelector selector = new DataSourceSelector(); selector.setEnvironment(true); PageResult result = dataSourceService.queryPageWithPermission(queryParam, selector); assertNotNull(result.getData()); } @Test public void testPreConnect() { for (DialectProperties dialectProperties : dialectPropertiesList) { DataSourcePreConnectParam param = new DataSourcePreConnectParam(); param.setType(dialectProperties.getDbType()); param.setUser(dialectProperties.getUsername()); param.setUrl(dialectProperties.getUrl()); param.setPassword(dialectProperties.getPassword()); param.setPort(String.valueOf(dialectProperties.getPort())); param.setHost("localhost"); param.setSsh(new SSHInfo()); param.setSsl(new SSLInfo()); param.setExtendInfo(new ArrayList()); ActionResult result = dataSourceService.preConnect(param); assertNotNull(result); Long consoleId= TestUtils.nextLong(); Long dataSourceId= TestUtils.nextLong(); TestUtils.buildContext(dialectProperties,dataSourceId,consoleId); ListResult connect = dataSourceService.connect(dataSourceId); assertNotNull(connect.getData()); dataSourceService.close(dataSourceId); } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DatabaseServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.SchemaOperationParam; import ai.chat2db.server.domain.api.param.SchemaQueryParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Sql; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.sql.SQLException; import java.util.List; import static org.junit.jupiter.api.Assertions.assertNotNull; public class DatabaseServiceTest extends TestApplication { @Autowired private DatabaseService databaseService; @Autowired private List dialectPropertiesList; @Test public void testQueryAll() { // MYSQL ORACLE POSTGRESQL MONGODB MARIADB for (DialectProperties dialectProperties : dialectPropertiesList) { String dbType = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DatabaseQueryAllParam queryAllParam = new DatabaseQueryAllParam(); queryAllParam.setDbType(dbType); queryAllParam.setDataSourceId(dataSourceId); ListResult result = databaseService.queryAll(queryAllParam); assertNotNull(result.getData()); } } @Test public void testQuerySchema() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); SchemaQueryParam schemaQueryParam = new SchemaQueryParam(); schemaQueryParam.setDataSourceId(dataSourceId); schemaQueryParam.setDataBaseName(dialectProperties.getDatabaseName()); ListResult schemaListResult = databaseService.querySchema(schemaQueryParam); assertNotNull(schemaListResult.getData()); } } @Test public void testQueryDatabaseSchema() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); MetaDataQueryParam param = new MetaDataQueryParam(); param.setDataSourceId(dataSourceId); param.setRefresh(true); DataResult metaSchemaDataResult = databaseService.queryDatabaseSchema(param); assertNotNull(metaSchemaDataResult.getData()); } } @Test public void testDeleteDatabase() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DatabaseCreateParam param = new DatabaseCreateParam(); param.setName("test"); ActionResult actionResult = databaseService.deleteDatabase(param); assertNotNull(actionResult); } } @Test public void testCreateDatabase() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); Database database = new Database(); DataResult database1 = databaseService.createDatabase(database); assertNotNull(database1); } } @Test public void testModifyDatabase() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DatabaseCreateParam databaseCreateParam = new DatabaseCreateParam(); databaseCreateParam.setName("test" + TestUtils.nextLong()); ActionResult actionResult = databaseService.modifyDatabase(databaseCreateParam); assertNotNull(actionResult); } } @Test public void testDeleteSchema() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); SchemaOperationParam operationParam = new SchemaOperationParam(); operationParam.setDatabaseName(dialectProperties.getDatabaseName()); operationParam.setSchemaName("test" + TestUtils.nextLong()); ActionResult actionResult = databaseService.deleteSchema(operationParam); assertNotNull(actionResult); } } @Test public void testCreateSchema() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); Schema schema = new Schema(); DataResult result = databaseService.createSchema(schema); assertNotNull(result); } } @Test public void testModifySchema() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); SchemaOperationParam schemaOperationParam = new SchemaOperationParam(); schemaOperationParam.setDatabaseName(dialectProperties.getDatabaseName()); schemaOperationParam.setSchemaName("test" + TestUtils.nextLong()); schemaOperationParam.setNewSchemaName("test" + TestUtils.nextLong()); ActionResult actionResult = databaseService.modifySchema(schemaOperationParam); assertNotNull(actionResult); } } // TODO:回头专门测试 @Test public void testExportDatabase() { for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DatabaseExportParam exportParam = new DatabaseExportParam(); exportParam.setDatabaseName(dialectProperties.getDatabaseName()); exportParam.setContainData(true); exportParam.setSchemaName("test" + TestUtils.nextLong()); try { String exportDatabase = databaseService.exportDatabase(exportParam); assertNotNull(exportDatabase); } catch (SQLException e) { e.printStackTrace(); } } } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/DlTemplateServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.model.Header; import ai.chat2db.spi.model.OrderBy; import ai.chat2db.spi.model.ResultOperation; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.List; public class DlTemplateServiceTest extends TestApplication { @Autowired private DlTemplateService dlTemplateService; @Autowired private List dialectPropertiesList; // MYSQL: ali_dbhub_test -- test_data // POSTGRESQL: ali_dbhub_test -- test -- test_data // ORACLE: TEST_USER -- test_data @Test public void testExecute() { userLoginIdentity(false, 6L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = 11L; Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); String testData = dialectProperties.getCrateTableSql("test_data006"); DlExecuteParam dlExecuteParam = new DlExecuteParam(); dlExecuteParam.setSql(testData); dlExecuteParam.setConsoleId(consoleId); dlExecuteParam.setDataSourceId(dataSourceId); dlExecuteParam.setTableName("test_data006"); dlExecuteParam.setPageNo(1); dlExecuteParam.setPageSize(10); dlExecuteParam.setPageSizeAll(false); if (dialectProperties.getDbType().equals("POSTGRESQL")) { dlExecuteParam.setDatabaseName(dialectProperties.getDatabaseName()); dlExecuteParam.setSchemaName("public"); } else if (dialectProperties.getDbType().equals("ORACLE")) { dlExecuteParam.setDatabaseName(""); dlExecuteParam.setSchemaName("TEST_USER"); } else if (dialectProperties.getDbType().equals("MYSQL")) { dlExecuteParam.setDatabaseName(dialectProperties.getDatabaseName()); dlExecuteParam.setSchemaName(""); } else { continue; } ListResult execute = dlTemplateService.execute(dlExecuteParam); Assertions.assertTrue(execute.getSuccess(), execute.errorMessage()); } } @Test public void testExecuteSelectTable() { userLoginIdentity(false, 3L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = 20858L; Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equals("MYSQL")) { DlExecuteParam dlExecuteParam = new DlExecuteParam(); dlExecuteParam.setConsoleId(consoleId); dlExecuteParam.setDataSourceId(dataSourceId); dlExecuteParam.setTableName("test_data004"); dlExecuteParam.setPageNo(1); dlExecuteParam.setPageSize(10); dlExecuteParam.setPageSizeAll(false); ListResult execute = dlTemplateService.executeSelectTable(dlExecuteParam); Assertions.assertTrue(execute.getSuccess(), execute.errorMessage()); } } } @Test public void testExecuteUpdate() { userLoginIdentity(false, 7L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); String testData = dialectProperties.getInsertSql("test_data006", new Timestamp(new Date().getTime()), 1L, "test"); DlExecuteParam dlExecuteParam = new DlExecuteParam(); dlExecuteParam.setSql(testData); dlExecuteParam.setConsoleId(consoleId); dlExecuteParam.setDataSourceId(dataSourceId); dlExecuteParam.setTableName("test_data006"); dlExecuteParam.setPageNo(1); dlExecuteParam.setPageSize(10); dlExecuteParam.setPageSizeAll(false); if (dialectProperties.getDbType().equals("POSTGRESQL")) { dlExecuteParam.setDatabaseName(dialectProperties.getDatabaseName()); dlExecuteParam.setSchemaName("public"); } else if (dialectProperties.getDbType().equals("ORACLE")) { dlExecuteParam.setDatabaseName(""); dlExecuteParam.setSchemaName("TEST_USER"); } else if (dialectProperties.getDbType().equals("MYSQL")) { dlExecuteParam.setDatabaseName(dialectProperties.getDatabaseName()); dlExecuteParam.setSchemaName(""); } else { continue; } DataResult result = dlTemplateService.executeUpdate(dlExecuteParam); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } @Test public void testCount() { userLoginIdentity(true, 8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DlCountParam param = new DlCountParam(); DataResult count = null; if (dialectProperties.getDbType().equals("MYSQL")) { param.setSql("select * from test_data"); count = dlTemplateService.count(param); System.out.println("Mysql Total rows:" + count.getData()); Assertions.assertTrue(count.getSuccess(), count.errorMessage()); } else if (dialectProperties.getDbType().equals("ORACLE")) { param.setSql("select * from TEST_USER.DEMO"); count = dlTemplateService.count(param); System.out.println("Oracle Total rows:" + count.getData()); Assertions.assertTrue(count.getSuccess(), count.errorMessage()); }else if (dialectProperties.getDbType().equals("POSTGRESQL")) { param.setSql("select * from test.reference_table"); count = dlTemplateService.count(param); System.out.println("PG Total rows:" + count.getData()); Assertions.assertTrue(count.getSuccess(), count.errorMessage()); } else if (dialectProperties.getDbType().equals("MARIADB")) { param.setSql("select * from test.test_data"); count = dlTemplateService.count(param); System.out.println("Mariadb Total rows:" + count.getData()); Assertions.assertTrue(count.getSuccess(), count.errorMessage()); } } } @Test public void testUpdateSelectResult() { userLoginIdentity(true, 8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); UpdateSelectResultParam param = new UpdateSelectResultParam(); param.setTableName("test_data"); param.setHeaderList(new ArrayList

()); param.setOperations(new ArrayList()); DataResult result = dlTemplateService.updateSelectResult(param); System.out.println("result:" + result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } @Test public void testGetOrderBySql() { userLoginIdentity(false, 4L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equals("MYSQL")) { ArrayList orderByList = new ArrayList<>(); OrderBy orderBy1 = new OrderBy(); orderBy1.setColumnName("number"); orderBy1.setAsc(true); orderByList.add(orderBy1); OrderByParam orderByParam = new OrderByParam(); orderByParam.setOrderByList(orderByList); orderByParam.setOriginSql("select * from test_data"); DataResult result = dlTemplateService.getOrderBySql(orderByParam); System.out.println("Mysql Final Sql:" + result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/EnvironmentServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; /** * @author Juechen * @version : EnvironmentServiceTest.java */ public class EnvironmentServiceTest extends TestApplication { @Autowired private EnvironmentService environmentService; @Autowired private List dialectPropertiesList; @Test public void testListQuery() { userLoginIdentity(false, 6L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); ArrayList list = new ArrayList<>(); list.add(1L); list.add(2L); list.add(3L); ListResult query = environmentService.listQuery(list); Assertions.assertTrue(query.getSuccess(), query.getErrorMessage()); Assertions.assertFalse(query.getData().isEmpty(), "Result should not be empty for non-empty input list"); } } @Test public void testPageQuery() { userLoginIdentity(false, 3L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); EnvironmentPageQueryParam param = new EnvironmentPageQueryParam(); param.setSearchKey("release"); // param.setSearchKey("test"); param.setPageNo(1); param.setPageSize(10); PageResult query = environmentService.pageQuery(param); Assertions.assertTrue(query.getSuccess(), query.getErrorMessage()); Assertions.assertFalse(query.getData().isEmpty(), "Result should not be empty for non-empty input list"); } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/FunctionServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.service.FunctionService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.Function; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : FunctionServiceTest.java */ public class FunctionServiceTest extends TestApplication { @Autowired private FunctionService functionService; @Autowired private List dialectPropertiesList; @Test public void testFunctions() { userLoginIdentity(false, 3L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); ListResult functions = functionService.functions(dialectProperties.getDatabaseName(), null); Assertions.assertTrue(functions.getSuccess(), functions.errorMessage()); if (dialectProperties.getDbType().equals("MYSQL")) { DataResult detail = functionService.detail(dialectProperties.getDatabaseName(), null, "add_numbers"); Assertions.assertTrue(detail.getSuccess(), detail.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/JdbcDriverServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.service.JdbcDriverService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.config.DBConfig; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @author Juechen * @version : JdbcDriverServiceTest.java */ public class JdbcDriverServiceTest extends TestApplication { @Autowired private JdbcDriverService jdbcDriverService; @Test public void testGetDrivers() { userLoginIdentity(false, 2L); String dbType = "POSTGRESQL"; DataResult drivers = jdbcDriverService.getDrivers(dbType); Assertions.assertTrue(drivers.success(), drivers.errorMessage()); } @Test public void testUpload() { userLoginIdentity(false, 2L); String dbType = "MYSQL"; ActionResult result = jdbcDriverService.upload(dbType, "com.mysql.cj.jdbc.Driver", "mysql-connector-java-8.0.30.jar"); Assertions.assertTrue(result.success(), result.errorMessage()); } @Test public void testDownload() { userLoginIdentity(false, 5L); String dbType = "ORACLE"; DataResult drivers = jdbcDriverService.getDrivers(dbType); Assertions.assertTrue(drivers.success(), drivers.errorMessage()); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/OperationLogServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : OperationLogServiceTest.java */ public class OperationLogServiceTest extends TestApplication { @Autowired private OperationLogService operationLogService; @Autowired private List dialectPropertiesList; @Test public void testCreate() { userLoginIdentity(true, 1L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationLogCreateParam param = new OperationLogCreateParam(); param.setDataSourceId(dataSourceId); param.setType(dialectProperties.getDbType()); DataResult result = operationLogService.create(param); Assertions.assertTrue(result.success(), result.errorMessage()); } } @Test public void testQueryPage() { userLoginIdentity(false,14L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equals("MYSQL")) { OperationLogPageQueryParam param = new OperationLogPageQueryParam(); param.setDataSourceId(dataSourceId); param.setSearchKey("test"); param.setUserId(3L); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName(""); param.setPageNo(1); param.setPageSize(10); PageResult queryPage = operationLogService.queryPage(param); System.out.println(queryPage.getData()); Assertions.assertTrue(queryPage.success(), queryPage.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/OperationServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.OperationService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : OperationServiceTest.java */ public class OperationServiceTest extends TestApplication { @Autowired private OperationService operationService; @Autowired private List dialectPropertiesList; @Test public void testCreateWithPermission() { userLoginIdentity(true, 7L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationSavedParam param = new OperationSavedParam(); param.setDataSourceId(dataSourceId); param.setType(dialectProperties.getDbType()); // param.setStatus("DRAFT"); param.setStatus("RELEASE"); DataResult result = operationService.createWithPermission(param); System.out.println(dialectProperties.getDbType() + "---" + result.getData()); Assertions.assertTrue(result.success(), result.getErrorMessage()); } } @Test public void testUpdateWithPermission() { userLoginIdentity(true, 3L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationUpdateParam param = new OperationUpdateParam(); param.setId(9L); ActionResult result = operationService.updateWithPermission(param); Assertions.assertTrue(result.success(), result.getErrorMessage()); } } @Test public void testFind() { userLoginIdentity(true, 6L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DataResult result = operationService.find(18L); Assertions.assertTrue(result.success(), result.getErrorMessage()); } } @Test public void testQueryExistent() { userLoginIdentity(false, 7L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationQueryParam param = new OperationQueryParam(); param.setId(11L); DataResult result = operationService.queryExistent(10L); DataResult result1 = operationService.queryExistent(param); Assertions.assertTrue(result.success(), result.getErrorMessage()); Assertions.assertTrue(result1.success(), result1.getErrorMessage()); } } @Test public void testDeleteWithPermission() { userLoginIdentity(true, 8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationSavedParam param = new OperationSavedParam(); param.setDataSourceId(10L); param.setType(dialectProperties.getDbType()); // param.setStatus("DRAFT"); param.setStatus("RELEASE"); DataResult service = operationService.createWithPermission(param); ActionResult result = operationService.deleteWithPermission(service.getData()); Assertions.assertTrue(result.success(), result.getErrorMessage()); } } @Test public void testQueryPage() { userLoginIdentity(false, 9L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); OperationPageQueryParam param = new OperationPageQueryParam(); param.setStatus("RELEASE"); param.setSearchKey("test"); param.setDataSourceId(dataSourceId); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setTabOpened("Y"); param.setPageNo(1); param.setPageSize(10); param.setOrderByDesc(true); param.setOrderByCreateDesc(true); PageResult result = operationService.queryPage(param); Assertions.assertTrue(result.success(), result.getErrorMessage()); } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/PinServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : PinServiceImplTest.java */ public class PinServiceTest extends TestApplication { @Autowired private PinService pinService; @Autowired private List dialectPropertiesList; @Test public void testPinTable() { userLoginIdentity(true, 7L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); PinTableParam param = new PinTableParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setUserId(7L); param.setDataSourceId(dataSourceId); param.setSchemaName("ali_dbhub_test"); param.setTableName("t_user"); ActionResult result = pinService.pinTable(param); Assertions.assertTrue(result.success(), result.errorMessage()); } } @Test public void testDeletePinTable() { userLoginIdentity(false,8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); PinTableParam param = new PinTableParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setUserId(91L); param.setDataSourceId(dataSourceId); param.setSchemaName("ali_dbhub_test"); param.setTableName("t_user"); ActionResult result = pinService.deletePinTable(param); Assertions.assertTrue(result.success(), result.errorMessage()); } } @Test public void testQueryPinTables() { userLoginIdentity(false,8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); PinTableParam param = new PinTableParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setUserId(18L); param.setDataSourceId(dataSourceId); param.setSchemaName("ali_dbhub_test"); param.setTableName("t_user"); ListResult result = pinService.queryPinTables(param); Assertions.assertTrue(result.success(), result.errorMessage()); } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/ProcedureServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.service.ProcedureService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.Procedure; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.sql.SQLException; import java.util.List; /** * @author Juechen * @version : ProcedureServiceTest.java */ public class ProcedureServiceTest extends TestApplication { @Autowired private ProcedureService procedureService; @Autowired private List dialectPropertiesList; @Test public void testProcedures() { userLoginIdentity(false, 2L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); String databaseName = "ali_dbhub_test"; // String databaseName = "Northwind"; // String databaseName = "618"; TestUtils.buildContext(dialectProperties, dataSourceId, consoleId, databaseName); if (dialectProperties.getDbType().equals("MYSQL")) { // No parameters are required here ListResult result = procedureService.procedures(databaseName, ""); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); for (Procedure procedure : result.getData()) { String procedureName = procedure.getProcedureName(); DataResult detail = procedureService.detail(databaseName, "", procedureName); System.out.println(detail.getData().getProcedureBody()); Assertions.assertTrue(detail.getSuccess(), detail.errorMessage()); } } else if (dialectProperties.getDbType().equals("POSTGRESQL")) { ListResult result = procedureService.procedures(databaseName, "test"); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); for (Procedure procedure : result.getData()) { String procedureName = procedure.getProcedureName(); DataResult detail = procedureService.detail(databaseName, "test", procedureName); System.out.println(detail.getData().getProcedureBody()); Assertions.assertTrue(detail.getSuccess(), detail.errorMessage()); } } else if (dialectProperties.getDbType().equals("ORACLE")) { ListResult result = procedureService.procedures("", "TEST_USER"); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); for (Procedure procedure : result.getData()) { String procedureName = procedure.getProcedureName(); DataResult detail = procedureService.detail("", "TEST_USER", procedureName); System.out.println(detail.getData().getProcedureBody()); Assertions.assertTrue(detail.getSuccess(), detail.errorMessage()); } } } } @Test public void testUpdate() throws SQLException { userLoginIdentity(false, 8L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); String databaseName = "ali_dbhub_test"; TestUtils.buildContext(dialectProperties, dataSourceId, consoleId, databaseName); if (dialectProperties.getDbType().equals("MYSQL")) { Procedure procedure = new Procedure(); procedure.setProcedureName("demo_procedure"); procedure.setProcedureBody("CREATE PROCEDURE demo_procedure(IN param1 VARCHAR(50))\n" + "BEGIN\n" + "END;"); ActionResult result = procedureService.update(databaseName, "", procedure); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (dialectProperties.getDbType().equals("ORACLE")) { Procedure procedure = new Procedure(); procedure.setProcedureName("demo_procedure"); procedure.setProcedureBody("CREATE OR REPLACE PROCEDURE demo_procedure12345 (\n" + " p_param1 NUMBER\n" + ")\n" + "IS\n" + "BEGIN\n" + "END;\n"); ActionResult result = procedureService.update("", "TEST_USER", procedure); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (dialectProperties.getDbType().equals("POSTGRESQL")) { Procedure procedure = new Procedure(); procedure.setProcedureName("demo_procedure"); procedure.setProcedureBody("CREATE OR REPLACE PROCEDURE demo_procedure(param123 VARCHAR)\n" + "LANGUAGE plpgsql\n" + "AS $$\n" + "BEGIN\n" + "END;\n" + "$$;"); ActionResult result = procedureService.update(databaseName, "test", procedure); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/TableServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.DropParam; import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.Table; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; import java.util.Objects; /** * @author Juechen * @version : TableServiceTest.java */ public class TableServiceTest extends TestApplication { @Autowired private TableService tableService; @Autowired private List dialectPropertiesList; @Test public void testShowCreateTable() { userLoginIdentity(false, 5L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (Objects.equals(dialectProperties.getDbType(), "MYSQL")) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName("chart"); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName(""); DataResult result = tableService.showCreateTable(param); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "ORACLE")) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName("DEMO"); param.setDatabaseName(""); param.setSchemaName("TEST_USER"); DataResult result = tableService.showCreateTable(param); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "MARIADB")) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName("test_data"); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName(""); DataResult result = tableService.showCreateTable(param); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } } @Test public void testDrop() { userLoginIdentity(false, 6L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); String databaseName = dialectProperties.getDatabaseName(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId, databaseName); if (Objects.equals(dialectProperties.getDbType(), "MYSQL")) { DropParam param = new DropParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchema(""); param.setName("employee_details"); ActionResult result = tableService.drop(param); Assertions.assertTrue(result.success(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "ORACLE")) { DropParam param = new DropParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchema("TEST_USER"); param.setName("TEST_USER.DEMO"); ActionResult result = tableService.drop(param); Assertions.assertTrue(result.success(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "MARIADB")) { DropParam param = new DropParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchema(""); param.setName("test_data"); ActionResult result = tableService.drop(param); Assertions.assertTrue(result.success(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "POSTGRESQL")) { DropParam param = new DropParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchema("test"); param.setName("test.categories_2"); ActionResult result = tableService.drop(param); Assertions.assertTrue(result.success(), result.errorMessage()); } } } @Test public void testQuery() { userLoginIdentity(false, 11L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (Objects.equals(dialectProperties.getDbType(), "MYSQL")) { TableQueryParam param = new TableQueryParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName(""); param.setTableName("access_control_apply_record"); TableSelector selector = new TableSelector(); selector.setColumnList(true); selector.setIndexList(false); DataResult
result = tableService.query(param, selector); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "ORACLE")) { TableQueryParam param = new TableQueryParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName("TEST_USER"); param.setTableName("DEMO_TABLE"); TableSelector selector = new TableSelector(); selector.setColumnList(true); selector.setIndexList(false); DataResult
result = tableService.query(param, selector); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } else if (Objects.equals(dialectProperties.getDbType(), "POSTGRESQL")) { TableQueryParam param = new TableQueryParam(); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName("test"); param.setTableName("dept"); TableSelector selector = new TableSelector(); selector.setColumnList(true); selector.setIndexList(false); DataResult
result = tableService.query(param, selector); System.out.println(result.getData()); Assertions.assertTrue(result.getSuccess(), result.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/TaskServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.param.TaskUpdateParam; import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : TaskServiceTest.java */ public class TaskServiceTest extends TestApplication { @Autowired private TaskService taskService; @Autowired private List dialectPropertiesList; @Test public void testCreate() { userLoginIdentity(true, 9L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equalsIgnoreCase("MYSQL")) { TaskCreateParam param = new TaskCreateParam(); param.setDataSourceId(dataSourceId); param.setDatabaseName(dialectProperties.getDatabaseName()); param.setSchemaName(""); param.setTableName("access_token"); param.setUserId(9L); // INIT -> DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE param.setTaskType("DOWNLOAD_DATA"); param.setTaskName("juechen"); DataResult result = taskService.create(param); DataResult taskDataResult = taskService.get(result.getData()); System.out.println(taskDataResult.getData()); Assertions.assertTrue(result.success(), result.errorMessage()); } } } @Test public void testPage() { userLoginIdentity(true, 12L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equalsIgnoreCase("MYSQL")) { TaskPageParam param = new TaskPageParam(); param.setPageNo(1); param.setPageSize(10); param.setUserId(9L); PageResult result = taskService.page(param); System.out.println(result.getData()); Assertions.assertTrue(result.success(), result.errorMessage()); } } } @Test public void testUpdateStatus() { userLoginIdentity(true, 5L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equalsIgnoreCase("MYSQL")) { TaskUpdateParam param = new TaskUpdateParam(); param.setId(9L); // DOWNLOAD_DATA, UPLOAD_TABLE_DATA, DOWNLOAD_TABLE_STRUCTURE, UPLOAD_TABLE_STRUCTURE param.setTaskStatus("DOWNLOAD_TABLE_STRUCTURE"); param.setContent(new byte[0]); param.setDownloadUrl("success!"); ActionResult result = taskService.updateStatus(param); DataResult taskDataResult = taskService.get(param.getId()); System.out.println(taskDataResult.getData()); Assertions.assertTrue(result.success(), result.errorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/TeamServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.core.impl.TeamServiceImpl; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; /** * @author Juechen * @version : TeamServiceTest.java */ public class TeamServiceTest extends TestApplication { @Autowired private TeamServiceImpl teamService; @Test public void testCreate() { userLoginIdentity(false,8L); TeamCreateParam param = new TeamCreateParam(); param.setCode("57935"); param.setStatus("VALID"); param.setRoleCode("87129"); param.setName("DEMO3"); param.setDescription("this is just a test!"); DataResult result = teamService.create(param); System.out.println("create team_id :" + result.getData()); Assertions.assertTrue(result.getSuccess(), result.getErrorMessage()); ArrayList list = new ArrayList<>(); list.add(result.getData()); ListResult result1 = teamService.listQuery(list); System.out.println("current team :" + result1.getData()); Assertions.assertTrue(result1.getSuccess(), result1.getErrorMessage()); TeamPageQueryParam pageQueryParam = new TeamPageQueryParam(); pageQueryParam.setPageNo(1); pageQueryParam.setPageSize(10); pageQueryParam.setSearchKey("MO"); pageQueryParam.setEnableReturnCount(true); pageQueryParam.setOrderByList(new ArrayList<>()); TeamSelector selector = new TeamSelector(); selector.setModifiedUser(false); PageResult result2 = teamService.pageQuery(pageQueryParam, selector); for (Team team : result2.getData()) { System.out.println("pageList :" + team + "/n"); } Assertions.assertTrue(result2.getSuccess(), result2.getErrorMessage()); TeamUpdateParam teamUpdateParam = new TeamUpdateParam(); teamUpdateParam.setId(result.getData()); teamUpdateParam.setStatus("INVALID"); teamUpdateParam.setDescription("already update!"); teamUpdateParam.setName("Juechen"); DataResult result3 = teamService.update(teamUpdateParam); System.out.println("update team_id :" + result3.getData()); Assertions.assertTrue(result3.getSuccess(), result3.getErrorMessage()); ArrayList list2 = new ArrayList<>(); list2.add(result.getData()); ListResult result4 = teamService.listQuery(list); System.out.println("current team :" + result4.getData()); Assertions.assertTrue(result4.getSuccess(), result4.getErrorMessage()); ActionResult delete = teamService.delete(result3.getData()); Assertions.assertTrue(delete.getSuccess(), delete.getErrorMessage()); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/TeamUserServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @author Juechen * @version : TeamUserServiceTest.java */ public class TeamUserServiceTest extends TestApplication { @Autowired private TeamUserService teamUserService; @Test public void testPageQuery() { userLoginIdentity(false,4L); TeamUserCreatParam teamUserCreatParam = new TeamUserCreatParam(); teamUserCreatParam.setTeamId(1L); teamUserCreatParam.setUserId(5L); DataResult longDataResult = teamUserService.create(teamUserCreatParam); System.out.println("create id :" + longDataResult.getData()); Assertions.assertTrue(longDataResult.getSuccess(),longDataResult.getErrorMessage()); TeamUserPageQueryParam param = new TeamUserPageQueryParam(); param.setTeamId(teamUserCreatParam.getTeamId()); param.setUserId(teamUserCreatParam.getUserId()); TeamUserSelector selector = new TeamUserSelector(); selector.setTeam(true); selector.setUser(true); PageResult pageQuery = teamUserService.pageQuery(param, selector); System.out.println("value :" + pageQuery.getData()); Assertions.assertTrue(pageQuery.getSuccess(),pageQuery.getErrorMessage()); TeamUserComprehensivePageQueryParam pageQueryParam = new TeamUserComprehensivePageQueryParam(); pageQueryParam.setPageNo(1); pageQueryParam.setPageSize(10); pageQueryParam.setUserId(teamUserCreatParam.getUserId()); pageQueryParam.setTeamId(teamUserCreatParam.getTeamId()); pageQueryParam.setTeamSearchKey("DE"); PageResult comprehensivePageQuery = teamUserService.comprehensivePageQuery(pageQueryParam, selector); System.out.println("total value :" + comprehensivePageQuery.getData()); Assertions.assertTrue(comprehensivePageQuery.getSuccess(),comprehensivePageQuery.getErrorMessage()); ActionResult actionResult = teamUserService.delete(longDataResult.getData()); Assertions.assertTrue(actionResult.getSuccess(),actionResult.getErrorMessage()); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/TriggerServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.service.TriggerService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.Trigger; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : TriggerServiceTest.java */ public class TriggerServiceTest extends TestApplication { @Autowired private TriggerService triggerService; @Autowired private List dialectPropertiesList; @Test public void testTriggers() { userLoginIdentity(false,9L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equalsIgnoreCase("mysql")) { String databaseName = "ali_dbhub_test"; ListResult triggers = triggerService.triggers(databaseName, ""); for (Trigger trigger : triggers.getData()) { DataResult detail = triggerService.detail(databaseName, "", trigger.getTriggerName()); System.out.println(detail.getData()); } Assertions.assertTrue(triggers.getSuccess(), triggers.getErrorMessage()); } else if (dialectProperties.getDbType().equalsIgnoreCase("postgresql")) { String databaseName = "ali_dbhub_test"; ListResult triggers = triggerService.triggers(databaseName, "test"); for (Trigger trigger : triggers.getData()) { DataResult detail = triggerService.detail(databaseName, "test", trigger.getTriggerName()); System.out.println(detail.getData()); } Assertions.assertTrue(triggers.getSuccess(), triggers.getErrorMessage()); } else if (dialectProperties.getDbType().equalsIgnoreCase("oracle")) { String schemaName = "TEST_USER"; ListResult triggers = triggerService.triggers("", schemaName); for (Trigger trigger : triggers.getData()) { DataResult detail = triggerService.detail("", schemaName, trigger.getTriggerName()); System.out.println(detail.getData()); } Assertions.assertTrue(triggers.getSuccess(), triggers.getErrorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/UserServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @author Juechen * @version : UserServiceTest.java */ public class UserServiceTest extends TestApplication { @Autowired private UserService userService; @Test public void testAllMethods() { userLoginIdentity(false,8L); UserCreateParam userCreateParam = new UserCreateParam(); userCreateParam.setUserName("test_username08"); userCreateParam.setEmail("123456789@gmail.com"); userCreateParam.setPassword("123456"); userCreateParam.setRoleCode("TEST"); userCreateParam.setStatus("VALID"); userCreateParam.setNickName("test_username686"); DataResult dataResult = userService.create(userCreateParam); System.out.println("create id:" + dataResult.getData()); Assertions.assertTrue(dataResult.getSuccess(),dataResult.getErrorMessage()); DataResult query = userService.query(dataResult.getData()); System.out.println("Specify id:" + query.getData()); Assertions.assertTrue(query.getSuccess(),query.getErrorMessage()); DataResult user_name = userService.query("_desktop_default_user_name"); System.out.println("Specify user_name: " + user_name.getData()); Assertions.assertTrue(user_name.getSuccess(),user_name.getErrorMessage()); UserPageQueryParam param = new UserPageQueryParam(); param.setPageNo(1); param.setPageSize(8); param.setEnableReturnCount(false); param.setSearchKey(""); UserSelector selector = new UserSelector(); selector.setModifiedUser(false); PageResult result = userService.pageQuery(param, selector); for (User user : result.getData()) { System.out.println("list:" + user); } Assertions.assertTrue(result.getSuccess(),result.getErrorMessage()); // if id is 1, an BusinessException will be thrown ActionResult actionResult = userService.delete(dataResult.getData()); Assertions.assertTrue(actionResult.getSuccess(),actionResult.getErrorMessage()); PageResult pageQuery = userService.pageQuery(param, selector); for (User user : pageQuery.getData()) { System.out.println("After deletion list:" + user); } Assertions.assertTrue(pageQuery.getSuccess(),pageQuery.getErrorMessage()); } @Test public void testUpdate() { userLoginIdentity(false,8L); UserUpdateParam userUpdateParam = new UserUpdateParam(); // If the id is 1, a "user.canNotOperateSystemAccount" exception will be thrown. // userUpdateParam.setId(1L); userUpdateParam.setId(3L); userUpdateParam.setRoleCode("TEST05"); userUpdateParam.setStatus("INVALID"); userUpdateParam.setEmail("385962@gmail.com"); userUpdateParam.setPassword("385962"); DataResult query = userService.query(userUpdateParam.getId()); System.out.println("Original data :" + query.getData()); Assertions.assertTrue(query.getSuccess(),query.getErrorMessage()); DataResult update = userService.update(userUpdateParam); System.out.println("update id :" + update.getData()); Assertions.assertTrue(update.getSuccess(),update.getErrorMessage()); DataResult result = userService.query(userUpdateParam.getId()); System.out.println("update data :" + result.getData()); Assertions.assertTrue(result.getSuccess(),result.getErrorMessage()); } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/ViewServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.service.ViewService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.start.test.TestApplication; import ai.chat2db.server.start.test.dialect.DialectProperties; import ai.chat2db.server.start.test.dialect.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.model.Table; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @author Juechen * @version : ViewServiceTest.java */ public class ViewServiceTest extends TestApplication { @Autowired private ViewService viewService; @Autowired private List dialectPropertiesList; @Test public void testViews() { userLoginIdentity(false, 9L); for (DialectProperties dialectProperties : dialectPropertiesList) { Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); if (dialectProperties.getDbType().equalsIgnoreCase("mysql")) { String databaseName = "ali_dbhub_test"; ListResult
views = viewService.views(databaseName, null); for (Table table : views.getData()) { DataResult
detail = viewService.detail(databaseName, null, table.getName()); System.out.println("mysql:" + detail.getData()); } Assertions.assertTrue(views.getSuccess(),views.getErrorMessage()); } else if (dialectProperties.getDbType().equalsIgnoreCase("postgresql")) { String databaseName = "ali_dbhub_test"; String schemaName = "test"; ListResult
views = viewService.views(databaseName, schemaName); for (Table table : views.getData()) { DataResult
detail = viewService.detail(databaseName, schemaName, table.getName()); System.out.println("postgresql:" + detail.getData()); } Assertions.assertTrue(views.getSuccess(),views.getErrorMessage()); } else if (dialectProperties.getDbType().equalsIgnoreCase("oracle")) { String schemaName = "TEST_USER"; ListResult
views = viewService.views("", schemaName); for (Table table : views.getData()) { DataResult
detail = viewService.detail("", schemaName, table.getName()); System.out.println("oracle:" + detail.getData()); } Assertions.assertTrue(views.getSuccess(),views.getErrorMessage()); } } } /** * Save the current user identity (administrator or normal user) and user ID to the context and database session for subsequent use. * * @param isAdmin * @param userId */ private static void userLoginIdentity(boolean isAdmin, Long userId) { Context context = Context.builder().loginUser( LoginUser.builder().admin(isAdmin).id(userId).build() ).build(); ContextUtils.setContext(context); Dbutils.setSession(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/core/WebhookServiceTest.java ================================================ package ai.chat2db.server.start.test.core; import ai.chat2db.server.domain.api.param.message.MessageCreateParam; import ai.chat2db.server.domain.core.notification.BaseWebhookSender; import ai.chat2db.server.start.test.TestApplication; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @author Juechen * @version : WebhookServiceTest.java */ public class WebhookServiceTest extends TestApplication { @Autowired private BaseWebhookSender baseWebhookSender; @Test public void test() { MessageCreateParam param = new MessageCreateParam(); // param.setServiceUrl("https://oapi.dingtalk.com/robot/send?access_token=3dc1c8a55a3ba966d38fb37466c93c536ac210895304e2682966252ea8f8a252"); // param.setSecretKey("SEC5058616c6ea2e5745abeb381d510579538ea5baa7cdd28a386c809289b1f1db9"); // param.setPlatformType("DingTalk"); // param.setTextTemplate("你好,钉钉!"); param.setServiceUrl("https://open.feishu.cn/open-apis/bot/v2/hook/da4c4585-b320-4a72-8fbe-920b48c4a0c9"); param.setSecretKey("tm3p2x2IBs8Lh8cBiJo1F"); param.setPlatformType("LaRK"); param.setTextTemplate("你好,飞书"); // param.setServiceUrl("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=346b7d1e-39bd-4146-89e4-bca5fe05f5b4"); // param.setSecretKey(""); // param.setPlatformType("WeCom"); // param.setTextTemplate("你好,企业微信"); baseWebhookSender.sendMessage(param); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/DialectProperties.java ================================================ package ai.chat2db.server.start.test.dialect; import java.util.Date; /** * Dialect configuration */ public interface DialectProperties { /** * Supported database types * * @return */ String getDbType(); /** * connection * * @return */ String getUrl(); /** * Abnormal connection * * @return */ String getErrorUrl(); /** * userName * * @return */ String getUsername(); /** * password * * @return */ String getPassword(); /** * Name database * * @return */ String getDatabaseName(); /** * The case depends on the specific database: * Create table structure: test table * Field: * id primary key auto-increment * date date is not empty * number long integer type * string string length 100 default value "DATA" * * Index (plus $tableName_ because some database indexes are globally unique): * $tableName_idx_date date index reverse order * $tableName_uk_number unique index * $tableName_idx_number_string joint index * * @return */ String getCrateTableSql(String tableName); /** * Create table structure * * @return */ String getDropTableSql(String tableName); /** * Create a piece of data * * @return */ String getInsertSql(String tableName, Date date, Long number, String string); /** * Query a query sql * * @return */ String getSelectSqlById(String tableName, Long id); /** * Get a sql whose table structure does not exist * * @return */ String getTableNotFoundSqlById(String tableName); /** * Convert case * Some database table structures store uppercase letters by default * Some databases store lowercase by default * * @param string * @return */ String toCase(String string); /** * port * @return */ Integer getPort(); } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/MariadbDialectProperties.java ================================================ package ai.chat2db.server.start.test.dialect; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; @Component public class MariadbDialectProperties implements DialectProperties{ @Override public String getDbType() { return "MARIADB"; } @Override public String getUrl() { return "jdbc:mariadb://localhost:3303/"; } @Override public String getErrorUrl() { return "jdbc:mariadb://error:3303/"; } @Override public String getUsername() { return "root"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return null; } @Override public String getCrateTableSql(String tableName) { return null; } @Override public String getDropTableSql(String tableName) { return null; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return null; } @Override public String getSelectSqlById(String tableName, Long id) { return null; } @Override public String getTableNotFoundSqlById(String tableName) { return null; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } @Override public Integer getPort() { return 13303; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/MongodbDialectProperties.java ================================================ package ai.chat2db.server.start.test.dialect; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; @Component public class MongodbDialectProperties implements DialectProperties{ @Override public String getDbType() { return "MONGODB"; } @Override public String getUrl() { return "mongodb://localhost:27017/"; } @Override public String getErrorUrl() { return "mongodb://error:27017/"; } @Override public String getUsername() { return "test"; } @Override public String getPassword() { return "test@123456"; } @Override public String getDatabaseName() { return null; } @Override public String getCrateTableSql(String tableName) { return null; } @Override public String getDropTableSql(String tableName) { return null; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return null; } @Override public String getSelectSqlById(String tableName, Long id) { return null; } @Override public String getTableNotFoundSqlById(String tableName) { return null; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } @Override public Integer getPort() { return 27017; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/MysqlDialectProperties.java ================================================ package ai.chat2db.server.start.test.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; /** * mysql * * @author Jiaju Zhuang */ @Component public class MysqlDialectProperties implements DialectProperties { @Override public String getDbType() { return "MYSQL"; } @Override public String getUrl() { return "jdbc:mysql://localhost:3306"; } @Override public String getErrorUrl() { return "jdbc:mysql://error.rm-8vb099vo8309mcngk.mysql.zhangbei.rds.aliyuncs.com:3306"; } @Override public String getUsername() { return "root"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "ali_dbhub_test"; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE `" + tableName + "`\n\t" + "(\n\t" + " `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n\t" + " `date` datetime(3) not null COMMENT 'date',\n\t" + " `number` bigint COMMENT 'long integer',\n\t" + " `string` VARCHAR(100) default 'DATA' COMMENT 'name',\n\t" + " index " + tableName + "_idx_date (date desc) comment 'date index',\n\t" + " unique " + tableName + "_uk_number (number) comment 'unique index',\n\t" + " index " + tableName + "_idx_number_string (number, date) comment 'Union index'\n\t" + ") COMMENT ='Test table';"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO `" + tableName + "` (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } @Override public Integer getPort() { return 3306; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/OracleDialectProperties.java ================================================ package ai.chat2db.server.start.test.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; @Component public class OracleDialectProperties implements DialectProperties { @Override public String getDbType() { return "ORACLE"; } @Override public String getUrl() { return "jdbc:oracle:thin:@localhost:1521:XE"; } @Override public String getErrorUrl() { return "jdbc:oracle:thin:@localhost:1521:XE1"; } @Override public String getUsername() { return "system"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "TEST_USER"; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE TEST_USER." + tableName + " (\n" + " id NUMBER PRIMARY KEY,\n" + " created_date DATE,\n" + " amount INT,\n" + " string VARCHAR2(100)\n" + ");"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO TEST_USER." + tableName + " (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } @Override public Integer getPort() { return 11521; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/PostgresqlDialectProperties.java ================================================ /** * Alipay.com Inc. * Copyright (c) 2004-2022 All Rights Reserved. */ package ai.chat2db.server.start.test.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; /** * @author jipengfei * @version : PgDialectProperties.java, v 0.1 December 13, 2022 21:48 jipengfei Exp $ */ @Component public class PostgresqlDialectProperties implements DialectProperties { @Override public String getDbType() { return "POSTGRESQL"; } @Override public String getUrl() { return "jdbc:postgresql://localhost:5431/ali_dbhub_test"; } @Override public String getErrorUrl() { return "jdbc:postgresql://error:5431/ali_dbhub"; } @Override public String getUsername() { return "ali_dbhub"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "ali_dbhub_test"; } @Override public String getCrateTableSql(String tableName) { String sql = "CREATE TABLE " + tableName + "\n" + "(\n" + " id serial\n" + " constraint " + tableName + "_pk primary key,\n" + " date timestamp,\n" + " number int,\n" + " string varchar(100) default 'DATA'\n" + ");\n"; return sql; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO " + tableName + " (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n" + "from " + tableName + "\n" + "where id = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } @Override public Integer getPort() { return 5432; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dialect/TestUtils.java ================================================ package ai.chat2db.server.start.test.dialect; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import java.util.concurrent.atomic.AtomicLong; /** * Test tool class */ public class TestUtils { public static final AtomicLong ATOMIC_LONG = new AtomicLong(); /** * a globally unique long * * @return */ public static long nextLong() { return ATOMIC_LONG.incrementAndGet(); } /** * If the default value is something like 'DATA' * then you need to remove '' * * @param defaultValue * @return */ public static String unWrapperDefaultValue(String defaultValue) { if (defaultValue == null) { return null; } if (defaultValue.startsWith("'") && defaultValue.endsWith("'")) { if (defaultValue.length() < 2) { return defaultValue; } else if (defaultValue.length() == 2) { return ""; } else { return defaultValue.substring(1, defaultValue.length() - 1); } } return defaultValue; } public static void buildContext(DialectProperties dialectProperties, Long dataSourceId, Long consoleId) { buildContext(dialectProperties, dataSourceId, consoleId, dialectProperties.getDatabaseName()); } public static void buildContext(DialectProperties dialectProperties, Long dataSourceId, Long consoleId, String databaseName) { ConnectInfo connectInfo = createConnectInfo(dialectProperties, dataSourceId, consoleId, databaseName); Chat2DBContext.putContext(connectInfo); } private static ConnectInfo createConnectInfo(DialectProperties dialectProperties, Long dataSourceId, Long consoleId, String databaseName) { ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setUser(dialectProperties.getUsername()); connectInfo.setPort(dialectProperties.getPort()); connectInfo.setHost("localhost"); connectInfo.setSsh(new SSHInfo()); connectInfo.setConsoleId(consoleId); connectInfo.setDataSourceId(dataSourceId); connectInfo.setPassword(dialectProperties.getPassword()); connectInfo.setDbType(dialectProperties.getDbType()); connectInfo.setUrl(dialectProperties.getUrl()); connectInfo.setDatabase(databaseName); connectInfo.setConsoleOwn(false); return connectInfo; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SerializationUtilsTest.java ================================================ package ai.chat2db.server.start.test.druid; import com.alibaba.fastjson2.JSON; import ai.chat2db.server.domain.core.cache.MemoryCacheManage; import ai.chat2db.server.start.test.dto.TestDTO; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.SerializationUtils; import org.junit.jupiter.api.Test; @Slf4j public class SerializationUtilsTest { @Test public void test() { TestDTO test = TestDTO.builder().name("test").build(); byte[] bytes = SerializationUtils.serialize(test); TestDTO t2 = SerializationUtils.deserialize(bytes); log.info("tt{}", t2); } @Test public void cache() throws InterruptedException { TestDTO test = TestDTO.builder().name("test").build(); MemoryCacheManage.put("t1", test); TestDTO t1 = MemoryCacheManage.get("t1"); log.info("t1:{}", JSON.toJSONString(t1)); Thread.sleep(12000); t1 = MemoryCacheManage.get("t1"); log.info("t1:{}", JSON.toJSONString(t1)); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest.java ================================================ package ai.chat2db.server.start.test.druid; import java.util.List; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; import com.alibaba.druid.sql.ast.SQLLimit; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; import com.alibaba.druid.sql.parser.SQLParserFeature; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j public class SqlUtilsTest { @Test public void test() { List sqlStatements = SQLUtils.parseStatements("select 1 from test;", DbType.mysql); log.info("Parse sql:{}", sqlStatements); sqlStatements = SQLUtils.parseStatements("use xxx;select 1 from test;explain select 1 from test", DbType.mysql); log.info("Parse sql:{}", sqlStatements); sqlStatements = SQLUtils.parseStatements("select 1 from1 test", DbType.mysql); log.info("Parse sql:{}", sqlStatements); } @Test public void test55() { List sqlStatements = SQLUtils.parseStatements("create table test(id int) comment 'xx';", DbType.mysql); log.info("Parse sql:{}", sqlStatements); } @Test public void test2() { String sql = "select * from test"; log.info("Pagination: {} ----- {} --- {}", PagerUtils.count(sql, DbType.mysql), PagerUtils.limit(sql, DbType.mysql, 1000, 999), PagerUtils.limit(sql, DbType.mysql, 1000, 999, true)); sql = "select * from test where id=1 limit 100;"; log.info("Pagination: {} ----- {} --- {}", PagerUtils.count(sql, DbType.mysql), PagerUtils.limit(sql, DbType.mysql, 1000, 999), PagerUtils.limit(sql, DbType.mysql, 1000, 999, true)); sql = "select * from test where id=1 limit 100,10;"; log.info("Pagination: {} ----- {} --- {}", PagerUtils.count(sql, DbType.mysql), PagerUtils.limit(sql, DbType.mysql, 1000, 999), PagerUtils.limit(sql, DbType.mysql, 1000, 999, true)); sql = "select * from test where id=1 limit 100,10;"; log.info("Pagination: {} ----- {} --- {}", PagerUtils.count(sql, DbType.mysql), PagerUtils.limit(sql, DbType.mysql, 2, 2), PagerUtils.limit(sql, DbType.mysql, 2, 2, true)); sql = "select * from test union select * from test2"; log.info("Pagination: {} ----- {} --- {}", PagerUtils.count(sql, DbType.mysql), PagerUtils.limit(sql, DbType.mysql, 2, 2), PagerUtils.limit(sql, DbType.mysql, 2, 2, true)); sql = "select * from test union select * from test2"; SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, DbType.mysql); SQLSelectStatement sqlSelectStatement = (SQLSelectStatement)sqlStatement; log.info("test{}", sqlSelectStatement); } @Test public void test56() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "create table test(id int ,name varchar(32) not null default 'xx' comment 'name',nu int auto_increment," + "index ds(id) ,primary key (id,nu)) " + "comment 'xx';", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void test4() { MySqlCreateTableStatement x = new MySqlCreateTableStatement(); x.setTableName("ff"); x.setComment(new MySqlCharExpr(null)); SQLColumnDefinition c = new SQLColumnDefinition(); x.addColumn(c); c.setName("name"); SQLDataTypeImpl sqlDataType = new SQLDataTypeImpl(); sqlDataType.setName("varchar(32)"); c.setDataType(sqlDataType); c.addConstraint(new SQLNotNullConstraint()); c.setComment(new MySqlCharExpr("xname")); //x.addColumn(); log.info(x.toString()); } @Test public void testreaname() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "rename table data_ops_table_test_1667268894825 to data_ops_table_test_166726889482511;", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void testcomment() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "\n" + "alter table data_ops_table_test_166726889482511\n" + " comment 'Test table 33';", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void dropindex() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "drop index data_ops_table_test_1667268894825_idx_date on data_ops_table_test_1667268894825;", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void createindex() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "\n" + "create index data_ops_table_test_1667268894825_idx_date\n" + " on data_ops_table_test_1667268894825 (date desc, id asc)\n" + " comment 'date index';", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void addColumn() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "alter table data_ops_table_test_1667268894825\n" + " add column_5 int default de null;", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void change() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "alter table data_ops_table_test_1667268894825\n" + " change number number1 bigint null comment 'long integer';", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void modify() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "alter table data_ops_table_test_1667268894825\n" + " modify number1 bigint null comment 'long integer';", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void dropColumn() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "alter table data_ops_table_test_1667268894825\n" + " drop column string;", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void dropPrimaryKey() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "ALTER TABLE `ali_dbhub_test`.`data_ops_table_test_1671368857363` \n" + "DROP PRIMARY KEY,\n" + "ADD PRIMARY KEY (`date`) USING BTREE;", DbType.mysql); log.info("Parse sql:{}", sqlStatement); } @Test public void coment() { try { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "comment on index DATA_OPS_TEMPLATE_TEST_1672663574919_idx_date is 'Date index xx';\n", DbType.h2, SQLParserFeature.PrintSQLWhileParsingFailed); log.info("Parse sql:{}", sqlStatement); } catch (Exception e) { log.error("error", e); } } @Test public void errro() { List sqlStatementList = SQLUtils.parseStatements( "alter table data_ops_table_test_1667268894825 drop column string;comment on index " + "DATA_OPS_TEMPLATE_TEST_1672663574919_idx_date is 'Date index xx';\n", DbType.h2, SQLParserFeature.PrintSQLWhileParsingFailed); log.info("Parse sql:{}", sqlStatementList); } @Test public void creattable() { List sqlStatementList = SQLUtils.parseStatements( "CREATE TABLE `data_ops_table_test_1673096155228`\n" + "\t(\n" + "\t `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n" + "\t `date` datetime(3) not null COMMENT 'date',\n" + "\t `number` bigint COMMENT 'long integer',\n" + "\t `string` VARCHAR(100) default 'DATA' COMMENT 'name',\n" + "\t index data_ops_table_test_1673096155228_idx_date (date desc) comment 'date index',\n" + "\t unique data_ops_table_test_1673096155228_uk_number (number) comment 'unique index',\n" + "\t index data_ops_table_test_1673096155228_idx_number_string (number, date) comment 'Union index'\n" + "\t) COMMENT ='Test table';", DbType.mysql); log.info("Parse sql:{}", sqlStatementList); } @Test public void testlimit2() { SQLLimit sqlLimit= SQLUtils.getLimit("select * from t_orderdetail limit 0,1",DbType.mysql); log.info("Parse sql:{}", sqlLimit); sqlLimit= SQLUtils.getLimit("select * from t_orderdetail",DbType.mysql); log.info("Parse sql:{}", sqlLimit); } @Test public void test57() { java.sql.Date date=new java.sql.Date(System.currentTimeMillis()); log.info("{}",date); java.sql.Timestamp ts=new java.sql.Timestamp(System.currentTimeMillis()); log.info("{}",ts); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest2.java ================================================ package ai.chat2db.server.start.test.druid; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j public class SqlUtilsTest2 { @Test public void coment() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "comment on index myindex is 'datexxx';\n", DbType.h2); log.info("Parse sql:{}", sqlStatement); } @Test public void SELECT() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement( "SELECT * FROM score a left join user b on a.id=b.id LIMIT 10", DbType.mysql); if (!(sqlStatement instanceof SQLSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } SQLSelectStatement sqlSelectStatement = (SQLSelectStatement)sqlStatement; log.info("解析sql1:{}", sqlSelectStatement); log.info("解析sql2:{}", sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom().toString()); } @Test public void select2() { log.info("tablename:{}",getTable("SELECT * FROM score LIMIT 10")); log.info("tablename:{}",getTable("SELECT * FROM score a LIMIT 10")); log.info("tablename:{}",getTable("SELECT * FROM score a left join user b on a.id=b.id LIMIT 10")); } @Test public void insert() { SQLStatement sqlStatement = SQLUtils.parseSingleStatement("INSERT INTO chat2db.`order` (id, user_id, total_price, created_at, updated_at) VALUES (8, 345, 5601.16, '2022-09-18 11:21:12', '2023-04-30 11:21:12');", DbType.mysql); log.info("Parse sql1:{}", sqlStatement); } private String getTable(String sql) { SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, DbType.mysql); if (!(sqlStatement instanceof SQLSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } SQLSelectStatement sqlSelectStatement = (SQLSelectStatement)sqlStatement; SQLExprTableSource sqlExprTableSource = (SQLExprTableSource)getSQLExprTableSource( sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); return sqlExprTableSource.getTableName(); } private SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSource) { if (sqlTableSource instanceof SQLExprTableSource sqlExprTableSource) { return sqlExprTableSource; } else if (sqlTableSource instanceof SQLJoinTableSource sqlJoinTableSource) { return getSQLExprTableSource(sqlJoinTableSource.getLeft()); } return null; } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto/TestDTO.java ================================================ package ai.chat2db.server.start.test.dto; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TestDTO implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; private String name; } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java ================================================ package ai.chat2db.server.start.test.mybatis; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import ai.chat2db.server.start.test.common.BaseTest; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.google.common.collect.Lists; import com.zaxxer.hikari.HikariDataSource; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; /** * Generate mapper of mybatis * * @author Jiaju Zhuang */ @Slf4j public class MybatisGeneratorTest extends BaseTest { @Resource private DataSource dataSource; @Test public void coreGenerator() { HikariDataSource dataSource = new HikariDataSource(); String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); if ("dev".equalsIgnoreCase(environment)) { dataSource.setJdbcUrl("jdbc:h2:file:~/.chat2db/db/chat2db_dev;MODE=MYSQL"); }else if ("test".equalsIgnoreCase(environment)) { dataSource.setJdbcUrl("jdbc:h2:file:~/.chat2db/db/chat2db_test;MODE=MYSQL"); }else { dataSource.setJdbcUrl("jdbc:h2:~/.chat2db/db/chat2db;MODE=MYSQL;FILE_LOCK=NO"); } dataSource.setDriverClassName("org.h2.Driver"); dataSource.setIdleTimeout(60000); dataSource.setAutoCommit(true); dataSource.setMaximumPoolSize(500); dataSource.setMinimumIdle(1); dataSource.setMaxLifetime(60000 * 10); dataSource.setConnectionTestQuery("SELECT 1"); this.dataSource = dataSource; //doGenerator(Lists.newArrayList("data_source")); //doGenerator(Lists.newArrayList("operation_log")); //doGenerator(Lists.newArrayList("operation_saved")); //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", // "dbhub_user")); doGenerator(Lists.newArrayList("TASK")); } private void doGenerator(List tableList) { // The current project address is the chat2db-server-start address. String outputDir = System.getProperty("user.dir") + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + "/java"; String xmlDir = System.getProperty("user.dir") + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + "/resources/mapper"; // Do not generate service controller Map pathInfo = new HashMap<>(); pathInfo.put(OutputFile.service, null); pathInfo.put(OutputFile.serviceImpl, null); pathInfo.put(OutputFile.xml, xmlDir); pathInfo.put(OutputFile.controller, null); FastAutoGenerator .create(new DataSourceConfig.Builder(dataSource) .typeConvert(new MySqlTypeConvert())) //Global configuration .globalConfig(builder -> { // Set author builder.author("chat2db") //Do not open the folder after execution .disableOpenDir() // Or use date .dateType(DateType.ONLY_DATE) // Specify output directory .outputDir(outputDir); }) //Package configuration .packageConfig(builder -> { // Set parent package name builder.parent("ai.chat2db.server.domain.repository") //Generate solid layer .entity("entity") .pathInfo(pathInfo) //Generate mapper layer .mapper("mapper"); }) //Policy configuration .strategyConfig(builder -> { // Set the table name to be generated builder.addInclude(tableList) //Enable entity class configuration .entityBuilder() .formatFileName("%sDO") .enableFileOverride() //.addTableFills(new Column("gmt_create", FieldFill.INSERT)) // Table field filling //.addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE)) // Table field filling //Turn on lombok .enableLombok() .mapperBuilder() //// overwrite file .enableFileOverride() ; }) //Template configuration .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板 //execute .execute(); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/sql/DbhubJdbcTemplateTest.java ================================================ package ai.chat2db.server.start.test.sql; import java.sql.Connection; import java.sql.Statement; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.jdbc.core.JdbcTemplate; /** * template * * @author Jiaju Zhuang */ @Slf4j public class DbhubJdbcTemplateTest { private static JdbcTemplate jdbcTemplate; @BeforeAll public static void prepare() throws Exception { log.info("connect mysql"); } @Test @Order(2) public void test() throws Exception { jdbcTemplate.execute("use data_ops_test"); Connection connection = jdbcTemplate.getDataSource().getConnection(); Statement statement = connection.createStatement(); boolean execute = statement.execute("select * from test_query"); log.info("execute:{}",execute); statement = connection.createStatement(); execute = statement.execute("INSERT INTO `test_query` (name,date,number) VALUES ('姓名','2022-01-01',123);"); log.info("execute:{}",execute); //Returns: //true if the first result is a ResultSet object; false if it is an update count or there are no results statement = connection.createStatement(); execute = statement.execute("show tables"); log.info("execute:{}",execute); } } ================================================ FILE: chat2db-server/chat2db-server-start/src/test/resources/logback-test-spring.xml ================================================ ${EASY_CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: chat2db-server/chat2db-server-test/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-test jar chat2db-server-test ai.chat2db chat2db-server-start org.springframework.boot spring-boot-starter-test test com.github.jsqlparser jsqlparser 4.6 test com.github.vertical-blank sql-formatter 2.0.4 test ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/common/BaseTest.java ================================================ package ai.chat2db.server.test.common; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.server.start.Application; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.boot.test.context.SpringBootTest; /** * Basic test class * * @author Jiaju Zhuang **/ @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j @TestMethodOrder(value = MethodOrderer.OrderAnnotation.class) public abstract class BaseTest { public void putConnect(String url, String username, String password, String dbType, String database, Long dataSourceId, Long consoleId) { ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setUser(username); connectInfo.setConsoleId(consoleId); connectInfo.setDataSourceId(dataSourceId); connectInfo.setPassword(password); connectInfo.setDbType(dbType); connectInfo.setUrl(url); connectInfo.setDatabase(database); connectInfo.setConsoleOwn(false); Chat2DBContext.putContext(connectInfo); } public void removeConnect() { Chat2DBContext.removeContext(); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConfigServiceTest.java ================================================ // //package ai.chat2db.server.test.domain.data.service; // //import ai.chat2db.server.domain.api.param.SystemConfigParam; //import ai.chat2db.server.domain.api.service.ConfigService; //import ai.chat2db.server.test.common.BaseTest; // //import lombok.extern.slf4j.Slf4j; //import org.junit.jupiter.api.Test; //import org.springframework.beans.factory.annotation.Autowired; // ///** // * @author jipengfei // * @version : ConfihServiceTest.java // */ //@Slf4j //public class ConfigServiceTest extends BaseTest { // // @Autowired // private ConfigService configService; // // @Test // public void testCreate() { // SystemConfigParam systemConfigParam = new SystemConfigParam(); // systemConfigParam.setCode("test"); // systemConfigParam.setContent("test1"); // configService.createOrUpdate(systemConfigParam); // } //} ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConsoleOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import java.util.List; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.test.domain.data.utils.TestUtils; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * Data source testing * * @author Jiaju Zhuang */ @Slf4j public class ConsoleOperationsTest extends BaseTest { @Resource private DataSourceService dataSourceService; @Resource private ConsoleService consoleService; @Autowired private List dialectPropertiesList; @Test @Order(1) public void createAndClose() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // creat ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); // close ConsoleCloseParam consoleCloseParam = new ConsoleCloseParam(); consoleCloseParam.setDataSourceId(dataSourceId); consoleCloseParam.setConsoleId(consoleId); consoleService.closeConsole(consoleCloseParam); TestUtils.remove(); } } @Test @Order(2) public void createAfterDataSourceClose() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); dataSourceService.close(dataSourceId); TestUtils.remove(); } } @Test @Order(3) public void closeDataSourceAfterCreateConsole() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, consoleId); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); dataSourceService.close(dataSourceId); TestUtils.remove(); } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/DatabaseOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import java.util.List; import ai.chat2db.spi.model.Database; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.test.domain.data.utils.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * Database testing * * @author Jiaju Zhuang */ @Slf4j public class DatabaseOperationsTest extends BaseTest { @Resource private DataSourceService dataSourceService; @Autowired private List dialectPropertiesList; @Resource private DatabaseService databaseService; @Test @Order(1) public void queryAll() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); // Prepare context putConnect(dialectProperties.getUrl(), dialectProperties.getUsername(), dialectProperties.getPassword(), dialectProperties.getDbType(), dialectProperties.getDatabaseName(), dataSourceId, null); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); DatabaseQueryAllParam databaseQueryAllParam = new DatabaseQueryAllParam(); databaseQueryAllParam.setDataSourceId(dataSourceId); ListResult databaseList = databaseService.queryAll(databaseQueryAllParam); log.info("Querying the database returns: {}", JSON.toJSONString(databaseList)); Database Database = databaseList.getData().stream() .filter(database -> dialectProperties.getDatabaseName().equals(database.getName())) .findFirst() .orElse(null); Assertions.assertNotNull(Database, "Query database failed"); removeConnect(); } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ExampleOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import java.util.List; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * Sample * * @author Jiaju Zhuang */ @Slf4j public class ExampleOperationsTest extends BaseTest { @Resource private TableService tableService; @Autowired private List dialectPropertiesList; @Test @Order(1) public void example() { for (DialectProperties dialectProperties : dialectPropertiesList) { DataResult createTable = tableService.createTableExample(dialectProperties.getDbType()); log.info("Return table creation statement: {}", createTable); Assertions.assertNotNull(createTable, "Query sample failed"); DataResult alterTable = tableService.alterTableExample(dialectProperties.getDbType()); log.info("Return the statement to create and modify the table: {}", alterTable); Assertions.assertNotNull(alterTable, "Query sample failed"); } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/JdbcOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import java.util.Date; import java.util.List; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.model.Header; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.test.domain.data.utils.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import com.alibaba.fastjson2.JSON; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * query test * * @author Jiaju Zhuang */ @Slf4j public class JdbcOperationsTest extends BaseTest { /** * Table Name */ public static final String TABLE_NAME = "DATA_OPS_TEMPLATE_TEST_" + System.currentTimeMillis(); private final static String STRING = "STR"; private final static Date DATE = new Date(); private final static long NUMBER = 1L; @Resource private DataSourceService dataSourceService; @Resource private ConsoleService consoleService; @Autowired private List dialectPropertiesList; @Resource private DlTemplateService dlTemplateService; @Test @Order(1) public void execute() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); // Prepare context putConnect(dialectProperties.getUrl(), dialectProperties.getUsername(), dialectProperties.getPassword(), dialectProperties.getDbType(), dialectProperties.getDatabaseName(), dataSourceId, consoleId); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getCrateTableSql(TABLE_NAME)); dlTemplateService.execute(templateQueryParam); // insert templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getInsertSql(TABLE_NAME, DATE, NUMBER, STRING)); ListResult executeResult = dlTemplateService.execute(templateQueryParam); Assertions.assertTrue(executeResult.getSuccess(), "Query data failed"); // Assertions.assertEquals(1, listResult.getUpdateCount(), "Query data failed"); // query templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getSelectSqlById(TABLE_NAME, 1L)); executeResult = dlTemplateService.execute(templateQueryParam); log.info("Return data:{}", JSON.toJSONString(executeResult)); Assertions.assertTrue(executeResult.getSuccess(), "Query data failed"); List
headerList = executeResult.getData().get(0).getHeaderList(); Assertions.assertEquals(4L, headerList.size(), "Query data failed"); Assertions.assertEquals(dialectProperties.toCase("ID"), headerList.get(0).getName(), "Query data failed"); List> dataList = executeResult.getData().get(0).getDataList(); Assertions.assertEquals(1L, dataList.size(), "Query data failed"); List data1 = dataList.get(0); Assertions.assertEquals(Long.toString(NUMBER), data1.get(0), "Query data failed"); log.info("date:{},{}", DATE, data1.get(1)); Assertions.assertEquals(DateUtil.format(DATE, DatePattern.NORM_DATETIME_FORMAT), data1.get(1), "Query data failed"); Assertions.assertEquals(Long.toString(NUMBER), data1.get(2), "Query data failed"); Assertions.assertEquals(STRING, data1.get(3), "Query data failed"); // Exception sql templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getTableNotFoundSqlById(TABLE_NAME)); executeResult = dlTemplateService.execute(templateQueryParam); log.info("Abnormal sql execution result: {}", JSON.toJSONString(executeResult)); Assertions.assertFalse(executeResult.getSuccess(), "Exception sql error"); Assertions.assertNotNull(executeResult.getErrorMessage(), "Exception sql error"); removeConnect(); } } @Test @Order(Integer.MAX_VALUE) public void dropTable() { for (DialectProperties dialectProperties : dialectPropertiesList) { try { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); // Create table structure DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getDropTableSql(TABLE_NAME)); dlTemplateService.execute(templateQueryParam); } catch (Exception e) { log.warn("Failed to delete table structure.", e); } } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/SQLExecutorOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.test.domain.data.utils.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import com.alibaba.fastjson2.JSON; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * Data source testing * * @author Jiaju Zhuang */ @Slf4j public class SQLExecutorOperationsTest extends BaseTest { @Resource private DataSourceService dataSourceService; @Autowired private List dialectPropertiesList; @Test @Order(1) public void createAndClose() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, null); // creat DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); ActionResult dataSourceConnect = dataSourceService.preConnect(dataSourceCreateParam); Assertions.assertTrue(dataSourceConnect.getSuccess(), "Failed to create database connection pool"); // Assertions.assertTrue(DataCenterUtils.JDBC_ACCESSOR_MAP.containsKey(dataSourceId), "Failed to create database connection pool"); // cloes dataSourceService.close(dataSourceId); TestUtils.remove(); } } @Test @Order(2) public void test() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); // creat DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getErrorUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); ActionResult dataSourceConnect = dataSourceService.preConnect(dataSourceCreateParam); log.info("Create database returns: {}", JSON.toJSONString(dataSourceConnect)); Assertions.assertFalse(dataSourceConnect.getSuccess(), "Database creation failed error"); } } @Test @Order(3) public void createDataSource(){ for (DialectProperties dialectProperties : dialectPropertiesList) { if(!dialectProperties.getDbType().equals("CLICKHOUSE")){ continue; } String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); TestUtils.buildContext(dialectProperties, dataSourceId, null); // creat DataSourceCreateParam dataSourceCreateParam = new DataSourceCreateParam(); dataSourceCreateParam.setAlias(dialectProperties.getDbType()+"_unittest_"+dialectProperties.getDbType()); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUserName(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); DataResult dataSourceConnect = dataSourceService.createWithPermission(dataSourceCreateParam); Assertions.assertTrue(dataSourceConnect.getSuccess(), "Failed to create database connection pool"); // Assertions.assertTrue(DataCenterUtils.JDBC_ACCESSOR_MAP.containsKey(dataSourceId), "Failed to create database connection pool"); } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/TableOperationsTest.java ================================================ package ai.chat2db.server.test.domain.data.service; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.alibaba.fastjson2.JSON; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.DropParam; import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.server.test.domain.data.utils.TestUtils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.enums.CollationEnum; import ai.chat2db.spi.enums.IndexTypeEnum; import ai.chat2db.spi.model.Sql; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * Data source testing * * @author Jiaju Zhuang */ @Slf4j public class TableOperationsTest extends BaseTest { /** * Table Name */ public static final String TABLE_NAME = "data_ops_table_test_" + System.currentTimeMillis(); @Resource private DataSourceService dataSourceService; @Resource private ConsoleService consoleService; @Autowired private List dialectPropertiesList; @Resource private DlTemplateService dlTemplateService; @Resource private TableService tableService; //@Resource //private SqlOperations sqlOperations; @Test @Order(1) public void table() { for (DialectProperties dialectProperties : dialectPropertiesList) { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); // Prepare context putConnect(dialectProperties.getUrl(), dialectProperties.getUsername(), dialectProperties.getPassword(), dialectProperties.getDbType(), dialectProperties.getDatabaseName(), dataSourceId, consoleId); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); // Create table structure DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getCrateTableSql(TABLE_NAME)); dlTemplateService.execute(templateQueryParam); // Query table creation statement ShowCreateTableParam showCreateTableParam = ShowCreateTableParam.builder() .dataSourceId(dataSourceId) .databaseName(dialectProperties.getDatabaseName()) .tableName(dialectProperties.toCase(TABLE_NAME)) .build(); if (dialectProperties.getDbType() == "POSTGRESQL") { showCreateTableParam.setSchemaName("public"); } DataResult createTable = tableService.showCreateTable(showCreateTableParam); log.info("Table creation statement: {}", createTable.getData()); if (dialectProperties.getDbType() != "H2") { Assertions.assertTrue(createTable.getData().contains(dialectProperties.toCase(TABLE_NAME)), "Query table structure failed"); } // Query table structure TablePageQueryParam tablePageQueryParam = new TablePageQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(TABLE_NAME)); if (dialectProperties.getDbType() == "POSTGRESQL") { tablePageQueryParam.setSchemaName("public"); } List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); log.info("Analyzing data returns {}", JSON.toJSONString(tableList)); Assertions.assertNotEquals(0L, tableList.size(), "Query table structure failed"); Table table = tableList.get(0); // Assertions.assertEquals(dialectProperties.toCase(TABLE_NAME), table.getName(), "Query table structure failed"); if (dialectProperties.getDbType() != "POSTGRESQL") { Assertions.assertEquals("Test table", table.getComment(), "Query table structure failed"); } TableQueryParam tableQueryParam = new TableQueryParam(); tableQueryParam.setTableName(table.getName()); tableQueryParam.setDataSourceId(dataSourceId); tableQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); if (dialectProperties.getDbType() == "POSTGRESQL") { tableQueryParam.setSchemaName("public"); } List columnList = tableService.queryColumns(tableQueryParam); Assertions.assertEquals(4L, columnList.size(), "Query table structure failed"); TableColumn id = columnList.get(0); Assertions.assertEquals(dialectProperties.toCase("id"), id.getName(), "Query table structure failed"); Assertions.assertEquals("Primary key auto-increment", id.getComment(), "Query table structure failed"); Assertions.assertTrue(id.getAutoIncrement(), "Query table structure failed"); //Assertions.assertFalse(id.getNullable(), "Query table structure failed"); TableColumn string = columnList.get(3); Assertions.assertEquals(dialectProperties.toCase("string"), string.getName(), "Query table structure failed"); //Assertions.assertTrue(string.getNullable(), "Query table structure failed"); Assertions.assertEquals("DATA", TestUtils.unWrapperDefaultValue(string.getDefaultValue()), "Query table structure failed"); if (dialectProperties.getDbType() == "POSTGRESQL") { tablePageQueryParam.setSchemaName("public"); } List tableIndexList = tableService.queryIndexes(tableQueryParam); log.info("Analyzing data returns {}", JSON.toJSONString(tableIndexList)); Assertions.assertEquals(4L, tableIndexList.size(), "Query table structure failed"); Map tableIndexMap = EasyCollectionUtils.toIdentityMap(tableIndexList, TableIndex::getName); TableIndex idxDate = tableIndexMap.get(dialectProperties.toCase(TABLE_NAME + "_idx_date")); Assertions.assertEquals("date index", idxDate.getComment(), "Query table structure failed"); Assertions.assertEquals(IndexTypeEnum.NORMAL.getCode(), idxDate.getType(), "Query table structure failed"); Assertions.assertEquals(1L, idxDate.getColumnList().size(), "Query table structure failed"); Assertions.assertEquals(dialectProperties.toCase("date"), idxDate.getColumnList().get(0).getColumnName(), "Query table structure failed"); Assertions.assertEquals(CollationEnum.DESC.getCode(), idxDate.getColumnList().get(0).getCollation(), "Query table structure failed"); TableIndex ukNumber = tableIndexMap.get(dialectProperties.toCase(TABLE_NAME + "_uk_number")); Assertions.assertEquals("unique index", ukNumber.getComment(), "Query table structure failed"); Assertions.assertEquals(IndexTypeEnum.UNIQUE.getCode(), ukNumber.getType(), "Query table structure failed"); TableIndex idxNumberString = tableIndexMap.get(dialectProperties.toCase(TABLE_NAME + "_idx_number_string")); Assertions.assertEquals(2, idxNumberString.getColumnList().size(), "Query table structure failed"); // Delete table structure DropParam dropParam = DropParam.builder() .dataSourceId(dataSourceId) .databaseName(dialectProperties.getDatabaseName()) .name(dialectProperties.toCase(TABLE_NAME)) .build(); tableService.drop(dropParam); // Query table structure tablePageQueryParam = new TablePageQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(TABLE_NAME)); tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); log.info("After deleting the table, the data returns {}", JSON.toJSONString(tableList)); Assertions.assertEquals(0L, tableList.size(), "Query table structure failed"); // Test the table creation statement testBuildSql(dialectProperties, dataSourceId, consoleId); removeConnect(); } } private void testBuildSql(DialectProperties dialectProperties, Long dataSourceId, Long consoleId) { if (dialectProperties.getDbType() != "MYSQL") { log.error("Currently the test case only supports mysql"); return; } // Create new table // CREATE TABLE `DATA_OPS_TEMPLATE_TEST_1673093980449` // ( // `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment', // `date` datetime(3) not null COMMENT 'date', // `number` bigint COMMENT 'long integer', // `string` VARCHAR(100) default 'DATA' COMMENT 'name', // index DATA_OPS_TEMPLATE_TEST_1673093980449_idx_date (date desc) comment 'date index', // unique DATA_OPS_TEMPLATE_TEST_1673093980449_uk_number (number) comment 'unique index', // index DATA_OPS_TEMPLATE_TEST_1673093980449_idx_number_string (number, date) comment 'Union index' //) COMMENT ='Test table'; // * The case depends on the specific database: //* Create table structure: Test table // * Fields: //* id Primary key auto-increment //* date date is not null // * number long integer // * string String length 100 default value "DATA" // * //* Index (plus $tableName_ because some database indexes are globally unique): //* $tableName_idx_date date index reverse order // * $tableName_uk_number unique index // * $tableName_idx_number_string Union index String tableName = dialectProperties.toCase("data_ops_table_test_" + System.currentTimeMillis()); Table newTable = new Table(); newTable.setName(tableName); newTable.setComment("Test table"); List tableColumnList = new ArrayList<>(); newTable.setColumnList(tableColumnList); //id TableColumn idTableColumn = new TableColumn(); idTableColumn.setName("id"); idTableColumn.setAutoIncrement(Boolean.TRUE); idTableColumn.setPrimaryKey(Boolean.TRUE); //idTableColumn.setNullable(Boolean.FALSE); idTableColumn.setComment("Primary key auto-increment"); idTableColumn.setColumnType("bigint"); tableColumnList.add(idTableColumn); // date TableColumn dateTableColumn = new TableColumn(); dateTableColumn.setName("date"); //dateTableColumn.setNullable(Boolean.FALSE); dateTableColumn.setComment("date"); dateTableColumn.setColumnType("datetime(3)"); tableColumnList.add(dateTableColumn); // number TableColumn numberTableColumn = new TableColumn(); numberTableColumn.setName("number"); numberTableColumn.setComment("long integer"); numberTableColumn.setColumnType("bigint"); tableColumnList.add(numberTableColumn); // string TableColumn stringTableColumn = new TableColumn(); stringTableColumn.setName("string"); stringTableColumn.setComment("name"); stringTableColumn.setColumnType("varchar(100)"); stringTableColumn.setDefaultValue("DATA"); tableColumnList.add(stringTableColumn); // index List tableIndexList = new ArrayList<>(); newTable.setIndexList(tableIndexList); // index DATA_OPS_TEMPLATE_TEST_1673093980449_idx_date (date desc) comment 'date index', tableIndexList.add(TableIndex.builder() .name(tableName + "_idx_date") .type(IndexTypeEnum.NORMAL.getCode()) .comment("date index") .columnList(Lists.newArrayList(TableIndexColumn.builder() .columnName("date") .collation(CollationEnum.DESC.getCode()) .build())) .build()); // unique DATA_OPS_TEMPLATE_TEST_1673093980449_uk_number (number) comment 'unique index', tableIndexList.add(TableIndex.builder() .name(tableName + "_uk_number") .type(IndexTypeEnum.UNIQUE.getCode()) .comment("unique index") .columnList(Lists.newArrayList(TableIndexColumn.builder() .columnName("number") .build())) .build()); // index DATA_OPS_TEMPLATE_TEST_1673093980449_idx_number_string (number, date) comment 'Union index' tableIndexList.add(TableIndex.builder() .name(tableName + "_idx_number_string") .type(IndexTypeEnum.NORMAL.getCode()) .comment("Union index") .columnList(Lists.newArrayList(TableIndexColumn.builder() .columnName("number") .build(), TableIndexColumn.builder() .columnName("date") .build())) .build()); // build sql List buildTableSqlList = tableService.buildSql(null, newTable).getData(); log.info("The structural statement to create a table is:{}", JSON.toJSONString(buildTableSqlList)); for (Sql sql : buildTableSqlList) { DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(sql.getSql()); dlTemplateService.execute(templateQueryParam); } // Check table structure checkTable(tableName, dialectProperties, dataSourceId); // Go to the database to query the table structure TableQueryParam tablePageQueryParam = new TableQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(tableName)); Table table = tableService.query(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); log.info("Analyzing data returns {}", JSON.toJSONString(table)); Assertions.assertNotNull(table, "Query table structure failed"); Table oldTable = table; Assertions.assertEquals(dialectProperties.toCase(tableName), oldTable.getName(), "Query table structure failed"); Assertions.assertEquals("Test table", oldTable.getComment(), "Query table structure failed"); // Modify table structure // build sql log.info("oldTable:{}", JSON.toJSONString(oldTable)); log.info("newTable:{}", JSON.toJSONString(newTable)); buildTableSqlList = tableService.buildSql(oldTable, newTable).getData(); log.info("Modify the table structure: {}", JSON.toJSONString(buildTableSqlList)); Assertions.assertTrue(!buildTableSqlList.isEmpty(), "构建sql失败"); // Let’s query again. There will be 2 objects. tablePageQueryParam = new TableQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(tableName)); newTable = tableService.query(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); // Modify fields // Add a new field newTable.getColumnList().add(TableColumn.builder() .name("add_string") .columnType("varchar(20)") .comment("New string") .build()); // Add a new index newTable.getIndexList().add(TableIndex.builder() .name(tableName + "_idx_string_new") .type(IndexTypeEnum.NORMAL.getCode()) .comment("new string index") .columnList(Lists.newArrayList(TableIndexColumn.builder() .columnName("add_string") .collation(CollationEnum.DESC.getCode()) .build())) .build()); // Query table structure changes log.info("oldTable:{}", JSON.toJSONString(oldTable)); log.info("newTable:{}", JSON.toJSONString(newTable)); buildTableSqlList = tableService.buildSql(oldTable, newTable).getData(); log.info("Modify the table structure: {}", JSON.toJSONString(buildTableSqlList)); // Delete table structure dropTable(tableName, dialectProperties, dataSourceId); } private void dropTable(String tableName, DialectProperties dialectProperties, Long dataSourceId) { // Delete table structure DropParam dropParam = DropParam.builder() .dataSourceId(dataSourceId) .databaseName(dialectProperties.getDatabaseName()) .name(dialectProperties.toCase(tableName)) .build(); tableService.drop(dropParam); // Query table structure TablePageQueryParam tablePageQueryParam = new TablePageQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(tableName)); List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); log.info("After deleting the table, the data returns {}", JSON.toJSONString(tableList)); Assertions.assertEquals(0L, tableList.size(), "Query table structure failed"); } private void checkTable(String tableName, DialectProperties dialectProperties, Long dataSourceId) { // Query table structure TablePageQueryParam tablePageQueryParam = new TablePageQueryParam(); tablePageQueryParam.setDataSourceId(dataSourceId); tablePageQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); tablePageQueryParam.setTableName(dialectProperties.toCase(tableName)); List
tableList = tableService.pageQuery(tablePageQueryParam, TableSelector.builder() .columnList(Boolean.TRUE) .indexList(Boolean.TRUE) .build()).getData(); log.info("Analyzing data returns {}", JSON.toJSONString(tableList)); Assertions.assertEquals(1L, tableList.size(), "Query table structure failed"); Table table = tableList.get(0); Assertions.assertEquals(dialectProperties.toCase(tableName), table.getName(), "Query table structure failed"); Assertions.assertEquals("Test table", table.getComment(), "Query table structure failed"); TableQueryParam tableQueryParam = new TableQueryParam(); tableQueryParam.setTableName(table.getName()); tableQueryParam.setDataSourceId(dataSourceId); tableQueryParam.setDatabaseName(dialectProperties.getDatabaseName()); List columnList = tableService.queryColumns(tableQueryParam); Assertions.assertEquals(4L, columnList.size(), "Query table structure failed"); TableColumn id = columnList.get(0); Assertions.assertEquals(dialectProperties.toCase("id"), id.getName(), "Query table structure failed"); Assertions.assertEquals("Primary key auto-increment", id.getComment(), "Query table structure failed"); Assertions.assertTrue(id.getAutoIncrement(), "Query table structure failed"); //Assertions.assertFalse(id.getNullable(), "Query table structure failed"); Assertions.assertTrue(id.getPrimaryKey(), "Query table structure failed"); TableColumn string = columnList.get(3); Assertions.assertEquals(dialectProperties.toCase("string"), string.getName(), "Query table structure failed"); //Assertions.assertTrue(string.getNullable(), "Query table structure failed"); Assertions.assertEquals("DATA", TestUtils.unWrapperDefaultValue(string.getDefaultValue()), "Query table structure failed"); List tableIndexList = tableService.queryIndexes(tableQueryParam); Assertions.assertEquals(4L, tableIndexList.size(), "Query table structure failed"); Map tableIndexMap = EasyCollectionUtils.toIdentityMap(tableIndexList, TableIndex::getName); TableIndex idxDate = tableIndexMap.get(dialectProperties.toCase(tableName + "_idx_date")); Assertions.assertEquals("date index", idxDate.getComment(), "Query table structure failed"); Assertions.assertEquals(IndexTypeEnum.NORMAL.getCode(), idxDate.getType(), "Query table structure failed"); Assertions.assertEquals(1L, idxDate.getColumnList().size(), "Query table structure failed"); Assertions.assertEquals(dialectProperties.toCase("date"), idxDate.getColumnList().get(0).getColumnName(), "Query table structure failed"); Assertions.assertEquals(CollationEnum.DESC.getCode(), idxDate.getColumnList().get(0).getCollation(), "Query table structure failed"); TableIndex ukNumber = tableIndexMap.get(dialectProperties.toCase(tableName + "_uk_number")); Assertions.assertEquals("unique index", ukNumber.getComment(), "Query table structure failed"); Assertions.assertEquals(IndexTypeEnum.UNIQUE.getCode(), ukNumber.getType(), "Query table structure failed"); TableIndex idxNumberString = tableIndexMap.get(dialectProperties.toCase(tableName + "_idx_number_string")); Assertions.assertEquals(2, idxNumberString.getColumnList().size(), "Query table structure failed"); } @Test @Order(Integer.MAX_VALUE) public void dropTable() { for (DialectProperties dialectProperties : dialectPropertiesList) { try { String dbTypeEnum = dialectProperties.getDbType(); Long dataSourceId = TestUtils.nextLong(); Long consoleId = TestUtils.nextLong(); DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType(dbTypeEnum); dataSourceCreateParam.setUrl(dialectProperties.getUrl()); dataSourceCreateParam.setUser(dialectProperties.getUsername()); dataSourceCreateParam.setPassword(dialectProperties.getPassword()); dataSourceService.preConnect(dataSourceCreateParam); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(dataSourceId); consoleCreateParam.setConsoleId(consoleId); consoleCreateParam.setDatabaseName(dialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); // Create table structure DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(consoleId); templateQueryParam.setDataSourceId(dataSourceId); templateQueryParam.setSql(dialectProperties.getDropTableSql(TABLE_NAME)); dlTemplateService.execute(templateQueryParam); } catch (Exception e) { log.warn("Failed to delete table structure.", e); } } } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/ClickHouseDialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; @Component public class ClickHouseDialectProperties implements DialectProperties { @Override public String getDbType() { return "CLICKHOUSE"; } @Override public String getUrl() { return "jdbc:clickhouse://localhost:8123"; } @Override public String getErrorUrl() { return "jdbc:postgresql://error:5432/ali_dbhub"; } @Override public String getUsername() { return "default"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return ""; } @Override public String getCrateTableSql(String tableName) { String sql = "CREATE TABLE " + tableName + "\n" + "(\n" + " id serial\n" + " constraint " + tableName + "_pk primary key,\n" + " date timestamp,\n" + " number int,\n" + " string varchar(100) default 'DATA'\n" + ");\n"; sql += "comment on table " + tableName + " is 'Test table';\n"; sql += "comment on column " + tableName + ".id is 'Primary key auto-increment';\n"; sql += "comment on column " + tableName + ".date is 'date';\n"; sql += "comment on column " + tableName + ".number is 'long integer';\n"; sql += "comment on column " + tableName + ".string is 'name';\n"; sql += "create index " + tableName + "idx_date on " + tableName + " (date desc);"; sql += "create unique index " + tableName + "_uk_number on " + tableName + " (number);"; sql += "create index " + tableName + "_idx_number_string on " + tableName + " (number, date);"; sql += "comment on index " + tableName + "_uk_number is 'date index';"; sql += "comment on index " + tableName + "_uk_number is 'unique index';"; sql += "comment on index " + tableName + "_idx_number_string is 'Union index';"; return sql; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO " + tableName + " (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n" + "from " + tableName + "\n" + "where id = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/DialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import java.util.Date; /** * Dialect configuration * * @author Jiaju Zhuang */ public interface DialectProperties { /** * Supported database types * * @return */ String getDbType(); /** * connection * * @return */ String getUrl(); /** * Abnormal connection * * @return */ String getErrorUrl(); /** * userName * * @return */ String getUsername(); /** * password * * @return */ String getPassword(); /** * Name database * * @return */ String getDatabaseName(); /** * The case depends on the specific database: * Create table structure: test table * Field: * id primary key auto-increment * date date is not empty * number long integer type * string string length 100 default value "DATA" * * Index (plus $tableName_ because some database indexes are globally unique): * $tableName_idx_date date index reverse order * $tableName_uk_number unique index * $tableName_idx_number_string joint index * * @return */ String getCrateTableSql(String tableName); /** * Create table structure * * @return */ String getDropTableSql(String tableName); /** * Create a piece of data * * @return */ String getInsertSql(String tableName, Date date, Long number, String string); /** * Query a query sql * * @return */ String getSelectSqlById(String tableName, Long id); /** * Get a sql whose table structure does not exist * * @return */ String getTableNotFoundSqlById(String tableName); /** * Convert case * Some database table structures store uppercase letters by default * Some databases store lowercase by default * * @param string * @return */ String toCase(String string); } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/H2DialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import java.util.Date; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; /** * h2 * * @author Jiaju Zhuang */ @Component public class H2DialectProperties implements DialectProperties { @Override public String getDbType() { return "H2"; } @Override public String getUrl() { return "jdbc:h2:~/.dbhub/db/ali_dbhub_dev;MODE=MYSQL"; } @Override public String getErrorUrl() { return "jdbc:h2:tcp://error:8084/error"; } @Override public String getUsername() { return null; } @Override public String getPassword() { return null; } @Override public String getDatabaseName() { return "ALI_DBHUB_DEV"; } @Override public String getCrateTableSql(String tableName) { // TODO druid has sql parsing bug String sql = "CREATE TABLE `" + tableName + "`\n\t" + "(\n\t" + " `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n\t" + " `date` datetime not null COMMENT 'date',\n\t" + " `number` bigint COMMENT 'long integer',\n\t" + " `string` VARCHAR(100) default 'DATA' COMMENT 'name'\n\t" + ");\n\t"; sql += "comment on table " + tableName + " is 'Test table';\n\t"; sql += "create index " + tableName + "_idx_date on " + tableName + "(DATE desc);\n\t"; sql += "comment on index " + tableName + "_idx_date is 'date index';\n\t"; sql += "create unique index " + tableName + "_uk_number on " + tableName + "(NUMBER);\n\t"; sql += "comment on index " + tableName + "_uk_number is 'unique index';\n\t"; sql += "create index " + tableName + "_idx_number_string on " + tableName + "(NUMBER, DATE);\n\t"; sql += "comment on index " + tableName + "_idx_number_string is 'Union index';\n\t"; return sql; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO `" + tableName + "` (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n\t" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootUpperCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/MysqlDialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import java.util.Date; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; /** * mysql * * @author Jiaju Zhuang */ @Component public class MysqlDialectProperties implements DialectProperties { @Override public String getDbType() { return "MYSQL"; } @Override public String getUrl() { return "jdbc:mysql://localhost:3306"; } @Override public String getErrorUrl() { return "jdbc:mysql://error.rm-8vb099vo8309mcngk.mysql.zhangbei.rds.aliyuncs.com:3306"; } @Override public String getUsername() { return "root"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "ali_dbhub_test"; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE `" + tableName + "`\n\t" + "(\n\t" + " `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n\t" + " `date` datetime(3) not null COMMENT 'date',\n\t" + " `number` bigint COMMENT 'long integer',\n\t" + " `string` VARCHAR(100) default 'DATA' COMMENT 'name',\n\t" + " index " + tableName + "_idx_date (date desc) comment 'date index',\n\t" + " unique " + tableName + "_uk_number (number) comment 'unique index',\n\t" + " index " + tableName + "_idx_number_string (number, date) comment 'Union index'\n\t" + ") COMMENT ='Test table';"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO `" + tableName + "` (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/OracleDialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; @Component public class OracleDialectProperties implements DialectProperties { @Override public String getDbType() { return "ORACLE"; } @Override public String getUrl() { return "jdbc:oracle:thin:@localhost:1521:XE"; } @Override public String getErrorUrl() { return "jdbc:oracle:thin:@localhost:1521:XE1"; } @Override public String getUsername() { return "system"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "XEPDB1"; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE `" + tableName + "`\n\t" + "(\n\t" + " `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n\t" + " `date` datetime(3) not null COMMENT 'date',\n\t" + " `number` bigint COMMENT 'long integer',\n\t" + " `string` VARCHAR(100) default 'DATA' COMMENT 'name',\n\t" + " index " + tableName + "_idx_date (date desc) comment 'date index',\n\t" + " unique " + tableName + "_uk_number (number) comment 'unique index',\n\t" + " index " + tableName + "_idx_number_string (number, date) comment 'Union index'\n\t" + ") COMMENT ='Test table';"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO `" + tableName + "` (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/PostgresqlDialectProperties.java ================================================ /** * Alipay.com Inc. * Copyright (c) 2004-2022 All Rights Reserved. */ package ai.chat2db.server.test.domain.data.service.dialect; import java.util.Date; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; /** * @author jipengfei * @version : PgDialectProperties.java, v 0.1 December 13, 2022 21:48 jipengfei Exp $ */ @Component public class PostgresqlDialectProperties implements DialectProperties { @Override public String getDbType() { return "POSTGRESQL"; } @Override public String getUrl() { return "jdbc:postgresql://localhost:5432/ali_dbhub_test"; } @Override public String getErrorUrl() { return "jdbc:postgresql://error:5432/ali_dbhub"; } @Override public String getUsername() { return "ali_dbhub"; } @Override public String getPassword() { return "ali_dbhub"; } @Override public String getDatabaseName() { return "ali_dbhub_test"; } @Override public String getCrateTableSql(String tableName) { String sql = "CREATE TABLE " + tableName + "\n" + "(\n" + " id serial\n" + " constraint " + tableName + "_pk primary key,\n" + " date timestamp,\n" + " number int,\n" + " string varchar(100) default 'DATA'\n" + ");\n"; sql += "comment on table " + tableName + " is 'Test table';\n"; sql += "comment on column " + tableName + ".id is 'Primary key auto-increment';\n"; sql += "comment on column " + tableName + ".date is 'date';\n"; sql += "comment on column " + tableName + ".number is 'long integer';\n"; sql += "comment on column " + tableName + ".string is 'name';\n"; sql += "create index " + tableName + "idx_date on " + tableName + " (date desc);"; sql += "create unique index " + tableName + "_uk_number on " + tableName + " (number);"; sql += "create index " + tableName + "_idx_number_string on " + tableName + " (number, date);"; sql += "comment on index " + tableName + "_uk_number is 'date index';"; sql += "comment on index " + tableName + "_uk_number is 'unique index';"; sql += "comment on index " + tableName + "_idx_number_string is 'Union index';"; return sql; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO " + tableName + " (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n" + "from " + tableName + "\n" + "where id = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLITEDialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; /** * @author jipengfei * @version : SQLITEDialectProperties.java */ @Component public class SQLITEDialectProperties implements DialectProperties{ @Override public String getDbType() { return "SQLITE"; } @Override public String getUrl() { return "jdbc:sqlite:identifier.sqlite"; } @Override public String getErrorUrl() { return null; } @Override public String getUsername() { return null; } @Override public String getPassword() { return null; } @Override public String getDatabaseName() { return "main"; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE `" + tableName + "`\n\t" + "(\n\t" + " `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'Primary key auto-increment',\n\t" + " `date` datetime(3) not null COMMENT 'date',\n\t" + " `number` bigint COMMENT 'long integer',\n\t" + " `string` VARCHAR(100) default 'DATA' COMMENT 'name',\n\t" + " index " + tableName + "_idx_date (date desc) comment 'date index',\n\t" + " unique " + tableName + "_uk_number (number) comment 'unique index',\n\t" + " index " + tableName + "_idx_number_string (number, date) comment 'Union index'\n\t" + ") COMMENT ='Test table';"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO `" + tableName + "` (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n" + "from " + tableName + "_notfound;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLServerDialectProperties.java ================================================ package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Date; /** * @author jipengfei * @version : SQLServerDialectProperties.java */ @Component public class SQLServerDialectProperties implements DialectProperties{ @Override public String getDbType() { return "SQLSERVER"; } @Override public String getUrl() { return "jdbc:sqlserver://localhost:1433;encrypt=true;trustServerCertificate=true;integratedSecurity=false;Trusted_Connection=yes"; } @Override public String getErrorUrl() { return "jdbc:sqlserver://localhost:14331;encrypt=true;trustServerCertificate=true;integratedSecurity=true;"; } @Override public String getUsername() { return "sa"; } @Override public String getPassword() { return "Ali_dbhub!"; } @Override public String getDatabaseName() { return null; } @Override public String getCrateTableSql(String tableName) { return "CREATE TABLE [dbo].["+tableName+"] ( [id] bigint NOT NULL, [date] datetime NOT NULL, [String] varchar(1) NOT NULL, [number] bigint NULL);CREATE UNIQUE CLUSTERED INDEX [id] ON [dbo].[table_name] ( [id] ASC);CREATE NONCLUSTERED INDEX [table_name_date_index] ON [dbo].[table_name] ( [date] ASC);CREATE NONCLUSTERED INDEX [table_name_String_index] ON [dbo].[table_name] ( [String] ASC);CREATE UNIQUE NONCLUSTERED INDEX [table_name_pk] ON [dbo].[table_name] ( [number] ASC);EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'id';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'date';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'String';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'number';"; } @Override public String getDropTableSql(String tableName) { return "drop table " + tableName + ";"; } @Override public String getInsertSql(String tableName, Date date, Long number, String string) { return "INSERT INTO " + tableName + " (date,number,string) VALUES ('" + DateUtil.format(date, DatePattern.NORM_DATETIME_MS_FORMAT) + "','" + number + "','" + string + "');"; } @Override public String getSelectSqlById(String tableName, Long id) { return "select *\n\t" + "from " + tableName + "\n\t" + "where `id` = '" + id + "';"; } @Override public String getTableNotFoundSqlById(String tableName) { return "select *\n\t" + "from " + tableName + "_not_find ;"; } @Override public String toCase(String string) { return StringUtils.toRootLowerCase(string); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/utils/TestUtils.java ================================================ package ai.chat2db.server.test.domain.data.utils; import java.util.concurrent.atomic.AtomicLong; import ai.chat2db.server.test.domain.data.service.dialect.DialectProperties; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; /** * Test tool class * * @author Jiaju Zhuang */ public class TestUtils { public static final AtomicLong ATOMIC_LONG = new AtomicLong(); /** * a globally unique long * * @return */ public static long nextLong() { return ATOMIC_LONG.incrementAndGet(); } /** * If the default value is something like 'DATA' * then you need to remove '' * * @param defaultValue * @return */ public static String unWrapperDefaultValue(String defaultValue) { if (defaultValue == null) { return null; } if (defaultValue.startsWith("'") && defaultValue.endsWith("'")) { if (defaultValue.length() < 2) { return defaultValue; } else if (defaultValue.length() == 2) { return ""; } else { return defaultValue.substring(1, defaultValue.length() - 1); } } return defaultValue; } public static void buildContext(DialectProperties dialectProperties,Long dataSourceId,Long consoleId){ ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setUser(dialectProperties.getUsername()); connectInfo.setConsoleId(consoleId); connectInfo.setDataSourceId(dataSourceId); connectInfo.setPassword(dialectProperties.getPassword()); connectInfo.setDbType(dialectProperties.getDbType()); connectInfo.setUrl(dialectProperties.getUrl()); connectInfo.setDatabase(dialectProperties.getDatabaseName()); connectInfo.setConsoleOwn(false); Chat2DBContext.putContext(connectInfo); } public static void remove(){ Chat2DBContext.removeContext(); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/HttpTest.java ================================================ ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SQLParseTest.java ================================================ package ai.chat2db.server.test.temp; import com.github.vertical_blank.sqlformatter.SqlFormatter; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; public class SQLParseTest { public static void main(String[] args) throws JSQLParserException { String sql = "CREATE OR REPLACE PROCEDURE public.raise_salary(emp_id integer, percentage numeric)\n" + " LANGUAGE plpgsql\n" + "AS $procedure$\n" + "BEGIN\n" + " UPDATE employees\n" + " SET salary = salary + (salary * percentage / 100)\n" + " WHERE id = emp_id;\n" + "COMMIT;\n" + "END; -- sdsd\n" + "$procedure$"; Statements statements = CCJSqlParserUtil.parseStatements(sql); // If there are multiple statements, the actual type after parsing is StatementList // Iterate through each statement for (Statement stmt : statements.getStatements()) { // If it is a single statement, the actual type is Statement System.out.println(stmt.toString()); System.out.println(" dddd:"+SqlFormatter.format(stmt.toString())); System.out.println(" hu:"+ cn.hutool.db.sql.SqlFormatter.format(stmt.toString())); } String s = SqlFormatter.format("SELECT * FROM table1"); System.out.println(s); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SqlTest.java ================================================ package ai.chat2db.server.test.temp; import java.util.ArrayList; import java.util.List; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.model.Sql; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.api.param.SqlAnalyseParam; import ai.chat2db.server.test.common.BaseTest; import ai.chat2db.server.test.domain.data.service.dialect.MysqlDialectProperties; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j public class SqlTest extends BaseTest { @Resource private TableService tableService; @Resource private MysqlDialectProperties mysqlDialectProperties; @Resource private DataSourceService dataSourceService; @Resource private ConsoleService consoleService; @Resource private DlTemplateService dlTemplateService; @Test public void test() { // creat DataSourcePreConnectParam dataSourceCreateParam = new DataSourcePreConnectParam(); dataSourceCreateParam.setType("MYSQL"); dataSourceCreateParam.setUrl(mysqlDialectProperties.getUrl()); dataSourceCreateParam.setUser(mysqlDialectProperties.getUsername()); dataSourceCreateParam.setPassword(mysqlDialectProperties.getPassword()); ActionResult actionResult = dataSourceService.preConnect(dataSourceCreateParam); DataResult createTable = tableService.createTableExample("MYSQL"); log.info("sql1:{}", createTable.getData()); SqlAnalyseParam sqlAnalyseParam = new SqlAnalyseParam(); sqlAnalyseParam.setDataSourceId(1L); sqlAnalyseParam.setSql(createTable.getData()); List sqlList = new ArrayList<>(); sqlList.add(Sql.builder().sql(createTable.getData()).build()); // Create a console ConsoleConnectParam consoleCreateParam = new ConsoleConnectParam(); consoleCreateParam.setDataSourceId(1L); consoleCreateParam.setConsoleId(1L); consoleCreateParam.setDatabaseName(mysqlDialectProperties.getDatabaseName()); consoleService.createConsole(consoleCreateParam); // delete DlExecuteParam templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(1L); templateQueryParam.setDataSourceId(1L); templateQueryParam.setSql("drop table test;"); ListResult executeResult = dlTemplateService.execute(templateQueryParam); log.info("result:{}", JSON.toJSONString(executeResult)); // Create table structure templateQueryParam = new DlExecuteParam(); templateQueryParam.setConsoleId(1L); templateQueryParam.setDataSourceId(1L); templateQueryParam.setSql(sqlList.get(0).getSql()); executeResult = dlTemplateService.execute(templateQueryParam); log.info("result:{}", JSON.toJSONString(executeResult)); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/TempTest.java ================================================ package ai.chat2db.server.test.temp; import lombok.extern.slf4j.Slf4j; @Slf4j public class TempTest { } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/UserTest.java ================================================ package ai.chat2db.server.test.temp; import ai.chat2db.server.test.common.BaseTest; import cn.hutool.crypto.digest.DigestUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @Slf4j public class UserTest extends BaseTest { @Test public void test() { log.info("password:{}", DigestUtil.bcrypt("dbhub")); } } ================================================ FILE: chat2db-server/chat2db-server-test/src/test/resources/h2/init.sql ================================================ DROP TABLE if exists test_query; CREATE TABLE `test_query` ( `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT '主键', `name` VARCHAR(100) COMMENT '名字', `date` datetime COMMENT '时间', `number` int COMMENT '数字' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='测试表'; INSERT INTO `test_query` (name,date,number) VALUES ('姓名','2022-01-01',123); ================================================ FILE: chat2db-server/chat2db-server-test/src/test/resources/h2/init_close.sql ================================================ DROP TABLE if exists test_close; CREATE TABLE `test_close` ( `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT '主键', `name` VARCHAR(100) COMMENT '名字', `date` datetime COMMENT '时间', `number` int COMMENT '数字' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='测试表'; INSERT INTO `test_close` (name,date,number) VALUES ('姓名','2022-01-01',123); ================================================ FILE: chat2db-server/chat2db-server-test/src/test/resources/h2/init_transaction.sql ================================================ DROP TABLE if exists test_transaction; CREATE TABLE `test_transaction` ( `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT '主键', `name` VARCHAR(100) COMMENT '名字', `date` datetime COMMENT '时间', `number` int COMMENT '数字' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='测试表'; INSERT INTO `test_transaction` (name,date,number) VALUES ('姓名','2022-01-01',123); ================================================ FILE: chat2db-server/chat2db-server-test/src/test/resources/logback-test-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/pom.xml ================================================ ai.chat2db chat2db-server-tools ${revision} ../pom.xml 4.0.0 chat2db-server-tools-base jar chat2db-server-tools-base org.projectlombok lombok provided org.hibernate.validator hibernate-validator provided ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/EasyToolsConstant.java ================================================ package ai.chat2db.server.tools.base.constant; /** * constant * * @author Shi Yi */ public interface EasyToolsConstant { /** * Log tracking id */ String LOG_TRACE_ID = "EAGLEEYE_TRACE_ID"; /** * Maximum paging size */ int MAX_PAGE_SIZE = 1000; /** * serializedid */ long SERIAL_VERSION_UID = 1L; /** * Maximum number of loops to prevent many loops from entering an infinite loop */ int MAXIMUM_ITERATIONS = 10 * 1000; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/SymbolConstant.java ================================================ package ai.chat2db.server.tools.base.constant; /** * Constant enumeration of common symbols * * @author Shi Yi **/ public class SymbolConstant { /** * + */ public static final String PLUS = "+"; /** * - */ public static final String MINUS = "-"; /** * * */ public static final String ASTERISK = "*"; /** * / */ public static final String SLASH = "/"; /** * apostrophe"'" */ public static final String SQUOT = "'"; /** * apostrophe""" */ public static final String DOUBLE_SQUOT = "\""; /** * empty string "" */ public static final String EMPTY = ""; /** * delimiter "-" */ public static final String SEPARATOR = "-"; /** * equal sign "=" */ public static final String EQ = "="; /** * semicolon ";" */ public static final String SEMICOLON = ";"; /** * comma "," */ public static final String COMMA = ","; /** * point "." */ public static final String DOT = "."; /** * colon ":" */ public static final String COLON = ":"; /** * blank line "\n\n" */ public static final String BLANK_LINE = "\n\n"; /** * new line "\n" */ public static final String NEW_LINE = "\n"; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/BaseEnum.java ================================================ package ai.chat2db.server.tools.base.enums; /** * Basic enumeration * * Due to the limitations of Java enumeration inheritance, the enumeration base class can only be designed as an interface. * Please ensure that the subclass must be an enumeration type. * * @author Jiaju Zhuang **/ public interface BaseEnum { /** * Returns the enumeration code. * It is generally recommended to directly return the name of the enumeration * * @return code */ T getCode(); /** * Returns the description of the enumeration. * Return the enumerated Chinese to facilitate front-end drop-down * * @return description */ String getDescription(); } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * @author moji * @version ConnectionTypeEnum.java, v 0.1 September 16, 2022 14:59 moji Exp $ * @date 2022/09/16 */ @Getter public enum DataSourceTypeEnum implements BaseEnum { /** * mysql database connection */ MYSQL("mysql database connection"), /** * redis database connection */ REDIS("redis database connection"), /** * sqlserver database connection */ SQLSERVER("sqlserver database connection"), /** * mongo database connection */ MONGODB("mongo database connection"), ; final String description; DataSourceTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DeletedIdEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * Delete mark enumeration *

* In order to be compatible with unique primary key + tombstone. * Use DeletedId to mark whether the current data is deleted. * If it is 0, it means it has not been deleted. * Anything else means it has been deleted. * When deleting, execute the statement: update set deleted_id = di where condition = condition; * * @author Shi Yi */ @Getter public enum DeletedIdEnum implements BaseEnum { /** * Not deleted */ NOT_DELETED(0L, "Not deleted"), ; final Long code; final String description; DeletedIdEnum(Long code, String description) { this.code = code; this.description = description; } /** * Determine whether the current data has been logically deleted * * @param deletedId deleted_id in table * @return Has it been deleted? */ public static boolean isDeleted(Long deletedId) { return !NOT_DELETED.getCode().equals(deletedId); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/OperationEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * Operation enumeration * * @author Shi Yi */ @Getter public enum OperationEnum implements BaseEnum { /** * creat */ CREATE("creat"), /** * update */ UPDATE("update"), /** * delete */ DELETE("delete"), ; final String description; OperationEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/OrderByDirectionEnum.java ================================================ package ai.chat2db.server.tools.base.enums; /** * Enumeration of sorting directions * * @author Shi Yi */ public enum OrderByDirectionEnum implements BaseEnum { /** * asc */ ASC, /** * desc */ DESC; @Override public String getCode() { return this.name(); } @Override public String getDescription() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/StatusEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * @author moji * @version StatusEnum.java, v 0.1 September 25, 2022 16:57 moji Exp $ * @date 2022/09/25 */ @Getter public enum StatusEnum implements BaseEnum { /** * draft */ DRAFT("draft"), /** * release */ RELEASE("release"), ; final String description; StatusEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/SystemEnvironmentEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * System environment * * @author Jiaju Zhuang */ @Getter public enum SystemEnvironmentEnum implements BaseEnum { /** * dev */ DEV("dev", "本地"), /** * test */ TEST("test", "测试"), /** * release */ RELEASE("release", "正式"), ; final String code; final String description; SystemEnvironmentEnum(String code, String description) { this.code = code; this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/WhiteListTypeEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * @author moji * @version WhiteListTypeEnum.java, v 0.1 September 25, 2022 16:57 moji Exp $ * @date 2022/09/25 */ @Getter public enum WhiteListTypeEnum implements BaseEnum { /** * vector interface */ VECTOR("VECTOR"), ; final String description; WhiteListTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/YesOrNoEnum.java ================================================ package ai.chat2db.server.tools.base.enums; import lombok.Getter; /** * Whether to enumerate * * @author Shi Yi */ @Getter public enum YesOrNoEnum implements BaseEnum { /** * yes */ YES("Y", "是", true), /** * no */ NO("N", "否", false), ; final String letter; final String description; final boolean booleanValue; YesOrNoEnum(String letter, String description, boolean booleanValue) { this.letter = letter; this.description = description; this.booleanValue = booleanValue; } @Override public String getCode() { return this.name(); } /** * Convert based on boolean value * * @param booleanValue Boolean value * @return */ public static YesOrNoEnum valueOf(Boolean booleanValue) { if (booleanValue == null) { return null; } if (booleanValue) { return YesOrNoEnum.YES; } return YesOrNoEnum.NO; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/excption/BusinessException.java ================================================ package ai.chat2db.server.tools.base.excption; import lombok.Data; /** * Business abnormality. * Those that do not require manual intervention are called business exceptions. * * @author zhuangjiaju * @date 2021/06/26 */ @Data public class BusinessException extends RuntimeException { /** * The encoding of the exception */ private String code; /** * Exception information parameters */ private Object[] args; public BusinessException() { this("common.businessError"); } public BusinessException(String code) { this(code, null); } public BusinessException(String code, Object[] args) { super(code); this.code = code; this.args = args; } public BusinessException(String code, Object[] args, Throwable throwable) { super(code, throwable); this.code = code; this.args = args; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/excption/SystemException.java ================================================ package ai.chat2db.server.tools.base.excption; import lombok.Data; /** * Business abnormality. * Simply put, exceptions that require manual intervention are called system exceptions. * * @author zhuangjiaju * @date 2021/06/26 */ @Data public class SystemException extends RuntimeException { /** * The encoding of the exception */ private String code; /** * Exception information parameters */ private Object[] args; public SystemException() { this("common.systemError"); } public SystemException(String code) { this(code, null); } public SystemException(String code, Object[] args) { super(code); this.code = code; this.args = args; } public SystemException(String code, Object[] args, Throwable throwable) { super(code, throwable); this.code = code; this.args = args; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/handler/EasyCallBackHandler.java ================================================ package ai.chat2db.server.tools.base.handler; /** * Callback handler * For example, this method will be executed when meatq calls back. * * @author Shi Yi */ public interface EasyCallBackHandler { /** * Called before handling the callback */ default void preHandle() { } /** * Called after handling the callback * If an exception is thrown, it will not be handled. */ default void postHandle() { } /** * Called after handling the callback * Will be called regardless of whether there is an exception or not */ default void afterCompletion() { } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Result.java ================================================ package ai.chat2db.server.tools.base.wrapper; /** * @author qiuyuyu * @date 2022/01/20 */ public interface Result extends Traceable{ /** * whether succeed * * @return * @mock true */ boolean success(); /** * Is the setting successful? * * @return */ void success(boolean success); /** * error coding * * @return * @mock 000000 */ String errorCode(); /** * Set error encoding * * @param errorCode */ void errorCode(String errorCode); /** * error message * * @return */ String errorMessage(); /** * Set error message * * @param errorMessage */ void errorMessage(String errorMessage); /** * error detail stack info */ void errorDetail(String errorDetail); /** * error detail * * @return */ String errorDetail(); /** * solution link */ void solutionLink(String solutionLink); /** * solution link * * @return */ String solutionLink(); } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Traceable.java ================================================ package ai.chat2db.server.tools.base.wrapper; /** * Is it possible to track * * @author Shi Yi */ public interface Traceable { /** * Get traceId * * @return traceId */ String getTraceId(); /** * Set traceId * * @param traceId */ void setTraceId(String traceId); } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/OrderBy.java ================================================ package ai.chat2db.server.tools.base.wrapper.param; import java.io.Serializable; import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * sorted objects * * @author Shi Yi */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class OrderBy implements Serializable { /** * sort field */ private String orderConditionName; /** * Sorting direction */ private OrderByDirectionEnum direction; public static OrderBy of(String property, OrderByDirectionEnum direction) { return new OrderBy(property, direction); } public static OrderBy asc(String property) { return new OrderBy(property, OrderByDirectionEnum.ASC); } public static OrderBy desc(String property) { return new OrderBy(property, OrderByDirectionEnum.DESC); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/OrderCondition.java ================================================ package ai.chat2db.server.tools.base.wrapper.param; /** * Sorting conditions * * @author Shi Yi */ public interface OrderCondition { /** * Return column name * * @return */ OrderBy getOrderBy(); } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java ================================================ package ai.chat2db.server.tools.base.wrapper.param; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; import org.hibernate.validator.constraints.Range; /** * Parameters of paging query * * @author zhuangjiaju * @date 2021/06/26 */ @Data @SuperBuilder @AllArgsConstructor public class PageQueryParam implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * page number */ @NotNull(message = "Pagination page number cannot be empty") @Min(value = 1, message = "Pagination page number must be greater than 0") private Integer pageNo; /** * Paging Size */ @NotNull(message = "Paging size cannot be empty") @Range(min = 1, max = EasyToolsConstant.MAX_PAGE_SIZE, message = "Paging size must be between 1-" + EasyToolsConstant.MAX_PAGE_SIZE) private Integer pageSize; /** * Whether to return the total number of items * Not returned by default to improve performance */ private Boolean enableReturnCount; /** * sort */ private List orderByList; public PageQueryParam() { this.pageNo = 1; this.pageSize = 100; this.enableReturnCount = Boolean.FALSE; } /** * Query all data */ public void queryAll() { this.pageNo = 1; this.pageSize = Integer.MAX_VALUE; } /** * Query 1 piece of data */ public void queryOne() { this.pageNo = 1; this.pageSize = 1; } /** * Add a new sort and replace the original sort * * @param orderBy sort * @return Sorting parameters */ public PageQueryParam orderBy(OrderBy orderBy) { orderByList = new ArrayList<>(); orderByList.add(orderBy); return this; } /** * Add a new sort and replace the original sort * * @param orderConditionName sort field * @param direction Sorting direction * @return Sorting parameters */ public PageQueryParam orderBy(String orderConditionName, OrderByDirectionEnum direction) { return orderBy(new OrderBy(orderConditionName, direction)); } /** * Add a new sort and replace the original sort * * @param orderCondition Sorting conditions * @return Sorting parameters */ public PageQueryParam orderBy(OrderCondition orderCondition) { return orderBy(orderCondition.getOrderBy()); } /** * Add a new sort * * @param orderBy sort * @return Sorting parameters */ public PageQueryParam andOrderBy(OrderBy orderBy) { orderByList.add(orderBy); return this; } /** * Add a new sort * * @param orderConditionName sort field * @param direction Sorting direction * @return Sorting parameters */ public PageQueryParam andOrderBy(String orderConditionName, OrderByDirectionEnum direction) { return andOrderBy(new OrderBy(orderConditionName, direction)); } /** * Add a new sort * * @param orderCondition Sorting conditions * @return Sorting parameters */ public PageQueryParam andOrderBy(OrderCondition orderCondition) { return andOrderBy(orderCondition.getOrderBy()); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/QueryParam.java ================================================ package ai.chat2db.server.tools.base.wrapper.param; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * query parameters * * @author zhuangjiaju * @date 2021/06/26 */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class QueryParam implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * sort */ private List orderByList; /** * Add a new sort and replace the original sort * * @param orderBy sort * @return Sorting parameters */ public QueryParam orderBy(OrderBy orderBy) { orderByList = new ArrayList<>(); orderByList.add(orderBy); return this; } /** * Add a new sort and replace the original sort * * @param orderConditionName sort field * @param direction Sorting direction * @return Sorting parameters */ public QueryParam orderBy(String orderConditionName, OrderByDirectionEnum direction) { return orderBy(new OrderBy(orderConditionName, direction)); } /** * Add a new sort and replace the original sort * * @param orderCondition Sorting conditions * @return Sorting parameters */ public QueryParam orderBy(OrderCondition orderCondition) { return orderBy(orderCondition.getOrderBy()); } /** * Add a new sort * * @param orderBy sort * @return Sorting parameters */ public QueryParam andOrderBy(OrderBy orderBy) { orderByList.add(orderBy); return this; } /** * Add a new sort * * @param orderConditionName sort field * @param direction Sorting direction * @return Sorting parameters */ public QueryParam andOrderBy(String orderConditionName, OrderByDirectionEnum direction) { return andOrderBy(new OrderBy(orderConditionName, direction)); } /** * Add a new sort * * @param orderCondition Sorting conditions * @return Sorting parameters */ public QueryParam andOrderBy(OrderCondition orderCondition) { return andOrderBy(orderCondition.getOrderBy()); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/request/PageQueryRequest.java ================================================ package ai.chat2db.server.tools.base.wrapper.request; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; import org.hibernate.validator.constraints.Range; /** * Parameters of paging query * * @author zhuangjiaju * @date 2021/06/26 */ @Data @SuperBuilder @AllArgsConstructor public class PageQueryRequest implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * page number * * @mock 1 */ @NotNull(message = "Pagination page number cannot be empty") private Integer pageNo; /** * Number of pagination items * * @demo 10 */ @NotNull(message = "Paging size cannot be empty") @Range(min = 1, max = EasyToolsConstant.MAX_PAGE_SIZE, message = "Paging size must be between 1-" + EasyToolsConstant.MAX_PAGE_SIZE) private Integer pageSize; public PageQueryRequest() { this.pageNo = 1; this.pageSize = 10; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java ================================================ package ai.chat2db.server.tools.base.wrapper.result; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.wrapper.Result; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; /** * action return object * * @author Shi Yi */ @Data @SuperBuilder @AllArgsConstructor public class ActionResult implements Serializable, Result { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * whether succeed * * @mock true */ private Boolean success; /** * error coding * * @see CommonErrorEnum */ private String errorCode; /** * error message */ private String errorMessage; /** * error detail */ private String errorDetail; /** * solution link */ private String solutionLink; /** * traceId */ private String traceId; public ActionResult() { this.success = Boolean.TRUE; } /** * Return success * * @return operation result */ public static ActionResult isSuccess() { return new ActionResult(); } @Override public boolean success() { return success; } @Override public void success(boolean success) { this.success = success; } @Override public String errorCode() { return errorCode; } @Override public void errorCode(String errorCode) { this.errorCode = errorCode; } @Override public String errorMessage() { return errorMessage; } @Override public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public void errorDetail(String errorDetail) { this.errorDetail = errorDetail; } @Override public String errorDetail() { return errorDetail; } @Override public void solutionLink(String solutionLink) { this.solutionLink = solutionLink; } @Override public String solutionLink() { return solutionLink; } /** * Return failure * * @param errorCode error code * @param errorMessage error message * @param errorDetail error detail * @return operation result */ public static ActionResult fail(String errorCode, String errorMessage, String errorDetail) { ActionResult result = new ActionResult(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = Boolean.FALSE; result.solutionLink("https://github.com/chat2db/Chat2DB/wiki/Chat2DB"); result.errorDetail(errorDetail); return result; } public DataResult toBooleaSuccessnDataResult() { return DataResult.builder() .success(success) .errorCode(errorCode) .errorMessage(errorMessage) .errorDetail(errorDetail) .solutionLink(solutionLink) .traceId(traceId) .data(Boolean.TRUE) .build(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/DataResult.java ================================================ package ai.chat2db.server.tools.base.wrapper.result; import java.io.Serializable; import java.util.function.Function; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.wrapper.Result; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; /** * data return object * * @author Shi Yi */ @Data @SuperBuilder @AllArgsConstructor public class DataResult implements Serializable, Result { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * whether succeed * * @mock true */ private Boolean success; /** * error coding * * @see CommonErrorEnum */ private String errorCode; /** * error message */ private String errorMessage; /** * error detail */ private String errorDetail; /** * solution link */ private String solutionLink; /** * Data information */ private T data; /** * traceId */ private String traceId; public DataResult() { this.success = Boolean.TRUE; } private DataResult(T data) { this(); this.data = data; } /** * Construct the return object * * @param data object to be constructed * @param The object type to be constructed * @return the returned result */ public static DataResult of(T data) { return new DataResult<>(data); } /** * Construct an empty return object * * @param The object type to be constructed * @return the returned result */ public static DataResult empty() { return new DataResult<>(); } /** * Build exception return * * @param errorCode error coding * @param errorMessage error message * @param The object type to be constructed * @return the returned result */ public static DataResult error(String errorCode, String errorMessage) { DataResult result = new DataResult<>(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = false; return result; } /** * Determine whether data exists * * @param dataResult * @return whether data exists */ public static boolean hasData(DataResult dataResult) { return dataResult != null && dataResult.getSuccess() && dataResult.getData() != null; } /** * Convert the current type to another type * * @param mapper conversion method * @param Return type * @return the returned result */ public DataResult map(Function mapper) { R returnData = hasData(this) ? mapper.apply(getData()) : null; DataResult dataResult = new DataResult<>(); dataResult.setSuccess(getSuccess()); dataResult.setErrorCode(getErrorCode()); dataResult.setErrorMessage(getErrorMessage()); dataResult.setData(returnData); dataResult.setTraceId(getTraceId()); return dataResult; } @Override public boolean success() { return success; } @Override public void success(boolean success) { this.success = success; } @Override public String errorCode() { return errorCode; } @Override public void errorCode(String errorCode) { this.errorCode = errorCode; } @Override public String errorMessage() { return errorMessage; } @Override public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public void errorDetail(String errorDetail) { this.errorDetail = errorDetail; } @Override public String errorDetail() { return errorDetail; } @Override public void solutionLink(String solutionLink) { this.solutionLink = solutionLink; } @Override public String solutionLink() { return solutionLink; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ListResult.java ================================================ package ai.chat2db.server.tools.base.wrapper.result; import java.io.Serial; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.wrapper.Result; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; /** * data return object * * @author Shi Yi */ @Data @SuperBuilder @AllArgsConstructor public class ListResult implements Serializable, Result { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * whether succeed * * @mock true */ private Boolean success; /** * error coding */ private String errorCode; /** * Exception information */ private String errorMessage; /** * Data information */ private List data; /** * traceId */ private String traceId; /** * error detail */ private String errorDetail; /** * solution link */ private String solutionLink; public ListResult() { this.success = Boolean.TRUE; } private ListResult(List data) { this(); this.data = data; } /** * Build the list and return the object * * @param data object to be constructed * @param The object type to be constructed * @return the returned list */ public static ListResult of(List data) { return new ListResult<>(data); } /** * Build an empty list and return the object * * @param The type of object to be constructed * @return the returned list */ public static ListResult empty() { return of(Collections.emptyList()); } /** * Build exception return list * * @param errorCode error coding * @param errorMessage error message * @param The object type to be constructed * @return the returned list */ public static ListResult error(String errorCode, String errorMessage) { ListResult result = new ListResult<>(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = Boolean.TRUE; return result; } /** * Determine whether data exists * * @param listResult * @return whether data exists */ public static boolean hasData(ListResult listResult) { return listResult != null && listResult.getSuccess() && listResult.getData() != null && !listResult.getData() .isEmpty(); } /** * Convert the current type to another type * * @param mapper conversion method * @param Return type * @return paging return object */ public ListResult map(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) : Collections.emptyList(); ListResult listResult = new ListResult<>(); listResult.setSuccess(getSuccess()); listResult.setErrorCode(getErrorCode()); listResult.setErrorMessage(getErrorMessage()); listResult.setData(returnData); listResult.setTraceId(getTraceId()); return listResult; } @Override public boolean success() { return success; } @Override public void success(boolean success) { this.success = success; } @Override public String errorCode() { return errorCode; } @Override public void errorCode(String errorCode) { this.errorCode = errorCode; } @Override public String errorMessage() { return errorMessage; } @Override public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public void errorDetail(String errorDetail) { this.errorDetail = errorDetail; } @Override public String errorDetail() { return errorDetail; } @Override public void solutionLink(String solutionLink) { this.solutionLink = solutionLink; } @Override public String solutionLink() { return solutionLink; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java ================================================ package ai.chat2db.server.tools.base.wrapper.result; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.wrapper.Result; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult.Page; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; /** * data return object * * @author Shi Yi */ @Data @SuperBuilder @AllArgsConstructor public class PageResult implements Serializable, Result> { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * whether succeed * * @mock true */ private Boolean success; /** * error coding */ private String errorCode; /** * Exception information */ private String errorMessage; /** * Data information */ private List data; /** * Page coding */ private Integer pageNo; /** * Paging Size */ private Integer pageSize; /** * Total */ private Long total; /** * traceId */ private String traceId; /** * Is there a next page? */ private Boolean hasNextPage; /** * error detail */ private String errorDetail; /** * solution link */ private String solutionLink; public PageResult() { this.pageNo = 1; this.pageSize = 10; this.total = 0L; this.success = Boolean.TRUE; } private PageResult(List data, Long total, Long pageNo, Long pageSize) { this(); this.data = data; this.total = total; if (pageNo != null) { this.pageNo = Math.toIntExact(pageNo); } if (pageSize != null) { this.pageSize = Math.toIntExact(pageSize); } } private PageResult(List data, Long total, Integer pageNo, Integer pageSize) { this(); this.data = data; this.total = total; if (pageNo != null) { this.pageNo = pageNo; } if (pageSize != null) { this.pageSize = pageSize; } } /** * Construct paging return object * * @param data object returned * @param total total number of items * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static PageResult of(List data, Long total, Long pageNo, Long pageSize) { return new PageResult<>(data, total, pageNo, pageSize); } /** * Construct paging return object * * @param data object returned * @param total total number of items * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static PageResult of(List data, Long total, Integer pageNo, Integer pageSize) { return new PageResult<>(data, total, pageNo, pageSize); } /** * Construct paging return object * * @param data object returned * @param total total number of items * @param param paging parameters * @param The returned object type * @return paging return object */ public static PageResult of(List data, Long total, PageQueryParam param) { return new PageResult<>(data, total, param.getPageNo(), param.getPageSize()); } /** * Construct paging return object * Total number of items returned * * @param data object returned * @param param paging parameters * @param The returned object type * @return paging return object */ public static PageResult of(List data, PageQueryParam param) { return new PageResult<>(data, 0L, param.getPageNo(), param.getPageSize()); } /** * Construct an empty return object * * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static PageResult empty(Long pageNo, Long pageSize) { return of(Collections.emptyList(), 0L, pageNo, pageSize); } /** * Construct an empty return object * * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static PageResult empty(Integer pageNo, Integer pageSize) { return of(Collections.emptyList(), 0L, pageNo, pageSize); } /** * Determine whether there is a next page * Calculated based on paging size to prevent total from being empty * * @return Is there a next page? */ public Boolean calculateHasNextPage() { // There is a paging size calculated based on the paging if (total > 0) { return (long)pageSize * pageNo <= total; } // No data, definitely no next page if (data == null || data.isEmpty()) { return false; } // The current number is less than the number of pages return data.size() >= pageSize; } /** * Determine whether there is a next page * Calculated based on paging size to prevent total from being empty * * @return Is there a next page? * @deprecated using {@link #getHasNextPage()} ()} */ @Deprecated public boolean hasNextPage() { return getHasNextPage(); } public Boolean getHasNextPage() { if (hasNextPage == null) { hasNextPage = calculateHasNextPage(); } return hasNextPage; } /** * Determine whether data exists * * @return whether data exists */ public boolean hasData() { return hasData(this); } /** * Return query exception information * * @param errorCode error coding * @param errorMessage error message * @param The returned object * @return paging return object */ public static PageResult error(String errorCode, String errorMessage) { PageResult result = new PageResult<>(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = Boolean.FALSE; return result; } /** * Determine whether data exists * * @param pageResult * @return whether data exists */ public static boolean hasData(PageResult pageResult) { return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null && !pageResult.getData() .isEmpty(); } /** * Convert the current type to another type * * @param mapper conversion method * @param Return type * @return paging return object */ public PageResult map(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) : Collections.emptyList(); PageResult pageResult = new PageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); pageResult.setErrorMessage(getErrorMessage()); pageResult.setData(returnData); pageResult.setPageNo(getPageNo()); pageResult.setPageSize(getPageSize()); pageResult.setTotal(getTotal()); pageResult.setTraceId(getTraceId()); return pageResult; } /** * Convert the current type to another type * * @param mapper conversion method * @param Return type * @return paging return object */ public ListResult mapToList(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) : Collections.emptyList(); ListResult result = new ListResult<>(); result.setSuccess(getSuccess()); result.setErrorCode(getErrorCode()); result.setErrorMessage(getErrorMessage()); result.setTraceId(getTraceId()); result.setData(returnData); return result; } /** * Convert the current type to another type * and converted to web type * Note here that if the current project also uses PageResult in the web layer, you can directly use the map method interface. * * @param mapper conversion method * @param Return type * @return paging return object */ public WebPageResult mapToWeb(Function mapper) { List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) : Collections.emptyList(); WebPageResult pageResult = new WebPageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); pageResult.setErrorMessage(getErrorMessage()); pageResult.setTraceId(getTraceId()); // Reset a paging information Page page = new Page<>(); pageResult.setData(page); page.setData(returnData); page.setPageNo(getPageNo()); page.setPageSize(getPageSize()); page.setTotal(getTotal()); pageResult.setData(page); return pageResult; } @Override public boolean success() { return success; } @Override public void success(boolean success) { this.success = success; } @Override public String errorCode() { return errorCode; } @Override public void errorCode(String errorCode) { this.errorCode = errorCode; } @Override public String errorMessage() { return errorMessage; } @Override public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public void errorDetail(String errorDetail) { this.errorDetail = errorDetail; } @Override public String errorDetail() { return errorDetail; } @Override public void solutionLink(String solutionLink) { this.solutionLink = solutionLink; } @Override public String solutionLink() { return solutionLink; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java ================================================ package ai.chat2db.server.tools.base.wrapper.result.web; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.wrapper.Result; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.experimental.SuperBuilder; /** *The return object of data * Consistent with PageResult, you can also use PageResult directly. * This is an additional class created because the front end of some projects needs to encapsulate data+pageNo together. * * @author Shi Yi */ @Data @SuperBuilder @AllArgsConstructor public class WebPageResult implements Serializable, Result> { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * whether succeed * * @mock true */ private Boolean success; /** * Exception coding */ private String errorCode; /** * Exception information */ private String errorMessage; /** * Data information */ private Page data; /** * traceId */ private String traceId; /** * error detail */ private String errorDetail; /** * solution link */ private String solutionLink; public WebPageResult() { this.success = Boolean.TRUE; this.data = new Page<>(); } private WebPageResult(List data, Long total, Long pageNo, Long pageSize) { this.success = Boolean.TRUE; this.data = new Page<>(data, total, pageNo, pageSize); } private WebPageResult(List data, Long total, Integer pageNo, Integer pageSize) { this.success = Boolean.TRUE; this.data = new Page<>(data, total, pageNo, pageSize); } /** * Build pagination return object * * @param data object returned * @param total total number of items * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static WebPageResult of(List data, Long total, Long pageNo, Long pageSize) { return new WebPageResult<>(data, total, pageNo, pageSize); } /** * Construct paging return object * * @param data object returned * @param total total number of items * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static WebPageResult of(List data, Long total, Integer pageNo, Integer pageSize) { return new WebPageResult<>(data, total, pageNo, pageSize); } /** * Construct paging return object * * @param data object returned * @param total total number of items * @param param paging parameters * @param The returned object type * @return paging return object */ public static WebPageResult of(List data, Long total, PageQueryParam param) { return new WebPageResult<>(data, total, param.getPageNo(), param.getPageSize()); } /** * Construct an empty return object * * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static WebPageResult empty(Long pageNo, Long pageSize) { return of(Collections.emptyList(), 0L, pageNo, pageSize); } /** * Construct an empty return object * * @param pageNo page number * @param pageSize paging size * @param The returned object type * @return paging return object */ public static WebPageResult empty(Integer pageNo, Integer pageSize) { return of(Collections.emptyList(), 0L, pageNo, pageSize); } /** * Determine whether there is a next page * Calculated based on paging size to prevent total from being empty * * @return Is there a next page? * @deprecated using {@link #getHasNextPage()} ()} */ @Deprecated public boolean hasNextPage() { return getHasNextPage(); } public Boolean getHasNextPage() { if (data == null) { return Boolean.FALSE; } return data.getHasNextPage(); } /** * Return query exception information * * @param errorCode error code * @param errorMessage error message * @param The returned object * @return paging return object */ public static WebPageResult error(String errorCode, String errorMessage) { WebPageResult result = new WebPageResult<>(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = Boolean.FALSE; return result; } /** * Determine whether data exists * * @param pageResult * @return whether data exists */ public static boolean hasData(WebPageResult pageResult) { return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null && pageResult.getData().getData() != null && !pageResult.getData().getData().isEmpty(); } /** * Convert the current type to another type * * @param mapper conversion method * @param Return type * @return paging return object */ public WebPageResult map(Function mapper) { List returnData = hasData(this) ? getData().getData().stream().map(mapper).collect(Collectors.toList()) : Collections.emptyList(); WebPageResult pageResult = new WebPageResult<>(); pageResult.setSuccess(getSuccess()); pageResult.setErrorCode(getErrorCode()); pageResult.setErrorMessage(getErrorMessage()); pageResult.setTraceId(getTraceId()); // Reset a paging information Page page = new Page<>(); pageResult.setData(page); page.setData(returnData); page.setPageNo(data.getPageNo()); page.setPageSize(data.getPageSize()); page.setTotal(data.getTotal()); return pageResult; } @Override public boolean success() { return success; } @Override public void success(boolean success) { this.success = success; } @Override public String errorCode() { return errorCode; } @Override public void errorCode(String errorCode) { this.errorCode = errorCode; } @Override public String errorMessage() { return errorMessage; } @Override public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public void errorDetail(String errorDetail) { this.errorDetail = errorDetail; } @Override public String errorDetail() { return errorDetail; } @Override public void solutionLink(String solutionLink) { this.solutionLink = solutionLink; } @Override public String solutionLink() { return solutionLink; } /** * Pagination information * * @param */ @Data public static class Page { /** * Data information */ private List data; /** * Page coding */ private Integer pageNo; /** * Paging Size */ private Integer pageSize; /** * Total */ private Long total; /** * Is there a next page? */ private Boolean hasNextPage; public Page() { this.pageNo = 1; this.pageSize = 10; this.total = 0L; } private Page(List data, Long total, Long pageNo, Long pageSize) { this(); this.data = data; this.total = total; if (pageNo != null) { this.pageNo = Math.toIntExact(pageNo); } if (pageSize != null) { this.pageSize = Math.toIntExact(pageSize); } } private Page(List data, Long total, Integer pageNo, Integer pageSize) { this(); this.data = data; this.total = total; if (pageNo != null) { this.pageNo = pageNo; } if (pageSize != null) { this.pageSize = pageSize; } } public Boolean getHasNextPage() { if (hasNextPage == null) { hasNextPage = calculateHasNextPage(); } return hasNextPage; } /** * Determine whether there is a next page * Calculated based on paging size to prevent total from being empty * * @return Is there a next page? */ public Boolean calculateHasNextPage() { // There is a paging size calculated based on the paging if (total > 0) { return (long)pageSize * pageNo <= total; } // No data, definitely no next page if (data == null || data.isEmpty()) { return false; } // The current number is less than the number of pages return data.size() >= pageSize; } } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml ================================================ ai.chat2db chat2db-server-tools ${revision} ../pom.xml 4.0.0 chat2db-server-tools-common jar chat2db-server-tools-common ai.chat2db chat2db-server-tools-base org.apache.commons commons-lang3 org.apache.commons commons-collections4 org.slf4j slf4j-api com.google.guava guava com.alibaba.fastjson2 fastjson2 cn.hutool hutool-all org.hibernate.validator hibernate-validator org.mapstruct mapstruct org.mapstruct mapstruct-processor org.springframework.boot spring-boot-starter org.zalando logbook-spring-boot-starter org.springframework spring-context-indexer com.baomidou mybatis-plus ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/Chat2dbProperties.java ================================================ package ai.chat2db.server.tools.common.config; import ai.chat2db.server.tools.common.enums.ModeEnum; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author moji * @version SystemProperties.java, v 0.1 November 13, 2022 14:28 moji Exp $ * @date 2022/11/13 */ @Configuration @ConfigurationProperties(prefix = "chat2db") @Data public class Chat2dbProperties { /** * version */ private String version; /** * gateway */ private GatewayProperties gateway; /** * mode */ private ModeEnum mode; @Data public static class GatewayProperties { private String baseUrl; private String modelBaseUrl; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/GlobalDict.java ================================================ package ai.chat2db.server.tools.common.config; import ai.chat2db.server.tools.common.util.ConfigUtils; import java.io.File; import java.util.Arrays; import java.util.List; /** * global dictionary * * @author lzy */ public interface GlobalDict { /** * template file **/ List TEMPLATE_FILE = Arrays.asList("template.html", "template_diy.docx", "sub_template_diy.docx"); /** * Template storage directory **/ String templateDir = ConfigUtils.CONFIG_BASE_PATH + File.separator + "template" + File.separator; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums/ModeEnum.java ================================================ package ai.chat2db.server.tools.common.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * model * * @author Jiaju Zhuang */ @Getter public enum ModeEnum implements BaseEnum { /** * DESKTOP */ DESKTOP("DESKTOP"), /** * WEB */ WEB("WEB"), ; final String description; ModeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/ConnectionException.java ================================================ package ai.chat2db.server.tools.common.exception; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; @Getter public class ConnectionException extends BusinessException { public ConnectionException() { this("connection.error"); } public ConnectionException(String code) { this(code, null); } public ConnectionException(String code, Object[] args) { super(code,args); } public ConnectionException(String code, Object[] args, Throwable throwable) { super(code,args, throwable); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/DataAlreadyExistsBusinessException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * Data already exists exception * * @author Jiaju Zhuang */ @Getter public class DataAlreadyExistsBusinessException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; public DataAlreadyExistsBusinessException() { super("common.dataAlreadyExists"); } public DataAlreadyExistsBusinessException(String key, Object value) { super("common.dataAlreadyExistsWithParam", new Object[] {key, value}); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/DataNotFoundException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * Data not found exceptions * * @author Jiaju Zhuang */ @Getter public class DataNotFoundException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; public DataNotFoundException() { super("common.dataNotFound"); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/NeedLoggedInBusinessException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * User login exception * * @author Jiaju Zhuang */ @Getter public class NeedLoggedInBusinessException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; public NeedLoggedInBusinessException() { super("common.needLoggedIn"); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/ParamBusinessException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * Parameter exceptions * * @author Jiaju Zhuang */ @Getter public class ParamBusinessException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; public ParamBusinessException() { super("common.paramError"); } public ParamBusinessException(String paramString) { super("common.paramDetailError", new Object[] {paramString}); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/PermissionDeniedBusinessException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * Permission Denied * * @author Jiaju Zhuang */ @Getter public class PermissionDeniedBusinessException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; public PermissionDeniedBusinessException() { super("common.permissionDenied"); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/exception/RedirectBusinessException.java ================================================ package ai.chat2db.server.tools.common.exception; import java.io.Serial; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.Getter; /** * Business exceptions that require redirection * * @author Jiaju Zhuang */ @Getter public class RedirectBusinessException extends BusinessException { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; private final String redirect; public RedirectBusinessException(String redirect) { super("common.redirect"); this.redirect = redirect; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java ================================================ package ai.chat2db.server.tools.common.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Configuration information for chat2db * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ConfigJson { /** * Last successfully launched version */ private String latestStartupSuccessVersion; /** * jwt */ private String jwtSecretKey; /** * The unique ID of the system */ private String systemUuid; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/Context.java ================================================ package ai.chat2db.server.tools.common.model; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * contextual information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Context implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /*** * User Info */ private LoginUser loginUser; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java ================================================ package ai.chat2db.server.tools.common.model; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.common.util.EasySqlUtils; import com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper; import com.baomidou.mybatisplus.core.conditions.SharedString; import com.baomidou.mybatisplus.core.conditions.query.Query; import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.toolkit.Assert; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.apache.commons.collections4.CollectionUtils; import static com.baomidou.mybatisplus.core.enums.SqlKeyword.EQ; import static com.baomidou.mybatisplus.core.enums.SqlKeyword.ORDER_BY; /** * Custom query wrapper * * @author Jiaju Zhuang */ public class EasyLambdaQueryWrapper extends AbstractLambdaWrapper> implements Query, T, SFunction> { public void orderBy(List orderByList) { if (CollectionUtils.isEmpty(orderByList)) { return; } for (OrderBy orderBy : orderByList) { appendSqlSegments(ORDER_BY, EasySqlUtils.columnToSqlSegment(orderBy.getOrderConditionName()), EasySqlUtils.parseOrderBy(orderBy.getDirection())); } } public EasyLambdaQueryWrapper eqWhenPresent(SFunction column, Object val) { if (val != null) { addCondition(true, column, EQ, val); } return typedThis; } public EasyLambdaQueryWrapper likeWhenPresent(SFunction column, Object val) { if (val != null) { return like(true, column, val); } return typedThis; } public EasyLambdaQueryWrapper inWhenPresent(SFunction column, Collection coll) { if (coll != null) { return in(true, column, coll); } return typedThis; } // The following are the methods that come with the system /** * Query field */ private SharedString sqlSelect = new SharedString(); public EasyLambdaQueryWrapper() { this((T)null); } public EasyLambdaQueryWrapper(T entity) { super.setEntity(entity); super.initNeed(); } public EasyLambdaQueryWrapper(Class entityClass) { super.setEntityClass(entityClass); super.initNeed(); } EasyLambdaQueryWrapper(T entity, Class entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq, Map paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias, SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) { super.setEntity(entity); super.setEntityClass(entityClass); this.paramNameSeq = paramNameSeq; this.paramNameValuePairs = paramNameValuePairs; this.expression = mergeSegments; this.sqlSelect = sqlSelect; this.paramAlias = paramAlias; this.lastSql = lastSql; this.sqlComment = sqlComment; this.sqlFirst = sqlFirst; } /** * SELECT some SQL settings * * @param columns query fields */ @SafeVarargs @Override public final EasyLambdaQueryWrapper select(SFunction... columns) { return select(Arrays.asList(columns)); } public EasyLambdaQueryWrapper select(List> columns) { if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(columns)) { this.sqlSelect.setStringValue(columnsToString(false, columns)); } return typedThis; } /** * Filter the field information of the query (except primary key!) *

Example 1: As long as the java field name starts with "test" -> select(i -> i.getProperty().startsWith("test"))

*

Example 2: As long as the java field attribute is of type CharSequence -> select(TableFieldInfo::isCharSequence)

*

Example 3: As long as the java field does not have a filling strategy -> select(i -> i.getFieldFill() == FieldFill.DEFAULT)

*

Example 4: Want all fields -> select(i -> true)

*

Example 5: As long as the primary key field -> select(i -> false)

* * @param predicate filtering method * @return this */ @Override public EasyLambdaQueryWrapper select(Class entityClass, Predicate predicate) { if (entityClass == null) { entityClass = getEntityClass(); } else { setEntityClass(entityClass); } Assert.notNull(entityClass, "entityClass can not be null"); this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(entityClass).chooseSelect(predicate)); return typedThis; } @Override public String getSqlSelect() { return sqlSelect.getStringValue(); } /** * Used to generate nested sql *

Therefore sqlSelect does not pass down

*/ @Override protected EasyLambdaQueryWrapper instance() { return new EasyLambdaQueryWrapper<>(getEntity(), getEntityClass(), null, paramNameSeq, paramNameValuePairs, new MergeSegments(), paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString()); } @Override public void clear() { super.clear(); sqlSelect.toNull(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/IntegerWrapper.java ================================================ package ai.chat2db.server.tools.common.model; import java.io.Serializable; /** * Plastic encapsulation class * * @author Shi Yi */ public class IntegerWrapper extends Number implements Serializable { private static final long serialVersionUID = 1L; private int value; public IntegerWrapper(int initialValue) { value = initialValue; } public IntegerWrapper() { } public final int get() { return value; } public final void set(int newValue) { value = newValue; } public final int getAndIncrement() { return getAndAdd(1); } public final int getAndDecrement() { return getAndAdd(-1); } public final int getAndAdd(int delta) { int oldValue = value; value += delta; return oldValue; } public final int incrementAndGet() { return addAndGet(1); } public final int decrementAndGet() { return addAndGet(-1); } public final int addAndGet(int delta) { value += delta; return value; } public final void increment() { add(1); } public final void decrement() { add(-1); } public final void add(int delta) { value += delta; } @Override public int intValue() { return get(); } @Override public long longValue() { return get(); } @Override public float floatValue() { return get(); } @Override public double doubleValue() { return get(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java ================================================ package ai.chat2db.server.tools.common.model; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Login user information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class LoginUser implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * user id */ private Long id; /** * nick name */ private String nickName; /** * Is it an administrator */ private Boolean admin; /** * role coding * * @see RoleCodeEnum */ private String roleCode; private String token; } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java ================================================ package ai.chat2db.server.tools.common.util; import ai.chat2db.server.tools.common.model.ConfigJson; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.UUID; import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.util.Optional; /** * Configure information on the user side * * @author Jiaju Zhuang */ @Slf4j public class ConfigUtils { public static String CONFIG_BASE_PATH = System.getProperty("user.home") + File.separator + ".chat2db"; public static final String APP_PATH = getAppPath(); private static String version = null; public static File versionFile = null; public static File configFile; private static ConfigJson config = null; public static File clientIdFile; private static String clientId = null; static { String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); if (APP_PATH != null) { versionFile = new File( getAppPath() + File.separator + "versions" + File.separator + "version"); if (!versionFile.exists()) { versionFile = null; } } configFile = new File( CONFIG_BASE_PATH + File.separator + "config" + File.separator + "config_" + environment + ".json"); if (!configFile.exists()) { FileUtil.writeUtf8String(JSON.toJSONString(new ConfigJson()), configFile); } clientIdFile = new File( CONFIG_BASE_PATH + File.separator + "config" + File.separator + "client_uuid"); if (!clientIdFile.exists()) { String uuid = UUID.fastUUID().toString(true); FileUtil.writeUtf8String(uuid, clientIdFile); clientId = uuid; } } public static void updateVersion(String version) { if (versionFile == null) { log.warn("VERSION_FILE is null"); return; } FileUtil.writeUtf8String(version, versionFile); ConfigUtils.version = version; } public static String getLocalVersion() { if (versionFile == null) { log.warn("VERSION_FILE is null"); return null; } if (version != null) { return version; } version = StringUtils.trim(FileUtil.readUtf8String(versionFile)); return version; } public static String getLatestLocalVersion() { if (versionFile == null) { log.warn("VERSION_FILE is null"); return null; } return StringUtils.trim(FileUtil.readUtf8String(versionFile)); } public static ConfigJson getConfig() { if (config == null) { config = JSON.parseObject(StringUtils.trim(FileUtil.readUtf8String(configFile)), ConfigJson.class); } return config; } public static String getClientId() { if (clientId == null) { clientId = StringUtils.trim(FileUtil.readUtf8String(clientIdFile)); } return clientId; } public static void setConfig(ConfigJson config) { String stringConfigJson = JSON.toJSONString(config); FileUtil.writeUtf8String(stringConfigJson, configFile); ConfigUtils.config = config; log.info("set config:{}", stringConfigJson); } private static String getAppPath() { try { String jarPath = System.getProperty("project.path"); return FileUtil.getParent(jarPath, 4); } catch (Exception e) { log.error("getAppPath error", e); return null; } } public static void initProcess() { try { ProcessHandle currentProcess = ProcessHandle.current(); long pid = currentProcess.pid(); String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); File pidFile = new File(CONFIG_BASE_PATH + File.separator + "config" + File.separator + environment + "app.pid"); if (!pidFile.exists()) { FileUtil.writeUtf8String(String.valueOf(pid), pidFile); } else { String oldPid = FileUtil.readUtf8String(pidFile); log.info("oldPid:{}", oldPid); if (StringUtils.isNotBlank(oldPid)) { Optional processHandle = ProcessHandle.of(Long.parseLong(oldPid)); //log.error("processHandle:{}", JSON.toJSONString(processHandle)); processHandle.ifPresent(handle -> { ProcessHandle.Info info = handle.info(); String[] arguments = info.arguments().orElse(null); log.info("arguments:{}", JSON.toJSONString(arguments)); if (arguments == null) { return; } for (String argument : arguments) { if (StringUtils.equals("chat2db-server-start.jar", argument)) { handle.destroy(); log.info("destroy old process--------"); break; } if (argument.contains("Application")) { handle.destroy(); log.info("destroy old process--------"); break; } } }); } FileUtil.writeUtf8String(String.valueOf(pid), pidFile); } } catch (Exception e) { log.error("updatePid error", e); } } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ContextUtils.java ================================================ package ai.chat2db.server.tools.common.util; import ai.chat2db.server.tools.common.exception.NeedLoggedInBusinessException; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import lombok.extern.slf4j.Slf4j; /** * Context tool class * * @author Jiaju Zhuang */ @Slf4j public class ContextUtils { /** * Store context */ private static final ThreadLocal CONTEXT_THREAD_LOCAL = new ThreadLocal<>(); /** * Get user id * * @return */ public static Long getUserId() { return getLoginUser().getId(); } /** * Get user information * * @return may return empty */ public static LoginUser queryLoginUser() { // Go to get login information Context context = queryContext(); if (context == null) { return null; } if (context.getLoginUser() == null) { return null; } return context.getLoginUser(); } /** * Get user information * * @return If it cannot be obtained, a re-login exception will be thrown. */ public static LoginUser getLoginUser() { // Go to get login information Context context = queryContext(); if (context != null && context.getLoginUser() != null) { return context.getLoginUser(); } // Determine that the user must log in throw new NeedLoggedInBusinessException(); } /** * query context * * @return The interceptor of SaTokenWebMvcConfigurer, when called elsewhere, at least a Context will be returned, and there will be at least tokenValue in it. */ public static Context queryContext() { return CONTEXT_THREAD_LOCAL.get(); } /** * Set context * * @param context * @return */ public static void setContext(Context context) { CONTEXT_THREAD_LOCAL.set(context); } /** * remove context */ public static void removeContext() { CONTEXT_THREAD_LOCAL.remove(); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyBooleanUtils.java ================================================ package ai.chat2db.server.tools.common.util; /** * Boolean tool class * * @author Jiaju Zhuang */ public class EasyBooleanUtils { /** * Determine whether two Boolean values are the same * * @param b1 * @param b2 * @param defaultValue Default value, assuming that b1 and b2 are empty, which default value should be taken? * @return */ public static boolean equals(Boolean b1, Boolean b2, Boolean defaultValue) { if (b1 == b2) { return true; } if (b1 == null) { b1 = defaultValue; } if (b2 == null) { b2 = defaultValue; } return b1 == b2; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyCollectionUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import cn.hutool.db.meta.Table; import org.apache.commons.lang3.ArrayUtils; import org.springframework.boot.autoconfigure.security.SecurityProperties; /** * Collection tool class * * @author Jiaju Zhuang */ public class EasyCollectionUtils { /** * Collection stream * * @param collection collection * @param Return type * @return the stream of the collection */ public static Stream stream(Collection collection) { return collection != null ? collection.stream() : Stream.empty(); } /** * Return the first element. If there is none, return empty. * * @param collection collection * @param data type * @return Returns the first element, which may be empty */ public static T findFirst(Collection collection) { return stream(collection) .findFirst() .orElse(null); } /** * Convert a collection into a list *

* Will filter the empty data before and after conversion in the collection, so the input and output parameters will be inconsistent. * * @param collection collection * @param function conversion function * @param Data type before conversion * @param Data type after conversion * @return list If the input parameter is empty, an empty array will be returned and cannot be modified. */ public static List toList(Collection collection, Function function) { return stream(collection) .filter(Objects::nonNull) .map(function) .filter(Objects::nonNull) .collect(Collectors.toList()); } /** * Convert a collection into a set *

* Will filter out empty data before and after conversion in the collection * * @param collection collection * @param function conversion function * @param Data type before conversion * @param Data type after conversion * @return list If the input parameter is empty, an empty array will be returned and cannot be modified. */ public static Set toSet(Collection collection, Function function) { return stream(collection) .filter(Objects::nonNull) .map(function) .filter(Objects::nonNull) .collect(Collectors.toSet()); } /** * Convert a set into a map. If there is a key conflict, the second one will prevail. * * @param collection collection * @param keyFunction keyFunction * @param valueFunction valueFunction * @param key data type * @param value data type * @param Data type before conversion * @return Convert to future map */ public static Map toMap(Collection collection, Function keyFunction, Function valueFunction) { return stream(collection) .filter(Objects::nonNull) .collect(Collectors.toMap(keyFunction, valueFunction, (oldValue, newValue) -> newValue)); } /** * Convert a set into a map. The value of the map is the value of the set. If there is a key conflict, the second one shall prevail. * * @param collection collection * @param keyFunction keyFunction * @param key data type * @param Data type before conversion * @return Convert to future map */ public static Map toIdentityMap(Collection collection, Function keyFunction) { return toMap(collection, keyFunction, Function.identity()); } /** * Add another set to a set * * @param collection original collection * @param collectionAdd The collection to be added * @param * @return whether data has been added */ public static boolean addAll(final Collection collection, final Collection collectionAdd) { if (collectionAdd == null) { return false; } return collection.addAll(collectionAdd); } /** * Determine if the length of a set is 0 but not null * * @param collection collection * @return */ public static boolean isEmptyButNotNull(final Collection collection) { return collection != null && collection.isEmpty(); } /** * Determine whether there is an array with a length of 0 but not null in a bunch of collections * * @param collections returns false if it is empty * @return */ public static boolean isAnyEmptyButNotNull(final Collection... collections) { if (ArrayUtils.isEmpty(collections)) { return false; } for (final Collection collection : collections) { if (isEmptyButNotNull(collection)) { return true; } } return false; } /** * Add an object to the collection * @param collection original collection * @param objectAdd the object to be added * @param */ public static void add(Collection collection, T objectAdd) { if(Objects.isNull(objectAdd)){ return; } collection.add(objectAdd); } /** * Deduplication based on specified field collection * @param collection original collection * @param keyFunction keyFunction * @param * @param * @return the collection after deduplication */ public static List distinctByKey(Collection collection, Function keyFunction){ return stream(collection).filter(distinctByKey(keyFunction)).collect(Collectors.toList()); } static Predicate distinctByKey(Function keyExtractor) { Map seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } public static List union(List list1, List list2) { ArrayList result = new ArrayList(); if(list1 != null && list1.size()>0) { result.addAll(list1); } if(list2!= null && list2.size()>0) { result.addAll(list2); } return result; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyEnumUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.tools.base.enums.BaseEnum; /** * enum tool class *

* Mainly to solve the problem of each enumeration, * you need to write a function to get the value according to the code, * which does not seem very friendly. * * @author Jiaju Zhuang */ public class EasyEnumUtils { /** * Enumeration cache does not need to loop to read the enumeration every time */ private static final Map>> ENUM_CACHE = new ConcurrentHashMap<>(); /** * Get the description of the enumeration based on an enumeration type * * @param clazz enumeration class * @param code Enumeration encoding * @param The type of enumeration * @return If the code cannot be found, the return value is empty. */ public static > String getDescription(final Class clazz, final String code) { BaseEnum baseEnum = getEnum(clazz, code); if (baseEnum == null) { return null; } return baseEnum.getDescription(); } /** * Get the description of the enumeration based on an enumeration type * * @param clazz enumeration class * @param code Enumeration encoding * @param The type of enumeration * @return If the code cannot be found, the return value is empty. */ public static > T getEnum(final Class clazz, final String code) { return getEnumMap(clazz).get(code); } /** * Verify whether it is a valid enumeration * * @param clazz enumeration class * @param code the encoding of the enumeration, null is also considered a valid enumeration * @param The type of enumeration * @return Is it valid? */ public static > boolean isValidEnum(final Class clazz, final String code) { return isValidEnum(clazz, code, true); } /** * Verify whether it is a valid enumeration * * @param clazz enumeration class * @param code The encoding of the enumeration. If it is empty, it is considered an invalid enumeration. * @param ignoreNull whether to ignore empty codes * @param The type of enumeration * @return Is it valid? */ public static > boolean isValidEnum(final Class clazz, final String code, final boolean ignoreNull) { if (code == null) { return ignoreNull; } return getEnumMap(clazz).containsKey(code); } /** * Get the map of an enumerated code Enum * * @param clazz enumeration class * @param The type of enumeration * @return Map */ public static > Map getEnumMap(final Class clazz) { String className = clazz.getName(); Map> result = ENUM_CACHE.computeIfAbsent(className, value -> { T[] baseEnums = clazz.getEnumConstants(); return Arrays.stream(baseEnums) .collect(Collectors.toMap(BaseEnum::getCode, Function.identity())); }); return (Map)result; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java ================================================ package ai.chat2db.server.tools.common.util; public class EasyIntegerUtils { /** * Determine whether two Boolean values are the same * * @param b1 * @param b2 * @param defaultValue default value, * assuming that b1 b2 is empty, * which default value should be taken? * @return */ public static boolean equals(Integer b1, Integer b2, Integer defaultValue) { if (b1 == b2) { return true; } if (b1 == null) { b1 = defaultValue; } if (b2 == null) { b2 = defaultValue; } return b1 == b2; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyOptionalUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.Optional; import java.util.function.Function; /** * Optional tool class * * @author Jiaju Zhuang */ public class EasyOptionalUtils { /** * Get the value of an object that may not be null * * @param source original object * @param function conversion method * @param * @param * @return Return value If empty, return null */ public static R mapTo(T source, Function function) { return mapTo(source, function, null); } /** * Get the value of an object that may not be null * * @param source original object * @param function conversion method * @param defaultValue default value * @param * @param * @return return value */ public static R mapTo(T source, Function function, R defaultValue) { return Optional.ofNullable(source).map(function).orElse(defaultValue); } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasySqlUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.Arrays; import java.util.List; import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import com.baomidou.mybatisplus.core.conditions.ISqlSegment; import com.baomidou.mybatisplus.core.conditions.segments.ColumnSegment; import com.baomidou.mybatisplus.core.conditions.segments.OrderBySegmentList; import com.baomidou.mybatisplus.core.enums.SqlKeyword; import org.apache.commons.collections4.CollectionUtils; import static com.baomidou.mybatisplus.core.enums.SqlKeyword.ASC; import static com.baomidou.mybatisplus.core.enums.SqlKeyword.DESC; /** * sql utils * * @author Jiaju Zhuang */ public class EasySqlUtils { public static String orderBy(List orderByList) { if (CollectionUtils.isEmpty(orderByList)) { return null; } OrderBySegmentList orderBySegmentList = new OrderBySegmentList(); for (OrderBy orderBy : orderByList) { orderBySegmentList.addAll( Arrays.asList(SqlKeyword.ORDER_BY, columnToSqlSegment(orderBy.getOrderConditionName()), parseOrderBy(orderBy.getDirection()))); } return orderBySegmentList.getSqlSegment(); } /** * get columnName */ public static ColumnSegment columnToSqlSegment(String column) { return () -> column; } public static ISqlSegment parseOrderBy(OrderByDirectionEnum direction) { if (direction == OrderByDirectionEnum.ASC) { return ASC; } return DESC; } public static String buildLikeRightFuzzy(String param) { if (param == null) { return null; } return param + "%"; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyStringUtils.java ================================================ package ai.chat2db.server.tools.common.util; import com.google.common.base.Strings; import com.google.common.collect.Maps; import jakarta.validation.constraints.NotNull; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; import java.util.stream.Collectors; /** * String utility class * * @author Jiaju Zhuang */ public class EasyStringUtils { /** * 0 characters */ private static final char ZERO_CHAR = '0'; /** * Remove the 0 in front of the job number * * @param userId employee ID * @return modified job number */ public static String cutUserId(String userId) { if (!StringUtils.isNumeric(userId)) { return userId; } int startIndex = 0; for (int i = 0; i < userId.length(); i++) { char c = userId.charAt(i); // Query the first position that is not 0 if (ZERO_CHAR == c) { startIndex = i + 1; } else { break; } } // Maybe the entire account is 0 if (startIndex == userId.length()) { return "0"; } return userId.substring(startIndex); } /** * Remove the job number after the flower name * * @param name name or nickname * @return the name or nickname after removing the work number */ public static String cutName(String name, String workNo) { if (StringUtils.isBlank(workNo) || StringUtils.isBlank(name)) { return name; } // There may be 0 knots here String cutName = RegExUtils.removeFirst(name, workNo); int lastIndex = cutName.length(); for (int i = cutName.length() - 1; i >= 0; i--) { char c = cutName.charAt(i); // Query the last position that is not 0 if (ZERO_CHAR == c) { lastIndex = i; } else { break; } } return cutName.substring(0, lastIndex); } /** * Add 0 in front of the job number * * @param userId employee ID * @return modified job number */ public static String padUserId(String userId) { if (!StringUtils.isNumeric(userId)) { return userId; } return StringUtils.leftPad(userId, 6, '0'); } /** * Build the name of the display * * @param name name * @param nickName flower name * @return display name name (flower name) */ public static String buildShowName(String name, String nickName) { StringBuilder showName = new StringBuilder(); if (StringUtils.isNotBlank(name)) { showName.append(name); } if (StringUtils.isNotBlank(nickName)) { showName.append("("); showName.append(nickName); showName.append(")"); } return showName.toString(); } /** * Splice multiple strings together * * @param delimiter delimiter cannot be empty * @param elements string can be empty and empty strings will be ignored * @return */ public static String join(CharSequence delimiter, CharSequence... elements) { if (elements == null) { return null; } List charSequenceList = Arrays.stream(elements).filter( org.apache.commons.lang3.StringUtils::isNotBlank).collect(Collectors.toList()); if (charSequenceList.isEmpty()) { return null; } return String.join(delimiter, charSequenceList); } /** * Limit the length of a string string. If it exceeds the length, it will be replaced with... * * @param str string * @param length limit length * @return */ public static String limitString(String str, int length) { if (Objects.isNull(str)) { return null; } String limitString = StringUtils.substring(str, 0, length); if (limitString.length() == length) { limitString += "..."; } return limitString; } /** * 对字符串中的特定字符进行转义处理。 *

* 根据提供的转义映射表,该方法遍历输入字符串中的每个字符,如果字符存在于映射表中, * 则在该字符前插入映射表中对应的转义字符,否则保持字符不变。这常用于创建符合特定格式要求的字符串, * 比如SQL查询中的字符串转义,或为文本添加特殊标记等。 * * @param str 目标字符串,需要进行转义处理的原始字符串。 * @param escapeMap 映射关系表,键为需要被转义的字符,值为该字符前应添加的转义字符。 * 例如,如果希望转义单引号('),可以传入Map中键为单引号,值也为单引号的映射。 * @return 转义后的字符串,含有特定字符前添加了转义符的副本。 */ public static String escapeString(@NotNull String str, Map escapeMap) { if (str == null) { return null; } if (StringUtils.isBlank(str)) { return str; } StringBuilder escapedString = new StringBuilder(str.length() * 2); for (char c : str.toCharArray()) { if (escapeMap.containsKey(c)) { escapedString.append(escapeMap.get(c)).append(c); } else { escapedString.append(c); } } return escapedString.toString(); } public static String escapeString(String str) { HashMap escapeMap = Maps.newHashMapWithExpectedSize(2); // (char)39 -> ' escapeMap.put((char) 39, (char) 39); // (char)92 -> \ escapeMap.put((char) 92, (char) 92); return escapeString(str, escapeMap); } /** * @param value str="'abc\" * @return "'''abc\\'" */ public static String escapeAndQuoteString(String value) { return quoteString(escapeString(value)); } /** * @param value "abcd" * @param quoteChar '%' * @return "%abcd%" */ public static String quoteString(String value, char quoteChar) { return quoteChar + value + quoteChar; } /** * @param value "abcd" * @return "'abcd'" */ public static String quoteString(String value) { // (char)39 -> ' return quoteString(value, (char) 39); } public static String getBitString(byte[] bytes, final int precision) { if (bytes == null || bytes.length == 0) { return ""; } StringBuilder builder = new StringBuilder(precision); for (byte b : bytes) { builder.append(Integer.toBinaryString(b & 0xFF)); } // 获取完整的二进制字符串 String bitString = builder.toString(); // 填充前导零以匹配所需的总长度 bitString = Strings.padStart(bitString, precision, '0'); return bitString; } public static String escapeLineString(String str) { if (StringUtils.isBlank(str)) { return str; } return str; // TODO Need to be implemented in the future with different data types // return str.replace("\r\n", "\\r\\n") // .replace("\n", "\\n") // .replace("\r", "\\r"); } public static String sqlEscape(String str) { if (StringUtils.isBlank(str)) { return str; } str = str.trim(); if (str.endsWith(";")) { str = str.substring(0, str.length() - 1); } return str; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.Locale; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.NoSuchMessageException; import org.springframework.context.annotation.Lazy; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; /** * i18n utility * * @author Jiaju Zhuang */ @Slf4j @Component @Lazy(value = false) public class I18nUtils implements InitializingBean { public static final String DEFAULT_MESSAGE_CODE="common.systemError"; @Resource private MessageSource messageSource; private static MessageSource messageSourceStatic; public static String getMessage(String messageCode) { return getMessage(messageCode, null); } public static String getMessage(String messageCode, @Nullable Object[] args) { try { return messageSourceStatic.getMessage(messageCode, args, LocaleContextHolder.getLocale()); } catch (NoSuchMessageException e) { log.error("no message.", e); } return messageSourceStatic.getMessage(DEFAULT_MESSAGE_CODE, args, LocaleContextHolder.getLocale()); } /** * Is it in English? * * @return */ public static Boolean isEn() { return LocaleContextHolder.getLocale().equals(Locale.US); } @Override public void afterPropertiesSet() throws Exception { messageSourceStatic = messageSource; } } ================================================ FILE: chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java ================================================ package ai.chat2db.server.tools.common.util; import java.util.Objects; import java.util.regex.Pattern; import cn.hutool.core.lang.UUID; import cn.hutool.core.net.NetUtil; import org.apache.commons.lang3.StringUtils; import org.zalando.logbook.HttpHeaders; import org.zalando.logbook.HttpRequest; /** * Log utility * * @author Jiaju Zhuang */ public class LogUtils { private static final ThreadLocal TRACE_ID_THREAD_LOCAL = new ThreadLocal<>(); /** * Request headers for client IPs */ private static final String[] CLIENT_IP_HEADERS = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; /** * Maximum log length */ public static final int MAX_LOG_LENGTH = 20000; public static final String TRACE_ID = "TRACE_ID"; public static final String TRACE_ID_HEADER = "X-Chat2DB-Trace-Id"; /** * newline character */ private static final Pattern LINE_FEED_PATTERN = Pattern.compile("\r|\n"); /** * mask string * * @param input * @return */ public static String maskString(String input) { if (StringUtils.isBlank(input)) { return input; } StringBuilder maskedString = new StringBuilder(input); for (int i = 0; i < input.length(); i += 4) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); } /** * Remove newlines * * @param log * @return */ public static String removeCrlf(String log) { if (Objects.isNull(log)) { return null; } return LINE_FEED_PATTERN.matcher(log).replaceAll(""); } /** * cut log * * @param log * @return */ public static String cutLog(Object log) { if (Objects.isNull(log)) { return null; } return EasyStringUtils.limitString(removeCrlf(log.toString()), MAX_LOG_LENGTH); } /** * Return traceId * * @return */ public static String generateTraceId() { String traceId = UUID.fastUUID().toString().replaceAll("-", ""); TRACE_ID_THREAD_LOCAL.set(traceId); return traceId; } /** * Gets the trace ID * * @return */ public static String getTraceId() { return TRACE_ID_THREAD_LOCAL.get(); } /** * Remove the trace ID * * @return */ public static void removeTraceId() { TRACE_ID_THREAD_LOCAL.remove(); } /** * Obtain the client IP * * @param request * @return */ public static String getClientIp(HttpRequest request) { HttpHeaders httpHeaders = request.getHeaders(); String ip; for (String header : CLIENT_IP_HEADERS) { ip = httpHeaders.getFirst(header); if (!NetUtil.isUnknown(ip)) { return NetUtil.getMultistageReverseProxyIp(ip); } } ip = request.getRemote(); return NetUtil.getMultistageReverseProxyIp(ip); } } ================================================ FILE: chat2db-server/chat2db-server-tools/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-tools pom chat2db-server-tools-base chat2db-server-tools-common ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml ================================================ ai.chat2db chat2db-server-web ${revision} ../pom.xml 4.0.0 chat2db-server-admin-api jar chat2db-server-admin-api ai.chat2db chat2db-server-tools-common ai.chat2db chat2db-server-domain-api ai.chat2db chat2db-server-common-api ai.chat2db chat2db-server-domain-core org.springframework.boot spring-boot-starter-web commons-io commons-io com.dtflys.forest forest-spring com.alibaba easyexcel ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.common; import java.util.List; import ai.chat2db.server.admin.api.controller.common.converter.CommonAdminConverter; import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Some general data queries * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/common") @RestController public class CommonAdminController { private static final DataSourceSelector DATA_SOURCE_SELECTOR = DataSourceSelector.builder() .environment(Boolean.TRUE) .build(); @Resource private UserService userService; @Resource private TeamService teamService; @Resource private DataSourceService dataSourceService; @Resource private CommonAdminConverter commonAdminConverter; /** * Fuzzy query of users or teams * * @param request * @return * @version 2.1.0 */ @GetMapping("/team_user/list") public ListResult teamUserList(@Valid CommonQueryRequest request) { UserPageQueryParam userPageQueryParam = commonAdminConverter.request2paramUser(request); List result = Lists.newArrayList(); result.addAll(userService.pageQuery(userPageQueryParam, null) .mapToList(commonAdminConverter::dto2voTeamUser) .getData()); TeamPageQueryParam teamPageQueryParam = commonAdminConverter.request2paramTeam(request); result.addAll(teamService.pageQuery(teamPageQueryParam, null) .mapToList(commonAdminConverter::dto2voTeamUser) .getData()); return ListResult.of(result); } /** * Fuzzy query of users * * @param request * @return * @version 2.1.0 */ @GetMapping("/user/list") public ListResult userList(@Valid CommonQueryRequest request) { return userService.pageQuery(commonAdminConverter.request2paramUser(request), null) .mapToList(commonAdminConverter::dto2voUser); } /** * Fuzzy query of teams * * @param request * @return * @version 2.1.0 */ @GetMapping("/team/list") public ListResult teamList(@Valid CommonQueryRequest request) { return teamService.pageQuery(commonAdminConverter.request2paramTeam(request), null) .mapToList(commonAdminConverter::dto2voTeam); } /** * Fuzzy query of data source * * @param request * @return * @version 2.1.0 */ @GetMapping("/data_source/list") public ListResult dataSourceList(@Valid CommonQueryRequest request) { return dataSourceService.queryPageWithPermission(commonAdminConverter.request2paramDataSource(request), DATA_SOURCE_SELECTOR) .mapToList(commonAdminConverter::dto2voDataSource); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.common.converter; import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring", imports = {AccessObjectTypeEnum.class, DataSourceKindEnum.class}) public abstract class CommonAdminConverter { /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "pageSize", expression = "java(10)"), }) public abstract TeamPageQueryParam request2paramTeam(CommonQueryRequest request); /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "pageSize", expression = "java(10)"), }) public abstract UserPageQueryParam request2paramUser(CommonQueryRequest request); /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "pageSize", expression = "java(10)"), @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), }) public abstract DataSourcePageQueryParam request2paramDataSource(CommonQueryRequest request); /** * conversion * * @param dto * @return */ public abstract SimpleTeamVO dto2voTeam(Team dto); /** * conversion * * @param dto * @return */ public abstract SimpleDataSourceVO dto2voDataSource(DataSource dto); /** * conversion * * @param dto * @return */ public abstract SimpleUserVO dto2voUser(User dto); /** * conversion * * @param dto * @return */ @Mappings({ @Mapping(target = "type", expression = "java(AccessObjectTypeEnum.TEAM.getCode())"), @Mapping(target = "code", source = "code"), @Mapping(target = "name", source = "name"), }) public abstract TeamUserListVO dto2voTeamUser(Team dto); /** * conversion * * @param dto * @return */ @Mappings({ @Mapping(target = "type", expression = "java(AccessObjectTypeEnum.USER.getCode())"), @Mapping(target = "code", source = "userName"), @Mapping(target = "name", source = "nickName"), }) public abstract TeamUserListVO dto2voTeamUser(User dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java ================================================ package ai.chat2db.server.admin.api.controller.common.request; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** * Common pagination query * * @author Jiaju Zhuang */ @Data public class TeamUserPageQueryRequest extends PageQueryRequest { /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java ================================================ package ai.chat2db.server.admin.api.controller.common.vo; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access Object * It could be a user or a team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamUserListVO implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; /** * The name of the code that belongs to the authorization type, such as user account, team code */ private String code; /** * Code that belongs to the authorization type, such as user name, team name */ private String name; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.datasource; import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAccessAdminConverter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Data Source Access Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/data_source/access") @RestController public class DataSourceAccessAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() .accessObject(Boolean.TRUE) .build(); @Resource private DataSourceAccessService dataSourceAccessService; @Resource private DataSourceAccessAdminConverter dataSourceAccessAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid DataSourceAccessPageQueryRequest request) { return dataSourceAccessService.comprehensivePageQuery(dataSourceAccessAdminConverter.request2param(request), DATA_SOURCE_ACCESS_SELECTOR) .mapToWeb(dataSourceAccessAdminConverter::dto2vo); } /** * batch * * @param request * @return * @version 2.1.0 */ @PostMapping("/batch_create") public ActionResult batchCreate(@Valid @RequestBody DataSourceAccessBatchCreateRequest request) { request.getAccessObjectList() .forEach(accessObject -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() .dataSourceId(request.getDataSourceId()) .accessObjectId(accessObject.getId()) .accessObjectType(accessObject.getType()) .build())); return ActionResult.isSuccess(); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.datasource; import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam.OrderCondition; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Data Source Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/data_source") @RestController public class DataSourceAdminController { private static final DataSourceSelector DATA_SOURCE_SELECTOR = DataSourceSelector.builder() .environment(Boolean.TRUE) .build(); @Resource private DataSourceService dataSourceService; @Resource private DataSourceAdminConverter dataSourceAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { DataSourcePageQueryParam param = dataSourceAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); return dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR) .mapToWeb(dataSourceAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/create") public DataResult create(@Valid @RequestBody DataSourceCreateRequest request) { DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); return dataSourceService.createWithPermission(param); } /** * update * * @param request * @return * @version 2.1.0 */ @PostMapping("/update") public DataResult update(@Valid @RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); return dataSourceService.updateWithPermission(param); } /** * clone * * @param request * @return * @version 2.1.0 */ @PostMapping("/clone") public DataResult clone(@RequestBody DataSourceCloneRequest request) { return dataSourceService.copyByIdWithPermission(request.getId()); } /** * delete * * @param id * @return * @version 2.1.0 */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return dataSourceService.deleteWithPermission(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) public abstract class DataSourceAccessAdminConverter { /** * convert * * @param request * @return */ @Mappings({ @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(DataSourceAccessPageQueryRequest request); /** * convert * * @param request * @return */ public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); /** * conversion * * @param dto * @return */ public abstract DataSourceAccessPageQueryVO dto2vo(DataSourceAccess dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) public abstract class DataSourceAdminConverter { /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "enableReturnCount", expression = "java(true)"), @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), }) public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); /** * conversion * * @param dto * @return */ public abstract DataSourcePageQueryVO dto2vo(DataSource dto); /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "user", target = "userName"), @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), }) public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "user", target = "userName") }) public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * create * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessBatchCreateRequest { /** * Data source id */ @NotNull private Long dataSourceId; /** * DataSource Access Object */ @NotNull private List accessObjectList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access Object * It could be a user or a team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessObjectRequest implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Common pagination query * * @author Jiaju Zhuang */ @Data public class DataSourceAccessPageQueryRequest extends PageQueryRequest { /** * Data source id */ @NotNull private Long dataSourceId; /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import lombok.Data; /** * @author moji * @version ConnectionCloneRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceCloneRequest { /** * primary key id */ private Long id; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceCreateRequest { /** * Connection alias */ private String alias; /** * connection address */ @NotNull private String url; /** * Connect username */ private String user; /** * password */ @NotNull private String password; /** * Certification type */ private String authenticationType; /** * Connection Type */ @NotNull private String type; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * environment id */ @NotNull private Long environmentId; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.request; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceUpdateRequest { /** * primary key id */ @NotNull private Long id; /** * Connection alias */ private String alias; /** * connection address */ private String url; /** * Connect users */ private String user; /** * password */ private String password; /** * Connection Type */ private String type; /** * environment type * * @see EnvTypeEnum */ private String envType; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * environment id */ @NotNull private Long environmentId; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.vo; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * DataSource Access Object * It could be a user or a team * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceAccessObjectVO implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ private String type; /** * The name of the code that belongs to the authorization type, such as user account, team code */ private String code; /** * Code that belongs to the authorization type, such as user name, team name */ private String name; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.vo; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class DataSourceAccessPageQueryVO { /** * primary key */ @NotNull private Long id; /** * Authorization type * * @see AccessObjectTypeEnum */ @NotNull private String accessObjectType; /** * Authorization ID, distinguish whether it is a user or a team according to the type */ @NotNull private Long accessObjectId; /** * Authorization object */ @NotNull private DataSourceAccessObjectVO accessObject; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.vo; import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class DataSourcePageQueryVO { /** * primary key id */ private Long id; /** * Connection alias */ private String alias; /** * connection address */ private String url; /** * environment id */ private Long environmentId; /** * environment */ private SimpleEnvironmentVO environment; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java ================================================ package ai.chat2db.server.admin.api.controller.datasource.vo; import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import lombok.Data; /** * Data Source * * @author Jiaju Zhuang */ @Data public class SimpleDataSourceVO { /** * primary key id */ private Long id; /** * Connection alias */ private String alias; /** * connection address */ private String url; /** * environment id */ private Long environmentId; /** * environment */ private SimpleEnvironmentVO environment; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.team; import ai.chat2db.server.admin.api.controller.team.converter.TeamAdminConverter; import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam.OrderCondition; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Team Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/team") @RestController public class TeamAdminController { private static final TeamSelector TEAM_SELECTOR = TeamSelector.builder() .modifiedUser(Boolean.TRUE) .build(); @Resource private TeamService teamService; @Resource private TeamAdminConverter teamAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { TeamPageQueryParam param = teamAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); return teamService.pageQuery(param, TEAM_SELECTOR) .mapToWeb(teamAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/create") public DataResult create(@RequestBody TeamCreateRequest request) { return teamService.create(teamAdminConverter.request2param(request)); } /** * update * * @param request * @return * @version 2.1.0 */ @PostMapping("/update") public DataResult update(@RequestBody TeamUpdateRequest request) { return teamService.update(teamAdminConverter.request2param(request)); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return teamService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.team; import ai.chat2db.server.admin.api.controller.team.converter.TeamDataSourcesAdminConverter; import ai.chat2db.server.admin.api.controller.team.request.TeamDataSourceBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Team Data Source Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/team/data_source") @RestController public class TeamDataSourceAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() .dataSource(Boolean.TRUE) .dataSourceSelector(DataSourceSelector.builder() .environment(Boolean.TRUE) .build()) .build(); @Resource private DataSourceAccessService dataSourceAccessService; @Resource private TeamDataSourcesAdminConverter teamDataSourcesAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid TeamPageCommonQueryRequest request) { return dataSourceAccessService.comprehensivePageQuery(teamDataSourcesAdminConverter.request2param(request), DATA_SOURCE_ACCESS_SELECTOR) .mapToWeb(teamDataSourcesAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/batch_create") public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest request) { request.getDataSourceIdList() .forEach(dataSourceId -> { DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.TEAM.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(request.getTeamId()); dataSourceAccessPageQueryParam.queryOne(); if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { return; } dataSourceAccessService.create(DataSourceAccessCreatParam.builder() .dataSourceId(dataSourceId) .accessObjectId(request.getTeamId()) .accessObjectType(AccessObjectTypeEnum.TEAM.getCode()) .build()); }); return ActionResult.isSuccess(); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.team; import ai.chat2db.server.admin.api.controller.team.converter.TeamUserAdminConverter; import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUserBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Team User Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/team/user") @RestController public class TeamUserAdminController { private static final TeamUserSelector TEAM_USER_SELECTOR = TeamUserSelector.builder() .user(Boolean.TRUE) .build(); @Resource private TeamUserService teamUserService; @Resource private TeamUserAdminConverter teamUserAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid TeamPageCommonQueryRequest request) { return teamUserService.comprehensivePageQuery(teamUserAdminConverter.request2param(request), TEAM_USER_SELECTOR) .mapToWeb(teamUserAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/batch_create") public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest request) { request.getUserIdList() .forEach(userId -> { TeamUserPageQueryParam teamUserPageQueryParam = new TeamUserPageQueryParam(); teamUserPageQueryParam.setTeamId(request.getTeamId()); teamUserPageQueryParam.setUserId(userId); teamUserPageQueryParam.queryOne(); if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { return; } teamUserService.create(TeamUserCreatParam.builder() .teamId(request.getTeamId()) .userId(userId) .build()); }); return ActionResult.isSuccess(); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return teamUserService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.team.converter; import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) public abstract class TeamAdminConverter { /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract TeamPageQueryParam request2param(CommonPageQueryRequest request); /** * conversion * * @param dto * @return */ public abstract TeamPageQueryVO dto2vo(Team dto); /** * conversion * * @param request * @return */ public abstract TeamCreateParam request2param(TeamCreateRequest request); /** * conversion * * @param request * @return */ public abstract TeamUpdateParam request2param(TeamUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.team.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) public abstract class TeamDataSourcesAdminConverter { /** * convert * * @param request * @return */ @Mappings({ @Mapping(target = "accessObjectId", source = "teamId"), @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.TEAM.name())"), @Mapping(source = "searchKey", target = "dataSourceSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); /** * convert * * @param request * @return */ public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); /** * conversion * * @param dto * @return */ @Mappings({ @Mapping(target = "teamId", source = "accessObjectId"), }) public abstract TeamDataSourcePageQueryVO dto2vo(DataSourceAccess dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.team.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring") public abstract class TeamUserAdminConverter { /** * convert * * @param request * @return */ @Mappings({ @Mapping(source = "searchKey", target = "userSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract TeamUserComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); /** * convert * * @param request * @return */ public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); /** * conversion * * @param dto * @return */ public abstract TeamUserPageQueryVO dto2vo(TeamUser dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.team.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create * * @author Jiaju Zhuang */ @Data public class TeamCreateRequest { /** * team coding */ @NotNull private String code; /** * Team Name */ @NotNull private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ @NotNull private String status; /** * Team description */ private String description; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.team.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * create * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamDataSourceBatchCreateRequest { /** * team id */ @NotNull private Long teamId; /** * Data Source id list */ @NotNull private List dataSourceIdList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java ================================================ package ai.chat2db.server.admin.api.controller.team.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class TeamPageCommonQueryRequest extends PageQueryRequest { /** * team id */ @NotNull private Long teamId; /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.team.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * update * * @author Jiaju Zhuang */ @Data public class TeamUpdateRequest { /** * primary key */ @NotNull private Long id; /** * Team Name */ @NotNull private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ @NotNull private String status; /** * role coding * * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum */ @NotNull private String roleCode; /** * Team description */ private String description; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.team.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * create * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TeamUserBatchCreateRequest { /** * team id */ @NotNull private Long teamId; /** * user id list */ @NotNull private List userIdList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java ================================================ package ai.chat2db.server.admin.api.controller.team.vo; import lombok.Data; /** * team * * @author Jiaju Zhuang */ @Data public class SimpleTeamVO { /** * primary key */ private Long id; /** * team coding */ private String code; /** * Team Name */ private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ private String status; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.team.vo; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class TeamDataSourcePageQueryVO { /** * primary key */ @NotNull private Long id; /** * team id */ private Long teamId; /** * Data Source */ private SimpleDataSourceVO dataSource; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.team.vo; import java.util.Date; import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class TeamPageQueryVO { /** * primary key */ private Long id; /** * team coding */ private String code; /** * Team Name */ private String name; /** * Team status * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ private String status; /** * Team description */ private String description; /** * Change the time */ private Date gmtModified; /** * Modifier user id */ private Long modifiedUserId; /** * Modifier user */ private SimpleUserVO modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.team.vo; import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class TeamUserPageQueryVO { /** * primary key */ private Long id; /** * team id */ private Long teamId; /** * user */ private SimpleUserVO user; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.user; import ai.chat2db.server.admin.api.controller.user.converter.UserAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam.OrderCondition; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * User Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/user") @RestController public class UserAdminController { private static final UserSelector USER_SELECTOR = UserSelector.builder() .modifiedUser(Boolean.TRUE) .build(); @Resource private UserService userService; @Resource private UserAdminConverter userAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { UserPageQueryParam param = userAdminConverter.request2param(request); param.orderBy(OrderCondition.ID_DESC); return userService.pageQuery(param, USER_SELECTOR) .mapToWeb(userAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/create") public DataResult create(@Valid @RequestBody UserCreateRequest request) { return userService.create(userAdminConverter.request2param(request)); } /** * update * * @param request * @return * @version 2.1.0 */ @PostMapping("/update") public DataResult update(@RequestBody UserUpdateRequest request) { return userService.update(userAdminConverter.request2param(request)); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return userService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.user; import ai.chat2db.server.admin.api.controller.user.converter.UserDataSourcesAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserDataSourceBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * User Data Source Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/user/data_source") @RestController public class UserDataSourceAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() .dataSource(Boolean.TRUE) .dataSourceSelector(DataSourceSelector.builder() .environment(Boolean.TRUE) .build()) .build(); @Resource private DataSourceAccessService dataSourceAccessService; @Resource private UserDataSourcesAdminConverter userDataSourcesAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid UserPageCommonQueryRequest request) { return dataSourceAccessService.comprehensivePageQuery(userDataSourcesAdminConverter.request2param(request), DATA_SOURCE_ACCESS_SELECTOR) .mapToWeb(userDataSourcesAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/batch_create") public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request) { request.getDataSourceIdList() .forEach(dataSourceId -> { DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(request.getUserId()); dataSourceAccessPageQueryParam.queryOne(); if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { return; } dataSourceAccessService.create(DataSourceAccessCreatParam.builder() .dataSourceId(dataSourceId) .accessObjectId(request.getUserId()) .accessObjectType(AccessObjectTypeEnum.USER.getCode()) .build()); }); return ActionResult.isSuccess(); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java ================================================ package ai.chat2db.server.admin.api.controller.user; import ai.chat2db.server.admin.api.controller.user.converter.UserTeamAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * User Team Management * * @author Jiaju Zhuang */ @RequestMapping("/api/admin/user/team") @RestController public class UserTeamAdminController { private static final TeamUserSelector TEAM_USER_SELECTOR = TeamUserSelector.builder() .team(Boolean.TRUE) .build(); @Resource private TeamUserService teamUserService; @Resource private UserTeamAdminConverter userTeamAdminConverter; /** * Pagination query * * @param request * @return * @version 2.1.0 */ @GetMapping("/page") public WebPageResult page(@Valid UserPageCommonQueryRequest request) { return teamUserService.comprehensivePageQuery(userTeamAdminConverter.request2param(request), TEAM_USER_SELECTOR) .mapToWeb(userTeamAdminConverter::dto2vo); } /** * create * * @param request * @return * @version 2.1.0 */ @PostMapping("/batch_create") public ActionResult batchCreate(@Valid @RequestBody UserTeamBatchCreateRequest request) { request.getTeamIdList() .forEach(teamId -> { TeamUserPageQueryParam teamUserPageQueryParam = new TeamUserPageQueryParam(); teamUserPageQueryParam.setTeamId(teamId); teamUserPageQueryParam.setUserId(request.getUserId()); teamUserPageQueryParam.queryOne(); if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { return; } teamUserService.create(TeamUserCreatParam.builder() .teamId(teamId) .userId(request.getUserId()) .build()); }); return ActionResult.isSuccess(); } /** * delete * * @param id * @return */ @DeleteMapping("/{id}") public DataResult delete(@PathVariable Long id) { return teamUserService.delete(id).toBooleaSuccessnDataResult(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.user.converter; import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring") public abstract class UserAdminConverter { /** * conversion * * @param request * @return */ @Mappings({ @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract UserPageQueryParam request2param(CommonPageQueryRequest request); /** * conversion * * @param dto * @return */ public abstract UserPageQueryVO dto2vo(User dto); /** * conversion * * @param request * @return */ public abstract UserCreateParam request2param(UserCreateRequest request); /** * conversion * * @param request * @return */ public abstract UserUpdateParam request2param(UserUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.user.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) public abstract class UserDataSourcesAdminConverter { /** * convert * * @param request * @return */ @Mappings({ @Mapping(source = "userId", target = "accessObjectId"), @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.USER.name())"), @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); /** * convert * * @param request * @return */ public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); /** * conversion * * @param dto * @return */ @Mappings({ @Mapping(target = "userId", source = "accessObjectId"), }) public abstract UserDataSourcePageQueryVO dto2vo(DataSourceAccess dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java ================================================ package ai.chat2db.server.admin.api.controller.user.converter; import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * converter * * @author Jiaju Zhuang */ @Mapper(componentModel = "spring") public abstract class UserTeamAdminConverter { /** * convert * * @param request * @return */ @Mappings({ @Mapping(source = "searchKey", target = "teamSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract TeamUserComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); /** * conversion * * @param dto * @return */ public abstract UserTeamPageQueryVO dto2vo(TeamUser dto); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.user.request; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create *@author Jiaju Zhuang */ @Data public class UserCreateRequest { /** * userName */ @NotNull private String userName; /** * password */ @NotNull private String password; /** * Nick name */ @NotNull private String nickName; /** * email */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ @NotNull private String roleCode; /** * user status * * @see ValidStatusEnum */ @NotNull private String status; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.user.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * create * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserDataSourceBatchCreateRequest { /** * user id */ private Long userId; /** * Data Source id list */ @NotNull private List dataSourceIdList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java ================================================ package ai.chat2db.server.admin.api.controller.user.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class UserPageCommonQueryRequest extends PageQueryRequest { /** * user id */ @NotNull private Long userId; /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.user.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * create * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserTeamBatchCreateRequest { /** * user id */ private Long userId; /** * team id list */ @NotNull private List teamIdList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java ================================================ package ai.chat2db.server.admin.api.controller.user.request; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * create *@author Jiaju Zhuang */ @Data public class UserUpdateRequest { /** * primary key */ @NotNull private Long id; /** * password */ private String password; /** * Nick name */ @NotNull private String nickName; /** * email */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ private String roleCode; /** * user status * * @see ValidStatusEnum */ @NotNull private String status; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java ================================================ package ai.chat2db.server.admin.api.controller.user.vo; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * user * * @author Jiaju Zhuang */ @Data public class SimpleUserVO { /** * primary key */ @NotNull private Long id; /** * userName */ @NotNull private String userName; /** * Nick name */ @NotNull private String nickName; /** * user status * * @see ValidStatusEnum */ private String status; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.user.vo; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class UserDataSourcePageQueryVO { /** * primary key */ @NotNull private Long id; /** * user id */ private Long userId; /** * Data Source */ private SimpleDataSourceVO dataSource; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.user.vo; import java.util.Date; import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class UserPageQueryVO { /** * primary key */ @NotNull private Long id; /** * userName */ @NotNull private String userName; /** * Nick name */ @NotNull private String nickName; /** * user status * * @see ValidStatusEnum */ private String status; /** * email */ @NotNull private String email; /** * role coding * * @see RoleCodeEnum */ private String roleCode; /** * Change the time */ private Date gmtModified; /** * Modifier user id */ private Long modifiedUserId; /** * Modifier user */ private SimpleUserVO modifiedUser; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java ================================================ package ai.chat2db.server.admin.api.controller.user.vo; import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import lombok.Data; /** * Pagination query * * @author Jiaju Zhuang */ @Data public class UserTeamPageQueryVO { /** * primary key */ private Long id; /** * user id */ private Long userId; /** * team */ private SimpleTeamVO team; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/pom.xml ================================================ ai.chat2db chat2db-server-web ${revision} ../pom.xml 4.0.0 chat2db-server-common-api jar chat2db-server-common-api ai.chat2db chat2db-server-tools-common ai.chat2db chat2db-server-domain-api org.springframework.boot spring-boot-starter-web ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java ================================================ package ai.chat2db.server.common.api.controller; import ai.chat2db.server.common.api.controller.converter.EnvironmentCommonConverter; import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Basic interface * * @author Jiaju Zhuang */ @RequestMapping("/api/common") @RestController public class CommonCommonController { @Resource private EnvironmentService environmentService; @Resource private EnvironmentCommonConverter environmentCommonConverter; /** * Query all environments * * @return * @version 2.1.0 */ @GetMapping("/environment/list_all") public ListResult environmentList() { EnvironmentPageQueryParam environmentPageQueryParam = new EnvironmentPageQueryParam(); environmentPageQueryParam.setPageSize(Integer.MIN_VALUE); return ListResult.of( environmentCommonConverter.dto2vo(environmentService.pageQuery(environmentPageQueryParam).getData())); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java ================================================ package ai.chat2db.server.common.api.controller.converter; import java.util.List; import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import ai.chat2db.server.domain.api.model.Environment; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; /** * converter * * @author Jiaju Zhuang */ @Slf4j @Mapper(componentModel = "spring") public abstract class EnvironmentCommonConverter { /** * convert * * @param list * @return */ public abstract List dto2vo(List list); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java ================================================ package ai.chat2db.server.common.api.controller.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** * Common pagination query * * @author Jiaju Zhuang */ @Data public class CommonPageQueryRequest extends PageQueryRequest { /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java ================================================ package ai.chat2db.server.common.api.controller.request; import lombok.Data; /** * Common query * * @author Jiaju Zhuang */ @Data public class CommonQueryRequest { /** * searchKey */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java ================================================ package ai.chat2db.server.common.api.controller.vo; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Environment * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SimpleEnvironmentVO implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ private Long id; /** * environment name */ private String name; /** * environment abbreviation */ private String shortName; /** * color */ private String color; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java ================================================ package ai.chat2db.server.common.api.controller.vo; import java.io.Serial; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * user * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SimpleUserVO implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * primary key */ private Long id; /** * username */ private String userName; /** * Nick name */ private String nickName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml ================================================ ai.chat2db chat2db-server-web ${revision} ../pom.xml 4.0.0 chat2db-server-web-api jar chat2db-server-web-api ai.chat2db chat2db-server-tools-common ai.chat2db chat2db-server-domain-api ai.chat2db chat2db-server-common-api ai.chat2db chat2db-server-domain-core org.springframework.boot spring-boot-starter-web org.springframework spring-aspects com.unfbx chatgpt-java com.theokanning.openai-gpt3-java service commons-io commons-io com.dtflys.forest forest-spring com.dtflys.forest forest-core com.alibaba easyexcel com.deepoove poi-tl com.itextpdf itext-asian com.itextpdf itextpdf org.apache.pdfbox pdfbox ai.chat2db chat2db-spi com.auth0 java-jwt org.springframework spring-context com.github.vertical-blank sql-formatter org.springframework.boot spring-boot-starter-websocket ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoAspect.java ================================================ package ai.chat2db.server.web.api.aspect; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author jipengfei * @version : ConnectionInfoAspect.java */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface ConnectionInfoAspect { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java ================================================ package ai.chat2db.server.web.api.aspect; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author jipengfei * @version : ConnectionInfoHandler.java */ @Component @Aspect @Slf4j public class ConnectionInfoHandler { @Autowired private DataSourceService dataSourceService; @Resource private DataSourceAccessBusinessService dataSourceAccessBusinessService; @Around("within(@ai.chat2db.server.web.api.aspect.ConnectionInfoAspect *)") public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { try { Object[] params = proceedingJoinPoint.getArgs(); if (params != null && params.length > 0) { for (int i = 0; i < params.length; i++) { Object param = params[i]; if (param instanceof DataSourceBaseRequest) { Long dataSourceId = ((DataSourceBaseRequest) param).getDataSourceId(); String schemaName = ((DataSourceBaseRequest) param).getSchemaName(); String database = ((DataSourceBaseRequest) param).getDatabaseName(); Chat2DBContext.putContext(toInfo(dataSourceId, database, null, schemaName)); } else if (param instanceof DataSourceConsoleRequestInfo) { Long dataSourceId = ((DataSourceConsoleRequestInfo) param).getDataSourceId(); Long consoleId = ((DataSourceConsoleRequestInfo) param).getConsoleId(); String database = ((DataSourceConsoleRequestInfo) param).getDatabaseName(); Chat2DBContext.putContext(toInfo(dataSourceId, database, consoleId, null)); } else if (param instanceof DataSourceBaseRequestInfo) { Long dataSourceId = ((DataSourceBaseRequestInfo) param).getDataSourceId(); String database = ((DataSourceBaseRequestInfo) param).getDatabaseName(); Chat2DBContext.putContext(toInfo(dataSourceId, database)); } } } return proceedingJoinPoint.proceed(); } finally { Chat2DBContext.removeContext(); } } public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); if (!result.success() || dataSource == null) { throw new ParamBusinessException("dataSourceId"); } // Verify permissions dataSourceAccessBusinessService.checkPermission(dataSource); ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setAlias(dataSource.getAlias()); connectInfo.setUser(dataSource.getUserName()); connectInfo.setConsoleId(consoleId); connectInfo.setDataSourceId(dataSourceId); connectInfo.setPassword(dataSource.getPassword()); connectInfo.setDbType(dataSource.getType()); connectInfo.setUrl(dataSource.getUrl()); connectInfo.setDatabase(database); connectInfo.setSchemaName(schemaName); connectInfo.setConsoleOwn(false); connectInfo.setDriver(dataSource.getDriver()); connectInfo.setSsh(dataSource.getSsh()); connectInfo.setSsl(dataSource.getSsl()); connectInfo.setJdbc(dataSource.getJdbc()); connectInfo.setExtendInfo(dataSource.getExtendInfo()); connectInfo.setUrl(dataSource.getUrl()); connectInfo.setPort(StringUtils.isNotBlank(dataSource.getPort()) ? Integer.parseInt(dataSource.getPort()) : null); connectInfo.setHost(dataSource.getHost()); connectInfo.setLoginUser(ContextUtils.getLoginUser().getId() + ""); DriverConfig driverConfig = dataSource.getDriverConfig(); if (driverConfig != null && driverConfig.notEmpty()) { connectInfo.setDriverConfig(driverConfig); } return connectInfo; } public ConnectInfo toInfo(Long dataSourceId, String database) { return toInfo(dataSourceId, database, null, null); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/PageController.java ================================================ package ai.chat2db.server.web.api.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * @author jipengfei * @version : PageController.java * patterns.add("/register.html"); * patterns.add("/login.html"); * patterns.add("/users/reg"); * patterns.add("/users/login"); */ @Slf4j @RequestMapping("/") @Controller public class PageController { @RequestMapping(value = "/login.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") public String login(){ return "login"; } @RequestMapping(value = "/register.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") public String register(){ return "register"; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java ================================================ package ai.chat2db.server.web.api.controller.ai; import java.util.Objects; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.response.ApiKeyResponse; import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; import ai.chat2db.server.web.api.http.response.QrCodeResponse; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * AI configuration information interface * * @author Jiaju Zhuang */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai/config") @Slf4j public class AiConfigController { @Resource private GatewayClientService gatewayClientService; @Autowired private ConfigService configService; @Resource private Chat2dbProperties chat2dbProperties; /** * AI configuration information interface * * @return */ @GetMapping("/getLoginQrCode") public DataResult getLoginQrCode() { LoginUser loginUser = ContextUtils.getLoginUser(); if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { return DataResult.of( QrCodeResponse.builder().tip(I18nUtils.getMessage("settings.permissionDeniedForAiConfig")).build()); } return gatewayClientService.getLoginQrCode(); } /** * Query login status * * @param token * @return */ @GetMapping("/getLoginStatus") public DataResult getLoginStatus(@RequestParam(required = false) String token) { LoginUser loginUser = ContextUtils.getLoginUser(); if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { return DataResult.of(QrCodeResponse.builder().build()); } DataResult dataResult = gatewayClientService.getLoginStatus(token); QrCodeResponse qrCodeResponse = dataResult.getData(); // Representative successfully logged in if (StringUtils.isNotBlank(qrCodeResponse.getApiKey())) { SystemConfigParam sqlSourceParam = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE) .content(AiSqlSourceEnum.CHAT2DBAI.getCode()).build(); configService.createOrUpdate(sqlSourceParam); SystemConfigParam param = SystemConfigParam.builder() .code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(qrCodeResponse.getApiKey()) .build(); configService.createOrUpdate(param); SystemConfigParam hostParam = SystemConfigParam.builder() .code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST) .content(chat2dbProperties.getGateway().getModelBaseUrl() + "/model") .build(); configService.createOrUpdate(hostParam); Chat2dbAIClient.refresh(); } return dataResult; } /** * Return remaining times * * @return */ @GetMapping("/remaininguses") public DataResult remaininguses() { String apiKey = getApiKey(); if (StringUtils.isBlank(apiKey)) { return DataResult.of(ApiKeyResponse.builder() .build()); } return gatewayClientService.remaininguses(apiKey); } /** * Obtain invitation QR code * * @return */ @GetMapping("/getInviteQrCode") public DataResult getInviteQrCode() { String apiKey = getApiKey(); if (StringUtils.isBlank(apiKey)) { return DataResult.of(new InviteQrCodeResponse()); } return gatewayClientService.getInviteQrCode(apiKey); } private String getApiKey() { DataResult apiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); return Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : null; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java ================================================ package ai.chat2db.server.web.api.controller.ai; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.enums.WhiteListTypeEnum; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatRole; import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient; import ai.chat2db.server.web.api.controller.ai.baichuan.listener.BaichuanChatAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.chat2db.listener.Chat2dbAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.claude.client.ClaudeAIClient; import ai.chat2db.server.web.api.controller.ai.claude.listener.ClaudeAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; import ai.chat2db.server.web.api.controller.ai.config.LocalCache; import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.fastchat.listener.FastChatAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; import ai.chat2db.server.web.api.controller.ai.openai.listener.OpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.controller.ai.request.ChatRequest; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.ai.rest.listener.RestAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient; import ai.chat2db.server.web.api.controller.ai.tongyi.listener.TongyiChatAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.wenxin.client.WenxinAIClient; import ai.chat2db.server.web.api.controller.ai.wenxin.listener.WenxinAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.zhipu.client.ZhipuChatAIClient; import ai.chat2db.server.web.api.controller.ai.zhipu.listener.ZhipuChatAIEventSourceListener; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.model.EsTableSchema; import ai.chat2db.server.web.api.http.model.TableSchema; import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.request.WhiteListRequest; import ai.chat2db.server.web.api.http.response.EsTableSchemaResponse; import ai.chat2db.server.web.api.http.response.TableSchemaResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSON; import com.google.common.collect.Lists; import com.unfbx.chatgpt.entity.chat.Message; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * description: * * @author https:www.unfbx.com * @date 2023-03-01 */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai") @Slf4j public class ChatController { @Autowired private TableService tableService; @Autowired private ChatConverter chatConverter; @Autowired private DataSourceService dataSourceService; @Value("${chatgpt.context.length}") private Integer contextLength; @Value("${chatgpt.version}") private String gptVersion; @Resource private GatewayClientService gatewayClientService; /** * chat timeout */ private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); /** * Maximum number of tokens for prompts */ private Integer MAX_PROMPT_LENGTH = 3850; /** * token conversion string length */ private Integer TOKEN_CONVERT_CHAR_LENGTH = 4; /** * Return token size */ private Integer RETURN_TOKEN_LENGTH = 150; /** * Custom model streaming output interface DEMO *

* Note: For custom AI that uses its own local streaming output, the interface input and output must be consistent with this sample. *

* * @param queryRequest * @return * @throws IOException */ @PostMapping("/custom/stream/chat") @CrossOrigin public SseEmitter customChat(@RequestBody ChatRequest queryRequest) throws IOException { SseEmitter emitter = new SseEmitter(CHAT_TIMEOUT); // Set event handler for SSEEmitter emitter.onCompletion(() -> log.info(LocalDateTime.now() + ", on completion")); emitter.onTimeout(() -> { log.info(LocalDateTime.now() + ", uid# on timeout"); emitter.complete(); }); // Start a new thread to generate SSE events new Thread(() -> { try { for (int i = 0; i < 10; i++) { emitter.send(SseEmitter.event().name("message").data("Event " + i)); Thread.sleep(1000); } } catch (Exception e) { emitter.completeWithError(e); } finally { emitter.complete(); } }).start(); return emitter; } /** * Custom model non-streaming output interface DEMO *

* Note: Use your own local flying flow output to customize the AI. The interface input and output must be consistent with this sample. *

* * @param queryRequest * @return * @throws IOException */ @PostMapping("/custom/non/stream/chat") @CrossOrigin public String customNonStreamChat(@RequestBody ChatRequest queryRequest) throws IOException { String data = "The custom AI sample interface is connected successfully! ! ! !"; return data; } /** * SQL conversion model * * @param queryRequest * @param headers * @return * @throws IOException */ @GetMapping("/chat") @CrossOrigin public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map headers) throws IOException { //The default timeout is 30 seconds. If set to 0L, it will never timeout. SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); String uid = headers.get("uid"); if (StrUtil.isBlank(uid)) { throw new ParamBusinessException("uid"); } //Prompt message cannot be empty if (StringUtils.isBlank(queryRequest.getMessage())) { throw new ParamBusinessException("message"); } return distributeAISql(queryRequest, sseEmitter, uid); } /** * distribute with different AI * * @return */ public SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); if (Objects.nonNull(config)) { aiSqlSource = config.getContent(); } AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); if (Objects.isNull(aiSqlSourceEnum)) { aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; } uid = aiSqlSourceEnum.getCode() + uid; switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : return chatWithOpenAi(queryRequest, sseEmitter, uid); case CHAT2DBAI: return chatWithChat2dbAi(queryRequest, sseEmitter, uid); case RESTAI : return chatWithRestAi(queryRequest, sseEmitter, uid); case FASTCHATAI: return chatWithFastChatAi(queryRequest, sseEmitter, uid); case AZUREAI : return chatWithAzureAi(queryRequest, sseEmitter, uid); case CLAUDEAI: return chatWithClaudeAi(queryRequest, sseEmitter, uid); case WENXINAI: return chatWithWenxinAi(queryRequest, sseEmitter, uid); case BAICHUANAI: return chatWithBaichuanAi(queryRequest, sseEmitter, uid); case TONGYIQIANWENAI: return chatWithTongyiChatAi(queryRequest, sseEmitter, uid); case ZHIPUAI: return chatWithZhipuChatAi(queryRequest, sseEmitter, uid); } return chatWithOpenAi(queryRequest, sseEmitter, uid); } /** * Chat using a custom AI interface * * @param prompt * @param sseEmitter * @return */ private SseEmitter chatWithRestAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); buildSseEmitter(sseEmitter, uid); RestAIEventSourceListener restAIEventSourceListener = new RestAIEventSourceListener(sseEmitter); RestAIClient.getInstance().streamCompletions(messages, restAIEventSourceListener); LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); return sseEmitter; } /** * Using the OPENAI SQL interface * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithOpenAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { log.error("The prompt exceeds the maximum length: {}, input length: {}, please re-enter", MAX_PROMPT_LENGTH, prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); throw new ParamBusinessException(); } List messages = new ArrayList<>(); prompt = prompt.replaceAll("#", ""); log.info(prompt); Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build(); messages.add(currentMessage); buildSseEmitter(sseEmitter, uid); OpenAIEventSourceListener openAIEventSourceListener = new OpenAIEventSourceListener(sseEmitter); OpenAIClient.getInstance().streamChatCompletion(messages, openAIEventSourceListener); LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); return sseEmitter; } /** * Using the OPENAI SQL interface * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithChat2dbAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { log.error("exceed max token length:{},input length:{}", MAX_PROMPT_LENGTH, prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); throw new ParamBusinessException(); } prompt = prompt.replaceAll("#", ""); log.info(prompt); Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build(); List messages = new ArrayList<>(); messages.add(currentMessage); buildSseEmitter(sseEmitter, uid); Chat2dbAIEventSourceListener openAIEventSourceListener = new Chat2dbAIEventSourceListener(sseEmitter); Chat2dbAIClient.getInstance().streamCompletions(messages, openAIEventSourceListener); LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); return sseEmitter; } /** * chat with azure openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { log.error("The prompt exceeds the maximum length: {}, input length: {}, please re-enter", MAX_PROMPT_LENGTH, prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); throw new ParamBusinessException(); } List messages = (List)LocalCache.CACHE.get(uid); if (CollectionUtils.isNotEmpty(messages)) { if (messages.size() >= contextLength) { messages = messages.subList(1, contextLength); } } else { messages = Lists.newArrayList(); } AzureChatMessage currentMessage = new AzureChatMessage(AzureChatRole.USER).setContent(prompt); messages.add(currentMessage); buildSseEmitter(sseEmitter, uid); AzureOpenAIEventSourceListener sourceListener = new AzureOpenAIEventSourceListener(sseEmitter); AzureOpenAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * chat with fast chat openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithFastChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); buildSseEmitter(sseEmitter, uid); FastChatAIEventSourceListener sourceListener = new FastChatAIEventSourceListener(sseEmitter); FastChatAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * chat with zhipu chat openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithZhipuChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); buildSseEmitter(sseEmitter, uid); ZhipuChatAIEventSourceListener sourceListener = new ZhipuChatAIEventSourceListener(sseEmitter); ZhipuChatAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * chat with tongyi chat openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithTongyiChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); buildSseEmitter(sseEmitter, uid); TongyiChatAIEventSourceListener sourceListener = new TongyiChatAIEventSourceListener(sseEmitter); TongyiChatAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * chat with baichuan chat openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithBaichuanAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); buildSseEmitter(sseEmitter, uid); BaichuanChatAIEventSourceListener sourceListener = new BaichuanChatAIEventSourceListener(sseEmitter); BaichuanAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * get fast chat message * * @param uid * @param prompt * @return */ private List getFastChatMessage(String uid, String prompt) { List messages = (List)LocalCache.CACHE.get(uid); if (CollectionUtils.isNotEmpty(messages)) { if (messages.size() >= contextLength) { messages = messages.subList(1, contextLength); } } else { messages = Lists.newArrayList(); } FastChatMessage currentMessage = new FastChatMessage(FastChatRole.USER).setContent(prompt); messages.add(currentMessage); return messages; } /** * chat with wenxin chat openai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithWenxinAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); List messages = getFastChatMessage(uid, prompt); if (messages.size() >= 2 && messages.size() % 2 == 0) { messages.remove(messages.size() - 1); } buildSseEmitter(sseEmitter, uid); WenxinAIEventSourceListener sourceListener = new WenxinAIEventSourceListener(sseEmitter); WenxinAIClient.getInstance().streamCompletions(messages, sourceListener); LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } /** * chat with claude ai * * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter chatWithClaudeAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { String prompt = buildPrompt(queryRequest); ClaudeChatMessage claudeChatMessage = new ClaudeChatMessage(); claudeChatMessage.setText(prompt); ClaudeChatCompletionsOptions chatCompletionsOptions = new ClaudeChatCompletionsOptions(); chatCompletionsOptions.setPrompt(prompt); claudeChatMessage.setCompletion(chatCompletionsOptions); buildSseEmitter(sseEmitter, uid); ClaudeAIEventSourceListener sourceListener = new ClaudeAIEventSourceListener(sseEmitter); ClaudeAIClient.getInstance().streamCompletions(claudeChatMessage, sourceListener); return sseEmitter; } /** * construct sseEmitter * * @param sseEmitter * @param uid * @return * @throws IOException */ private SseEmitter buildSseEmitter(SseEmitter sseEmitter, String uid) throws IOException { sseEmitter.send(SseEmitter.event().id(uid).name("connect successfully!!!!").data(LocalDateTime.now()).reconnectTime(3000)); sseEmitter.onCompletion(() -> { log.info(LocalDateTime.now() + ", uid#" + uid + ", on completion"); }); sseEmitter.onTimeout( () -> log.info(LocalDateTime.now() + ", uid#" + uid + ", on timeout#" + sseEmitter.getTimeout())); sseEmitter.onError( throwable -> { try { log.info(LocalDateTime.now() + ", uid#" + "765431" + ", on error#" + throwable.toString()); sseEmitter.send(SseEmitter.event().id("765431").name("An exception occurs!").data(throwable.getMessage()) .reconnectTime(3000)); } catch (IOException e) { log.error("An exception occurs!{}", e.getMessage(), e); } } ); return sseEmitter; } /** * Build schema parameters * * @param tableQueryParam * @param tableNames * @return */ private String buildTableColumn(TableQueryParam tableQueryParam, List tableNames) { if (CollectionUtils.isEmpty(tableNames)) { return ""; } List schemaContent = Lists.newArrayList(); try { schemaContent = tableNames.stream().map(tableName -> { tableQueryParam.setTableName(tableName); return queryTableDdl(tableName, tableQueryParam); }).collect(Collectors.toList()); } catch (Exception exception) { log.error("query table error, do nothing"); } return JSON.toJSONString(schemaContent); } /** * query table schema * * @param tableName * @param request * @return */ private String queryTableDdl(String tableName, TableQueryParam request) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName(tableName); param.setDataSourceId(request.getDataSourceId()); param.setDatabaseName(request.getDatabaseName()); param.setSchemaName(request.getSchemaName()); DataResult tableSchema = tableService.showCreateTable(param); return tableSchema.getData(); } /** * build prompt * * @param queryRequest * @return */ private String buildPrompt(ChatQueryRequest queryRequest) { if (PromptType.TEXT_GENERATION.getCode().equals(queryRequest.getPromptType())) { return queryRequest.getMessage(); } // Query schema information String dataSourceType = queryDatabaseType(queryRequest); String properties = ""; if (CollectionUtils.isNotEmpty(queryRequest.getTableNames())) { TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); properties = buildTableColumn(queryParam, queryRequest.getTableNames()); } else { properties = mappingDatabaseSchema(queryRequest); } String prompt = queryRequest.getMessage(); String promptType = StringUtils.isBlank(queryRequest.getPromptType()) ? PromptType.NL_2_SQL.getCode() : queryRequest.getPromptType(); PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType); String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : ""; String schemaProperty = StringUtils.isNotEmpty(properties) ? String.format( "### Please follow the below table properties and SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " + "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType, properties, prompt) : String.format("### Please follow the below SQL input%s. %s\n#\n### SQL input: %s", pType.getDescription(), ext, prompt); switch (pType) { case SQL_2_SQL: schemaProperty = StringUtils.isNotBlank(queryRequest.getDestSqlType()) ? String.format( "%s\n#\n### Target SQL type: %s", schemaProperty, queryRequest.getDestSqlType()) : String.format( "%s\n#\n### Target SQL type: %s", schemaProperty, dataSourceType); default: break; } String cleanedInput = schemaProperty.replaceAll("[\r\t]", ""); return cleanedInput; } /** * query chat2db apikey * * @return */ public String getApiKey() { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); // only sync for chat2db ai if (Objects.isNull(config) || !aiSqlSource.equals(config.getContent())) { return null; } Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { return null; } return keyConfig.getContent(); } /** * query database type * * @param queryRequest * @return */ public String queryDatabaseType(ChatQueryRequest queryRequest) { // Query schema information DataResult dataResult = dataSourceService.queryById(queryRequest.getDataSourceId()); String dataSourceType = dataResult.getData().getType(); if (StringUtils.isBlank(dataSourceType)) { dataSourceType = "MYSQL"; } return dataSourceType; } public String mappingDatabaseSchema(ChatQueryRequest queryRequest) { String properties = ""; String apiKey = getApiKey(); if (StringUtils.isNotBlank(apiKey)) { boolean res = gatewayClientService.checkInWhite(new WhiteListRequest(apiKey, WhiteListTypeEnum.VECTOR.getCode())).getData(); if (res) { // properties = queryDatabaseSchema(queryRequest) + querySchemaByEs(queryRequest); properties = queryDatabaseSchema(queryRequest); } } return properties; } /** * query database schema * * @param queryRequest * @return * @throws IOException */ public String queryDatabaseSchema(ChatQueryRequest queryRequest) { // request embedding FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); List> contentVector = new ArrayList<>(); if (Objects.isNull(response) || CollectionUtils.isEmpty(response.getData())) { return ""; } contentVector.add(response.getData().get(0).getEmbedding()); // search embedding TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); tableSchemaRequest.setSchemaVector(contentVector); tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); tableSchemaRequest.setDataSourceSchema(queryRequest.getSchemaName()); ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { return ""; } tableSchemaRequest.setApiKey(keyConfig.getContent()); try { DataResult result = gatewayClientService.schemaVectorSearch(tableSchemaRequest); List schemas = Lists.newArrayList(); if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { for(TableSchema data: result.getData().getTableSchemas()){ schemas.add(data.getTableSchema()); } } if (CollectionUtils.isEmpty(schemas)) { return ""; } String res = JSON.toJSONString(schemas); log.info("search vector result:{}", res); return res; } catch (Exception exception) { log.error("query table error, do nothing"); return ""; } } /** * query database schema * * @param queryRequest * @return * @throws IOException */ public String querySchemaByEs(ChatQueryRequest queryRequest) { // search embedding EsTableSchemaRequest tableSchemaRequest = new EsTableSchemaRequest(); tableSchemaRequest.setSearchKey(queryRequest.getMessage()); tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); tableSchemaRequest.setSchemaName(queryRequest.getSchemaName()); ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { return ""; } tableSchemaRequest.setApiKey(keyConfig.getContent()); try { DataResult result = gatewayClientService.schemaEsSearch(tableSchemaRequest); List schemas = Lists.newArrayList(); if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { for(EsTableSchema data: result.getData().getTableSchemas()){ schemas.add(data.getTableSchemaContent()); } } if (CollectionUtils.isEmpty(schemas)) { return ""; } String res = JSON.toJSONString(schemas); log.info("search es result:{}", res); return res; } catch (Exception exception) { log.error("query es table error, do nothing"); return ""; } } /** * distribute embedding with different AI * * @return */ public FastChatEmbeddingResponse distributeAIEmbedding(String input) { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); String aiSqlSource = config.getContent(); if (Objects.isNull(aiSqlSource)) { return null; } AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case CHAT2DBAI: return embeddingWithChat2dbAi(input); case FASTCHATAI: return embeddingWithFastChatAi(input); } return null; } /** * embedding with fast chat openai * * @param input * @return * @throws IOException */ private FastChatEmbeddingResponse embeddingWithFastChatAi(String input) { FastChatEmbeddingResponse response = FastChatAIClient.getInstance().embeddings(input); return response; } /** * embedding with open ai * * @param input * @return */ private FastChatEmbeddingResponse embeddingWithChat2dbAi(String input) { FastChatEmbeddingResponse embeddings = Chat2dbAIClient.getInstance().embeddings(input); return embeddings; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/AbstractParser.java ================================================ package ai.chat2db.server.web.api.controller.ai.DocParser; import java.io.InputStream; import java.util.List; /** * @author CYY * @date March 20, 2023 8:13 am * @description */ public abstract class AbstractParser { public abstract List parse(InputStream inputStream) throws Exception; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/PdfParse.java ================================================ package ai.chat2db.server.web.api.controller.ai.DocParser; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; /** * @author CYY * @date March 11, 2023 3:23 pm * @description */ public class PdfParse extends AbstractParser { private static final int MAX_LENGTH = 200; @Override public List parse(InputStream inputStream) throws IOException { // Open PDF file PDDocument document = PDDocument.load(inputStream); // Create a PDFTextStripper object PDFTextStripper stripper = new PDFTextStripper(); // Get text content String text = stripper.getText(document); // Filter characters text = text.replaceAll("\\s", " ").replaceAll("(\\r\\n|\\r|\\n|\\n\\r)"," "); String[] sentence = text.split("。"); List ans = new ArrayList<>(); for (String s : sentence) { if (s.length() > MAX_LENGTH) { for (int index = 0; index < sentence.length; index = (index + 1) * MAX_LENGTH) { String substring = s.substring(index, MAX_LENGTH); if(substring.length() < 5) continue; ans.add(substring); } } else { ans.add(s); } } // Close document document.close(); return ans; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java ================================================ package ai.chat2db.server.web.api.controller.ai; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.param.TableVectorParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.enums.WhiteListTypeEnum; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableMilvusQueryRequest; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.request.WhiteListRequest; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.model.Table; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * @author moji */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai/embedding") @Slf4j public class EmbeddingController extends ChatController { @Resource private GatewayClientService gatewayClientService; @Autowired private RdbWebConverter rdbWebConverter; @Autowired private TableService tableService; /** * check if in white list */ @GetMapping("/white/check") public DataResult checkInWhite(WhiteListRequest request) { request.setWhiteType(WhiteListTypeEnum.VECTOR.getCode()); if (StringUtils.isBlank(request.getApiKey())) { return DataResult.of(false); } try { DataResult res = gatewayClientService.checkInWhite(request); } catch (Exception ex) { log.error("checkInWhite error", ex); } return DataResult.of(false); } /** * save datasource embeddings * * @param request * @return * @throws IOException */ @PostMapping("/datasource") @CrossOrigin public ActionResult embeddings(@Valid TableMilvusQueryRequest request) throws Exception { // query tables request.setPageNo(1); request.setPageSize(1000); TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); tableSelector.setIndexList(false); PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List
tables = tableDTOPageResult.getData(); if (CollectionUtils.isEmpty(tables)) { return ActionResult.isSuccess(); } String tableName = tables.get(0).getName(); String tableSchema = queryTableDdl(tableName, request); if (StringUtils.isBlank(tableSchema)) { throw new ParamBusinessException("tableSchema is empty"); } // save first table embedding TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); tableSchemaRequest.setDataSourceId(request.getDataSourceId()); tableSchemaRequest.setApiKey(request.getApikey()); tableSchemaRequest.setDeleteBeforeInsert(true); tableSchemaRequest.setDataSourceSchema(request.getSchemaName()); tableSchemaRequest.setDatabaseName(request.getDatabaseName()); saveTableEmbedding(tableSchema, tableSchemaRequest); // save other table embedding tableSchemaRequest.setDeleteBeforeInsert(false); for (int i = 1; i < tables.size(); i++) { tableName = tables.get(i).getName(); tableSchema = queryTableDdl(tableName, request); if (StringUtils.isBlank(tableSchema)) { continue; } saveTableEmbedding(tableSchema, tableSchemaRequest); } // query all the tables Long totalTableCount = tableDTOPageResult.getTotal(); Integer pageSize = queryParam.getPageSize(); if (pageSize < totalTableCount) { for (int i = 2; i < totalTableCount/pageSize + 1; i++) { queryParam.setPageNo(i); tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); tables = tableDTOPageResult.getData(); for (Table table : tables) { tableName = table.getName(); tableSchema = queryTableDdl(tableName, request); if (StringUtils.isBlank(tableSchema)) { continue; } saveTableEmbedding(tableSchema, tableSchemaRequest); } } } return ActionResult.isSuccess(); } /** * save datasource schema * * @param request * @return * @throws IOException */ @PostMapping("/datasource/es") @CrossOrigin public ActionResult es(@Valid EsTableSchemaRequest request) throws Exception { // query tables TablePageQueryParam queryParam = rdbWebConverter.schemaReq2page(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); tableSelector.setIndexList(false); queryParam.setPageNo(1); queryParam.setPageSize(1000); PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List
tables = tableDTOPageResult.getData(); if (CollectionUtils.isEmpty(tables)) { return ActionResult.isSuccess(); } String tableName = tables.get(0).getName(); String tableSchema = queryTableDdlByEs(tableName, request); request.setTableName(tableName); request.setTableSchemaContent(tableSchema); if (StringUtils.isBlank(tableSchema)) { throw new ParamBusinessException("tableSchema is empty"); } // save first table embedding request.setDeleteBeforeInsert(true); saveTableEs(request); // save other table embedding request.setDeleteBeforeInsert(false); for (int i = 1; i < tables.size(); i++) { tableName = tables.get(i).getName(); tableSchema = queryTableDdlByEs(tableName, request); if (StringUtils.isBlank(tableSchema)) { continue; } request.setTableName(tableName); request.setTableSchemaContent(tableSchema); saveTableEs(request); } // query all the tables Long totalTableCount = tableDTOPageResult.getTotal(); Integer pageSize = queryParam.getPageSize(); if (pageSize < totalTableCount) { for (int i = 2; i < totalTableCount/pageSize + 1; i++) { queryParam.setPageNo(i); tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); tables = tableDTOPageResult.getData(); for (Table table : tables) { tableName = table.getName(); tableSchema = queryTableDdlByEs(tableName, request); if (StringUtils.isBlank(tableSchema)) { continue; } request.setTableName(tableName); request.setTableSchemaContent(tableSchema); saveTableEs(request); } } } return ActionResult.isSuccess(); } /** * sync table vector * * @param param */ public void syncTableVector(TableBriefQueryRequest param) throws Exception { TableVectorParam vectorParam = rdbWebConverter.param2param(param); if (Objects.isNull(vectorParam.getDataSourceId())) { return; } if (StringUtils.isBlank(vectorParam.getDatabase()) && StringUtils.isBlank(vectorParam.getSchema())) { return; } String apiKey = getApiKey(); if (StringUtils.isBlank(apiKey)) { return; } TableMilvusQueryRequest request = rdbWebConverter.request2request(param); request.setApikey(apiKey); vectorParam.setApiKey(apiKey); DataResult result = tableService.checkTableVector(vectorParam); if (result.getData()) { return; } // check if in white list boolean res = gatewayClientService.checkInWhite(new WhiteListRequest(apiKey, WhiteListTypeEnum.VECTOR.getCode())).getData(); if (!res) { return; } embeddings(request); tableService.saveTableVector(vectorParam); } /** * save table embedding * * @param tableSchema * @param tableSchemaRequest * @throws Exception */ private void saveTableEmbedding(String tableSchema, TableSchemaRequest tableSchemaRequest) throws Exception{ List schemaList = Lists.newArrayList(tableSchema); tableSchemaRequest.setSchemaList(schemaList); List> contentVector = new ArrayList<>(); for(String str : schemaList){ // request embedding FastChatEmbeddingResponse response = distributeAIEmbedding(str); if(response == null){ throw new ParamBusinessException(); } contentVector.add(response.getData().get(0).getEmbedding()); } if (CollectionUtils.isEmpty(contentVector)) { throw new ParamBusinessException(); } tableSchemaRequest.setSchemaVector(contentVector); // save table embedding gatewayClientService.schemaVectorSave(tableSchemaRequest); } /** * sync table vector * * @param param */ public void syncTableEs(TableBriefQueryRequest param) throws Exception { EsTableSchemaRequest esParam = rdbWebConverter.req2req(param); if (Objects.isNull(esParam.getDataSourceId())) { return; } if (StringUtils.isBlank(esParam.getDatabaseName()) && StringUtils.isBlank(esParam.getSchemaName())) { return; } String apiKey = getApiKey(); if (StringUtils.isBlank(apiKey)) { return; } esParam.setApiKey(apiKey); es(esParam); } /** * save table schema * * @param tableSchemaRequest * @throws Exception */ private void saveTableEs(EsTableSchemaRequest tableSchemaRequest) throws Exception{ // save table es gatewayClientService.schemaEsSave(tableSchemaRequest); } /** * query table schema * * @param tableName * @param request * @return */ private String queryTableDdl(String tableName, TableBriefQueryRequest request) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName(tableName); param.setDataSourceId(request.getDataSourceId()); param.setDatabaseName(request.getDatabaseName()); param.setSchemaName(request.getSchemaName()); DataResult tableSchema = tableService.showCreateTable(param); return tableSchema.getData(); } /** * query table schema * * @param tableName * @param request * @return */ private String queryTableDdlByEs(String tableName, EsTableSchemaRequest request) { ShowCreateTableParam param = new ShowCreateTableParam(); param.setTableName(tableName); param.setDataSourceId(request.getDataSourceId()); param.setDatabaseName(request.getDatabaseName()); param.setSchemaName(request.getSchemaName()); DataResult tableSchema = tableService.showCreateTable(param); return tableSchema.getData(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java ================================================ package ai.chat2db.server.web.api.controller.ai; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.DocParser.AbstractParser; import ai.chat2db.server.web.api.controller.ai.DocParser.PdfParse; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.model.Knowledge; import ai.chat2db.server.web.api.http.request.KnowledgeRequest; import ai.chat2db.server.web.api.http.response.KnowledgeResponse; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.math.BigDecimal; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @author moji */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai/knowledge") @Slf4j public class KnowledgeController extends ChatController { /** * chat timeout */ private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); @Resource private GatewayClientService gatewayClientService; /** * save knowledge from pdf file * * @param file * @return * @throws IOException */ @PostMapping("/embeddings") @CrossOrigin public ActionResult embeddings(MultipartFile file, HttpServletRequest request) throws Exception { AbstractParser pdfParse = new PdfParse(); List sentenceList = pdfParse.parse(file.getInputStream()); List contentWordCount = new ArrayList<>(); List> contentVector = new ArrayList<>(); for(String str : sentenceList){ contentWordCount.add(str.length()); // request embedding FastChatEmbeddingResponse response = distributeAIEmbedding(str); if(response == null){ continue; } contentVector.add(response.getData().get(0).getEmbedding()); } KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); knowledgeRequest.setContentVector(contentVector); knowledgeRequest.setSentenceList(sentenceList); // save knowledge embedding ActionResult actionResult = gatewayClientService.knowledgeVectorSave(knowledgeRequest); return actionResult; } /** * search knowledge * * @param queryRequest * @return * @throws IOException */ @GetMapping("/search") @CrossOrigin public SseEmitter search(ChatQueryRequest queryRequest, @RequestHeader Map headers) throws Exception { // request embedding FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); List> contentVector = new ArrayList<>(); contentVector.add(response.getData().get(0).getEmbedding()); // search embedding KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); knowledgeRequest.setContentVector(contentVector); DataResult result = gatewayClientService.knowledgeVectorSearch(knowledgeRequest); queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); String prompt = queryRequest.getMessage(); if (CollectionUtils.isNotEmpty(result.getData().getKnowledgeList())) { List contents = new ArrayList<>(); for(Knowledge data: result.getData().getKnowledgeList()){ contents.add(data.getContent()); } prompt = String.format("Based on %s. Please answer %s.", JSON.toJSONString(contents), prompt); queryRequest.setMessage(prompt); } // chat with AI SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); String uid = headers.get("uid"); if (StrUtil.isBlank(uid)) { throw new ParamBusinessException("uid"); } if (StringUtils.isBlank(queryRequest.getMessage())) { throw new ParamBusinessException("message"); } return distributeAISql(queryRequest, sseEmitter, uid); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java ================================================ package ai.chat2db.server.web.api.controller.ai; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.http.GatewayClientService; import cn.hutool.core.util.StrUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.time.Duration; import java.util.Map; /** * @author moji */ @RestController @ConnectionInfoAspect @RequestMapping("/api/ai/text/generation") @Slf4j public class TextGenerationController extends ChatController { /** * chat timeout time */ private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); @Resource private GatewayClientService gatewayClientService; /** * sql auto complete * * @param queryRequest * @return * @throws IOException */ @GetMapping("/prompt") @CrossOrigin public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map headers) throws Exception { queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); String promptTemplate = "### Instructions:\n" + "Your task is generate a SQL query according to the prompt %s.\n" + "Adhere to these rules:\n" + "- **Deliberately go through the prompt and database schema word by word** to appropriately answer the question\n" + "- **Use Table Aliases** to prevent ambiguity. For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.\n" + "\n" + "### Input:\n" + "Generate a SQL query according to the prompt `%s`.\n" + "%s\n" + "\n" + "### Response:\n" + "Based on your instructions, here is the SQL query I have generated to complete the prompt `{%s}`:\n" + "```sql"; // query database schema info String databaseType = queryDatabaseType(queryRequest); String schemas = queryDatabaseSchema(queryRequest); if (StringUtils.isNotBlank(schemas)) { databaseType = String.format(", given a %s database schema", databaseType); schemas = String.format("This query will run on a database whose schema is represented in this string:\n$s", schemas); } else { databaseType = ""; schemas = ""; } String prompt = String.format(promptTemplate, databaseType, queryRequest.getMessage(), schemas, queryRequest.getMessage()); queryRequest.setMessage(prompt); // chat with AI SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); String uid = headers.get("uid"); if (StrUtil.isBlank(uid)) { throw new ParamBusinessException("uid"); } if (StringUtils.isBlank(queryRequest.getMessage())) { throw new ParamBusinessException("message"); } return distributeAISql(queryRequest, sseEmitter, uid); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author jipengfei * @version : OpenAIClient.java */ @Slf4j public class AzureOpenAIClient { /** * AZURE OPENAI KEY */ public static final String AZURE_CHATGPT_API_KEY = "azure.chatgpt.apiKey"; /** * AZURE OPENAI ENDPOINT */ public static final String AZURE_CHATGPT_ENDPOINT = "azure.chatgpt.endpoint"; /** * AZURE OPENAI DEPLOYMENT ID */ public static final String AZURE_CHATGPT_DEPLOYMENT_ID = "azure.chatgpt.deployment.id"; private static AzureOpenAiStreamClient OPEN_AI_CLIENT; private static String apiKey; public static AzureOpenAiStreamClient getInstance() { if (OPEN_AI_CLIENT != null) { return OPEN_AI_CLIENT; } else { return singleton(); } } private static AzureOpenAiStreamClient singleton() { if (OPEN_AI_CLIENT == null) { synchronized (AzureOpenAIClient.class) { if (OPEN_AI_CLIENT == null) { refresh(); } } } return OPEN_AI_CLIENT; } public static void refresh() { String key = ""; String apiEndpoint = ""; String deployId = "gpt-3.5-turbo"; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiEndpoint = apiHostConfig.getContent(); } Config config = configService.find(AZURE_CHATGPT_API_KEY).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { key = config.getContent(); } Config deployConfig = configService.find(AZURE_CHATGPT_DEPLOYMENT_ID).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { deployId = deployConfig.getContent(); } log.info("refresh azure openai apikey:{}", maskApiKey(key)); OPEN_AI_CLIENT = AzureOpenAiStreamClient.builder().apiKey(key).endpoint(apiEndpoint).deployId(deployId) .build(); apiKey = key; } private static String maskApiKey(String input) { if (input == null) { return input; } StringBuilder maskedString = new StringBuilder(input); for (int i = input.length() / 2; i < input.length() / 2; i++) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.client; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.azure.interceptor.AzureHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; /** * Custom AI interface client * * @author moji */ @Slf4j public class AzureOpenAiStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; /** * endpoint */ @Getter @NotNull private String endpoint; /** * deployId */ @Getter private String deployId; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private AzureOpenAiStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.endpoint = builder.endpoint; this.deployId = builder.deployId; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new AzureHeaderAuthorizationInterceptor(this.apiKey)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static AzureOpenAiStreamClient.Builder builder() { return new AzureOpenAiStreamClient.Builder(); } public static final class Builder { private String apiKey; private String endpoint; private String deployId; /** * Customize OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public AzureOpenAiStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } /** * @param endpointValue * @return */ public AzureOpenAiStreamClient.Builder endpoint(String endpointValue) { this.endpoint = endpointValue; return this; } /** * @param deployIdValue * @return */ public AzureOpenAiStreamClient.Builder deployId(String deployIdValue) { this.deployId = deployIdValue; return this; } public AzureOpenAiStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public AzureOpenAiStreamClient build() { return new AzureOpenAiStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Azure Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:AzureEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Azure Open AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { AzureChatCompletionsOptions chatCompletionsOptions = new AzureChatCompletionsOptions(chatMessages); chatCompletionsOptions.setStream(true); chatCompletionsOptions.setModel(this.deployId); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); if (!endpoint.endsWith("/")) { endpoint = endpoint + "/"; } String url = this.endpoint + "openai/deployments/"+ deployId + "/chat/completions?api-version=2023-05-15"; Request request = new Request.Builder() .url(url) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking azure ai"); } catch (Exception e) { log.error("azure ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/interceptor/AzureHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.interceptor; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import cn.hutool.core.util.RandomUtil; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; /** * Description: Request to add header apikey * * @author grt * @since 2023-03-23 */ @Getter public class AzureHeaderAuthorizationInterceptor implements Interceptor { private String apiKey; public AzureHeaderAuthorizationInterceptor(String apiKey) { this.apiKey = apiKey; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() .header("api-key", apiKey) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); return chain.proceed(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.listener; import java.io.IOException; import java.util.Objects; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatChoice; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletions; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureCompletionsUsage; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class AzureOpenAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public AzureOpenAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("AzureOpenAI建立sse连接..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("AzureOpenAI returns data: {}", data); if (data.equals("[DONE]")) { log.info("AzureOpenAI returns data ended"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } AzureChatCompletions chatCompletions = mapper.readValue(data, AzureChatCompletions.class); String text = ""; log.info("Model ID={} is created at {}.", chatCompletions.getId(), chatCompletions.getCreated()); for (AzureChatChoice choice : chatCompletions.getChoices()) { AzureChatMessage message = choice.getDelta(); if (message != null) { log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); if (message.getContent() != null) { text = message.getContent(); } } } AzureCompletionsUsage usage = chatCompletions.getUsage(); if (usage != null) { log.info( "Usage: number of prompt token is {}, number of completion token is {}, and number of total " + "tokens in request and response is {}.%n", usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens()); } Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); log.info("AzureOpenAI close sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { bodyString = t.getMessage(); } log.error("Azure OpenAI sse response:{}", bodyString); } else { log.error("Azure OpenAI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Azure OpenAI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Azure OpenAI sends data abnormally:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * The representation of a single prompt completion as part of an overall chat completions request. Generally, `n` * choices are generated per provided prompt with a default value of 1. Token limits and other settings may limit the * number of choices generated. */ @Data public final class AzureChatChoice { /* * The chat message for a given chat completions prompt. */ @JsonProperty(value = "message") private AzureChatMessage message; /* * The ordered index associated with this chat completions choice. */ @JsonProperty(value = "index") private int index; /* * The reason that this chat completions choice completed its generated. */ @JsonProperty(value = "finish_reason") private AzureCompletionsFinishReason finishReason; /* * The delta message content for a streaming response. */ @JsonProperty(value = "delta") private AzureChatMessage delta; /** * Creates an instance of ChatChoice class. * * @param index the index value to set. * @param finishReason the finishReason value to set. */ @JsonCreator private AzureChatChoice( @JsonProperty(value = "index") int index, @JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) { this.index = index; this.finishReason = finishReason; } /** * Get the message property: The chat message for a given chat completions prompt. * * @return the message value. */ public AzureChatMessage getMessage() { return this.message; } /** * Get the index property: The ordered index associated with this chat completions choice. * * @return the index value. */ public int getIndex() { return this.index; } /** * Get the finishReason property: The reason that this chat completions choice completed its generated. * * @return the finishReason value. */ public AzureCompletionsFinishReason getFinishReason() { return this.finishReason; } /** * Get the delta property: The delta message content for a streaming response. * * @return the delta value. */ public AzureChatMessage getDelta() { return this.delta; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class AzureChatCompletions { /* * A unique identifier associated with this chat completions response. */ private String id; /* * The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. */ private int created; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ @JsonProperty(value = "choices") private List choices; /* * Usage information for tokens processed and generated as part of this completions operation. */ private AzureCompletionsUsage usage; /** * Creates an instance of ChatCompletions class. * * @param id the id value to set. * @param created the created value to set. * @param choices the choices value to set. * @param usage the usage value to set. */ @JsonCreator private AzureChatCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") int created, @JsonProperty(value = "choices") List choices, @JsonProperty(value = "usage") AzureCompletionsUsage usage) { this.id = id; this.created = created; this.choices = choices; this.usage = usage; } /** * Get the id property: A unique identifier associated with this chat completions response. * * @return the id value. */ public String getId() { return this.id; } /** * Get the created property: The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. * * @return the created value. */ public int getCreated() { return this.created; } /** * Get the choices property: The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other * settings may limit the number of choices generated. * * @return the choices value. */ public List getChoices() { return this.choices; } /** * Get the usage property: Usage information for tokens processed and generated as part of this completions * operation. * * @return the usage value. */ public AzureCompletionsUsage getUsage() { return this.usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * The configuration information for a chat completions request. Completions support a wide variety of tasks and * generate text that continues from or "completes" provided prompt data. */ @Data public final class AzureChatCompletionsOptions { /* * The collection of context messages associated with this chat completions request. * Typical usage begins with a chat message for the System role that provides instructions for * the behavior of the assistant, followed by alternating messages between the User and * Assistant roles. */ private List messages; ///* // * The maximum number of tokens to generate. // */ ////@JsonProperty(value = "max_tokens") //private Integer maxTokens; // ///* // * The sampling temperature to use that controls the apparent creativity of generated completions. // * Higher values will make output more random while lower values will make results more focused // * and deterministic. // * It is not recommended to modify temperature and top_p for the same completions request as the // * interaction of these two settings is difficult to predict. // */ ////@JsonProperty(value = "temperature") //private Double temperature; // ///* // * An alternative to sampling with temperature called nucleus sampling. This value causes the // * model to consider the results of tokens with the provided probability mass. As an example, a // * value of 0.15 will cause only the tokens comprising the top 15% of probability mass to be // * considered. // * It is not recommended to modify temperature and top_p for the same completions request as the // * interaction of these two settings is difficult to predict. // */ ////@JsonProperty(value = "top_p") //private Double topP; // ///* // * A map between GPT token IDs and bias scores that influences the probability of specific tokens // * appearing in a completions response. Token IDs are computed via external tokenizer tools, while // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to // * a full ban or exclusive selection of a token, respectively. The exact behavior of a given bias // * score varies by model. // */ ////@JsonProperty(value = "logit_bias") //private Map logitBias; // ///* // * An identifier for the caller or end user of the operation. This may be used for tracking // * or rate-limiting purposes. // */ ////@JsonProperty(value = "user") //private String user; // ///* // * The number of chat completions choices that should be generated for a chat completions // * response. // * Because this setting can generate many completions, it may quickly consume your token quota. // * Use carefully and ensure reasonable settings for max_tokens and stop. // */ ////@JsonProperty(value = "n") //private Integer n; // ///* // * A collection of textual sequences that will end completions generation. // */ ////@JsonProperty(value = "stop") //private List stop; // ///* // * A value that influences the probability of generated tokens appearing based on their existing // * presence in generated text. // * Positive values will make tokens less likely to appear when they already exist and increase the // * model's likelihood to output new topics. // */ ////@JsonProperty(value = "presence_penalty") //private Double presencePenalty; // ///* // * A value that influences the probability of generated tokens appearing based on their cumulative // * frequency in generated text. // * Positive values will make tokens less likely to appear as their frequency increases and // * decrease the likelihood of the model repeating the same statements verbatim. // */ ////@JsonProperty(value = "frequency_penalty") //private Double frequencyPenalty; /* * A value indicating whether chat completions should be streamed for this request. */ //@JsonProperty(value = "stream") private Boolean stream; // /* * The model name to provide as part of this completions request. * Not applicable to Azure OpenAI, where deployment information should be included in the Azure * resource URI that's connected to. */ //@JsonProperty(value = "model") private String model; /** * Creates an instance of ChatCompletionsOptions class. * * @param messages the messages value to set. */ @JsonCreator public AzureChatCompletionsOptions(@JsonProperty(value = "messages") List messages) { this.messages = messages; } // ///** // * Get the messages property: The collection of context messages associated with this chat completions request. // * Typical usage begins with a chat message for the System role that provides instructions for the behavior of the // * assistant, followed by alternating messages between the User and Assistant roles. // * // * @return the messages value. // */ //public List getMessages() { // return this.messages; //} // ///** // * Get the maxTokens property: The maximum number of tokens to generate. // * // * @return the maxTokens value. // */ //public Integer getMaxTokens() { // return this.maxTokens; //} // ///** // * Set the maxTokens property: The maximum number of tokens to generate. // * // * @param maxTokens the maxTokens value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setMaxTokens(Integer maxTokens) { // this.maxTokens = maxTokens; // return this; //} // ///** // * Get the temperature property: The sampling temperature to use that controls the apparent creativity of generated // * completions. Higher values will make output more random while lower values will make results more focused and // * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the // * interaction of these two settings is difficult to predict. // * // * @return the temperature value. // */ //public Double getTemperature() { // return this.temperature; //} // ///** // * Set the temperature property: The sampling temperature to use that controls the apparent creativity of generated // * completions. Higher values will make output more random while lower values will make results more focused and // * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the // * interaction of these two settings is difficult to predict. // * // * @param temperature the temperature value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setTemperature(Double temperature) { // this.temperature = temperature; // return this; //} // ///** // * Get the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the // * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will // * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to // * modify temperature and top_p for the same completions request as the interaction of these two settings is // * difficult to predict. // * // * @return the topP value. // */ //public Double getTopP() { // return this.topP; //} // ///** // * Set the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the // * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will // * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to // * modify temperature and top_p for the same completions request as the interaction of these two settings is // * difficult to predict. // * // * @param topP the topP value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setTopP(Double topP) { // this.topP = topP; // return this; //} // ///** // * Get the logitBias property: A map between GPT token IDs and bias scores that influences the probability of // * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or // * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model. // * // * @return the logitBias value. // */ //public Map getLogitBias() { // return this.logitBias; //} // ///** // * Set the logitBias property: A map between GPT token IDs and bias scores that influences the probability of // * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while // * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or // * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model. // * // * @param logitBias the logitBias value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setLogitBias(Map logitBias) { // this.logitBias = logitBias; // return this; //} // ///** // * Get the user property: An identifier for the caller or end user of the operation. This may be used for tracking // * or rate-limiting purposes. // * // * @return the user value. // */ //public String getUser() { // return this.user; //} // ///** // * Set the user property: An identifier for the caller or end user of the operation. This may be used for tracking // * or rate-limiting purposes. // * // * @param user the user value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setUser(String user) { // this.user = user; // return this; //} // ///** // * Get the n property: The number of chat completions choices that should be generated for a chat completions // * response. Because this setting can generate many completions, it may quickly consume your token quota. Use // * carefully and ensure reasonable settings for max_tokens and stop. // * // * @return the n value. // */ //public Integer getN() { // return this.n; //} // ///** // * Set the n property: The number of chat completions choices that should be generated for a chat completions // * response. Because this setting can generate many completions, it may quickly consume your token quota. Use // * carefully and ensure reasonable settings for max_tokens and stop. // * // * @param n the n value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setN(Integer n) { // this.n = n; // return this; //} // ///** // * Get the stop property: A collection of textual sequences that will end completions generation. // * // * @return the stop value. // */ //public List getStop() { // return this.stop; //} // ///** // * Set the stop property: A collection of textual sequences that will end completions generation. // * // * @param stop the stop value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setStop(List stop) { // this.stop = stop; // return this; //} // ///** // * Get the presencePenalty property: A value that influences the probability of generated tokens appearing based on // * their existing presence in generated text. Positive values will make tokens less likely to appear when they // * already exist and increase the model's likelihood to output new topics. // * // * @return the presencePenalty value. // */ //public Double getPresencePenalty() { // return this.presencePenalty; //} // ///** // * Set the presencePenalty property: A value that influences the probability of generated tokens appearing based on // * their existing presence in generated text. Positive values will make tokens less likely to appear when they // * already exist and increase the model's likelihood to output new topics. // * // * @param presencePenalty the presencePenalty value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setPresencePenalty(Double presencePenalty) { // this.presencePenalty = presencePenalty; // return this; //} // ///** // * Get the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on // * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their // * frequency increases and decrease the likelihood of the model repeating the same statements verbatim. // * // * @return the frequencyPenalty value. // */ //public Double getFrequencyPenalty() { // return this.frequencyPenalty; //} // ///** // * Set the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on // * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their // * frequency increases and decrease the likelihood of the model repeating the same statements verbatim. // * // * @param frequencyPenalty the frequencyPenalty value to set. // * @return the ChatCompletionsOptions object itself. // */ //public AzureChatCompletionsOptions setFrequencyPenalty(Double frequencyPenalty) { // this.frequencyPenalty = frequencyPenalty; // return this; //} /** * Get the stream property: A value indicating whether chat completions should be streamed for this request. * * @return the stream value. */ public Boolean isStream() { return this.stream; } /** * Set the stream property: A value indicating whether chat completions should be streamed for this request. * * @param stream the stream value to set. * @return the ChatCompletionsOptions object itself. */ public AzureChatCompletionsOptions setStream(Boolean stream) { this.stream = stream; return this; } /** * Get the model property: The model name to provide as part of this completions request. Not applicable to Azure * OpenAI, where deployment information should be included in the Azure resource URI that's connected to. * * @return the model value. */ public String getModel() { return this.model; } /** * Set the model property: The model name to provide as part of this completions request. Not applicable to Azure * OpenAI, where deployment information should be included in the Azure resource URI that's connected to. * * @param model the model value to set. * @return the ChatCompletionsOptions object itself. */ public AzureChatCompletionsOptions setModel(String model) { this.model = model; return this; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class AzureChatMessage { /* * The role associated with this message payload. */ @JsonProperty(value = "role") private AzureChatRole role; /* * The text associated with this message payload. */ @JsonProperty(value = "content") private String content; /** * Creates an instance of ChatMessage class. * * @param role the role value to set. */ @JsonCreator public AzureChatMessage(@JsonProperty(value = "role") AzureChatRole role) { this.role = role; } /** * Get the role property: The role associated with this message payload. * * @return the role value. */ public AzureChatRole getRole() { return this.role; } /** * Get the content property: The text associated with this message payload. * * @return the content value. */ public String getContent() { return this.content; } /** * Set the content property: The text associated with this message payload. * * @param content the content value to set. * @return the ChatMessage object itself. */ public AzureChatMessage setContent(String content) { this.content = content; return this; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.Collection; import com.fasterxml.jackson.annotation.JsonCreator; public class AzureChatRole extends AzureExpandableStringEnum { /** The role that instructs or sets the behavior of the assistant. */ public static final AzureChatRole SYSTEM = fromString("system"); /** The role that provides responses to system-instructed, user-prompted input. */ public static final AzureChatRole ASSISTANT = fromString("assistant"); /** The role that provides input for chat completions. */ public static final AzureChatRole USER = fromString("user"); /** * Creates a new instance of ChatRole value. * * @deprecated Use the {@link #fromString(String)} factory method. */ @Deprecated public AzureChatRole() {} /** * Creates or finds a ChatRole from its string representation. * * @param name a name to look for. * @return the corresponding ChatRole. */ @JsonCreator public static AzureChatRole fromString(String name) { return fromString(name, AzureChatRole.class); } /** * Gets known ChatRole values. * * @return known ChatRole values. */ public static Collection values() { return values(AzureChatRole.class); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of * choices generated. */ @Data public final class AzureChoice { /* * The generated text for a given completions prompt. */ @JsonProperty(value = "text") private String text; /* * The ordered index associated with this completions choice. */ @JsonProperty(value = "index") private int index; /* * The log probabilities model for tokens associated with this completions choice. */ @JsonProperty(value = "logprobs") private AzureCompletionsLogProbabilityModel logprobs; /* * Reason for finishing */ @JsonProperty(value = "finish_reason") private AzureCompletionsFinishReason finishReason; /** * Creates an instance of Choice class. * * @param text the text value to set. * @param index the index value to set. * @param logprobs the logprobs value to set. * @param finishReason the finishReason value to set. */ @JsonCreator private AzureChoice( @JsonProperty(value = "text") String text, @JsonProperty(value = "index") int index, @JsonProperty(value = "logprobs") AzureCompletionsLogProbabilityModel logprobs, @JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) { this.text = text; this.index = index; this.logprobs = logprobs; this.finishReason = finishReason; } /** * Get the text property: The generated text for a given completions prompt. * * @return the text value. */ public String getText() { return this.text; } /** * Get the index property: The ordered index associated with this completions choice. * * @return the index value. */ public int getIndex() { return this.index; } /** * Get the logprobs property: The log probabilities model for tokens associated with this completions choice. * * @return the logprobs value. */ public AzureCompletionsLogProbabilityModel getLogprobs() { return this.logprobs; } /** * Get the finishReason property: Reason for finishing. * * @return the finishReason value. */ public AzureCompletionsFinishReason getFinishReason() { return this.finishReason; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class AzureCompletions { /* * A unique identifier associated with this completions response. */ @JsonProperty(value = "id") private String id; /* * The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. */ @JsonProperty(value = "created") private int created; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ @JsonProperty(value = "choices") private List choices; /* * Usage information for tokens processed and generated as part of this completions operation. */ @JsonProperty(value = "usage") private AzureCompletionsUsage usage; /** * Creates an instance of Completions class. * * @param id the id value to set. * @param created the created value to set. * @param choices the choices value to set. * @param usage the usage value to set. */ @JsonCreator private AzureCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") int created, @JsonProperty(value = "choices") List choices, @JsonProperty(value = "usage") AzureCompletionsUsage usage) { this.id = id; this.created = created; this.choices = choices; this.usage = usage; } /** * Get the id property: A unique identifier associated with this completions response. * * @return the id value. */ public String getId() { return this.id; } /** * Get the created property: The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. * * @return the created value. */ public int getCreated() { return this.created; } /** * Get the choices property: The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other * settings may limit the number of choices generated. * * @return the choices value. */ public List getChoices() { return this.choices; } /** * Get the usage property: Usage information for tokens processed and generated as part of this completions * operation. * * @return the usage value. */ public AzureCompletionsUsage getUsage() { return this.usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.Collection; import com.fasterxml.jackson.annotation.JsonCreator; /** Representation of the manner in which a completions response concluded. */ public final class AzureCompletionsFinishReason extends AzureExpandableStringEnum { /** Completions ended normally and reached its end of token generation. */ public static final AzureCompletionsFinishReason STOPPED = fromString("stopped"); /** Completions exhausted available token limits before generation could complete. */ public static final AzureCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached"); /** * Completions generated a response that was identified as potentially sensitive per content moderation policies. */ public static final AzureCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered"); /** * Creates a new instance of CompletionsFinishReason value. * * @deprecated Use the {@link #fromString(String)} factory method. */ @Deprecated public AzureCompletionsFinishReason() {} /** * Creates or finds a CompletionsFinishReason from its string representation. * * @param name a name to look for. * @return the corresponding CompletionsFinishReason. */ @JsonCreator public static AzureCompletionsFinishReason fromString(String name) { return fromString(name, AzureCompletionsFinishReason.class); } /** * Gets known CompletionsFinishReason values. * * @return known CompletionsFinishReason values. */ public static Collection values() { return values(AzureCompletionsFinishReason.class); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** Representation of a log probabilities model for a completions generation. */ @Data public final class AzureCompletionsLogProbabilityModel { /* * The textual forms of tokens evaluated in this probability model. */ @JsonProperty(value = "tokens") private List tokens; /* * A collection of log probability values for the tokens in this completions data. */ @JsonProperty(value = "token_logprobs") private List tokenLogprobs; /* * A mapping of tokens to maximum log probability values in this completions data. */ @JsonProperty(value = "top_logprobs") private List> topLogprobs; /* * The text offsets associated with tokens in this completions data. */ @JsonProperty(value = "text_offset") private List textOffset; /** * Creates an instance of CompletionsLogProbabilityModel class. * * @param tokens the tokens value to set. * @param tokenLogprobs the tokenLogprobs value to set. * @param topLogprobs the topLogprobs value to set. * @param textOffset the textOffset value to set. */ @JsonCreator private AzureCompletionsLogProbabilityModel( @JsonProperty(value = "tokens") List tokens, @JsonProperty(value = "token_logprobs") List tokenLogprobs, @JsonProperty(value = "top_logprobs") List> topLogprobs, @JsonProperty(value = "text_offset") List textOffset) { this.tokens = tokens; this.tokenLogprobs = tokenLogprobs; this.topLogprobs = topLogprobs; this.textOffset = textOffset; } /** * Get the tokens property: The textual forms of tokens evaluated in this probability model. * * @return the tokens value. */ public List getTokens() { return this.tokens; } /** * Get the tokenLogprobs property: A collection of log probability values for the tokens in this completions data. * * @return the tokenLogprobs value. */ public List getTokenLogprobs() { return this.tokenLogprobs; } /** * Get the topLogprobs property: A mapping of tokens to maximum log probability values in this completions data. * * @return the topLogprobs value. */ public List> getTopLogprobs() { return this.topLogprobs; } /** * Get the textOffset property: The text offsets associated with tokens in this completions data. * * @return the textOffset value. */ public List getTextOffset() { return this.textOffset; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, * choices, choice alternates, best_of generations, and other consumers. */ @Data public final class AzureCompletionsUsage { /* * The number of tokens generated across all completions emissions. */ @JsonProperty(value = "completion_tokens") private int completionTokens; /* * The number of tokens in the provided prompts for the completions request. */ @JsonProperty(value = "prompt_tokens") private int promptTokens; /* * The total number of tokens processed for the completions request and response. */ @JsonProperty(value = "total_tokens") private int totalTokens; /** * Creates an instance of CompletionsUsage class. * * @param completionTokens the completionTokens value to set. * @param promptTokens the promptTokens value to set. * @param totalTokens the totalTokens value to set. */ @JsonCreator private AzureCompletionsUsage( @JsonProperty(value = "completion_tokens") int completionTokens, @JsonProperty(value = "prompt_tokens") int promptTokens, @JsonProperty(value = "total_tokens") int totalTokens) { this.completionTokens = completionTokens; this.promptTokens = promptTokens; this.totalTokens = totalTokens; } /** * Get the completionTokens property: The number of tokens generated across all completions emissions. * * @return the completionTokens value. */ public int getCompletionTokens() { return this.completionTokens; } /** * Get the promptTokens property: The number of tokens in the provided prompts for the completions request. * * @return the promptTokens value. */ public int getPromptTokens() { return this.promptTokens; } /** * Get the totalTokens property: The total number of tokens processed for the completions request and response. * * @return the totalTokens value. */ public int getTotalTokens() { return this.totalTokens; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package ai.chat2db.server.web.api.controller.ai.azure.model; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils; import com.fasterxml.jackson.annotation.JsonValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.lang.invoke.MethodType.methodType; /** * Base implementation for expandable, single string enums. * * @param a specific expandable enum type */ public abstract class AzureExpandableStringEnum> { private static final Map, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>(); private static final Map, ConcurrentHashMap>> VALUES = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(AzureExpandableStringEnum.class); private String name; private Class clazz; /** * Creates a new instance of {@link AzureExpandableStringEnum} without a {@link #toString()} value. *

* This constructor shouldn't be called as it will produce a {@link AzureExpandableStringEnum} which doesn't * have a String enum value. * * @deprecated Use the {@link #fromString(String, Class)} factory method. */ @Deprecated public AzureExpandableStringEnum() { } /** * Creates an instance of the specific expandable string enum from a String. * * @param name The value to create the instance from. * @param clazz The class of the expandable string enum. * @param the class of the expandable string enum. * @return The expandable string enum instance. * * @throws RuntimeException wrapping implementation class constructor exception (if any is thrown). */ @SuppressWarnings({"unchecked", "deprecation"}) protected static > T fromString(String name, Class clazz) { if (name == null) { return null; } ConcurrentHashMap clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>()); T value = (T) clazzValues.get(name); if (value != null) { return value; } else { MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, AzureExpandableStringEnum::getDefaultConstructor); if (ctor == null) { // logged in ExpandableStringEnum::getDefaultConstructor return null; } try { value = (T) ctor.invoke(); } catch (Throwable e) { LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e); return null; } return value.nameAndAddValue(name, value, clazz); } } private static MethodHandle getDefaultConstructor(Class clazz) { try { MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz); return lookup.findConstructor(clazz, methodType(void.class)); } catch (NoSuchMethodException | IllegalAccessException e) { LOGGER.info("Can't find or access default constructor for {}, make sure corresponding package is open to azure-core", clazz.getName(), e); } catch (Exception e) { LOGGER.info("Failed to get lookup for {}", clazz.getName(), e); } return null; } @SuppressWarnings("unchecked") T nameAndAddValue(String name, T value, Class clazz) { this.name = name; this.clazz = clazz; ((ConcurrentHashMap) VALUES.get(clazz)).put(name, value); return (T) this; } /** * Gets a collection of all known values to an expandable string enum type. * * @param clazz the class of the expandable string enum. * @param the class of the expandable string enum. * @return A collection of all known values for the given {@code clazz}. */ @SuppressWarnings("unchecked") protected static > Collection values(Class clazz) { return new ArrayList((Collection) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values()); } @Override @JsonValue public String toString() { return this.name; } @Override public int hashCode() { return Objects.hash(this.clazz, this.name); } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) { return false; } else if (obj == this) { return true; } else if (this.name == null) { return ((AzureExpandableStringEnum) obj).name == null; } else { return this.name.equals(((AzureExpandableStringEnum) obj).name); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/util/AzureReflectionUtils.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package ai.chat2db.server.web.api.controller.ai.azure.util; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.security.PrivilegedExceptionAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility methods that aid in performing reflective operations. */ @SuppressWarnings("deprecation") public final class AzureReflectionUtils { private static final Logger LOGGER = LoggerFactory.getLogger(AzureReflectionUtils.class); private static final boolean MODULE_BASED; private static final MethodHandle CLASS_GET_MODULE_METHOD_HANDLE; private static final MethodHandle MODULE_IS_NAMED_METHOD_HANDLE; private static final MethodHandle MODULE_ADD_READS_METHOD_HANDLE; private static final MethodHandle METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE; private static final MethodHandle MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE; private static final MethodHandle MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE; private static final MethodHandles.Lookup LOOKUP; private static final Object CORE_MODULE; private static final MethodHandle JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR; static { boolean moduleBased = false; MethodHandle classGetModule = null; MethodHandle moduleIsNamed = null; MethodHandle moduleAddReads = null; MethodHandle methodHandlesPrivateLookupIn = null; MethodHandle moduleIsOpenUnconditionally = null; MethodHandle moduleIsOpenToOtherModule = null; MethodHandles.Lookup lookup = MethodHandles.lookup(); Object coreModule = null; MethodHandle jdkInternalPrivateLookupInConstructor = null; try { Class moduleClass = Class.forName("java.lang.Module"); classGetModule = lookup.unreflect(Class.class.getDeclaredMethod("getModule")); moduleIsNamed = lookup.unreflect(moduleClass.getDeclaredMethod("isNamed")); moduleAddReads = lookup.unreflect(moduleClass.getDeclaredMethod("addReads", moduleClass)); methodHandlesPrivateLookupIn = lookup.findStatic(MethodHandles.class, "privateLookupIn", MethodType.methodType(MethodHandles.Lookup.class, Class.class, MethodHandles.Lookup.class)); moduleIsOpenUnconditionally = lookup.unreflect(moduleClass.getDeclaredMethod("isOpen", String.class)); moduleIsOpenToOtherModule = lookup.unreflect( moduleClass.getDeclaredMethod("isOpen", String.class, moduleClass)); coreModule = classGetModule.invokeWithArguments(AzureReflectionUtils.class); moduleBased = true; } catch (Throwable throwable) { if (throwable instanceof Error) { throw (Error) throwable; } else { LOGGER.error("Unable to create MethodHandles to use Java 9+ MethodHandles.privateLookupIn. " + "Will attempt to fallback to using the package-private constructor.", throwable); } } if (!moduleBased) { try { Constructor privateLookupInConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); if (!privateLookupInConstructor.isAccessible()) { privateLookupInConstructor.setAccessible(true); } jdkInternalPrivateLookupInConstructor = lookup.unreflectConstructor(privateLookupInConstructor); } catch (ReflectiveOperationException ex) { LOGGER.error("Unable to use package-private MethodHandles.Lookup constructor.", ex); } } MODULE_BASED = moduleBased; CLASS_GET_MODULE_METHOD_HANDLE = classGetModule; MODULE_IS_NAMED_METHOD_HANDLE = moduleIsNamed; MODULE_ADD_READS_METHOD_HANDLE = moduleAddReads; METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE = methodHandlesPrivateLookupIn; MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE = moduleIsOpenUnconditionally; MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE = moduleIsOpenToOtherModule; LOOKUP = lookup; CORE_MODULE = coreModule; JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR = jdkInternalPrivateLookupInConstructor; } /** * Gets the {@link MethodHandles.Lookup} to use when performing reflective operations. *

* If Java 8 is being used this will always return {@link MethodHandles.Lookup#publicLookup()} as Java 8 doesn't * have module boundaries that will prevent reflective access to the {@code targetClass}. *

* If Java 9 or above is being used this will return a {@link MethodHandles.Lookup} based on whether the module * containing the {@code targetClass} exports the package containing the class. Otherwise, the * {@link MethodHandles.Lookup} associated to {@code com.azure.core} will attempt to read the module containing * {@code targetClass}. * * @param targetClass The {@link Class} that will need to be reflectively accessed. * @return The {@link MethodHandles.Lookup} that will allow {@code com.azure.core} to access the {@code targetClass} * reflectively. * @throws Exception If the underlying reflective calls throw an exception. */ public static MethodHandles.Lookup getLookupToUse(Class targetClass) throws Exception { try { if (MODULE_BASED) { Object responseModule = CLASS_GET_MODULE_METHOD_HANDLE.invoke(targetClass); // The unnamed module is opened unconditionally, have Core read it and use a private proxy lookup to // enable all lookup scenarios. if (!(boolean) MODULE_IS_NAMED_METHOD_HANDLE.invoke(responseModule)) { MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule); return performSafePrivateLookupIn(targetClass); } // If the response module is the Core module return the Core private lookup. if (responseModule == CORE_MODULE) { return LOOKUP; } // Next check if the target class module is opened either unconditionally or to Core's module. If so, // also use a private proxy lookup to enable all lookup scenarios. String packageName = targetClass.getPackage().getName(); if ((boolean) MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE .invokeWithArguments(responseModule, packageName) || (boolean) MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE .invokeWithArguments(responseModule, packageName, CORE_MODULE)) { MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule); return performSafePrivateLookupIn(targetClass); } // Otherwise, return the public lookup as there are no specialty ways to access the other module. return MethodHandles.publicLookup(); } else { return (MethodHandles.Lookup) JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR.invoke(targetClass); } } catch (Throwable throwable) { // invoke(Class targetClass) throws Throwable { // MethodHandles::privateLookupIn() throws SecurityException if denied by the security manager if (System.getSecurityManager() == null) { return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE .invokeExact(targetClass, LOOKUP); } else { return java.security.AccessController.doPrivileged((PrivilegedExceptionAction) () -> { try { return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE .invokeExact(targetClass, LOOKUP); } catch (Throwable throwable) { if (throwable instanceof Error) { throw (Error) throwable; } else { throw (Exception) throwable; } } }); } } public static boolean isModuleBased() { return MODULE_BASED; } AzureReflectionUtils() { } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @date 23/09/26 */ @Slf4j public class BaichuanAIClient { /** * BAICHUAN OPENAI KEY */ public static final String BAICHUAN_API_KEY = "baichuan.chatgpt.apiKey"; /** * BAICHUAN OPENAI SECRET KEY */ public static final String BAICHUAN_SECRET_KEY = "baichuan.chatgpt.secretKey"; /** * BAICHUAN OPENAI HOST */ public static final String BAICHUAN_HOST = "baichuan.host"; /** * BAICHUAN OPENAI model */ public static final String BAICHUAN_MODEL= "baichuan.model"; /** * BAICHUAN OPENAI embedding model */ public static final String BAICHUAN_EMBEDDING_MODEL = "baichuan.embedding.model"; private static BaichuanAIStreamClient BAICHUAN_AI_CLIENT; public static BaichuanAIStreamClient getInstance() { if (BAICHUAN_AI_CLIENT != null) { return BAICHUAN_AI_CLIENT; } else { return singleton(); } } private static BaichuanAIStreamClient singleton() { if (BAICHUAN_AI_CLIENT == null) { synchronized (BaichuanAIClient.class) { if (BAICHUAN_AI_CLIENT == null) { refresh(); } } } return BAICHUAN_AI_CLIENT; } public static void refresh() { String apiKey = ""; String apiHost = "https://api.baichuan-ai.com/v1/stream/chat"; String model = "Baichuan2-53B"; String secretKey = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(BAICHUAN_HOST).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiHost = apiHostConfig.getContent(); if (apiHost.endsWith("/")) { apiHost = apiHost.substring(0, apiHost.length() - 1); } } Config config = configService.find(BAICHUAN_API_KEY).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { apiKey = config.getContent(); } Config secretConfig = configService.find(BAICHUAN_SECRET_KEY).getData(); if (secretConfig != null && StringUtils.isNotBlank(secretConfig.getContent())) { secretKey = secretConfig.getContent(); } Config deployConfig = configService.find(BAICHUAN_MODEL).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { model = deployConfig.getContent(); } BAICHUAN_AI_CLIENT = BaichuanAIStreamClient.builder().apiKey(apiKey).secretKey(secretKey) .apiHost(apiHost).model(model).build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.baichuan.interceptor.BaichuanHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.sse.EventSourceListener; import okio.BufferedSource; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Fast Chat Aligned Client * * @author moji */ @Slf4j public class BaichuanAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; @Getter @NotNull private String secretKey; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private BaichuanAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; this.model = builder.model; this.secretKey = builder.secretKey; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new BaichuanHeaderAuthorizationInterceptor(this.apiKey, this.secretKey)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static BaichuanAIStreamClient.Builder builder() { return new BaichuanAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String secretKey; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public BaichuanAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } public BaichuanAIStreamClient.Builder secretKey(String secretKey) { this.secretKey = secretKey; return this; } /** * @param apiHostValue * @return */ public BaichuanAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public BaichuanAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public BaichuanAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public BaichuanAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public BaichuanAIStreamClient build() { return new BaichuanAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Baichuan Chat Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:Baichuan ChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Baichuan AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { BaichuanChatCompletionsOptions chatCompletionsOptions = new BaichuanChatCompletionsOptions(); chatCompletionsOptions.setModel(this.model); chatCompletionsOptions.setMessages(chatMessages); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event //Send the request and process the response try (Response response = this.okHttpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } //Read and output response data BufferedSource source = response.body().source(); while (!source.exhausted()) { String content = source.readUtf8Line(); eventSourceListener.onEvent(null, "[DATA]", null, content); } eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); } catch (Exception e) { log.error("baichuan ai error", e); } log.info("finish invoking baichuan ai"); } catch (Exception e) { log.error("baichuan ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/interceptor/BaichuanHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.interceptor; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okio.Buffer; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; /** * header apikey * * @author grt * @since 2023-03-23 */ @Slf4j @Getter public class BaichuanHeaderAuthorizationInterceptor implements Interceptor { private String apiKey; private String secretKey; public BaichuanHeaderAuthorizationInterceptor(String apiKey, String secretKey) { this.apiKey = apiKey; this.secretKey = secretKey; } @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); // Get the current timestamp (UTC standard timestamp) long timestamp = System.currentTimeMillis() / 1000; // Get the original HTTP-Body RequestBody originalRequestBody = originalRequest.body(); Buffer buffer = new Buffer(); if (originalRequestBody != null) { originalRequestBody.writeTo(buffer); } String httpBody = buffer.readUtf8(); // Calculate X-BC-Signature String signature = calculateSignature(secretKey, httpBody, timestamp); //Create a new request and add custom request headers Request newRequest = originalRequest.newBuilder() .addHeader("Authorization", "Bearer " + apiKey) .addHeader("Content-Type", "application/json") .addHeader("X-BC-Sign-Algo", "MD5") .addHeader("X-BC-Timestamp", String.valueOf(timestamp)) .addHeader("X-BC-Signature", signature) .method(originalRequest.method(), RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), httpBody)) .build(); return chain.proceed(newRequest); } private String calculateSignature(String secretKey, String httpBody, long timestamp) { String toHash = secretKey + httpBody + timestamp; return md5(toHash); } private String md5(String s) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] result = digest.digest(s.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : result) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { log.error("baichuan secret key md5 error", e); return ""; } } private String calculateSignature(String secretKey, RequestBody body, long timestamp) { try { String requestBody = bodyToString(body); String rawSignature = secretKey + requestBody + timestamp; // Use MD5 to calculate signature MessageDigest md = MessageDigest.getInstance("MD5"); byte[] mdBytes = md.digest(rawSignature.getBytes(StandardCharsets.UTF_8)); // Convert MD5 byte array to Base64 encoded string return Base64.getEncoder().encodeToString(mdBytes); } catch (IOException | NoSuchAlgorithmException e) { log.error("baichuan secret key md5 error", e); return ""; } } private String bodyToString(RequestBody body) throws IOException { //Convert RequestBody to string return body == null ? "" : body.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/listener/BaichuanChatAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.listener; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletions; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Objects; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class BaichuanChatAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public BaichuanChatAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Baichuan Chat Sse connecting..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Baichuan Chat AI response data:{}", data); if (data.equals("[DONE]")) { log.info("Baichuan Chat AI closed"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } BaichuanChatCompletions chatCompletions = mapper.readValue(data, BaichuanChatCompletions.class); String text = ""; log.info("code={} msg={}", chatCompletions.getCode(), chatCompletions.getMsg()); for (BaichuanChatMessage message : chatCompletions.getData().getMessages()) { if (message != null) { log.info("message: {}, Chat Role: {}", message.getContent(), message.getRole()); if (message.getContent() != null) { text = message.getContent(); } } } Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); log.info("FastChatAI close sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString)) { if (Objects.nonNull(t)) { bodyString = t.getMessage(); } else { bodyString = String.valueOf(response.code()); } } log.error("Baichuan Chat AI sse response:{}", bodyString); } else { log.error("Baichuan Chat AI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Baichuan Chat AI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Baichuan Chat AI send data error:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class BaichuanChatCompletions { /* * A unique identifier associated with this chat completions response. */ private String msg; private int code; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ @JsonProperty(value = "data") private BaichuanChatData data; /* * Usage information for tokens processed and generated as part of this completions operation. */ private BaichuanChatCompletionsUsage usage; /** * Creates an instance of ChatCompletions class. * * @param msg the id value to set. * @param code the created value to set. * @param choices the choices value to set. * @param usage the usage value to set. */ @JsonCreator private BaichuanChatCompletions( @JsonProperty(value = "msg") String msg, @JsonProperty(value = "code") int code, @JsonProperty(value = "data") BaichuanChatData choices, @JsonProperty(value = "usage") BaichuanChatCompletionsUsage usage) { this.msg = msg; this.code = code; this.data = choices; this.usage = usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.baichuan.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * The configuration information for a chat completions request. Completions support a wide variety of tasks and * generate text that continues from or "completes" provided prompt data. */ @Data @NoArgsConstructor @AllArgsConstructor public final class BaichuanChatCompletionsOptions { /* * The collection of context messages associated with this chat completions request. * Typical usage begins with a chat message for the System role that provides instructions for * the behavior of the assistant, followed by alternating messages between the User and * Assistant roles. */ private List messages; // /* * The model name to provide as part of this completions request. * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat * resource URI that's connected to. */ private String model; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatCompletionsUsage.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.baichuan.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.NoArgsConstructor; /** * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, * choices, choice alternates, best_of generations, and other consumers. */ @Data @NoArgsConstructor public final class BaichuanChatCompletionsUsage { /* * The number of tokens generated across all completions emissions. */ @JsonProperty(value = "answer_tokens") private int answerTokens; /* * The number of tokens in the provided prompts for the completions request. */ @JsonProperty(value = "prompt_tokens") private int promptTokens; /* * The total number of tokens processed for the completions request and response. */ @JsonProperty(value = "total_tokens") private int totalTokens; /** * Creates an instance of CompletionsUsage class. * * @param completionTokens the completionTokens value to set. * @param promptTokens the promptTokens value to set. * @param totalTokens the totalTokens value to set. */ @JsonCreator private BaichuanChatCompletionsUsage( @JsonProperty(value = "answer_tokens") int completionTokens, @JsonProperty(value = "prompt_tokens") int promptTokens, @JsonProperty(value = "total_tokens") int totalTokens) { this.answerTokens = completionTokens; this.promptTokens = promptTokens; this.totalTokens = totalTokens; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatData.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.baichuan.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of * choices generated. */ @Data public final class BaichuanChatData { /* * The log probabilities model for tokens associated with this completions choice. */ @JsonProperty(value = "messages") private List messages; /** * Creates an instance of Choice class. * * @param message the message value to set */ @JsonCreator private BaichuanChatData( @JsonProperty(value = "messages") List message) { this.messages = message; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/model/BaichuanChatMessage.java ================================================ package ai.chat2db.server.web.api.controller.ai.baichuan.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class BaichuanChatMessage { /* * The role associated with this message payload. */ @JsonProperty(value = "role") private FastChatRole role; /* * The text associated with this message payload. */ @JsonProperty(value = "content") private String content; /* * Reason for finishing */ @JsonProperty(value = "finish_reason") private String finishReason; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.chat2db.client; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.chat2db.interceptor.Chat2dbHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatOpenAiApi; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.ChatCompletion; import com.unfbx.chatgpt.entity.chat.Message; import com.unfbx.chatgpt.interceptor.HeaderAuthorizationInterceptor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Fast Chat Aligned Client * * @author moji */ @Slf4j public class Chat2DBAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; @Getter private FastChatOpenAiApi fastChatOpenAiApi; /** * @param builder */ private Chat2DBAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; if (!apiHost.endsWith("/")){ apiHost = apiHost + "/"; } this.model = builder.model; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; this.fastChatOpenAiApi = new Retrofit.Builder() .baseUrl(apiHost) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create()) .build().create(FastChatOpenAiApi.class); } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new Chat2dbHeaderAuthorizationInterceptor(this.apiKey, this.model)) .connectTimeout(50, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static Chat2DBAIStreamClient.Builder builder() { return new Chat2DBAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public Chat2DBAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } /** * @param apiHostValue * @return */ public Chat2DBAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public Chat2DBAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public Chat2DBAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public Chat2DBAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public Chat2DBAIStreamClient build() { return new Chat2DBAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Chat Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:ChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } try { ChatCompletion chatCompletion = ChatCompletion.builder() .messages(chatMessages) .stream(true) .build(); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletion); Request request = new Request.Builder() .url(this.apiHost + "v1/chat/completions") .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking chat ai"); } catch (Exception e) { log.error("chat ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } /** * Creates an embedding vector representing the input text. * * @param input * @return EmbeddingResponse */ public FastChatEmbeddingResponse embeddings(String input) { FastChatEmbedding embedding = FastChatEmbedding.builder().input(input).build(); if (StringUtils.isNotBlank(this.embeddingModel)) { embedding.setModel(this.embeddingModel); } return this.embeddings(embedding); } /** * Creates an embedding vector representing the input text. * * @param embedding * @return EmbeddingResponse */ public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { try { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(embedding); Request request = new Request.Builder() .url(this.apiHost + "v1/embeddings") .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); FastChatEmbeddingResponse chatEmbeddingResponse = null; Response response = this.okHttpClient.newCall(request).execute(); if (response.isSuccessful()) { String body = response.body().string(); chatEmbeddingResponse = mapper.readValue(body, FastChatEmbeddingResponse.class); } log.info("finish invoking chat embedding"); return chatEmbeddingResponse; } catch (Exception e) { log.error("chat ai error", e); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2dbAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.chat2db.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import com.unfbx.chatgpt.constant.OpenAIConst; import lombok.extern.slf4j.Slf4j; import java.util.Objects; /** * @author jipengfei * @version : OpenAIClient.java */ @Slf4j public class Chat2dbAIClient { public static final String CHAT2DB_OPENAI_KEY = "chat2db.apiKey"; /** * OPENAI interface domain name */ public static final String CHAT2DB_OPENAI_HOST = "chat2db.apiHost"; /** * OPENAI model */ public static final String CHAT2DB_OPENAI_MODEL = "chat2db.model"; /** * FASTCHAT OPENAI embedding model */ public static final String CHAT2DB_EMBEDDING_MODEL= "fastchat.embedding.model"; private static volatile Chat2DBAIStreamClient CHAT2DB_AI_STREAM_CLIENT; public static Chat2DBAIStreamClient getInstance() { if (CHAT2DB_AI_STREAM_CLIENT != null) { return CHAT2DB_AI_STREAM_CLIENT; } else { return singleton(); } } private static Chat2DBAIStreamClient singleton() { if (CHAT2DB_AI_STREAM_CLIENT == null) { synchronized (Chat2dbAIClient.class) { if (CHAT2DB_AI_STREAM_CLIENT == null) { refresh(); } } } return CHAT2DB_AI_STREAM_CLIENT; } public static void refresh() { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); CHAT2DB_AI_STREAM_CLIENT = Chat2DBAIStreamClient.builder().apiHost(getApiHost(configService)) .apiKey(getApiKey(configService)).model(getModel(configService)).build(); } private static String getApiHost(ConfigService configService) { Config apiHostConfig = configService.find(CHAT2DB_OPENAI_HOST).getData(); if (Objects.nonNull(apiHostConfig)) { return apiHostConfig.getContent(); } String apiHost = ApplicationContextUtil.getProperty(CHAT2DB_OPENAI_HOST); if (apiHost.isBlank()) { return OpenAIConst.OPENAI_HOST; } return apiHost; } private static String getApiKey(ConfigService configService) { String apiKey; Config config = configService.find(CHAT2DB_OPENAI_KEY).getData(); if (Objects.nonNull(config)) { apiKey = config.getContent(); } else { apiKey = ApplicationContextUtil.getProperty(CHAT2DB_OPENAI_KEY); } log.info("refresh chat2db apikey:{}", maskApiKey(apiKey)); return apiKey; } private static String getModel(ConfigService configService) { Config modelConfig = configService.find(CHAT2DB_OPENAI_MODEL).getData(); if (Objects.nonNull(modelConfig)) { return modelConfig.getContent(); } return null; } private static String maskApiKey(String input) { if (Objects.isNull(input)) { return null; } StringBuilder maskedString = new StringBuilder(input); for (int i = input.length() / 4; i < input.length() / 2; i++) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/interceptor/Chat2dbHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.chat2db.interceptor; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.web.api.util.StringUtils; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * Description: Request to add header apikey * * @author grt * @since 2023-03-23 */ @Getter public class Chat2dbHeaderAuthorizationInterceptor implements Interceptor { private String apiKey; private String model; public Chat2dbHeaderAuthorizationInterceptor(String apiKey, String model) { this.apiKey = apiKey; this.model = model; if (StringUtils.isEmpty(model)) { this.model = AiSqlSourceEnum.OPENAI.getCode(); } } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() .header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey) .header("X-CHAT2DB-AI-TYPE", model) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); return chain.proceed(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.chat2db.listener; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletions; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletions; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.Objects; /** * Description: Chat2dbAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class Chat2dbAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; public Chat2dbAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Chat2db AI 建立sse连接..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Chat2db AI returns data: {}", data); if (data.equals("[DONE]")) { log.info("Chat2db AI return data is over"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); String text = completionResponse.getChoices().get(0).getDelta() == null ? completionResponse.getChoices().get(0).getText() : completionResponse.getChoices().get(0).getDelta().getContent(); String completionId = completionResponse.getId(); Message message = new Message(); if (text != null) { message.setContent(text); sseEmitter.send(SseEmitter.event() .id(completionId) .data(message) .reconnectTime(3000)); } } @Override public void onClosed(EventSource eventSource) { sseEmitter.complete(); log.info("Chat2db AI closes sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = null; if (Objects.nonNull(body)) { bodyString = body.string(); log.error("Chat2db AI sse connection exception data: {}", bodyString, t); } else { log.error("Chat2db AI sse connection exception data: {}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Chat2db AI Error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Exception in sending data:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author jipengfei * @version : OpenAIClient.java */ @Slf4j public class ClaudeAIClient { public static final String CLAUDE_SESSION_KEY = "claude.sessionKey"; public static final String CLAUDE_API_HOST = "claude.apiHost"; public static final String CLAUDE_ORG_ID = "claude.orgId"; public static final String CLAUDE_USER_ID = "claude.userId"; private static ClaudeAiStreamClient CLAUDE_AI_STREAM_CLIENT; private static String apiKey; public static ClaudeAiStreamClient getInstance() { if (CLAUDE_AI_STREAM_CLIENT != null) { return CLAUDE_AI_STREAM_CLIENT; } else { return singleton(); } } private static ClaudeAiStreamClient singleton() { if (CLAUDE_AI_STREAM_CLIENT == null) { synchronized (ClaudeAIClient.class) { if (CLAUDE_AI_STREAM_CLIENT == null) { refresh(); } } } return CLAUDE_AI_STREAM_CLIENT; } public static void refresh() { String apikey = ""; String orgId = ""; String userId = ""; String apiHost = "https://claude.ai/api/append_message"; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(CLAUDE_API_HOST).getData(); if (apiHostConfig != null) { apiHost = apiHostConfig.getContent(); } Config config = configService.find(CLAUDE_SESSION_KEY).getData(); if (config != null) { apikey = config.getContent(); } Config orgConfig = configService.find(CLAUDE_ORG_ID).getData(); if (orgConfig != null) { orgId = orgConfig.getContent(); } Config userConfig = configService.find(CLAUDE_USER_ID).getData(); if (userConfig != null) { userId = userConfig.getContent(); } log.info("refresh claude sessionKey:{}", maskApiKey(apikey)); CLAUDE_AI_STREAM_CLIENT = ClaudeAiStreamClient.builder().apiHost(apiHost) .sessionKey(apikey).orgId(orgId).userId(userId).build(); apiKey = apikey; } private static String maskApiKey(String input) { if (StringUtils.isBlank(input)) { return input; } StringBuilder maskedString = new StringBuilder(input); for (int i = input.length() / 4; i < input.length() / 2; i++) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.claude.interceptor.ClaudeHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Custom AI interface client * * @author moji */ @Slf4j public class ClaudeAiStreamClient { /** * apikey */ @Getter @NotNull private String sessionKey; /** * endpoint */ @Getter @NotNull private String orgId; /** * deployId */ @Getter private String apiHost; @Getter private String userId; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private ClaudeAiStreamClient(Builder builder) { this.sessionKey = builder.sessionKey; this.orgId = builder.orgId; this.apiHost = builder.apiHost; this.userId = builder.userId; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new ClaudeHeaderAuthorizationInterceptor(this.sessionKey, this.orgId)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static ClaudeAiStreamClient.Builder builder() { return new ClaudeAiStreamClient.Builder(); } public static final class Builder { private String sessionKey; private String orgId; private String apiHost; private String userId; /** * Customize OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public ClaudeAiStreamClient.Builder sessionKey(String sessionKey) { this.sessionKey = sessionKey; return this; } /** * @param apiHost * @return */ public ClaudeAiStreamClient.Builder apiHost(String apiHost) { this.apiHost = apiHost; return this; } /** * @param orgId * @return */ public ClaudeAiStreamClient.Builder orgId(String orgId) { this.orgId = orgId; return this; } public ClaudeAiStreamClient.Builder userId(String userId) { this.userId = userId; return this; } public ClaudeAiStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public ClaudeAiStreamClient build() { return new ClaudeAiStreamClient(this); } } /** * chat * * @param claudeChatMessage * @param eventSourceListener */ public void streamCompletions(ClaudeChatMessage claudeChatMessage, EventSourceListener eventSourceListener) { if (Objects.isNull(eventSourceListener)) { log.error("param error:AzureEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Claude AI, prompt:{}", claudeChatMessage.getText()); try { claudeChatMessage.setOrganization_uuid(this.orgId); claudeChatMessage.setConversation_uuid(this.userId); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(claudeChatMessage); Request request = new Request.Builder() .url(this.apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking claude ai"); } catch (Exception e) { log.error("claude ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.interceptor; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * Description: Request to add header apikey * * @author grt * @since 2023-03-23 */ @Getter public class ClaudeHeaderAuthorizationInterceptor implements Interceptor { private String sessionKey; private String orgId; public ClaudeHeaderAuthorizationInterceptor(String sessionKey, String orgId) { this.orgId = orgId; this.sessionKey = sessionKey; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() .header("Cookie", "sessionKey=" + sessionKey) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); return chain.proceed(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.listener; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeCompletionResponse; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.Objects; /** * ClaudeAIEventSourceListener */ @Slf4j public class ClaudeAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public ClaudeAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("ClaudeAIEventSourceListener..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Claude AI data:{}", data); if (data.equals("[DONE]")) { log.info("Claude AI end"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } // Read JSON ClaudeCompletionResponse completionResponse = mapper.readValue(data, ClaudeCompletionResponse.class); String text = completionResponse.getCompletion(); Message message = new Message(); if (text != null) { message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } } @Override public void onClosed(EventSource eventSource) { sseEmitter.complete(); log.info("Claude AI closed..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = null; if (Objects.nonNull(body)) { bodyString = body.string(); log.error("Claude sse error:{}", bodyString, t); } else { log.error("Claude sse body error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Claude sse error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Exception in sending data:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.claude.model; import lombok.Data; @Data public final class ClaudeChatCompletionsOptions { private Boolean incremental = true; private String model = "claude-2"; private String prompt; private String timezone = "Asia/Shanghai"; private Boolean stream = true; public Boolean isStream() { return this.stream; } public ClaudeChatCompletionsOptions setStream(Boolean stream) { this.stream = stream; return this; } public String getModel() { return this.model; } public ClaudeChatCompletionsOptions setModel(String model) { this.model = model; return this; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.model; import lombok.Data; @Data public class ClaudeChatMessage { private String conversation_uuid; private String organization_uuid; private String text; private ClaudeChatCompletionsOptions completion; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.model; import com.unfbx.chatgpt.entity.common.Usage; import lombok.Data; import java.io.Serial; import java.io.Serializable; /** * @author moji * @version : ClaudeCompletionResponse.java */ @Data public class ClaudeCompletionResponse implements Serializable { @Serial private static final long serialVersionUID = 4968922211204353592L; private String log_id; private String stop_reason; private String stop; private String model; private String completion; private Usage usage; private ClaudeMessageLimit messageLimit; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java ================================================ package ai.chat2db.server.web.api.controller.ai.claude.model; import lombok.Data; @Data public class ClaudeMessageLimit { private String type; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/config/LocalCache.java ================================================ package ai.chat2db.server.web.api.controller.ai.config; import cn.hutool.cache.CacheUtil; import cn.hutool.cache.impl.TimedCache; import cn.hutool.core.date.DateUnit; /** * description: * * @author https:www.unfbx.com * @date 2023-03-10 */ public class LocalCache { /** * Cache duration */ public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis(); /** * Cleanup interval */ private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis(); /** * Cache object */ public static final TimedCache CACHE = CacheUtil.newTimedCache(TIMEOUT); static { //Start a scheduled task CACHE.schedulePrune(CLEAN_TIMEOUT); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/converter/ChatConverter.java ================================================ package ai.chat2db.server.web.api.controller.ai.converter; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatItem; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import com.unfbx.chatgpt.entity.common.Usage; import com.unfbx.chatgpt.entity.embeddings.EmbeddingResponse; import com.unfbx.chatgpt.entity.embeddings.Item; import org.mapstruct.Mapper; /** * @author moji * @version ChatConverter.java, v 0.1 April 2, 2023 13:31 moji Exp $ * @date 2023/04/02 */ @Mapper(componentModel = "spring") public abstract class ChatConverter { /** * Parameter conversion * * @param request * @return */ public abstract TableQueryParam chat2tableQuery(ChatQueryRequest request); /** * chat convert * * @param item * @return */ public abstract FastChatItem item2ChatItem(Item item); /** * usage convert * * @param usage * @return */ public abstract FastChatCompletionsUsage usage2usage(Usage usage); /** * response convert * * @param embeddingResponse * @return */ public abstract FastChatEmbeddingResponse response2response(EmbeddingResponse embeddingResponse); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/GptVersionType.java ================================================ package ai.chat2db.server.web.api.controller.ai.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * @author moji * @version GptModelType.java, v 0.1 April 9, 2023 19:05 moji Exp $ * @date 2023/04/09 */ @Getter public enum GptVersionType implements BaseEnum { /** * GPT-3 */ GPT3("GPT-3"), /** * GPT-3-5 */ GPT35("GPT-3.5"), ; final String description; GptVersionType(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java ================================================ package ai.chat2db.server.web.api.controller.ai.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * prompt type * * @author moji * @version PromptType.java, v 0.1 April 9, 2023 15:36 moji Exp $ * @date 2023/04/09 */ @Getter public enum PromptType implements BaseEnum { /** * Convert natural language to SQL */ NL_2_SQL("Convert natural language into SQL queries"), /** * Interpret SQL */ SQL_EXPLAIN("Interpret SQL"), /** * SQL optimization */ SQL_OPTIMIZER("Provide optimization suggestions"), /** * SQL conversion */ SQL_2_SQL("Perform SQL conversion"), /** * text generation */ TEXT_GENERATION("text generation"), ; final String description; PromptType(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @date 23/09/26 */ @Slf4j public class FastChatAIClient { /** * FASTCHAT OPENAI KEY */ public static final String FASTCHAT_API_KEY = "fastchat.chatgpt.apiKey"; /** * FASTCHAT OPENAI HOST */ public static final String FASTCHAT_HOST = "fastchat.host"; /** * FASTCHAT OPENAI model */ public static final String FASTCHAT_MODEL= "fastchat.model"; /** * FASTCHAT OPENAI embedding model */ public static final String FASTCHAT_EMBEDDING_MODEL = "fastchat.embedding.model"; private static FastChatAIStreamClient FASTCHAT_AI_CLIENT; public static FastChatAIStreamClient getInstance() { if (FASTCHAT_AI_CLIENT != null) { return FASTCHAT_AI_CLIENT; } else { return singleton(); } } private static FastChatAIStreamClient singleton() { if (FASTCHAT_AI_CLIENT == null) { synchronized (FastChatAIClient.class) { if (FASTCHAT_AI_CLIENT == null) { refresh(); } } } return FASTCHAT_AI_CLIENT; } public static void refresh() { String apiKey = ""; String apiHost = ""; String model = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(FASTCHAT_HOST).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiHost = apiHostConfig.getContent(); } Config config = configService.find(FASTCHAT_API_KEY).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { apiKey = config.getContent(); } Config deployConfig = configService.find(FASTCHAT_MODEL).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { model = deployConfig.getContent(); } FASTCHAT_AI_CLIENT = FastChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) .build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.reactivex.Single; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Fast Chat Aligned Client * * @author moji */ @Slf4j public class FastChatAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; @Getter private FastChatOpenAiApi fastChatOpenAiApi; /** * @param builder */ private FastChatAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; this.model = builder.model; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; this.fastChatOpenAiApi = new Retrofit.Builder() .baseUrl(apiHost) .client(okHttpClient) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create()) .build().create(FastChatOpenAiApi.class); } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static FastChatAIStreamClient.Builder builder() { return new FastChatAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public FastChatAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } /** * @param apiHostValue * @return */ public FastChatAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public FastChatAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public FastChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public FastChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public FastChatAIStreamClient build() { return new FastChatAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Fast Chat Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:FastChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Fast Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); chatCompletionsOptions.setStream(true); chatCompletionsOptions.setModel(this.model); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking fast chat ai"); } catch (Exception e) { log.error("fast chat ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } /** * Creates an embedding vector representing the input text. * * @param input * @return EmbeddingResponse */ public FastChatEmbeddingResponse embeddings(String input) { FastChatEmbedding embedding = FastChatEmbedding.builder().input(input).build(); if (StringUtils.isNotBlank(this.embeddingModel)) { embedding.setModel(this.embeddingModel); } return this.embeddings(embedding); } /** * Creates an embedding vector representing the input text. * * @param embedding * @return EmbeddingResponse */ public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { Single embeddings = this.fastChatOpenAiApi.embeddings(embedding); return embeddings.blockingGet(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.client; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import com.unfbx.chatgpt.entity.billing.CreditGrantsResponse; import com.unfbx.chatgpt.entity.chat.ChatCompletion; import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse; import com.unfbx.chatgpt.entity.common.DeleteResponse; import com.unfbx.chatgpt.entity.common.OpenAiResponse; import com.unfbx.chatgpt.entity.completions.Completion; import com.unfbx.chatgpt.entity.completions.CompletionResponse; import com.unfbx.chatgpt.entity.edits.Edit; import com.unfbx.chatgpt.entity.edits.EditResponse; import com.unfbx.chatgpt.entity.embeddings.Embedding; import com.unfbx.chatgpt.entity.embeddings.EmbeddingResponse; import com.unfbx.chatgpt.entity.engines.Engine; import com.unfbx.chatgpt.entity.files.File; import com.unfbx.chatgpt.entity.files.UploadFileResponse; import com.unfbx.chatgpt.entity.fineTune.Event; import com.unfbx.chatgpt.entity.fineTune.FineTune; import com.unfbx.chatgpt.entity.fineTune.FineTuneResponse; import com.unfbx.chatgpt.entity.images.Image; import com.unfbx.chatgpt.entity.images.ImageResponse; import com.unfbx.chatgpt.entity.models.Model; import com.unfbx.chatgpt.entity.models.ModelResponse; import com.unfbx.chatgpt.entity.moderations.Moderation; import com.unfbx.chatgpt.entity.moderations.ModerationResponse; import com.unfbx.chatgpt.entity.whisper.WhisperResponse; import io.reactivex.Single; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.http.*; import java.util.Map; /** * Description: open ai official api interface * * @author https:www.unfbx.com * 2023-02-15 */ public interface FastChatOpenAiApi { /** * Creates an embedding vector representing the input text. * * @param embedding * @return Single EmbeddingResponse */ @POST("v1/embeddings") Single embeddings(@Body FastChatEmbedding embedding); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; import com.fasterxml.jackson.annotation.JsonInclude; import com.unfbx.chatgpt.exception.BaseException; import com.unfbx.chatgpt.exception.CommonError; import lombok.*; import lombok.extern.slf4j.Slf4j; import java.io.Serializable; import java.util.Objects; /** * description: * * @author https:www.unfbx.com * 2023-02-15 */ @Getter @Slf4j @Builder @JsonInclude(JsonInclude.Include.NON_NULL) @NoArgsConstructor @AllArgsConstructor public class FastChatEmbedding implements Serializable { @NonNull @Builder.Default private String model = Model.TEXT_EMBEDDING_ADA_002.getName(); /** * Required: Length cannot exceed: 8192 */ @NonNull private String input; private String user; public void setModel(Model model) { if (Objects.isNull(model)) { model = Model.TEXT_EMBEDDING_ADA_002; } this.model = model.getName(); } public void setModel(String model) { if (Objects.isNull(model)) { model = Model.TEXT_EMBEDDING_ADA_002.getName(); } this.model = model; } public void setInput(String input) { if (input == null || "".equals(input)) { log.error("input cannot be empty"); throw new BaseException(CommonError.PARAM_ERROR); } if (input.length() > 8192) { log.error("input is too long"); throw new BaseException(CommonError.PARAM_ERROR); } this.input = input; } public void setUser(String user) { this.user = user; } @Getter @AllArgsConstructor public enum Model { TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), ; private String name; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; import com.unfbx.chatgpt.entity.common.Usage; import lombok.Data; import java.io.Serializable; import java.util.List; /** * description: * * @author https:www.unfbx.com * 2023-02-15 */ @Data public class FastChatEmbeddingResponse implements Serializable { private String object; private List data; private String model; private Usage usage; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; @Data public class FastChatItem implements Serializable { private String object; private List embedding; private Integer index; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.interceptor; import cn.hutool.core.util.RandomUtil; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import com.google.common.collect.Lists; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * header apikey * * @author grt * @since 2023-03-23 */ @Getter public class FastChatHeaderAuthorizationInterceptor implements Interceptor { private String apiKey; public FastChatHeaderAuthorizationInterceptor(String apiKey) { this.apiKey = apiKey; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() // replace to your corresponding field and value .header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); return chain.proceed(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.listener; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Objects; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class FastChatAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public FastChatAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Fast Chat Sse connecting..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Fast Chat AI response data:{}", data); if (data.equals("[DONE]")) { log.info("Fast Chat AI closed"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } FastChatCompletions chatCompletions = mapper.readValue(data, FastChatCompletions.class); String text = ""; log.info("Model={} is created at {}.", chatCompletions.getId(), chatCompletions.getCreated()); for (FastChatChoice choice : chatCompletions.getChoices()) { FastChatMessage message = choice.getDelta(); if (message != null) { log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); if (message.getContent() != null) { text = message.getContent(); } } } FastChatCompletionsUsage usage = chatCompletions.getUsage(); if (usage != null) { log.info( "Usage: number of prompt token is {}, number of completion token is {}, and number of total " + "tokens in request and response is {}.%n", usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens()); } Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); log.info("FastChatAI close sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { bodyString = t.getMessage(); } log.error("Fast Chat AI sse response:{}", bodyString); } else { log.error("Fast Chat AI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Fast Chat AI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Fast Chat AI send data error:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of * choices generated. */ @Data public final class FastChatChoice { /* * The generated text for a given completions prompt. */ @JsonProperty(value = "text") private String text; /* * The ordered index associated with this completions choice. */ @JsonProperty(value = "index") private int index; /* * The log probabilities model for tokens associated with this completions choice. */ @JsonProperty(value = "delta") private FastChatMessage delta; /* * Reason for finishing */ @JsonProperty(value = "finish_reason") private FastChatCompletionsFinishReason finishReason; /** * Creates an instance of Choice class. * * @param text the text value to set. * @param index the index value to set. * @param delta the message value to set * @param finishReason the finishReason value to set. */ @JsonCreator private FastChatChoice( @JsonProperty(value = "text") String text, @JsonProperty(value = "index") int index, @JsonProperty(value = "delta") FastChatMessage delta, @JsonProperty(value = "finish_reason") FastChatCompletionsFinishReason finishReason) { this.text = text; this.index = index; this.delta = delta; this.finishReason = finishReason; } /** * Get the text property: The generated text for a given completions prompt. * * @return the text value. */ public String getText() { return this.text; } /** * Get the index property: The ordered index associated with this completions choice. * * @return the index value. */ public int getIndex() { return this.index; } /** * Get the logprobs property: The log probabilities model for tokens associated with this completions choice. * * @return the logprobs value. */ public FastChatMessage getDelta() { return this.delta; } /** * Get the finishReason property: Reason for finishing. * * @return the finishReason value. */ public FastChatCompletionsFinishReason getFinishReason() { return this.finishReason; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class FastChatCompletions { /* * A unique identifier associated with this chat completions response. */ private String id; /* * The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. */ private int created; /** * model */ private String model; /** * object */ private String object; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ @JsonProperty(value = "choices") private List choices; /* * Usage information for tokens processed and generated as part of this completions operation. */ private FastChatCompletionsUsage usage; /** * Creates an instance of ChatCompletions class. * * @param id the id value to set. * @param created the created value to set. * @param choices the choices value to set. * @param usage the usage value to set. */ @JsonCreator private FastChatCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") int created, @JsonProperty(value = "model") String model, @JsonProperty(value = "object") String object, @JsonProperty(value = "choices") List choices, @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { this.id = id; this.created = created; this.model = model; this.object = object; this.choices = choices; this.usage = usage; } /** * Get the id property: A unique identifier associated with this chat completions response. * * @return the id value. */ public String getId() { return this.id; } /** * Get the created property: The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. * * @return the created value. */ public int getCreated() { return this.created; } /** * Get the choices property: The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other * settings may limit the number of choices generated. * * @return the choices value. */ public List getChoices() { return this.choices; } /** * Get the usage property: Usage information for tokens processed and generated as part of this completions * operation. * * @return the usage value. */ public FastChatCompletionsUsage getUsage() { return this.usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import java.util.Collection; /** Representation of the manner in which a completions response concluded. */ public final class FastChatCompletionsFinishReason extends FastChatExpandableStringEnum { /** Completions ended normally and reached its end of token generation. */ public static final FastChatCompletionsFinishReason STOPPED = fromString("stopped"); /** Completions exhausted available token limits before generation could complete. */ public static final FastChatCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached"); /** * Completions generated a response that was identified as potentially sensitive per content moderation policies. */ public static final FastChatCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered"); /** * Creates a new instance of CompletionsFinishReason value. * * @deprecated Use the {@link #fromString(String)} factory method. */ @Deprecated public FastChatCompletionsFinishReason() {} /** * Creates or finds a CompletionsFinishReason from its string representation. * * @param name a name to look for. * @return the corresponding CompletionsFinishReason. */ @JsonCreator public static FastChatCompletionsFinishReason fromString(String name) { return fromString(name, FastChatCompletionsFinishReason.class); } /** * Gets known CompletionsFinishReason values. * * @return known CompletionsFinishReason values. */ public static Collection values() { return values(FastChatCompletionsFinishReason.class); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * The configuration information for a chat completions request. Completions support a wide variety of tasks and * generate text that continues from or "completes" provided prompt data. */ @Data public final class FastChatCompletionsOptions { /* * The collection of context messages associated with this chat completions request. * Typical usage begins with a chat message for the System role that provides instructions for * the behavior of the assistant, followed by alternating messages between the User and * Assistant roles. */ private List messages; /* * A value indicating whether chat completions should be streamed for this request. */ private Boolean stream; // /* * The model name to provide as part of this completions request. * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat * resource URI that's connected to. */ private String model; /** * Creates an instance of ChatCompletionsOptions class. * * @param messages the messages value to set. */ @JsonCreator public FastChatCompletionsOptions(@JsonProperty(value = "messages") List messages) { this.messages = messages; } /** * Get the stream property: A value indicating whether chat completions should be streamed for this request. * * @return the stream value. */ public Boolean isStream() { return this.stream; } /** * Set the stream property: A value indicating whether chat completions should be streamed for this request. * * @param stream the stream value to set. * @return the ChatCompletionsOptions object itself. */ public FastChatCompletionsOptions setStream(Boolean stream) { this.stream = stream; return this; } /** * Get the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, * where deployment information should be included in the Fast Chat AI resource URI that's connected to. * * @return the model value. */ public String getModel() { return this.model; } /** * Set the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, * where deployment information should be included in the Fast Chat AI resource URI that's connected to. * * @param model the model value to set. * @return the ChatCompletionsOptions object itself. */ public FastChatCompletionsOptions setModel(String model) { this.model = model; return this; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.NoArgsConstructor; /** * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, * choices, choice alternates, best_of generations, and other consumers. */ @Data @NoArgsConstructor public final class FastChatCompletionsUsage { /* * The number of tokens generated across all completions emissions. */ @JsonProperty(value = "completion_tokens") private int completionTokens; /* * The number of tokens in the provided prompts for the completions request. */ @JsonProperty(value = "prompt_tokens") private int promptTokens; /* * The total number of tokens processed for the completions request and response. */ @JsonProperty(value = "total_tokens") private int totalTokens; /** * Creates an instance of CompletionsUsage class. * * @param completionTokens the completionTokens value to set. * @param promptTokens the promptTokens value to set. * @param totalTokens the totalTokens value to set. */ @JsonCreator private FastChatCompletionsUsage( @JsonProperty(value = "completion_tokens") int completionTokens, @JsonProperty(value = "prompt_tokens") int promptTokens, @JsonProperty(value = "total_tokens") int totalTokens) { this.completionTokens = completionTokens; this.promptTokens = promptTokens; this.totalTokens = totalTokens; } /** * Get the completionTokens property: The number of tokens generated across all completions emissions. * * @return the completionTokens value. */ public int getCompletionTokens() { return this.completionTokens; } /** * Get the promptTokens property: The number of tokens in the provided prompts for the completions request. * * @return the promptTokens value. */ public int getPromptTokens() { return this.promptTokens; } /** * Get the totalTokens property: The total number of tokens processed for the completions request and response. * * @return the totalTokens value. */ public int getTotalTokens() { return this.totalTokens; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package ai.chat2db.server.web.api.controller.ai.fastchat.model; import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils; import com.fasterxml.jackson.annotation.JsonValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import static java.lang.invoke.MethodType.methodType; /** * Base implementation for expandable, single string enums. * * @param a specific expandable enum type */ public abstract class FastChatExpandableStringEnum> { private static final Map, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>(); private static final Map, ConcurrentHashMap>> VALUES = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(FastChatExpandableStringEnum.class); private String name; private Class clazz; /** * Creates a new instance of {@link FastChatExpandableStringEnum} without a {@link #toString()} value. *

* This constructor shouldn't be called as it will produce a {@link FastChatExpandableStringEnum} which doesn't * have a String enum value. * * @deprecated Use the {@link #fromString(String, Class)} factory method. */ @Deprecated public FastChatExpandableStringEnum() { } /** * Creates an instance of the specific expandable string enum from a String. * * @param name The value to create the instance from. * @param clazz The class of the expandable string enum. * @param the class of the expandable string enum. * @return The expandable string enum instance. * * @throws RuntimeException wrapping implementation class constructor exception (if any is thrown). */ @SuppressWarnings({"unchecked", "deprecation"}) protected static > T fromString(String name, Class clazz) { if (name == null) { return null; } ConcurrentHashMap clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>()); T value = (T) clazzValues.get(name); if (value != null) { return value; } else { MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, FastChatExpandableStringEnum::getDefaultConstructor); if (ctor == null) { // logged in ExpandableStringEnum::getDefaultConstructor return null; } try { value = (T) ctor.invoke(); } catch (Throwable e) { LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e); return null; } return value.nameAndAddValue(name, value, clazz); } } private static MethodHandle getDefaultConstructor(Class clazz) { try { MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz); return lookup.findConstructor(clazz, methodType(void.class)); } catch (NoSuchMethodException | IllegalAccessException e) { LOGGER.info("Can't find or access default constructor for {}", clazz.getName(), e); } catch (Exception e) { LOGGER.info("Failed to get lookup for {}", clazz.getName(), e); } return null; } @SuppressWarnings("unchecked") T nameAndAddValue(String name, T value, Class clazz) { this.name = name; this.clazz = clazz; ((ConcurrentHashMap) VALUES.get(clazz)).put(name, value); return (T) this; } /** * Gets a collection of all known values to an expandable string enum type. * * @param clazz the class of the expandable string enum. * @param the class of the expandable string enum. * @return A collection of all known values for the given {@code clazz}. */ @SuppressWarnings("unchecked") protected static > Collection values(Class clazz) { return new ArrayList((Collection) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values()); } @Override @JsonValue public String toString() { return this.name; } @Override public int hashCode() { return Objects.hash(this.clazz, this.name); } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) { return false; } else if (obj == this) { return true; } else if (this.name == null) { return ((FastChatExpandableStringEnum) obj).name == null; } else { return this.name.equals(((FastChatExpandableStringEnum) obj).name); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class FastChatMessage { /* * The role associated with this message payload. */ @JsonProperty(value = "role") private FastChatRole role; /* * The text associated with this message payload. */ @JsonProperty(value = "content") private String content; /** * Creates an instance of ChatMessage class. * * @param role the role value to set. */ @JsonCreator public FastChatMessage(@JsonProperty(value = "role") FastChatRole role) { this.role = role; } /** * Get the role property: The role associated with this message payload. * * @return the role value. */ public FastChatRole getRole() { return this.role; } /** * Get the content property: The text associated with this message payload. * * @return the content value. */ public String getContent() { return this.content; } /** * Set the content property: The text associated with this message payload. * * @param content the content value to set. * @return the ChatMessage object itself. */ public FastChatMessage setContent(String content) { this.content = content; return this; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java ================================================ package ai.chat2db.server.web.api.controller.ai.fastchat.model; import com.fasterxml.jackson.annotation.JsonCreator; import java.util.Collection; public class FastChatRole extends FastChatExpandableStringEnum { /** The role that instructs or sets the behavior of the assistant. */ public static final FastChatRole SYSTEM = fromString("system"); /** The role that provides responses to system-instructed, user-prompted input. */ public static final FastChatRole ASSISTANT = fromString("assistant"); /** The role that provides input for chat completions. */ public static final FastChatRole USER = fromString("user"); /** * Creates a new instance of ChatRole value. * * @deprecated Use the {@link #fromString(String)} factory method. */ @Deprecated public FastChatRole() {} /** * Creates or finds a ChatRole from its string representation. * * @param name a name to look for. * @return the corresponding ChatRole. */ @JsonCreator public static FastChatRole fromString(String name) { return fromString(name, FastChatRole.class); } /** * Gets known ChatRole values. * * @return known ChatRole values. */ public static Collection values() { return values(FastChatRole.class); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.openai.client; import java.net.InetSocketAddress; import java.net.Proxy; import java.util.Objects; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import com.google.common.collect.Lists; import com.unfbx.chatgpt.OpenAiStreamClient; import com.unfbx.chatgpt.constant.OpenAIConst; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import org.apache.commons.lang3.StringUtils; /** * @author jipengfei * @version : OpenAIClient.java */ @Slf4j public class OpenAIClient { public static final String OPENAI_KEY = "chatgpt.apiKey"; /** * OPENAI interface domain name */ public static final String OPENAI_HOST = "chatgpt.apiHost"; /** * Proxy IP */ public static final String PROXY_HOST = "chatgpt.proxy.host"; /** * proxy port */ public static final String PROXY_PORT = "chatgpt.proxy.port"; private static OpenAiStreamClient OPEN_AI_STREAM_CLIENT; private static String apiKey; public static OpenAiStreamClient getInstance() { if (OPEN_AI_STREAM_CLIENT != null) { return OPEN_AI_STREAM_CLIENT; } else { return singleton(); } } private static OpenAiStreamClient singleton() { if (OPEN_AI_STREAM_CLIENT == null) { synchronized (OpenAIClient.class) { if (OPEN_AI_STREAM_CLIENT == null) { refresh(); } } } return OPEN_AI_STREAM_CLIENT; } public static void refresh() { String apikey; String apiHost = ApplicationContextUtil.getProperty(OPENAI_HOST); if (StringUtils.isBlank(apiHost)) { apiHost = OpenAIConst.OPENAI_HOST; } ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(OPENAI_HOST).getData(); if (apiHostConfig != null) { apiHost = apiHostConfig.getContent(); } Config config = configService.find(OPENAI_KEY).getData(); if (config != null) { apikey = config.getContent(); } else { apikey = ApplicationContextUtil.getProperty(OPENAI_KEY); } String host = System.getProperty("http.proxyHost"); Config hostConfig = configService.find(PROXY_HOST).getData(); if (hostConfig != null) { host = hostConfig.getContent(); } Integer port = Objects.nonNull(System.getProperty("http.proxyPort")) ? Integer.valueOf( System.getProperty("http.proxyPort")) : null; Config portConfig = configService.find(PROXY_PORT).getData(); if (portConfig != null && StringUtils.isNotBlank(portConfig.getContent())) { port = Integer.valueOf(portConfig.getContent()); } log.info("refresh openai apikey:{}", maskApiKey(apikey)); if (Objects.nonNull(host) && Objects.nonNull(port)) { Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); OkHttpClient okHttpClient = new OkHttpClient.Builder().proxy(proxy).build(); OPEN_AI_STREAM_CLIENT = OpenAiStreamClient.builder().apiHost(apiHost).apiKey( Lists.newArrayList(apikey)).okHttpClient(okHttpClient).build(); } else { OPEN_AI_STREAM_CLIENT = OpenAiStreamClient.builder().apiHost(apiHost).apiKey( Lists.newArrayList(apikey)).build(); } apiKey = apikey; } private static String maskApiKey(String input) { if (input == null) { return input; } StringBuilder maskedString = new StringBuilder(input); for (int i = input.length() / 4; i < input.length() / 2; i++) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.openai.listener; import java.util.Objects; import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class OpenAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; public OpenAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("OpenAI建立sse连接..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("OpenAI returns data: {}", data); if (data.equals("[DONE]")) { log.info("OpenAI returns data ended"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Read JSON ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); String text = completionResponse.getChoices().get(0).getDelta() == null ? completionResponse.getChoices().get(0).getText() : completionResponse.getChoices().get(0).getDelta().getContent(); Message message = new Message(); if (text != null) { message.setContent(text); sseEmitter.send(SseEmitter.event() .id(completionResponse.getId()) .data(message) .reconnectTime(3000)); } } @Override public void onClosed(EventSource eventSource) { sseEmitter.complete(); log.info("OpenAI closes sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); if ("No route to host".equals(message)) { message = "The network connection timed out. Please Baidu solve the network problem by yourself."; } Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = null; if (Objects.nonNull(body)) { bodyString = body.string(); log.error("OpenAI sse connection exception data: {}", bodyString, t); } else { log.error("OpenAI sse connection exception data: {}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("An exception occurred, please view the detailed log in the help:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Exception in sending data:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.ai.request; import java.util.List; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * Chat query input parameters * * @author moji * @version ChatQueryRequest.java, v 0.1 April 2, 2023 13:28 moji Exp $ * @date 2023/04/02 */ @Data public class ChatQueryRequest extends DataSourceBaseRequest { /** * Enter message */ private String message; /** * SQL function type * @see PromptType */ private String promptType; /** * table name list */ private List tableNames; /** * Target SQL data type * @see ai.chat2db.server.domain.support.enums.DbTypeEnum */ private String destSqlType; /** * More remarks: such as requirements or restrictions, etc. */ private String ext; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/request/ChatRequest.java ================================================ package ai.chat2db.server.web.api.controller.ai.request; import java.util.List; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * Chat query input parameters * * @author moji * @version ChatQueryRequest.java, v 0.1 April 2, 2023 13:28 moji Exp $ * @date 2023/04/02 */ @Data public class ChatRequest { private String prompt; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatChoice.java ================================================ package ai.chat2db.server.web.api.controller.ai.response; import java.io.Serial; import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.unfbx.chatgpt.entity.chat.Message; import lombok.Data; /** * @author jipengfei * @version : ChatChoice.java */ @Data @JsonIgnoreProperties(ignoreUnknown = true) public class ChatChoice implements Serializable { @Serial private static final long serialVersionUID = 6347129660363472014L; private long index; /** * If the request parameter stream is true, the return value is delta. */ @JsonProperty("delta") private Message delta; /** * If the request parameter stream is false, the return value is message. */ @JsonProperty("message") private Message message; /** * If the request parameter stream is false, the return value is message. */ @JsonProperty("finish_reason") private String finishReason; /** * If the request parameter stream is true, the return value is text. */ private String text; /** * If the request parameter stream is true, the return value is logprobs. */ private String logprobs; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatCompletionResponse.java ================================================ package ai.chat2db.server.web.api.controller.ai.response; import java.io.Serial; import java.io.Serializable; import java.util.List; import com.unfbx.chatgpt.entity.common.Usage; import lombok.Data; /** * @author jipengfei * @version : ChatCompletionResponse.java */ @Data public class ChatCompletionResponse implements Serializable { @Serial private static final long serialVersionUID = 4968922211204353592L; private String id; private String object; private long created; private String model; private List choices; private Usage usage; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.rest.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @version : RestAIClient.java */ @Slf4j public class RestAIClient { /** * Interface source selected by AI SQL */ public static final String AI_SQL_SOURCE = "ai.sql.source"; /** * Customized AI interface KEY */ public static final String REST_AI_API_KEY = "rest.ai.apiKey"; /** * Customized AI interface address */ public static final String REST_AI_URL = "rest.ai.url"; /** * Custom AI interface request method */ public static final String REST_AI_STREAM_OUT = "rest.ai.stream"; /** * Custom AI interface model */ public static final String REST_AI_MODEL = "rest.ai.model"; private static RestAIStreamClient REST_AI_STREAM_CLIENT; public static RestAIStreamClient getInstance() { if (REST_AI_STREAM_CLIENT != null) { return REST_AI_STREAM_CLIENT; } else { return singleton(); } } private static RestAIStreamClient singleton() { if (REST_AI_STREAM_CLIENT == null) { synchronized (RestAIClient.class) { if (REST_AI_STREAM_CLIENT == null) { refresh(); } } } return REST_AI_STREAM_CLIENT; } /** * Refresh client */ public static void refresh() { String apiUrl = ""; String apiKey = ""; String model = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(REST_AI_URL).getData(); if (apiHostConfig != null) { apiUrl = apiHostConfig.getContent(); } Config config = configService.find(REST_AI_API_KEY).getData(); if (config != null) { apiKey = config.getContent(); } Config deployConfig = configService.find(REST_AI_MODEL).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { model = deployConfig.getContent(); } REST_AI_STREAM_CLIENT = RestAIStreamClient.builder().apiKey(apiKey).apiHost(apiUrl).model(model) .build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.rest.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Custom AI interface client * @author moji */ @Slf4j public class RestAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * Construct instance object * * @param builder */ public RestAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; this.model = builder.model; this.okHttpClient = new OkHttpClient .Builder() .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); } /** * structure * * @return */ public static RestAIStreamClient.Builder builder() { return new RestAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String apiHost; private String model; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public RestAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } /** * @param apiHostValue * @return */ public RestAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public RestAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public RestAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public RestAIStreamClient build() { return new RestAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Rest AI Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:RestAIEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Rest AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); chatCompletionsOptions.setStream(true); chatCompletionsOptions.setModel(this.model); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking rest ai"); } catch (Exception e) { log.error("rest ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.rest.listener; import java.io.IOException; import java.util.Objects; import ai.chat2db.server.web.api.controller.ai.rest.model.RestAIChatCompletions; import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletions; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * description:RESTAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class RestAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; public RestAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("REST AI建立sse连接..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("REST AI return data:{}", data); String end = "[DONE]"; if (data.equals(end)) { log.info("REST AI returns data finished"); sseEmitter.send(SseEmitter.event() .id(end) .data(end) .reconnectTime(3000)); sseEmitter.complete(); return; } Message message = new Message(); if (StringUtils.isNotBlank(data)) { RestAIChatCompletions chatCompletions = mapper.readValue(data, RestAIChatCompletions.class); String text = chatCompletions.getChoices().get(0).getDelta()==null? chatCompletions.getChoices().get(0).getText() :chatCompletions.getChoices().get(0).getDelta().getContent(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(id) .data(message) .reconnectTime(3000)); } } @SneakyThrows @Override public void onClosed(EventSource eventSource) { log.info("REST AI close sse connection..."); try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = null; if (Objects.nonNull(body)) { bodyString = body.string(); log.error("REST AI sse body error:{},exception:{}", bodyString, t); } else { log.error("REST AI sse response error:{},exception:{}", response, t); } if (Objects.nonNull(eventSource)) { eventSource.cancel(); } Message message = new Message(); message.setContent("Rest AI Error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Exception in sending data:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/model/RestAIChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.rest.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class RestAIChatCompletions { /* * A unique identifier associated with this chat completions response. */ private String id; /* * The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. */ private int created; /** * model */ private String model; /** * object */ private String object; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ @JsonProperty(value = "choices") private List choices; /** * Creates an instance of ChatCompletions class. * * @param id the id value to set. * @param created the created value to set. * @param choices the choices value to set. */ @JsonCreator private RestAIChatCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") int created, @JsonProperty(value = "model") String model, @JsonProperty(value = "object") String object, @JsonProperty(value = "choices") List choices) { this.id = id; this.created = created; this.model = model; this.object = object; this.choices = choices; } /** * Get the id property: A unique identifier associated with this chat completions response. * * @return the id value. */ public String getId() { return this.id; } /** * Get the created property: The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. * * @return the created value. */ public int getCreated() { return this.created; } /** * Get the choices property: The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other * settings may limit the number of choices generated. * * @return the choices value. */ public List getChoices() { return this.choices; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/model/RestAiCompletion.java ================================================ package ai.chat2db.server.web.api.controller.ai.rest.model; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author moji * @version RestAiCompletion.java, v 0.1 May 27, 2023 14:00 moji Exp $ * @date 2023/05/27 */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class RestAiCompletion implements Serializable { /** * hint */ private String prompt; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.tongyi.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @date 23/09/26 */ @Slf4j public class TongyiChatAIClient { /** * TONGYI OPENAI KEY */ public static final String TONGYI_API_KEY = "tongyi.chatgpt.apiKey"; /** * TONGYI OPENAI HOST */ public static final String TONGYI_HOST = "tongyi.host"; /** * TONGYI OPENAI model */ public static final String TONGYI_MODEL= "tongyi.model"; /** * TONGYI OPENAI embedding model */ public static final String TONGYI_EMBEDDING_MODEL = "tongyi.embedding.model"; private static TongyiChatAIStreamClient TONGYI_AI_CLIENT; public static TongyiChatAIStreamClient getInstance() { if (TONGYI_AI_CLIENT != null) { return TONGYI_AI_CLIENT; } else { return singleton(); } } private static TongyiChatAIStreamClient singleton() { if (TONGYI_AI_CLIENT == null) { synchronized (TongyiChatAIClient.class) { if (TONGYI_AI_CLIENT == null) { refresh(); } } } return TONGYI_AI_CLIENT; } public static void refresh() { String apiKey = ""; String apiHost = ""; String model = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(TONGYI_HOST).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiHost = apiHostConfig.getContent(); } Config config = configService.find(TONGYI_API_KEY).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { apiKey = config.getContent(); } Config deployConfig = configService.find(TONGYI_MODEL).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { model = deployConfig.getContent(); } TONGYI_AI_CLIENT = TongyiChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) .build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.tongyi.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatMessage; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * tongyi Chat Aligned Client * * @author moji */ @Slf4j public class TongyiChatAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private TongyiChatAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; this.model = builder.model; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static TongyiChatAIStreamClient.Builder builder() { return new TongyiChatAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public TongyiChatAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; return this; } /** * @param apiHostValue * @return */ public TongyiChatAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public TongyiChatAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public TongyiChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public TongyiChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public TongyiChatAIStreamClient build() { return new TongyiChatAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Tongyi Chat Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:TongyiChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Tongyi Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { TongyiChatCompletionsOptions chatCompletionsOptions = new TongyiChatCompletionsOptions(); chatCompletionsOptions.setStream(true); chatCompletionsOptions.setModel(this.model); Map parameters = new HashMap<>(); parameters.put("result_format", "text"); chatCompletionsOptions.setParameters(parameters); TongyiChatMessage tongyiChatMessage = new TongyiChatMessage(); tongyiChatMessage.setMessages(chatMessages); chatCompletionsOptions.setInput(tongyiChatMessage); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking tongyi chat ai"); } catch (Exception e) { log.error("tongyi chat ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/listener/TongyiChatAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.tongyi.listener; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatCompletions; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Objects; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class TongyiChatAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public TongyiChatAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Tongyi Chat Sse connecting..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Tongyi Chat AI response data:{}", data); if (data.equals("[DONE]")) { log.info("Tongyi Chat AI closed"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } TongyiChatCompletions chatCompletions = mapper.readValue(data, TongyiChatCompletions.class); String text = chatCompletions.getOutput().getText(); log.info("id: {}, text: {}", chatCompletions.getId(), text); Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); log.info("TongyiChatAI close sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { bodyString = t.getMessage(); } log.error("Tongyi Chat AI sse response:{}", bodyString); } else { log.error("Tongyi Chat AI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Tongyi Chat AI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Tongyi Chat AI send data error:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.tongyi.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class TongyiChatCompletions { /* * A unique identifier associated with this chat completions response. */ @JsonProperty(value = "request_id") private String id; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ private TongyiChatOutput output; /* * Usage information for tokens processed and generated as part of this completions operation. */ private TongyiChatCompletionsUsage usage; /** * Creates an instance of ChatCompletions class. * * @param id the id value to set. * @param choices the choices value to set. * @param usage the usage value to set. */ @JsonCreator private TongyiChatCompletions( @JsonProperty(value = "request_id") String id, @JsonProperty(value = "output") TongyiChatOutput choices, @JsonProperty(value = "usage") TongyiChatCompletionsUsage usage) { this.id = id; this.output = choices; this.usage = usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.tongyi.model; import lombok.Data; import java.util.Map; /** * The configuration information for a chat completions request. Completions support a wide variety of tasks and * generate text that continues from or "completes" provided prompt data. */ @Data public final class TongyiChatCompletionsOptions { /* * The collection of context messages associated with this chat completions request. * Typical usage begins with a chat message for the System role that provides instructions for * the behavior of the assistant, followed by alternating messages between the User and * Assistant roles. */ private TongyiChatMessage input; /* * A value indicating whether chat completions should be streamed for this request. */ private Boolean stream; /* * The model name to provide as part of this completions request. * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat * resource URI that's connected to. */ private String model; /** * parameters */ private Map parameters; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatCompletionsUsage.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.tongyi.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.NoArgsConstructor; /** * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, * choices, choice alternates, best_of generations, and other consumers. */ @Data @NoArgsConstructor public final class TongyiChatCompletionsUsage { /* * The number of tokens generated across all completions emissions. */ @JsonProperty(value = "output_tokens") private int outputTokens; /* * The number of tokens in the provided prompts for the completions request. */ @JsonProperty(value = "input_tokens") private int inputTokens; /** * Creates an instance of CompletionsUsage class. * * @param completionTokens the completionTokens value to set. * @param promptTokens the promptTokens value to set. */ @JsonCreator private TongyiChatCompletionsUsage( @JsonProperty(value = "output_tokens") int completionTokens, @JsonProperty(value = "input_tokens") int promptTokens) { this.outputTokens = completionTokens; this.inputTokens = promptTokens; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatMessage.java ================================================ package ai.chat2db.server.web.api.controller.ai.tongyi.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import lombok.Data; import java.util.List; @Data public class TongyiChatMessage { private List messages; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/model/TongyiChatOutput.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.tongyi.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; /** * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of * choices generated. */ @Data public final class TongyiChatOutput { /* * The generated text for a given completions prompt. */ @JsonProperty(value = "text") private String text; /* * Reason for finishing */ @JsonProperty(value = "finish_reason") private String finishReason; /** * Creates an instance of Choice class. * * @param text the text value to set. * @param finishReason the finishReason value to set. */ @JsonCreator private TongyiChatOutput( @JsonProperty(value = "text") String text, @JsonProperty(value = "finish_reason") String finishReason) { this.text = text; this.finishReason = finishReason; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.wenxin.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIStreamClient; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @date 23/09/26 */ @Slf4j public class WenxinAIClient { /** * WENXIN_ACCESS_TOKEN */ public static final String WENXIN_ACCESS_TOKEN = "wenxin.access.token"; /** * WENXIN_HOST */ public static final String WENXIN_HOST = "wenxin.host"; /** * WENXIN_MODEL */ public static final String WENXIN_MODEL= "wenxin.model"; /** * Wenxin embedding model */ public static final String WENXIN_EMBEDDING_MODEL = "wenxin.embedding.model"; private static WenxinAIStreamClient WENXIN_AI_CLIENT; public static WenxinAIStreamClient getInstance() { if (WENXIN_AI_CLIENT != null) { return WENXIN_AI_CLIENT; } else { return singleton(); } } private static WenxinAIStreamClient singleton() { if (WENXIN_AI_CLIENT == null) { synchronized (WenxinAIClient.class) { if (WENXIN_AI_CLIENT == null) { refresh(); } } } return WENXIN_AI_CLIENT; } public static void refresh() { String apiHost = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro"; String accessToken = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(WENXIN_HOST).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiHost = apiHostConfig.getContent(); if (apiHost.endsWith("/")) { apiHost = apiHost.substring(0, apiHost.length() - 1); } } Config config = configService.find(WENXIN_ACCESS_TOKEN).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { accessToken = config.getContent(); } WENXIN_AI_CLIENT = WenxinAIStreamClient.builder().accessToken(accessToken).apiHost(apiHost).build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.wenxin.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.wenxin.interceptor.AccessTokenInterceptor; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Fast Chat Aligned Client * * @author moji */ @Slf4j public class WenxinAIStreamClient { /** * apikey */ @Getter @NotNull private String accessToken; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private WenxinAIStreamClient(Builder builder) { this.accessToken = builder.accessToken; this.apiHost = builder.apiHost; this.model = builder.model; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new AccessTokenInterceptor(this.accessToken)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static WenxinAIStreamClient.Builder builder() { return new WenxinAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String accessToken; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public WenxinAIStreamClient.Builder accessToken(String accessToken) { this.accessToken = accessToken; return this; } /** * @param apiHostValue * @return */ public WenxinAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public WenxinAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public WenxinAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public WenxinAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public WenxinAIStreamClient build() { return new WenxinAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Wenxin Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:WenxinEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Wenxin Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); chatCompletionsOptions.setStream(true); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking fast chat ai"); } catch (Exception e) { log.error("wenxin chat ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/interceptor/AccessTokenInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.wenxin.interceptor; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class AccessTokenInterceptor implements Interceptor { private final String accessToken; public AccessTokenInterceptor(String accessToken) { this.accessToken = accessToken; } @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); HttpUrl originalHttpUrl = originalRequest.url(); // Use HttpUrl.Builder to add query parameter access_token HttpUrl urlWithAccessToken = originalHttpUrl.newBuilder() .addQueryParameter("access_token", accessToken) .build(); // Create a new request and apply the new URL to it Request newRequest = originalRequest.newBuilder() .url(urlWithAccessToken) .build(); return chain.proceed(newRequest); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/listener/WenxinAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.wenxin.listener; import ai.chat2db.server.web.api.controller.ai.wenxin.model.WenxinChatCompletions; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.Objects; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class WenxinAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public WenxinAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Wenxin chat Sse connecting..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, String data) { log.info("Wenxin AI response data:{}", data); if (data.equals("[DONE]")) { log.info("Wenxin AI closed"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } WenxinChatCompletions chatCompletions = mapper.readValue(data, WenxinChatCompletions.class); String text = chatCompletions.getResult(); log.info("Model={} is created at {}. message:{}", chatCompletions.getObject(), chatCompletions.getCreated(), text); Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { try { sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); } catch (IOException e) { throw new RuntimeException(e); } sseEmitter.complete(); log.info("WenxinChatAI close sse connection..."); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { bodyString = t.getMessage(); } log.error("Wenxin chat AI sse response:{}", bodyString); } else { log.error("Wenxin chat AI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Wenxin chat AI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Wenxin chat AI send data error:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/model/WenxinChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.wenxin.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class WenxinChatCompletions { /* * A unique identifier associated with this chat completions response. */ private String id; /* * The first timestamp associated with generation activity for this completions response, * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. */ private int created; /** * model */ @JsonProperty(value = "is_truncated") private String isTruncated; @JsonProperty(value = "need_clear_history") private String needClearHistory; /** * object */ private String object; /* * The collection of completions choices associated with this completions response. * Generally, `n` choices are generated per provided prompt with a default value of 1. * Token limits and other settings may limit the number of choices generated. */ private String result; /* * Usage information for tokens processed and generated as part of this completions operation. */ private FastChatCompletionsUsage usage; /** * Creates an instance of ChatCompletions class. * * @param id the id value to set. * @param created the created value to set. * @param result the result value to set. * @param usage the usage value to set. */ @JsonCreator private WenxinChatCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") int created, @JsonProperty(value = "is_truncated") String isTruncated, @JsonProperty(value = "need_clear_history") String needClearHistory, @JsonProperty(value = "object") String object, @JsonProperty(value = "result") String result, @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { this.id = id; this.created = created; this.isTruncated = isTruncated; this.needClearHistory = needClearHistory; this.object = object; this.result = result; this.usage = usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.client; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; /** * @author moji * @date 23/09/26 */ @Slf4j public class ZhipuChatAIClient { /** * ZHIPU OPENAI KEY */ public static final String ZHIPU_API_KEY = "zhipu.chatgpt.apiKey"; /** * ZHIPU OPENAI HOST */ public static final String ZHIPU_HOST = "zhipu.host"; /** * ZHIPU OPENAI model */ public static final String ZHIPU_MODEL= "zhipu.model"; /** * ZHIPU OPENAI embedding model */ public static final String ZHIPU_EMBEDDING_MODEL = "zhipu.embedding.model"; private static ZhipuChatAIStreamClient ZHIPU_AI_CLIENT; public static ZhipuChatAIStreamClient getInstance() { if (ZHIPU_AI_CLIENT != null) { return ZHIPU_AI_CLIENT; } else { return singleton(); } } private static ZhipuChatAIStreamClient singleton() { if (ZHIPU_AI_CLIENT == null) { synchronized (ZhipuChatAIClient.class) { if (ZHIPU_AI_CLIENT == null) { refresh(); } } } return ZHIPU_AI_CLIENT; } public static void refresh() { String apiKey = ""; String apiHost = ""; String model = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(ZHIPU_HOST).getData(); if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiHost = apiHostConfig.getContent(); } Config config = configService.find(ZHIPU_API_KEY).getData(); if (config != null && StringUtils.isNotBlank(config.getContent())) { apiKey = config.getContent(); } Config deployConfig = configService.find(ZHIPU_MODEL).getData(); if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { model = deployConfig.getContent(); } ZHIPU_AI_CLIENT = ZhipuChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) .build(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.zhipu.interceptor.ZhipuChatHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletionsOptions; import cn.hutool.http.ContentType; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Zhipu Chat Aligned Client * * @author moji */ @Slf4j public class ZhipuChatAIStreamClient { /** * apikey */ @Getter @NotNull private String apiKey; @Getter private String key; @Getter private String secret; /** * apiHost */ @Getter @NotNull private String apiHost; /** * model */ @Getter private String model; /** * embeddingModel */ @Getter private String embeddingModel; /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; /** * @param builder */ private ZhipuChatAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.key = builder.key; this.secret = builder.secret; this.apiHost = builder.apiHost; this.model = builder.model; this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } okHttpClient = builder.okHttpClient; } /** * okhttpclient */ private OkHttpClient okHttpClient() { OkHttpClient okHttpClient = new OkHttpClient .Builder() .addInterceptor(new ZhipuChatHeaderAuthorizationInterceptor(this.key, this.secret)) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(50, TimeUnit.SECONDS) .readTimeout(50, TimeUnit.SECONDS) .build(); return okHttpClient; } /** * structure * * @return */ public static ZhipuChatAIStreamClient.Builder builder() { return new ZhipuChatAIStreamClient.Builder(); } /** * builder */ public static final class Builder { private String apiKey; private String key; private String secret; private String apiHost; private String model; private String embeddingModel; /** * OkhttpClient */ private OkHttpClient okHttpClient; public Builder() { } public ZhipuChatAIStreamClient.Builder apiKey(String apiKeyValue) { this.apiKey = apiKeyValue; String[] arrStr = apiKey.split("\\."); if (arrStr.length != 2) { throw new RuntimeException("invalid apiSecretKey"); } this.key = arrStr[0]; this.secret = arrStr[1]; return this; } /** * @param apiHostValue * @return */ public ZhipuChatAIStreamClient.Builder apiHost(String apiHostValue) { this.apiHost = apiHostValue; return this; } /** * @param modelValue * @return */ public ZhipuChatAIStreamClient.Builder model(String modelValue) { this.model = modelValue; return this; } public ZhipuChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { this.embeddingModel = embeddingModelValue; return this; } public ZhipuChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; } public ZhipuChatAIStreamClient build() { return new ZhipuChatAIStreamClient(this); } } /** * Q&A interface stream form * * @param chatMessages * @param eventSourceListener */ public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("param error:Zhipu Chat Prompt cannot be empty"); throw new ParamBusinessException("prompt"); } if (Objects.isNull(eventSourceListener)) { log.error("param error:Zhipu ChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } log.info("Zhipu Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { // It is recommended to check the demo package code directly. The update here may not be timely. ZhipuChatCompletionsOptions completionsOptions = new ZhipuChatCompletionsOptions(); completionsOptions.setPrompt(chatMessages); completionsOptions.setModel(this.model); String requestId = String.valueOf(System.currentTimeMillis()); completionsOptions.setRequestId(requestId); ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(completionsOptions); log.info("使用的model:{}", this.model); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); Request request = new Request.Builder() .url(apiHost) .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) .build(); //Create event EventSource eventSource = factory.newEventSource(request, eventSourceListener); log.info("finish invoking zhipu chat ai"); } catch (Exception e) { log.error("fast chat ai error", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/interceptor/ZhipuChatHeaderAuthorizationInterceptor.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.interceptor; import ai.chat2db.server.web.api.controller.ai.zhipu.util.ZhipuUtils; import cn.hutool.http.ContentType; import cn.hutool.http.Header; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; /** * header apikey * * @author grt * @since 2023-03-23 */ @Getter public class ZhipuChatHeaderAuthorizationInterceptor implements Interceptor { private String key; private String secret; public ZhipuChatHeaderAuthorizationInterceptor(String key, String secret) { this.key = key; this.secret = secret; } @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); String token = ZhipuUtils.getToken(key, secret); Request request = original.newBuilder() // replace to your corresponding field and value .header(Header.AUTHORIZATION.getValue(), token) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); return chain.proceed(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/listener/ZhipuChatAIEventSourceListener.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.listener; import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletions; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.Objects; /** * description:OpenAIEventSourceListener * * @author https:www.unfbx.com * @date 2023-02-22 */ @Slf4j public class ZhipuChatAIEventSourceListener extends EventSourceListener { private SseEmitter sseEmitter; private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public ZhipuChatAIEventSourceListener(SseEmitter sseEmitter) { this.sseEmitter = sseEmitter; } /** * {@inheritDoc} */ @Override public void onOpen(EventSource eventSource, Response response) { log.info("Zhipu Chat Sse connecting..."); } /** * {@inheritDoc} */ @SneakyThrows @Override public void onEvent(EventSource eventSource, String id, String type, @NotNull String data) { log.info("Zhipu Chat AI response data:{}", data); if (data.equals("[DONE]")) { log.info("Zhipu Chat AI closed"); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]") .reconnectTime(3000)); sseEmitter.complete(); return; } ZhipuChatCompletions chatCompletions = mapper.readValue(data, ZhipuChatCompletions.class); String text = chatCompletions.getChoices().get(0).getDelta()==null? chatCompletions.getChoices().get(0).getText() :chatCompletions.getChoices().get(0).getDelta().getContent(); Message message = new Message(); message.setContent(text); sseEmitter.send(SseEmitter.event() .id(null) .data(message) .reconnectTime(3000)); } @Override public void onClosed(EventSource eventSource) { log.info("Zhipu Chat AI closes sse connection closed"); } @Override public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(sseMessage)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); return; } ResponseBody body = response.body(); String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; if (Objects.nonNull(body)) { bodyString = body.string(); if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { bodyString = t.getMessage(); } log.error("Zhipu Chat AI sse response:{}", bodyString); } else { log.error("Zhipu Chat AI sse response:{},error:{}", response, t); } eventSource.cancel(); Message message = new Message(); message.setContent("Zhipu Chat AI error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); sseEmitter.send(SseEmitter.event() .id("[DONE]") .data("[DONE]")); sseEmitter.complete(); } catch (Exception exception) { log.error("Zhipu Chat AI send data error:", exception); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatBody.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.zhipu.model; import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatMessage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of * choices generated. */ @Data public final class ZhipuChatBody { /* * The log probabilities model for tokens associated with this completions choice. */ @JsonProperty(value = "choices") private List choices; @JsonProperty(value = "usage") private FastChatCompletionsUsage usage; @JsonCreator private ZhipuChatBody( @JsonProperty(value = "choices") List choices, @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { this.choices = choices; this.usage = usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletions.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class ZhipuChatCompletions { /* * A unique identifier associated with this chat completions response. */ @JsonProperty(value = "id") private String id; @JsonProperty(value = "created") private Long created; @JsonProperty(value = "choices") private List choices; @JsonProperty(value = "usage") private FastChatCompletionsUsage usage; @JsonCreator private ZhipuChatCompletions( @JsonProperty(value = "id") String id, @JsonProperty(value = "created") Long created, @JsonProperty(value = "choices") List choices, @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { this.id = id; this.created = created; this.choices = choices; this.usage = usage; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/model/ZhipuChatCompletionsOptions.java ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. package ai.chat2db.server.web.api.controller.ai.zhipu.model; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * The configuration information for a chat completions request. Completions support a wide variety of tasks and * generate text that continues from or "completes" provided prompt data. */ @Data public final class ZhipuChatCompletionsOptions { @JsonProperty(value = "request_id") private String requestId; // sse-params @JsonProperty(value = "stream") private Boolean stream = true; @JsonProperty(value = "sseFormat") private String sseFormat = "data"; /* * The collection of context messages associated with this chat completions request. * Typical usage begins with a chat message for the System role that provides instructions for * the behavior of the assistant, followed by alternating messages between the User and * Assistant roles. */ @JsonProperty(value = "messages") private List prompt; // /* * The model name to provide as part of this completions request. * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat * resource URI that's connected to. */ @JsonProperty(value = "model") private String model; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/util/ZhipuUtils.java ================================================ package ai.chat2db.server.web.api.controller.ai.zhipu.util; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import lombok.extern.slf4j.Slf4j; import java.util.Calendar; import java.util.HashMap; import java.util.Map; @Slf4j public class ZhipuUtils { private static final String tokenV3KeyPrefix = "zhipu_oapi_token_v3"; public static String getToken(String key, String secret) { String newToken = createJwt(key, secret); return newToken; } private static String createJwt(String key, String secret) { Algorithm alg; try { alg = Algorithm.HMAC256(secret.getBytes("utf-8")); } catch (Exception e) { log.info("create jwt error", e); return null; } Map payload = new HashMap<>(); payload.put("api_key", key); payload.put("exp", System.currentTimeMillis() + 30 * 60 * 1000); payload.put("timestamp", Calendar.getInstance().getTimeInMillis()); Map headerClaims = new HashMap<>(); headerClaims.put("alg", "HS256"); headerClaims.put("sign_type", "SIGN"); String token = JWT.create().withPayload(payload).withHeader(headerClaims).sign(alg); return token; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java ================================================ package ai.chat2db.server.web.api.controller.config; import java.util.Objects; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.AIConfig; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient; import ai.chat2db.server.web.api.controller.ai.wenxin.client.WenxinAIClient; import ai.chat2db.server.web.api.controller.ai.zhipu.client.ZhipuChatAIClient; import ai.chat2db.server.web.api.controller.config.request.AIConfigCreateRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author jipengfei * @version : ConfigController.java */ @ConnectionInfoAspect @RequestMapping("/api/config") @RestController public class ConfigController { @Autowired private ConfigService configService; @PostMapping("/system_config") public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { SystemConfigParam param = SystemConfigParam.builder().code(request.getCode()).content(request.getContent()) .build(); configService.createOrUpdate(param); if (OpenAIClient.OPENAI_KEY.equals(request.getCode())) { OpenAIClient.refresh(); } return ActionResult.isSuccess(); } /** * Save ChatGPT related configurations * * @param request * @return */ @PostMapping("/system_config/ai") public ActionResult addChatGptSystemConfig(@RequestBody AIConfigCreateRequest request) { PermissionUtils.checkDeskTopOrAdmin(); String sqlSource = request.getAiSqlSource(); AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); if (Objects.isNull(aiSqlSourceEnum)) { sqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI; } SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) .build(); configService.createOrUpdate(param); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI: saveOpenAIConfig(request); break; case CHAT2DBAI: saveChat2dbAIConfig(request); break; case RESTAI: saveRestAIConfig(request); break; case AZUREAI: saveAzureAIConfig(request); break; case FASTCHATAI: saveFastChatAIConfig(request); break; case TONGYIQIANWENAI: saveTongyiChatAIConfig(request); break; case WENXINAI: saveWenxinAIConfig(request); break; case BAICHUANAI: saveBaichuanAIConfig(request); break; case ZHIPUAI: saveZhipuChatAIConfig(request); break; } return ActionResult.isSuccess(); } /** * save chat2db ai config * * @param request */ private void saveChat2dbAIConfig(AIConfigCreateRequest request) { SystemConfigParam param = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content( request.getApiKey()).build(); configService.createOrUpdate(param); SystemConfigParam hostParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_HOST).content( request.getApiHost()).build(); configService.createOrUpdate(hostParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL).content( request.getModel()).build(); configService.createOrUpdate(modelParam); Chat2dbAIClient.refresh(); } /** * save open ai config * * @param request */ private void saveOpenAIConfig(AIConfigCreateRequest request) { SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content( request.getApiKey()).build(); configService.createOrUpdate(param); SystemConfigParam hostParam = SystemConfigParam.builder().code(OpenAIClient.OPENAI_HOST).content( request.getApiHost()).build(); configService.createOrUpdate(hostParam); SystemConfigParam httpProxyHostParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_HOST).content( request.getHttpProxyHost()).build(); configService.createOrUpdate(httpProxyHostParam); SystemConfigParam httpProxyPortParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_PORT).content( request.getHttpProxyPort()).build(); configService.createOrUpdate(httpProxyPortParam); OpenAIClient.refresh(); } /** * save rest ai config * * @param request */ private void saveRestAIConfig(AIConfigCreateRequest request) { SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.REST_AI_API_KEY).content( request.getApiKey()).build(); configService.createOrUpdate(param); SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content( request.getApiHost()).build(); configService.createOrUpdate(restParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_MODEL) .content(request.getModel()).build(); configService.createOrUpdate(modelParam); RestAIClient.refresh(); } /** * save azure config * * @param request */ private void saveAzureAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY) .content( request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT) .content( request.getApiHost()).build(); configService.createOrUpdate(endpointParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID) .content( request.getModel()).build(); configService.createOrUpdate(modelParam); AzureOpenAIClient.refresh(); } /** * save common fast chat ai config * * @param request */ private void saveFastChatAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_API_KEY) .content(request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam apiHostParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_HOST) .content(request.getApiHost()).build(); configService.createOrUpdate(apiHostParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_MODEL) .content(request.getModel()).build(); configService.createOrUpdate(modelParam); FastChatAIClient.refresh(); } /** * save common zhipu chat ai config * * @param request */ private void saveZhipuChatAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_API_KEY) .content(request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam apiHostParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_HOST) .content(request.getApiHost()).build(); configService.createOrUpdate(apiHostParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(ZhipuChatAIClient.ZHIPU_MODEL) .content(request.getModel()).build(); configService.createOrUpdate(modelParam); ZhipuChatAIClient.refresh(); } /** * save common tongyi chat ai config * * @param request */ private void saveTongyiChatAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_API_KEY) .content(request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam apiHostParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_HOST) .content(request.getApiHost()).build(); configService.createOrUpdate(apiHostParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(TongyiChatAIClient.TONGYI_MODEL) .content(request.getModel()).build(); configService.createOrUpdate(modelParam); TongyiChatAIClient.refresh(); } /** * save common wenxin chat ai config * * @param request */ private void saveWenxinAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(WenxinAIClient.WENXIN_ACCESS_TOKEN) .content(request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam apiHostParam = SystemConfigParam.builder().code(WenxinAIClient.WENXIN_HOST) .content(request.getApiHost()).build(); configService.createOrUpdate(apiHostParam); WenxinAIClient.refresh(); } /** * save common fast chat ai config * * @param request */ private void saveBaichuanAIConfig(AIConfigCreateRequest request) { SystemConfigParam apikeyParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_API_KEY) .content(request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); SystemConfigParam secretKeyParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_SECRET_KEY) .content(request.getSecretKey()).build(); configService.createOrUpdate(secretKeyParam); SystemConfigParam apiHostParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_HOST) .content(request.getApiHost()).build(); configService.createOrUpdate(apiHostParam); SystemConfigParam modelParam = SystemConfigParam.builder().code(BaichuanAIClient.BAICHUAN_MODEL) .content(request.getModel()).build(); configService.createOrUpdate(modelParam); BaichuanAIClient.refresh(); } @GetMapping("/system_config/{code}") public DataResult getSystemConfig(@PathVariable("code") String code) { DataResult result = configService.find(code); return DataResult.of(result.getData()); } /** * ai config info * * @return */ @GetMapping("/system_config/ai") public DataResult getChatAiSystemConfig(String aiSqlSource) { DataResult dbSqlSource = configService.find(RestAIClient.AI_SQL_SOURCE); if (StringUtils.isBlank(aiSqlSource)) { if (Objects.nonNull(dbSqlSource.getData())) { aiSqlSource = dbSqlSource.getData().getContent(); } } AIConfig config = new AIConfig(); AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); if (Objects.isNull(aiSqlSourceEnum)) { aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); config.setAiSqlSource(aiSqlSource); return DataResult.of(config); } config.setAiSqlSource(aiSqlSource); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI: DataResult apiKey = configService.find(OpenAIClient.OPENAI_KEY); DataResult apiHost = configService.find(OpenAIClient.OPENAI_HOST); DataResult httpProxyHost = configService.find(OpenAIClient.PROXY_HOST); DataResult httpProxyPort = configService.find(OpenAIClient.PROXY_PORT); config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : ""); config.setHttpProxyHost( Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : ""); config.setHttpProxyPort( Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : ""); break; case CHAT2DBAI: DataResult chat2dbApiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); DataResult chat2dbApiHost = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_HOST); DataResult chat2dbModel = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_MODEL); config.setApiKey(Objects.nonNull(chat2dbApiKey.getData()) ? chat2dbApiKey.getData().getContent() : ""); config.setApiHost( Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(chat2dbModel.getData()) ? chat2dbModel.getData().getContent() : ""); break; case AZUREAI: DataResult azureApiKey = configService.find(AzureOpenAIClient.AZURE_CHATGPT_API_KEY); DataResult azureEndpoint = configService.find(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT); DataResult azureDeployId = configService.find(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID); config.setApiKey(Objects.nonNull(azureApiKey.getData()) ? azureApiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : ""); config.setModel(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() : ""); break; case RESTAI: DataResult restAIApiKey = configService.find(RestAIClient.REST_AI_API_KEY); DataResult restAIApiHost = configService.find(RestAIClient.REST_AI_URL); DataResult restAIModel = configService.find(RestAIClient.REST_AI_MODEL); config.setApiKey(Objects.nonNull(restAIApiKey.getData()) ? restAIApiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(restAIApiHost.getData()) ? restAIApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(restAIModel.getData()) ? restAIModel.getData().getContent() : ""); break; case FASTCHATAI: DataResult fastChatApiKey = configService.find(FastChatAIClient.FASTCHAT_API_KEY); DataResult fastChatApiHost = configService.find(FastChatAIClient.FASTCHAT_HOST); DataResult fastChatModel = configService.find(FastChatAIClient.FASTCHAT_MODEL); config.setApiKey(Objects.nonNull(fastChatApiKey.getData()) ? fastChatApiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(fastChatApiHost.getData()) ? fastChatApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(fastChatModel.getData()) ? fastChatModel.getData().getContent() : ""); break; case WENXINAI: DataResult wenxinAccessToken = configService.find(WenxinAIClient.WENXIN_ACCESS_TOKEN); DataResult wenxinApiHost = configService.find(WenxinAIClient.WENXIN_HOST); config.setApiKey(Objects.nonNull(wenxinAccessToken.getData()) ? wenxinAccessToken.getData().getContent() : ""); config.setApiHost(Objects.nonNull(wenxinApiHost.getData()) ? wenxinApiHost.getData().getContent() : ""); break; case BAICHUANAI: DataResult baichuanApiKey = configService.find(BaichuanAIClient.BAICHUAN_API_KEY); DataResult baichuanSecretKey = configService.find(BaichuanAIClient.BAICHUAN_SECRET_KEY); DataResult baichuanApiHost = configService.find(BaichuanAIClient.BAICHUAN_HOST); DataResult baichuanModel = configService.find(BaichuanAIClient.BAICHUAN_MODEL); config.setApiKey(Objects.nonNull(baichuanApiKey.getData()) ? baichuanApiKey.getData().getContent() : ""); config.setSecretKey(Objects.nonNull(baichuanSecretKey.getData()) ? baichuanSecretKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(baichuanApiHost.getData()) ? baichuanApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(baichuanModel.getData()) ? baichuanModel.getData().getContent() : ""); break; case TONGYIQIANWENAI: DataResult tongyiApiKey = configService.find(TongyiChatAIClient.TONGYI_API_KEY); DataResult tongyiApiHost = configService.find(TongyiChatAIClient.TONGYI_HOST); DataResult tongyiModel = configService.find(TongyiChatAIClient.TONGYI_MODEL); config.setApiKey(Objects.nonNull(tongyiApiKey.getData()) ? tongyiApiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(tongyiApiHost.getData()) ? tongyiApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(tongyiModel.getData()) ? tongyiModel.getData().getContent() : ""); break; case ZHIPUAI: DataResult zhipuApiKey = configService.find(ZhipuChatAIClient.ZHIPU_API_KEY); DataResult zhipuApiHost = configService.find(ZhipuChatAIClient.ZHIPU_HOST); DataResult zhipuModel = configService.find(ZhipuChatAIClient.ZHIPU_MODEL); config.setApiKey(Objects.nonNull(zhipuApiKey.getData()) ? zhipuApiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(zhipuApiHost.getData()) ? zhipuApiHost.getData().getContent() : ""); config.setModel(Objects.nonNull(zhipuModel.getData()) ? zhipuModel.getData().getContent() : ""); break; default: break; } return DataResult.of(config); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AIConfigCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.config.request; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author jipengfei * @version : SystemConfigRequest.java */ @Data public class AIConfigCreateRequest { /** * APIKEY */ private String apiKey; /** * SECRETKEY */ private String secretKey; /** * APIHOST */ private String apiHost; /** * api http proxy host */ private String httpProxyHost; /** * api http proxy port */ private String httpProxyPort; /** * @see AiSqlSourceEnum */ @NotNull private String aiSqlSource; /** * return data stream * Optional, default value is TRUE */ private Boolean stream = Boolean.TRUE; /** * deployed model, default gpt-3.5-turbo */ private String model; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java ================================================ package ai.chat2db.server.web.api.controller.config.request; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import lombok.Data; /** * @author jipengfei * @version : SystemConfigRequest.java */ @Data public class AISystemConfigRequest { /** * chat2db APIKEY */ private String chat2dbApiKey; /** * chat2db APIHOST */ private String chat2dbApiHost; /** * OpenAi APIKEY * Required when using the OpenAi interface, you can go to the OpenAI official website to view APIKEY */ private String apiKey; /** * OpenAi APIHOST * Optional, the default value is https://api.openai.com/ */ private String apiHost; /** * http proxy Host * Optional, used to set the HTTP proxy host when requesting the OPENAI interface */ private String httpProxyHost; /** * http proxy Port * Optional, used to set the HTTP proxy port when requesting the OPENAI interface */ private String httpProxyPort; /** * AI source * @see AiSqlSourceEnum */ private String aiSqlSource; /** * Customized AI interface * Required when selecting custom AI, used to set the REST interface URL of the custom AI */ private String restAiUrl; /** * Whether the Rest interface has streaming output * Optional, default value is TRUE */ private Boolean restAiStream = Boolean.TRUE; /** * Get Azure OpenAI key credential from the Azure Portal */ private String azureApiKey; /** * Get Azure OpenAI endpoint from the Azure Portal */ private String azureEndpoint; /** * deploymentId of the deployed model, default gpt-3.5-turbo */ private String azureDeploymentId; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/SystemConfigRequest.java ================================================ package ai.chat2db.server.web.api.controller.config.request; import lombok.Data; /** * @author jipengfei * @version : SystemConfigRequest.java */ @Data public class SystemConfigRequest { private String code; private String content; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java ================================================ package ai.chat2db.server.web.api.controller.dashboard; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartListQueryParam; import ai.chat2db.server.domain.api.chart.ChartQueryParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.dashboard.converter.ChartWebConverter; import ai.chat2db.server.web.api.controller.dashboard.request.ChartCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartQueryRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.ChartVO; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * Save chart class * * @author moji * @version ChartController.java, v 0.1 September 18, 2022 10:55 moji Exp $ * @date 2022/09/18 */ @RequestMapping("/api/chart") @RestController public class ChartController { @Autowired private ChartService chartService; @Autowired private ChartWebConverter chartWebConverter; /** * Query chart details based on id * * @param id * @return */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { ChartQueryParam param = new ChartQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); return chartService.queryExistent(param) .map(chartWebConverter::model2vo); } /** * Query report list based on ID list * * @param request * @return */ @GetMapping("/listByIds") public ListResult list(ChartQueryRequest request) { ChartListQueryParam param = new ChartListQueryParam(); param.setIdList(request.getIds()); param.setUserId(ContextUtils.getUserId()); return chartService.listQuery(param) .map(chartWebConverter::model2vo); } /** * Save chart * * @param request * @return */ @PostMapping("/create") public DataResult create(@Valid @RequestBody ChartCreateRequest request) { ChartCreateParam chartCreateParam = chartWebConverter.req2param(request); return chartService.createWithPermission(chartCreateParam); } /** * Update chart * * @param request * @return */ @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody ChartUpdateRequest request) { ChartUpdateParam param = chartWebConverter.req2updateParam(request); return chartService.updateWithPermission(param); } /** * Delete chart * * @param id * @return */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { return chartService.deleteWithPermission(id); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java ================================================ package ai.chat2db.server.web.api.controller.dashboard; import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.dashboard.converter.DashboardWebConverter; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.DashboardVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * Save dashboard class * * @author moji * @version DashboardController.java, v 0.1 September 18, 2022 10:55 moji Exp $ * @date 2022/09/18 */ @RequestMapping("/api/dashboard") @RestController public class DashboardController { @Autowired private DashboardService dashboardService; @Autowired private DashboardWebConverter dashboardWebConverter; /** * Query dashboard list * * @param request * @return */ @GetMapping("/list") public WebPageResult list(DashboardPageQueryParam request) { request.setUserId(ContextUtils.getUserId()); PageResult result = dashboardService.queryPage(request); List dashboardVOS = dashboardWebConverter.model2vo(result.getData()); return WebPageResult.of(dashboardVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); } /** * Query dashboard details based on id * * @param id * @return */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { DashboardQueryParam param = new DashboardQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); return dashboardService.queryExistent(param) .map(dashboardWebConverter::model2vo); } /** * Save dashboard * * @param request * @return */ @PostMapping("/create") public DataResult create(@RequestBody DashboardCreateRequest request) { DashboardCreateParam param = dashboardWebConverter.req2param(request); return dashboardService.createWithPermission(param); } /** * Update dashboard * * @param request * @return */ @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody DashboardUpdateRequest request) { DashboardUpdateParam param = dashboardWebConverter.req2updateParam(request); return dashboardService.updateWithPermission(param); } /** * Delete dashboard * * @param id * @return */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { return dashboardService.deleteWithPermission(id); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/ChartWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.chart.ChartCreateParam; import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.web.api.controller.dashboard.request.ChartCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.ChartVO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version ChartWebConverter.java, v 0.1 June 9, 2023 15:46 moji Exp $ * @date 2023/06/09 */ @Mapper(componentModel = "spring") public abstract class ChartWebConverter { /** * Model conversion * * @param chart * @return */ @Mappings({ @Mapping(target = "connectable", expression = "java(chart.getDataSourceName() != null)"), }) public abstract ChartVO model2vo(Chart chart); /** * Model conversion * * @param charts * @return */ public abstract List model2vo(List charts); /** * Parameter conversion * * @param request * @return */ public abstract ChartCreateParam req2param(ChartCreateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract ChartUpdateParam req2updateParam(ChartUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/DashboardWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.DashboardVO; import org.mapstruct.Mapper; /** * @author moji * @version DashboardWebConverter.java, v 0.1 June 9, 2023 15:45 moji Exp $ * @date 2023/06/09 */ @Mapper(componentModel = "spring") public abstract class DashboardWebConverter { /** * Model conversion * * @param dashboard * @return */ public abstract DashboardVO model2vo(Dashboard dashboard); /** * Model conversion * * @param dashboards * @return */ public abstract List model2vo(List dashboards); /** * Parameter conversion * * @param request * @return */ public abstract DashboardCreateParam req2param(DashboardCreateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract DashboardUpdateParam req2updateParam(DashboardUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.request; import lombok.Data; /** * @author moji * @version ChartCreateParam.java, v 0.1 June 9, 2023 15:38 moji Exp $ * @date 2023/06/09 */ @Data public class ChartCreateRequest { /** * Chart name */ private String name; /** * description */ private String description; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * ddl content */ private String ddl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.request; import java.util.List; import lombok.Data; /** * @author moji * @version ChartQueryRequest.java, v 0.1 June 9, 2023 17:46 moji Exp $ * @date 2023/06/09 */ @Data public class ChartQueryRequest { /** * Chart ID list */ private List ids; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.request; import java.time.LocalDateTime; import lombok.Data; /** * @author moji * @version ChartUpdateParam.java, v 0.1 June 9, 2023 15:39 moji Exp $ * @date 2023/06/09 */ @Data public class ChartUpdateRequest { /** * primary key */ private Long id; /** * Chart name */ private String name; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * ddl content */ private String ddl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/DashboardCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.request; import java.time.LocalDateTime; import java.util.List; import lombok.Data; /** * @author moji * @version DashboardSaveParam.java, v 0.1 June 9, 2023 15:29 moji Exp $ * @date 2023/06/09 */ @Data public class DashboardCreateRequest { /** * Dashboard name */ private String name; /** * Dashboard layout information */ private String schema; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/DashboardUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.request; import java.util.List; import lombok.Data; /** * @author moji * @version DashboardSaveParam.java, v 0.1 June 9, 2023 15:29 moji Exp $ * @date 2023/06/09 */ @Data public class DashboardUpdateRequest { /** * primary key */ private Long id; /** * Dashboard name */ private String name; /** * Dashboard layout information */ private String schema; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/ChartVO.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.vo; import java.util.Date; import lombok.Data; /** * @author moji * @version Chart.java, v 0.1 June 9, 2023 15:37 moji Exp $ * @date 2023/06/09 */ @Data public class ChartVO { /** * primary key */ private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Chart name */ private String name; /** * Chart description */ private String description; /** * chart information */ private String schema; /** * Data source connection ID */ private Long dataSourceId; /** * Data source name */ private String dataSourceName; /** * Database type */ private String type; /** * DB name */ private String databaseName; /** * schema name */ private String schemaName; /** * ddl content */ private String ddl; /** * Whether it can be connected, false means it cannot be connected, which means the data source has been deleted or does not exist. */ private Boolean connectable; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/DashboardVO.java ================================================ package ai.chat2db.server.web.api.controller.dashboard.vo; import java.util.Date; import java.util.List; import lombok.Data; /** * @author moji * @version Dashboard.java, v 0.1 June 9, 2023 15:32 moji Exp $ * @date 2023/06/09 */ @Data public class DashboardVO { /** * primary key */ private Long id; /** * creation time */ private Date gmtCreate; /** * modified time */ private Date gmtModified; /** * Dashboard name */ private String name; /** * Dashboard description */ private String description; /** * Dashboard layout information */ private String schema; /** * Chart ID list */ private List chartIds; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java ================================================ package ai.chat2db.server.web.api.controller.data.source; import java.util.List; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.ssh.SSHManager; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.converter.DataSourceWebConverter; import ai.chat2db.server.web.api.controller.data.source.converter.SSHWebConverter; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleConnectRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceAttachRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCloneRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCreateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceTestRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.request.SSHTestRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DataSourceVO; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; import com.jcraft.jsch.Session; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * Database connection class * * @author moji * @version ConnectionController.java, v 0.1 September 16, 2022 14:07 moji Exp $ * @date 2022/09/16 */ @ConnectionInfoAspect @RequestMapping("/api/connection") @RestController @Slf4j public class DataSourceController { private static final DataSourceSelector DATA_SOURCE_SELECTOR = DataSourceSelector.builder() .environment(Boolean.TRUE) .build(); @Autowired private DataSourceService dataSourceService; @Autowired private ConsoleService consoleService; @Autowired private DataSourceWebConverter dataSourceWebConverter; @Autowired private SSHWebConverter sshWebConverter; /** * Database connection test * * @param request * @return */ @RequestMapping("/datasource/pre_connect") public ActionResult preConnect(@RequestBody DataSourceTestRequest request) { DataSourcePreConnectParam param = dataSourceWebConverter.testRequest2param(request); return dataSourceService.preConnect(param); } /** * Database connection test * * @param request * @return */ @RequestMapping("/ssh/pre_connect") public ActionResult sshConnect(@RequestBody SSHTestRequest request) { Session session = null; try { session = SSHManager.getSSHSession(sshWebConverter.toInfo(request)); } catch (Exception e) { log.error("sshConnect error", e); throw new ConnectionException("connection.ssh.error", null, e); } finally { if (session != null) { session.disconnect(); } } return ActionResult.isSuccess(); } /** * Database connection * * @param request * @return */ @GetMapping("/datasource/connect") public ListResult attach(@Valid @NotNull DataSourceAttachRequest request) { ListResult databaseDTOListResult = dataSourceService.connect(request.getId()); List databaseVOS = dataSourceWebConverter.databaseDto2vo(databaseDTOListResult.getData()); return ListResult.of(databaseVOS); } /** * Close database connection * * @param request * @return */ @GetMapping("/datasource/close") public ActionResult close(@Valid @NotNull DataSourceCloseRequest request) { return dataSourceService.close(request.getId()); } /** * Console connection * * @param request * @return */ @GetMapping("/console/connect") public ActionResult connect(@Valid @NotNull ConsoleConnectRequest request) { ConsoleConnectParam consoleConnectParam = dataSourceWebConverter.request2connectParam(request); return consoleService.createConsole(consoleConnectParam); } /** * Close the Console connection * * @param request * @return */ @GetMapping("/console/close") public ActionResult closeConsole(@Valid @NotNull ConsoleCloseRequest request) { ConsoleCloseParam closeParam = dataSourceWebConverter.request2closeParam(request); return consoleService.closeConsole(closeParam); } /** * Query the database connection I established * * @param request * @return * @version 2.1.0 */ @GetMapping("/datasource/list") public WebPageResult list(DataSourceQueryRequest request) { DataSourcePageQueryParam param = dataSourceWebConverter.queryReq2param(request); PageResult result = dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR); List dataSourceVOS = dataSourceWebConverter.dto2vo(result.getData()); return WebPageResult.of(dataSourceVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); } /** * Get connection content * * @param id * @return */ @GetMapping("/datasource/{id}") public DataResult queryById(@PathVariable("id") Long id) { DataResult dataResult = dataSourceService.queryExistent(id, DATA_SOURCE_SELECTOR); DataSourceVO dataSourceVO = dataSourceWebConverter.dto2vo(dataResult.getData()); if (StringUtils.isNotBlank(dataSourceVO.getUser())) { dataSourceVO.setAuthenticationType("1"); } else { dataSourceVO.setAuthenticationType("2"); } return DataResult.of(dataSourceVO); } /** * save connection * * @param request * @return */ @PostMapping("/datasource/create") public DataResult create(@RequestBody DataSourceCreateRequest request) { DataSourceCreateParam param = dataSourceWebConverter.createReq2param(request); return dataSourceService.createWithPermission(param); } /** * Update connection * * @param request * @return */ @RequestMapping(value = "/datasource/update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult update(@RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceWebConverter.updateReq2param(request); return dataSourceService.updateWithPermission(param); } /** * clone connection * * @param request * @return */ @PostMapping("/datasource/clone") public DataResult copy(@RequestBody DataSourceCloneRequest request) { return dataSourceService.copyByIdWithPermission(request.getId()); } /** * Delete connection * * @param id * @return */ @DeleteMapping("/datasource/{id}") public ActionResult delete(@PathVariable Long id) { return dataSourceService.deleteWithPermission(id); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.data.source.converter; import java.util.List; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleConnectRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceCreateRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceTestRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DataSourceVO; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; import ai.chat2db.spi.model.Database; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version DataSourceWebConverter.java, v 0.1 September 23, 2022 16:45 moji Exp $ * @date 2022/09/23 */ @Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) public abstract class DataSourceWebConverter { /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "user", target = "userName"), @Mapping(target = "kind", expression = "java(DataSourceKindEnum.PRIVATE.getCode())") }) public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "user", target = "userName") }) public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract DataSourcePageQueryParam queryReq2param(DataSourceQueryRequest request); /** * Model conversion * * @param dataSource * @return */ @Mappings({ @Mapping(target = "user", source = "userName") }) public abstract DataSourceVO dto2vo(DataSource dataSource); /** * Model conversion * * @param dataSources * @return */ public abstract List dto2vo(List dataSources); /** * Model conversion * * @param databaseDTO * @return */ public abstract DatabaseVO databaseDto2vo(Database databaseDTO); /** * Model conversion * * @param databaseDTOS * @return */ public abstract List databaseDto2vo(List databaseDTOS); /** * Parameter conversion * * @param request * @return */ public abstract DataSourcePreConnectParam testRequest2param(DataSourceTestRequest request); /** * Parameter conversion * * @param request * @return */ public abstract ConsoleConnectParam request2connectParam(ConsoleConnectRequest request); /** * Parameter conversion * * @param request * @return */ public abstract ConsoleCloseParam request2closeParam(ConsoleCloseRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/SSHWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.data.source.converter; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.server.web.api.controller.data.source.request.SSHTestRequest; import org.mapstruct.Mapper; /** * @author jipengfei * @version : SSHWebConverter.java */ @Mapper(componentModel = "spring") public abstract class SSHWebConverter { /** * Parameter conversion * * @param request * @return */ public abstract SSHInfo toInfo(SSHTestRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/ConsoleCloseRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import lombok.Data; /** * @author moji * @version ConsoleContentRequest.java, v 0.1 October 30, 2022 15:52 moji Exp $ * @date 2022/10/30 */ @Data public class ConsoleCloseRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo{ /** * console id */ private Long consoleId; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/ConsoleConnectRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import lombok.Data; /** * @author moji * @version ConsoleContentRequest.java, v 0.1 October 30, 2022 15:52 moji Exp $ * @date 2022/10/30 */ @Data public class ConsoleConnectRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * console id */ private Long consoleId; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceAttachRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceAttachRequest implements DataSourceBaseRequestInfo{ /** * primary key id */ @NotNull private Long id; @Override public Long getDataSourceId() { return id; } @Override public String getDatabaseName() { return null; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version MysqlBaseRequest.java, v 0.1 September 18, 2022 11:51 moji Exp $ * @date 2022/09/18 */ @Data public class DataSourceBaseRequest implements DataSourceBaseRequestInfo{ /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequestInfo.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; /** * @author jipengfei * @version : DataSourceBaseRequestInfo.java */ public interface DataSourceBaseRequestInfo { /** * Get datasource id * @return */ Long getDataSourceId(); /** * get datasource name * @return */ String getDatabaseName(); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCloneRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import lombok.Data; /** * @author moji * @version ConnectionCloneRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceCloneRequest { /** * primary key id */ private Long id; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCloseRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceCloseRequest { /** * primary key id */ @NotNull private Long id; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceConsoleRequestInfo.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; /** * @author jipengfei * @version : DataSourceConsoleRequestInfo.java */ public interface DataSourceConsoleRequestInfo extends DataSourceBaseRequestInfo{ /** * * @return */ Long getConsoleId(); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceCreateRequest { /** * Connection alias */ private String alias; /** * connection address */ @NotNull private String url; /** * Connect username */ private String user; /** * password */ @NotNull private String password; /** * Certification type */ private String authenticationType; /** * Connection Type */ @NotNull private String type; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * environment id */ @NotNull private Long environmentId; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceQueryRequest extends PageQueryRequest { /** * Alias fuzzy search terms */ private String searchKey; /** * Connection Type * * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceTestRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceTestRequest { /** * Connection alias */ private String alias; /** * connection address */ @NotNull private String url; /** * Connect users */ private String user; /** * password */ @NotNull private String password; /** * Database connection type */ @NotNull private String type; /** * Certification type */ private String authenticationType; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import java.util.List; import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import lombok.Data; /** * @author moji * @version ConnectionCreateRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceUpdateRequest { /** * primary key id */ @NotNull private Long id; /** * Connection alias */ private String alias; /** * connection address */ private String url; /** * Connect users */ private String user; /** * password */ private String password; /** * Connection Type */ private String type; /** * environment type * @see EnvTypeEnum */ private String envType; /** * environment id */ private Integer environmentId; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/SSHTestRequest.java ================================================ package ai.chat2db.server.web.api.controller.data.source.request; import lombok.Data; /** * @author jipengfei * @version : SSHTestRequest.java */ @Data public class SSHTestRequest { private boolean use; private String hostName; private String port; private String userName; private String localPort; private String authenticationType; private String password; private String keyFile; private String passphrase; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DataSourceVO.java ================================================ package ai.chat2db.server.web.api.controller.data.source.vo; import java.util.List; import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import lombok.Data; /** * @author moji * @version ConnectionVO.java, v 0.1 September 16, 2022 14:15 moji Exp $ * @date 2022/09/16 */ @Data public class DataSourceVO { /** * primary key id */ private Long id; /** * Connection alias */ private String alias; /** * connection address */ private String url; /** * Connect users */ private String user; /** * password */ private String password; /** * Certification type */ private String authenticationType; /** * Connection Type */ private String type; /** * environment type */ private String envType; /** * host */ private String host; /** * port */ private String port; /** * ssh */ private SSHInfo ssh; ///** // * ssh // */ //private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; /** * Driver configuration */ private DriverConfig driverConfig; /** * environment id */ private Long environmentId; /** * environment */ private SimpleEnvironmentVO environment; /** * Connection Type * * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; /** * service name */ private String serviceName; /** * Service type */ private String serviceType; /** * Whether to support database */ private boolean supportDatabase; /** * Whether to support schema */ private boolean supportSchema; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DatabaseVO.java ================================================ package ai.chat2db.server.web.api.controller.data.source.vo; import lombok.Data; /** * @author moji * @version DatabaseVO.java, v 0.1 September 16, 2022 17:24 moji Exp $ * @date 2022/09/16 */ @Data public class DatabaseVO { /** * DB name */ private String name; /** * DB description */ private String description; /** * The number of tables or keys under DB */ private Integer count; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/EnvVO.java ================================================ package ai.chat2db.server.web.api.controller.data.source.vo; import lombok.Data; /** * @author moji * @version EnvVO.java, v 0.1 September 18, 2022 14:06 moji Exp $ * @date 2022/09/18 */ @Data public class EnvVO { /** * environment code */ private String code; /** * environment name */ private String name; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java ================================================ package ai.chat2db.server.web.api.controller.driver; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import ai.chat2db.server.domain.api.service.JdbcDriverService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.controller.driver.request.JdbcDriverRequest; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.JdbcJarUtils; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /** * JDBC driver management * * @author moji * @version JdbcDriverController.java, v 0.1 September 16, 2022 17:41 moji Exp $ * @date 2022/09/16 */ @RequestMapping("/api/jdbc/driver") @RestController public class JdbcDriverController { @Autowired private JdbcDriverService jdbcDriverService; /** * Query current DB driver information * * @param dbType * @return */ @GetMapping("/list") public DataResult list(@RequestParam String dbType) { return jdbcDriverService.getDrivers(dbType); } /** * Download driver * * @param dbType * @return */ @GetMapping("/download") public ActionResult download(@RequestParam String dbType) { return jdbcDriverService.download(dbType); } /** * Upload driver * * @param multipartFiles * @return */ @PostMapping("/upload") public ListResult upload(@RequestParam MultipartFile[] multipartFiles) { List list = new ArrayList<>(); for (int i = 0; i < multipartFiles.length; i++) { MultipartFile multipartFile = multipartFiles[i]; String originalFilename = FilenameUtils.getName(multipartFile.getOriginalFilename()); String location = JdbcJarUtils.PATH + originalFilename; try { multipartFile.transferTo(new File(location)); } catch (IOException e) { throw new RuntimeException(e); } list.add(originalFilename); } return ListResult.of(list); } /** * save * * @param request * @return */ @PostMapping("/save") public ActionResult save(@RequestBody JdbcDriverRequest request) { return jdbcDriverService.upload(request.getDbType(), request.getJdbcDriverClass(), String.join(",", request.getJdbcDriver())); } ///** // * Delete driver // * // * @param request // * @return // */ //@DeleteMapping("/delete") //public ActionResult delete(@RequestBody KeyDeleteRequest request) { // return null; //} } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/request/JdbcDriverRequest.java ================================================ package ai.chat2db.server.web.api.controller.driver.request; import java.util.List; import lombok.Data; @Data public class JdbcDriverRequest { String jdbcDriverClass; String dbType; List jdbcDriver; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java ================================================ package ai.chat2db.server.web.api.controller.ncx; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; import ai.chat2db.server.web.api.util.FileUtils; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.util.Objects; import java.util.UUID; /** * ConverterController * * @author lzy **/ @RequestMapping("/api/converter") @RestController @Slf4j public class ConverterController { @Autowired private ConverterService converterService; /** * Export tutorial * * @param file file * @return DataResult * @see **/ @SneakyThrows @PostMapping("/ncx/upload") public DataResult ncxUploadFile(@RequestParam("file") MultipartFile file) { log.info("Start uploading ncx"); // Verify file suffix String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.NCX.name())) { return DataResult.error("1", "The uploaded file must be an ncx file!"); } File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); file.transferTo(temp); return DataResult.of(converterService.uploadFile(temp)); } @SneakyThrows @PostMapping("/dbp/upload") public DataResult edbpUploadFile(@RequestParam("file") MultipartFile file) { // Verify file suffix String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.DBP.name())) { return DataResult.error("1", "The uploaded file must be a dbp file!"); } File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); file.transferTo(temp); return DataResult.of(converterService.dbpUploadFile(temp)); } /** * Import the connection information of datagrip, copy the connection through ctrl/cmd + c (shift multiple selection), and then import it. * There is no password in the currently copied connection information, and there is no ssh connection information either. * * @param text text * @return DataResult **/ @SneakyThrows @PostMapping("/datagrip/upload") public DataResult datagripUploadFile(@RequestParam("text") String text) { return DataResult.of(converterService.datagripUploadFile(text)); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java ================================================ package ai.chat2db.server.web.api.controller.ncx.cipher; import java.util.Formatter; /** * CommonCipher Public encryption/decryption * * @author lzy */ public abstract class CommonCipher { public String encryptString(String plaintext) { return null; } public String decryptString(String ciphertext) { return null; } public String printHexBinary(byte[] data) { StringBuilder hexBuilder = new StringBuilder(); Formatter formatter = new Formatter(hexBuilder); for (byte b : data) { formatter.format("%02x", b); } return hexBuilder.toString(); } public static byte[] parseHexBinary(String data) { return hexStringToByteArray(data); } public static byte[] hexStringToByteArray(String hex) { if (hex.length() % 2 != 0) { throw new IllegalArgumentException("Hex string length must be even"); } byte[] bytes = new byte[hex.length() / 2]; for (int i = 0; i < hex.length(); i += 2) { String byteString = hex.substring(i, i + 2); bytes[i / 2] = (byte) Integer.parseInt(byteString, 16); } return bytes; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java ================================================ package ai.chat2db.server.web.api.controller.ncx.cipher; import org.apache.commons.lang3.StringUtils; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Arrays; /** * Navicat11 and below password encryption and decryption * * @author lzy */ public class Navicat11Cipher extends CommonCipher { public static final String DefaultUserKey = "3DC5CA39"; private static byte[] IV; private static SecretKeySpec key; private static Cipher encryptor; private static Cipher decrypt; private static void initKey() { try { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); byte[] userKey_data = Navicat11Cipher.DefaultUserKey.getBytes(StandardCharsets.UTF_8); sha1.update(userKey_data, 0, userKey_data.length); key = new SecretKeySpec(sha1.digest(), "Blowfish"); } catch (Exception e) { throw new RuntimeException(e); } } private static void initCipherEncrypt() { try { // Must use NoPadding encryptor = Cipher.getInstance("Blowfish/ECB/NoPadding"); encryptor.init(Cipher.ENCRYPT_MODE, key); } catch (Exception e) { throw new RuntimeException(e); } } private static void initCipherDecrypt() { try { // Must use NoPadding decrypt = Cipher.getInstance("Blowfish/ECB/NoPadding"); decrypt.init(Cipher.DECRYPT_MODE, key); } catch (Exception e) { throw new RuntimeException(e); } } private static void initIV() { try { byte[] initVec = parseHexBinary("FFFFFFFFFFFFFFFF"); IV = encryptor.doFinal(initVec); } catch (Exception e) { throw new RuntimeException(e); } } private void xorBytes(byte[] a, byte[] b) { for (int i = 0; i < a.length; i++) { int aVal = a[i] & 0xff; // convert byte to integer int bVal = b[i] & 0xff; a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte } } private void xorBytes(byte[] a, byte[] b, int l) { for (int i = 0; i < l; i++) { int aVal = a[i] & 0xff; // convert byte to integer int bVal = b[i] & 0xff; a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte } } static { initKey(); initCipherEncrypt(); initCipherDecrypt(); initIV(); } private byte[] Encrypt(byte[] inData) { try { byte[] CV = Arrays.copyOf(IV, IV.length); byte[] ret = new byte[inData.length]; int blocks_len = inData.length / 8; int left_len = inData.length % 8; for (int i = 0; i < blocks_len; i++) { byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); xorBytes(temp, CV); temp = encryptor.doFinal(temp); xorBytes(CV, temp); System.arraycopy(temp, 0, ret, i * 8, 8); } if (left_len != 0) { CV = encryptor.doFinal(CV); byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); xorBytes(temp, CV, left_len); System.arraycopy(temp, 0, ret, blocks_len * 8, temp.length); } return ret; } catch (Exception e) { throw new RuntimeException(e); } } @Override public String encryptString(String inputString) { try { byte[] inData = inputString.getBytes(StandardCharsets.UTF_8); byte[] outData = Encrypt(inData); return printHexBinary(outData); } catch (Exception e) { throw new RuntimeException(e); } } private byte[] Decrypt(byte[] inData) { try { byte[] cv = Arrays.copyOf(IV, IV.length); byte[] ret = new byte[inData.length]; int blocks_len = inData.length / 8; int left_len = inData.length % 8; for (int i = 0; i < blocks_len; i++) { byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); temp = decrypt.doFinal(temp); xorBytes(temp, cv); System.arraycopy(temp, 0, ret, i * 8, 8); for (int j = 0; j < cv.length; j++) { cv[j] = (byte) (cv[j] ^ inData[i * 8 + j]); } } if (left_len != 0) { cv = encryptor.doFinal(cv); byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); xorBytes(temp, cv, left_len); for (int j = 0; j < temp.length; j++) { ret[blocks_len * 8 + j] = temp[j]; } } return ret; } catch (Exception e) { throw new RuntimeException(e); } } @Override public String decryptString(String hexString) { if (StringUtils.isEmpty(hexString)) { return ""; } try { byte[] inData = parseHexBinary(hexString); byte[] outData = Decrypt(inData); return new String(outData, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java ================================================ package ai.chat2db.server.web.api.controller.ncx.cipher; import org.apache.commons.lang3.StringUtils; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; /** * Navicat12 and above password encryption and decryption * * @author lzy */ public class Navicat12Cipher extends CommonCipher { private static final SecretKeySpec AES_KEY; private static final IvParameterSpec AES_IV; static { AES_KEY = new SecretKeySpec("libcckeylibcckey".getBytes(StandardCharsets.UTF_8), "AES"); AES_IV = new IvParameterSpec("libcciv libcciv ".getBytes(StandardCharsets.UTF_8)); } @Override public String encryptString(String plaintext) { try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, AES_KEY, AES_IV); byte[] ret = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); return printHexBinary(ret); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String decryptString(String ciphertext) { if (StringUtils.isEmpty(ciphertext)) { return ""; } try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, AES_KEY, AES_IV); byte[] ret = cipher.doFinal(parseHexBinary(ciphertext)); return new String(ret, StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DBSValueEncryptor.java ================================================ /* * DBeaver - Universal Database Manager * Copyright (C) 2010-2023 DBeaver Corp and others * * Licensed 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. */ package ai.chat2db.server.web.api.controller.ncx.dbeaver; /** * Value encryptor */ public interface DBSValueEncryptor { byte[] encryptValue(byte[] value); byte[] decryptValue(byte[] value); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DefaultValueEncryptor.java ================================================ /* * DBeaver - Universal Database Manager * Copyright (C) 2010-2023 DBeaver Corp and others * * Licensed 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. */ package ai.chat2db.server.web.api.controller.ncx.dbeaver; import org.apache.poi.util.IOUtils; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * Default value encryptor. * * Uses Eclipse secure preferences to read/write secrets. */ public class DefaultValueEncryptor implements DBSValueEncryptor { public static final String CIPHER_NAME = "AES/CBC/PKCS5Padding"; public static final String KEY_ALGORITHM = "AES"; private static final byte[] LOCAL_KEY_CACHE = new byte[] { -70, -69, 74, -97, 119, 74, -72, 83, -55, 108, 45, 101, 61, -2, 84, 74 }; private final SecretKey secretKey; private final Cipher cipher; public DefaultValueEncryptor(SecretKey secretKey) { this.secretKey = secretKey; try { this.cipher = Cipher.getInstance(CIPHER_NAME); } catch (Exception e) { throw new IllegalStateException("Internal error during encrypted init", e); } } /** * View the default SecretKey through DBeaver source code **/ public static SecretKey getLocalSecretKey() { return new SecretKeySpec(LOCAL_KEY_CACHE, DefaultValueEncryptor.KEY_ALGORITHM); } public static SecretKey makeSecretKeyFromPassword(String password) { byte[] bytes = password.getBytes(StandardCharsets.UTF_8); byte[] passBytes = Arrays.copyOf(bytes, 16); return new SecretKeySpec(passBytes, KEY_ALGORITHM); } @Override public byte[] encryptValue(byte[] value) { try { cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] iv = cipher.getIV(); ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream(); try (CipherOutputStream cipherOut = new CipherOutputStream(resultBuffer, cipher)) { resultBuffer.write(iv); cipherOut.write(value); } return resultBuffer.toByteArray(); } catch (Exception e) { throw new RuntimeException("Error encrypting value", e); } } @Override public byte[] decryptValue(byte[] value) { try (InputStream byteStream = new ByteArrayInputStream(value)) { byte[] fileIv = new byte[16]; byteStream.read(fileIv); cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv)); try (CipherInputStream cipherIn = new CipherInputStream(byteStream, cipher)) { ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream(); IOUtils.copy(cipherIn, resultBuffer); return resultBuffer.toByteArray(); } } catch (Exception e) { throw new RuntimeException("Error decrypting value", e); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java ================================================ package ai.chat2db.server.web.api.controller.ncx.enums; import lombok.Getter; import org.apache.commons.lang3.StringUtils; /** * DataBaseType * * @author lzy **/ @Getter public enum DataBaseType { /** * MYSQL */ MYSQL("jdbc:mysql://%s:%s"), /** * ORACLE */ ORACLE("jdbc:oracle:thin:@%s:%s:XE"), /** * SQL_SERVER */ SQLSERVER("jdbc:sqlserver://%s:%s"), /** * SQL_SERVER */ SQLITE("jdbc:sqlite:%s"), /** * POSTGRESQL **/ POSTGRESQL("jdbc:postgresql://%s:%s"), /** * DB2 **/ DB2("jdbc:db2://%s:%s"), /** * Mariadb **/ MARIADB("jdbc:mariadb://%s:%s"), /** * DM **/ DM("jdbc:dm://%s:%s"), /** * KINGBASE **/ KINGBASE("jdbc:kingbase8://%s:%s"), /** * Presto **/ PRESTO("jdbc:presto://%s:%s"), /** * OceanBase **/ OCEANBASE("jdbc:oceanbase://%s:%s"), /** * Hive **/ HIVE("jdbc:hive2://%s:%s"), /** * ClickHouse **/ CLICKHOUSE("jdbc:clickhouse://%s:%s"); private String urlString; DataBaseType(String urlString) { this.urlString = urlString; } public void setUrlString(String urlString) { this.urlString = urlString; } public static DataBaseType matchType(String value) { if (StringUtils.isNotEmpty(value)) { for (DataBaseType dataBase : DataBaseType.values()) { //kingbase -> kingbase8 if (value.toUpperCase().contains(dataBase.name())) { return dataBase; } } } return null; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java ================================================ /* * DBeaver - Universal Database Manager * Copyright (C) 2010-2023 DBeaver Corp and others * * Licensed 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. */ package ai.chat2db.server.web.api.controller.ncx.enums; /** * Import/Export constants */ public class ExportConstants { public static final String ARCHIVE_FILE_EXT = ".dbp"; //NON-NLS-1 public static final String CONFIG_FILE = ".dbeaver"; //NON-NLS-1 public static final String CONFIG_DATASOURCE_FILE = "data-sources.json"; //NON-NLS-1 public static final String CONFIG_CREDENTIALS_FILE = "credentials-config.json"; //NON-NLS-1 public static final String DIR_PROJECTS = "projects"; //NON-NLS-1 public static final String DIR_DRIVERS = "drivers"; //NON-NLS-1 public static final String DIR_CONNECTIONS = "connections"; //NON-NLS-1 public static final String DIR_CONFIGURATION = "configuration"; //NON-NLS-1 public static final String GENERIC = "generic"; //NON-NLS-1 public static final String META_FILENAME = "meta.xml"; //NON-NLS-1 public static final String TAG_ARCHIVE = "archive"; //NON-NLS-1 public static final String TAG_SOURCE = "source"; public static final String TAG_PROJECTS = "projects"; //NON-NLS-1 public static final String TAG_PROJECT = "project"; //NON-NLS-1 public static final String TAG_RESOURCE = "resource"; //NON-NLS-1 public static final String TAG_ATTRIBUTE = "attribute"; //NON-NLS-1 public static final String TAG_LIBRARIES = "libraries"; //NON-NLS-1 public static final String ATTR_VERSION = "version"; //NON-NLS-1 public static final String ATTR_HOST = "host"; public static final String ATTR_ADDRESS = "address"; public static final String ATTR_TIME = "time"; public static final String ATTR_QUALIFIER = "qualifier"; //NON-NLS-1 public static final String ATTR_NAME = "name"; //NON-NLS-1 public static final String ATTR_VALUE = "value"; //NON-NLS-1 public static final String ATTR_DIRECTORY = "directory"; //NON-NLS-1 public static final String ATTR_DESCRIPTION = "description"; //NON-NLS-1 public static final String ATTR_CHARSET = "charset"; //NON-NLS-1 public static final String ATTR_PATH = "path"; //NON-NLS-1 public static final String ATTR_FILE = "file"; //NON-NLS-1 } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java ================================================ package ai.chat2db.server.web.api.controller.ncx.enums; /** * navicat version enumeration (version distinguishes navicat encryption algorithm) * * @author lzy */ public enum VersionEnum { /** * navicat11 */ native11, /** * navicat12+ */ navicat12more } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java ================================================ package ai.chat2db.server.web.api.controller.ncx.factory; import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat11Cipher; import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat12Cipher; import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; import lombok.SneakyThrows; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * CipherFactory * * @author lzy **/ @Service public class CipherFactory { /** * NavicatCipher cache pool */ private static final Map REPORT_POOL = new ConcurrentHashMap<>(0); static { REPORT_POOL.put(VersionEnum.native11.name(), new Navicat11Cipher()); REPORT_POOL.put(VersionEnum.navicat12more.name(), new Navicat12Cipher()); } /** * Get the corresponding encryption/decryption method * * @param type type * @return ITokenGranter */ @SneakyThrows public static CommonCipher get(String type) { CommonCipher cipher = REPORT_POOL.get(type); if (cipher == null) { throw new ClassNotFoundException("no CommonCipher was found"); } else { return cipher; } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java ================================================ package ai.chat2db.server.web.api.controller.ncx.service; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; import java.io.File; import java.io.InputStream; /** * ConverterService * * @author lzy **/ public interface ConverterService { UploadVO uploadFile(File file); UploadVO dbpUploadFile(File file); UploadVO datagripUploadFile(String text); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java ================================================ package ai.chat2db.server.web.api.controller.ncx.service.impl; import ai.chat2db.server.domain.core.util.DesUtil; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.mapper.ChartMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; import ai.chat2db.server.web.api.controller.ncx.dbeaver.DefaultValueEncryptor; import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; import ai.chat2db.server.web.api.controller.ncx.enums.ExportConstants; import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; import ai.chat2db.server.web.api.controller.ncx.factory.CipherFactory; import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; import ai.chat2db.server.web.api.util.XMLUtils; import ai.chat2db.spi.model.SSHInfo; import cn.hutool.core.io.FileUtil; import com.alibaba.excel.util.FileUtils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * ConverterServiceImpl * * @author lzy **/ @Service @Slf4j @Transactional(rollbackFor = Exception.class) public class ConverterServiceImpl implements ConverterService { private static final double NAVICAT11 = 1.1D; private static CommonCipher cipher; /** * Connection information header **/ private static final String DATASOURCE_SETTINGS = "#DataSourceSettings#"; private static final String XML_HEADER = ""; /** * xml connection information start flag **/ private static final String BEGIN = "#BEGIN#"; /** * Password json key **/ private static final String connection = "#connection"; private DataSourceMapper getDataSourceMapper(){ return Dbutils.getMapper(DataSourceMapper.class); } /** * jdbc universal matching ip and port */ public static final Pattern IP_PORT = Pattern.compile("jdbc:(?[a-z]+)://(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); /** * oracle matching ip and port */ public static final Pattern ORACLE_IP_PORT = Pattern.compile("jdbc:(?[a-z]+):(?[a-z]+):@(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); @Override public UploadVO uploadFile(File file) { UploadVO vo = new UploadVO(); try { // List>> The connection to be imported List>> configMap = new ArrayList<>(); //1、Create a DocumentBuilderFactory object DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); //2、Create a DocumentBuilder object //Create DocumentBuilder object DocumentBuilder db = dbf.newDocumentBuilder(); //3、Load the xml file into the current project through the parser method of the DocumentBuilder object Document document = db.parse(file); //Get the collection of all Connections nodes NodeList connectList = document.getElementsByTagName("Connection"); NodeList nodeList = document.getElementsByTagName("Connections"); //Select the first node NamedNodeMap verMap = nodeList.item(0).getAttributes(); double version = Double.parseDouble((verMap.getNamedItem("Ver").getNodeValue())); if (version <= NAVICAT11) { cipher = CipherFactory.get(VersionEnum.native11.name()); } else { cipher = CipherFactory.get(VersionEnum.navicat12more.name()); } //Configure map Map> connectionMap = new HashMap<>(); //Traverse each Connections node for (int i = 0; i < connectList.getLength(); i++) { //Get a Connection node through the item(i) method, the index value of nodeList starts from 0 Node connect = connectList.item(i); //Get the collection of all properties of the Connection node NamedNodeMap attrs = connect.getAttributes(); //Traverse the properties of Connection Map map = new HashMap<>(0); for (int j = 0; j < attrs.getLength(); j++) { //Obtain a certain attribute of the connect node through the item(index) method Node attr = attrs.item(j); map.put(attr.getNodeName(), attr.getNodeValue()); } connectionMap.put(map.get("ConnectionName") + map.get("ConnType"), map); } configMap.add(connectionMap); log.info("insert to db, param:{}", JSON.toJSONString(configMap)); // Get the link imported from navicat and write it into the h2 database of chat2db insertDBConfig(configMap); log.info("insert to h2 success"); //Delete temporary files FileUtils.delete(file); } catch (Exception e) { throw new RuntimeException(e); } return vo; } @SneakyThrows @Override public UploadVO dbpUploadFile(File file) { UploadVO vo = new UploadVO(); Document metaTree; //Projects waiting to be deleted List projects = new ArrayList<>(); try (ZipFile zipFile = new ZipFile(file, ZipFile.OPEN_READ)) { ZipEntry metaEntry = zipFile.getEntry(ExportConstants.META_FILENAME); if (metaEntry == null) { throw new RuntimeException("Cannot find meta file"); } try (InputStream metaStream = zipFile.getInputStream(metaEntry)) { metaTree = XMLUtils.parseDocument(metaStream); } catch (Exception e) { throw new RuntimeException("Cannot parse meta file: " + e.getMessage()); } Element projectsElement = XMLUtils.getChildElement(metaTree.getDocumentElement(), ExportConstants.TAG_PROJECTS); if (projectsElement != null) { final Collection projectList = XMLUtils.getChildElementList(projectsElement, ExportConstants.TAG_PROJECT); for (Element projectElement : projectList) { //Get project name String projectName = projectElement.getAttribute(ExportConstants.ATTR_NAME); //Import matching file directory String config = ConfigUtils.CONFIG_BASE_PATH + File.separator + projectName + File.separator + ExportConstants.CONFIG_FILE; importDbeaverConfig(new File(config), projectElement, //Cannot be replaced by File.separator ExportConstants.DIR_PROJECTS + "/" + projectName + "/", zipFile); //Add to delete list projects.add(projectName); //Configured json file File json = new File(config + File.separator + ExportConstants.CONFIG_DATASOURCE_FILE); JSONObject jsonObject = JSON.parseObject(new FileInputStream(json)); JSONObject connections = jsonObject.getJSONObject(ExportConstants.DIR_CONNECTIONS); Set keys = connections.keySet(); for (String key : keys) { JSONObject configurations = connections.getJSONObject(key); JSONObject configuration = configurations.getJSONObject(ExportConstants.DIR_CONFIGURATION); //Match database type String provider = configurations.getString("provider"); if (provider.equals(ExportConstants.GENERIC)) { //Custom driverCustom driver JSONObject drivers = jsonObject.getJSONObject(ExportConstants.DIR_DRIVERS); //Get driver id String driverId = configurations.getString("driver"); //Get all generic JSONObject generics = drivers.getJSONObject(provider); //Get your own driver JSONObject generic = generics.getJSONObject(driverId); //If it does not exist, it will not be imported. if (null == generic) { continue; } //Assign driver name to determine the type of database provider = generic.getString("name"); } DataBaseType dataBaseType = DataBaseType.matchType(provider.toUpperCase()); DataSourceDO dataSourceDO; //The database type is not matched. For example: dbeaver supports custom drivers, etc., but chat2DB does not support it yet. if (null != dataBaseType) { //Password information File credentials = new File(config + File.separator + ExportConstants.CONFIG_CREDENTIALS_FILE); DefaultValueEncryptor defaultValueEncryptor = new DefaultValueEncryptor(DefaultValueEncryptor.getLocalSecretKey()); JSONObject credentialsJson = JSON.parseObject(defaultValueEncryptor.decryptValue(Files.readAllBytes(credentials.toPath()))); dataSourceDO = new DataSourceDO(); Date dateTime = new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); //Insert user id dataSourceDO.setUserId(ContextUtils.getUserId()); dataSourceDO.setAlias(configurations.getString("name")); dataSourceDO.setHost(configuration.getString("host")); dataSourceDO.setPort(configuration.getString("port")); dataSourceDO.setUrl(configuration.getString("url")); //ssh is set to false SSHInfo sshInfo = new SSHInfo(); sshInfo.setUse(false); dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); if (null != credentialsJson) { JSONObject userInfo = credentialsJson.getJSONObject(key); if (null != userInfo) { JSONObject userPassword = userInfo.getJSONObject(connection); dataSourceDO.setUserName(userPassword.getString("user")); DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); String password = userPassword.getString("password"); String encryptStr = desUtil.encrypt(Optional.ofNullable(password).orElse(""), "CBC"); dataSourceDO.setPassword(encryptStr); } } dataSourceDO.setType(dataBaseType.name()); getDataSourceMapper().insert(dataSourceDO); } } } } } //Delete temporary files FileUtils.delete(file); //Delete the temporary configuration file generated by dbp when importing dbeaver projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v))); return vo; } @SneakyThrows private static void importDbeaverConfig(File resource, Element resourceElement, String containerPath, ZipFile zipFile) { for (Element childElement : XMLUtils.getChildElementList(resourceElement, ExportConstants.TAG_RESOURCE)) { String childName = childElement.getAttribute(ExportConstants.ATTR_NAME); String entryPath = containerPath + childName; ZipEntry resourceEntry = zipFile.getEntry(entryPath); if (resourceEntry == null) { continue; } boolean isDirectory = resourceEntry.isDirectory(); if (isDirectory) { File folder = new File(resource.getPath()); if (!folder.exists()) { FileUtil.mkdir(folder); } importDbeaverConfig(folder, childElement, entryPath + "/", zipFile); } else { File file = new File(resource.getPath() + File.separator + childName); FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file, true); } } } @SneakyThrows @Override public UploadVO datagripUploadFile(String text) { UploadVO vo = new UploadVO(); if (!text.startsWith(DATASOURCE_SETTINGS)) { throw new RuntimeException("连接信息的头部不正确!"); } String[] items = text.split("\n"); List configs = new ArrayList<>(); for (int i = 0; i < items.length; i++) { if (items[i].equals(BEGIN)) { configs.add(XML_HEADER + items[i + 1]); } } for (String config : configs) { //1、Create a DocumentBuilderFactory object DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); //2、Create a DocumentBuilder object //Create DocumentBuilder object DocumentBuilder db = dbf.newDocumentBuilder(); //3、Load the xml file into the current project through the parser method of the DocumentBuilder object try (InputStream inputStream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8))) { Document document = db.parse(inputStream); // Get the root element Element rootElement = document.getDocumentElement(); //Create datasource DataSourceDO dataSourceDO = new DataSourceDO(); Date dateTime = new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(rootElement.getAttribute("name")); //Insert user id dataSourceDO.setUserId(ContextUtils.getUserId()); // Get child elements database-info Element databaseInfoElement = (Element) rootElement.getElementsByTagName("database-info").item(0); // Get connection related information String type = databaseInfoElement.getAttribute("dbms"); String jdbcUrl = rootElement.getElementsByTagName("jdbc-url").item(0).getTextContent(); String username = rootElement.getElementsByTagName("user-name").item(0).getTextContent(); String driverName = rootElement.getElementsByTagName("jdbc-driver").item(0).getTextContent(); String host = ""; String port = ""; if (type.equals(DataBaseType.ORACLE.name())) { // Create Matcher object Matcher matcher = ORACLE_IP_PORT.matcher(jdbcUrl); // Find matching IP address and port number if (matcher.find()) { host = matcher.group("host"); port = matcher.group("port"); } } else { // Create Matcher object Matcher matcher = IP_PORT.matcher(jdbcUrl); // Find matching IP address and port number if (matcher.find()) { host = matcher.group("host"); port = matcher.group("port"); } } //ssh is set to false SSHInfo sshInfo = new SSHInfo(); sshInfo.setUse(false); dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); dataSourceDO.setHost(host); dataSourceDO.setPort(port); dataSourceDO.setUrl(jdbcUrl); dataSourceDO.setUserName(username); dataSourceDO.setDriver(driverName); dataSourceDO.setType(type); getDataSourceMapper().insert(dataSourceDO); } } return vo; } /** * Write to database * * @param list Read data from ncx file */ @SneakyThrows public void insertDBConfig(List>> list) { for (Map> map : list) { for (Map.Entry> valueMap : map.entrySet()) { Map resultMap = valueMap.getValue(); // The version of mysql cannot be distinguished yet DataBaseType dataBaseType = DataBaseType.matchType(resultMap.get("ConnType")); DataSourceDO dataSourceDO; if (null == dataBaseType) { //The database type is not matched. For example: navicat supports MongoDB, etc., but chat2DB does not support it yet. continue; } else { dataSourceDO = new DataSourceDO(); dataSourceDO.setHost(resultMap.get("Host")); dataSourceDO.setPort(resultMap.get("Port")); dataSourceDO.setUrl(String.format(dataBaseType.getUrlString(), dataSourceDO.getHost(), dataSourceDO.getPort())); } // Decrypt password String password = cipher.decryptString(resultMap.getOrDefault("Password", "")); Date dateTime =new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(resultMap.get("ConnectionName")); dataSourceDO.setUserName(resultMap.get("UserName")); dataSourceDO.setType(resultMap.get("ConnType")); //Insert user id dataSourceDO.setUserId(ContextUtils.getUserId()); //Password is the decrypted ciphertext, and then uses chat2db for encryption. DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); String encryptStr = desUtil.encrypt(password, "CBC"); dataSourceDO.setPassword(encryptStr); SSHInfo sshInfo = new SSHInfo(); if ("false".equals(resultMap.get("SSH"))) { sshInfo.setUse(false); } else { sshInfo.setUse(true); sshInfo.setHostName(resultMap.get("SSH_Host")); sshInfo.setPort(resultMap.get("SSH_Port")); sshInfo.setUserName(resultMap.get("SSH_UserName")); // Currently chat2DB only supports password and Private key boolean passwordType = "password".equalsIgnoreCase(resultMap.get("SSH_AuthenMethod")); sshInfo.setAuthenticationType(passwordType ? "password" : "Private key"); if (passwordType) { // Decrypt password String ssh_password = cipher.decryptString(resultMap.getOrDefault("SSH_Password", "")); sshInfo.setPassword(ssh_password); } else { sshInfo.setKeyFile(resultMap.get("SSH_PrivateKey")); sshInfo.setPassphrase(resultMap.get("SSH_Passphrase")); } } dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); log.info("begin insert:{}", JSON.toJSONString(dataSourceDO)); getDataSourceMapper().insert(dataSourceDO); } } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java ================================================ package ai.chat2db.server.web.api.controller.ncx.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * UploadVO * * @author lzy **/ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UploadVO { private String result; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java ================================================ package ai.chat2db.server.web.api.controller.operation.log; import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.log.converter.OperationLogWebConverter; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogCreateRequest; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogQueryRequest; import ai.chat2db.server.web.api.controller.operation.log.vo.OperationLogVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * History service class * * @author moji * @version HistoryManageController.java, v 0.1 September 18, 2022 10:55 moji Exp $ * @date 2022/09/18 */ @RequestMapping("/api/operation/log") @RestController public class OperationLogController { @Autowired private OperationLogService operationLogService; @Autowired private OperationLogWebConverter operationLogWebConverter; /** * Query execution records * * @param request * @return */ @GetMapping("/list") public WebPageResult list(OperationLogQueryRequest request) { OperationLogPageQueryParam param = operationLogWebConverter.req2param(request); param.setUserId(ContextUtils.getUserId()); PageResult result = operationLogService.queryPage(param); List operationLogVOList = operationLogWebConverter.dto2vo(result.getData()); return WebPageResult.of(operationLogVOList, result.getTotal(), result.getPageNo(), result.getPageSize()); } /** * Add history * * @param request * @return */ @PostMapping("/create") public DataResult create(@RequestBody OperationLogCreateRequest request) { OperationLogCreateParam param = operationLogWebConverter.createReq2param(request); return operationLogService.create(param); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/converter/OperationLogWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.operation.log.converter; import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogCreateRequest; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogQueryRequest; import ai.chat2db.server.web.api.controller.operation.log.vo.OperationLogVO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version HistoryWebConverter.java, v 0.1 September 25, 2022 16:53 moji Exp $ * @date 2022/09/25 */ @Mapper(componentModel = "spring") public abstract class OperationLogWebConverter { /** * Parameter conversion * * @param request * @return */ public abstract OperationLogCreateParam createReq2param(OperationLogCreateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract OperationLogPageQueryParam req2param(OperationLogQueryRequest request); /** * Model conversion * * @param ddlDTO * @return */ @Mappings({ @Mapping(source = "ddl", target = "name"), @Mapping(target = "connectable", expression = "java(ddlDTO.getDataSourceName() != null)"), }) public abstract OperationLogVO dto2vo(OperationLog ddlDTO); /** * Model conversion * * @param ddlDTOS * @return */ public abstract List dto2vo(List ddlDTOS); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/request/OperationLogCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.log.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version DdlCreateRequest.java, v 0.1 September 18, 2022 11:13 moji Exp $ * @date 2022/09/18 */ @Data public class OperationLogCreateRequest extends DataSourceBaseRequest { /** * file alias */ private String name; /** * ddl type */ @NotNull private String type; /** * ddl content */ @NotNull private String ddl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/request/OperationLogQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.log.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** * @author moji * @version DdlCreateRequest.java, v 0.1 September 18, 2022 11:13 moji Exp $ * @date 2022/09/18 */ @Data public class OperationLogQueryRequest extends PageQueryRequest { /** * Fuzzy word search */ private String searchKey; /** * Data source id */ private Long dataSourceId; /** * Name database */ private String databaseName; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/vo/OperationLogVO.java ================================================ package ai.chat2db.server.web.api.controller.operation.log.vo; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; /** * @author moji * @version DdlVO.java, v 0.1 September 18, 2022 11:06 moji Exp $ * @date 2022/09/18 */ @Data public class OperationLogVO { /** * primary key */ private Long id; /** * creation time */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime gmtCreate; /** * modified time */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime gmtModified; /** * file alias */ private String name; /** * Data source id */ private Long dataSourceId; /** * Data source name */ private String dataSourceName; /** * Is it connectable? */ private Boolean connectable; /** * DB name */ private String databaseName; /** * ddl language type */ private String type; /** * ddl content */ private String ddl; /** * state */ private String status; /** * Number of operation lines */ private Long operationRows; /** * Length of use */ private Long useTime; /** * Extended Information */ private String extendInfo; /** * schema name */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved; import java.util.List; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.OperationService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.saved.converter.OperationWebConverter; import ai.chat2db.server.web.api.controller.operation.saved.request.BatchTabCloseRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationCreateRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationQueryRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationUpdateRequest; import ai.chat2db.server.web.api.controller.operation.saved.vo.OperationVO; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * My save service class * * @author moji * @version DdlManageController.java, v 0.1 September 16, 2022 19:59 moji Exp $ * @date 2022/09/16 */ @RequestMapping("/api/operation/saved") @RestController public class OperationSavedController { @Autowired private OperationService operationService; @Autowired private OperationWebConverter operationWebConverter; /** * Check my saves * * @param request * @return */ @GetMapping("/list") public WebPageResult list(OperationQueryRequest request) { OperationPageQueryParam param = operationWebConverter.queryReq2param(request,ContextUtils.getUserId()); param.setUserId(ContextUtils.getUserId()); PageResult dtoPageResult = operationService.queryPage(param); List operationVOS = operationWebConverter.dto2vo(dtoPageResult.getData()); return WebPageResult.of(operationVOS, dtoPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } /** * Query console based on id * * @param id * @return */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { OperationQueryParam param = new OperationQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); return operationService.queryExistent(param).map(operationWebConverter::dto2vo); } /** * Add my save * * @param request * @return */ @PostMapping("/create") public DataResult create(@RequestBody OperationCreateRequest request) { OperationSavedParam param = operationWebConverter.req2param(request); param.setTabOpened("y"); return operationService.createWithPermission(param); } /** * Update my save * * @param request * @return */ @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody OperationUpdateRequest request) { OperationUpdateParam param = operationWebConverter.updateReq2param(request); return operationService.updateWithPermission(param); } /** * Close tags in batches * * @param request * @return */ @RequestMapping(value = "/batch_tab_close", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult batchTabClose(@RequestBody BatchTabCloseRequest request) { if (CollectionUtils.isEmpty(request.getIdList())) { return ActionResult.isSuccess(); } request.getIdList().forEach(id -> { OperationUpdateParam param = new OperationUpdateParam(); param.setId(id); param.setTabOpened("n"); operationService.updateWithPermission(param); }); return ActionResult.isSuccess(); } /** * delete my save * * @param id * @return */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { return operationService.deleteWithPermission(id); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/converter/OperationWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.converter; import java.util.List; import ai.chat2db.server.domain.api.model.Operation; import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationCreateRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationQueryRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationUpdateRequest; import ai.chat2db.server.web.api.controller.operation.saved.vo.OperationVO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version DdlManageWebConverter.java, v 0.1 September 26, 2022 10:08 moji Exp $ * @date 2022/09/26 */ @Mapper(componentModel = "spring") public abstract class OperationWebConverter { /** * Parameter conversion * * @param request * @return */ public abstract OperationSavedParam req2param(OperationCreateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract OperationUpdateParam updateReq2param(OperationUpdateRequest request); /** * Parameter conversion * * @param request * @return */ public abstract OperationPageQueryParam queryReq2param(OperationQueryRequest request, Long userId); /** * Model conversion * * @param ddlDTO * @return */ @Mappings({ @Mapping(target = "connectable", expression = "java(ddlDTO.getDataSourceName() != null)"), }) public abstract OperationVO dto2vo(Operation ddlDTO); /** * Model conversion * * @param ddlDTOS * @return */ public abstract List dto2vo(List ddlDTOS); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/BatchTabCloseRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.request; import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * close tab * @author Jiaju Zhuang */ @Data public class BatchTabCloseRequest { /** * primary key */ @NotNull private List idList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.tools.base.enums.StatusEnum; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version DdlCreateRequest.java, v 0.1 September 18, 2022 11:13 moji Exp $ * @date 2022/09/18 */ @Data public class OperationCreateRequest extends DataSourceBaseRequest { /** * file alias */ private String name; /** * Save state * @see StatusEnum */ @NotNull private String status; /** * DB TYPE */ @NotNull private String type; /** * ddl content */ @NotNull private String ddl; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** * @author moji * @version DdlCreateRequest.java, v 0.1 September 18, 2022 11:13 moji Exp $ * @date 2022/09/18 */ @Data public class OperationQueryRequest extends PageQueryRequest { /** * Data source id */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * Fuzzy search terms */ private String searchKey; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * orderBy modify time desc */ private Boolean orderByDesc; /** * orderBy create time desc */ private Boolean orderByCreateDesc; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version DdlCreateRequest.java, v 0.1 September 18, 2022 11:13 moji Exp $ * @date 2022/09/18 */ @Data public class OperationUpdateRequest { /** * primary key */ @NotNull private Long id; /** * file alias */ private String name; /** * Data source connection ID */ private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * Database type */ private String type; /** * ddl content */ @NotNull private String ddl; /** * Update status DRAFT/RELEASE */ private String status; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; /** * user id */ private Long userId; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/vo/OperationVO.java ================================================ package ai.chat2db.server.web.api.controller.operation.saved.vo; import lombok.Data; /** * @author moji * @version DdlVO.java, v 0.1 September 18, 2022 11:06 moji Exp $ * @date 2022/09/18 */ @Data public class OperationVO { /** * primary key */ private Long id; /** * file alias */ private String name; /** * Data source id */ private Long dataSourceId; /** * Data source name */ private String dataSourceName; /** * Is it connectable? */ private Boolean connectable; /** * DB name */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * ddl language type */ private String type; /** * ddl content */ private String ddl; /** * ddl statement status: DRAFT/RELEASE */ private String status; /** * Whether it is opened in the tab, y means open, n means not opened */ private String tabOpened; /** * operation type */ private String operationType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java ================================================ package ai.chat2db.server.web.api.controller.pin; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.web.api.controller.pin.converter.PinWebConverter; import ai.chat2db.server.web.api.controller.pin.request.PinTableRequest; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/pin") @RestController public class PinController { @Autowired private PinService pinService; @Autowired private PinWebConverter pinWebConverter; @PostMapping("/table/add") public ActionResult add(@Valid @RequestBody PinTableRequest request) { return pinService.pinTable(pinWebConverter.req2param(request)); } @PostMapping("/table/delete") public ActionResult delete(@Valid @RequestBody PinTableRequest request) { return pinService.deletePinTable(pinWebConverter.req2param(request)); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/converter/PinWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.pin.converter; import ai.chat2db.server.domain.api.param.PinTableParam; import ai.chat2db.server.web.api.controller.pin.request.PinTableRequest; import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public abstract class PinWebConverter { public abstract PinTableParam req2param(PinTableRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/request/PinTableRequest.java ================================================ package ai.chat2db.server.web.api.controller.pin.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class PinTableRequest { /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located */ private String schemaName; /** * Pin table name */ private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.enums.TaskStatusEnum; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; import ai.chat2db.server.web.api.controller.rdb.converter.DatabaseConverter; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.data.service.DatabaseDataService; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskManager; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskState; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseCreateRequest; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseExportDataRequest; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseExportRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateDatabaseRequest; import ai.chat2db.server.web.api.controller.rdb.vo.MetaSchemaVO; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Sql; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.io.PrintWriter; import java.util.Objects; /** * database controller */ @ConnectionInfoAspect @RequestMapping("/api/rdb/database") @RestController @Slf4j public class DatabaseController { @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DatabaseService databaseService; @Autowired public DatabaseConverter databaseConverter; @Autowired private DatabaseDataService databaseDataService; /** * Query the database_schema_list contained in the database * * @param request * @return */ @GetMapping("/database_schema_list") public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()) .refresh( request.isRefresh()).build(); DataResult result = databaseService.queryDatabaseSchema(queryParam); MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); return DataResult.of(schemaDto2vo); } @GetMapping("list") public ListResult databaseList(@Valid DataSourceBaseRequest request) { DatabaseQueryAllParam queryParam = DatabaseQueryAllParam.builder().dataSourceId(request.getDataSourceId()) .refresh( request.isRefresh()).build(); ListResult result = databaseService.queryAll(queryParam); return ListResult.of(rdbWebConverter.databaseDto2vo(result.getData())); } /** * Delete database * * @param request * @return */ @PostMapping("/delete_database") public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()).build(); return databaseService.deleteDatabase(param); } /** * create database * * @param request * @return */ @PostMapping("/create_database_sql") public DataResult createDatabase(@Valid @RequestBody DatabaseCreateRequest request) { if (StringUtils.isBlank(request.getName())) { request.setName(request.getDatabaseName()); } Database database = databaseConverter.request2param(request); return databaseService.createDatabase(database); } /** * Modify database * * @param request * @return */ @PostMapping("/modify_database") public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()) .name(request.getNewDatabaseName()).build(); return databaseService.modifyDatabase(param); } @PostMapping("/export") public void exportDatabase(@Valid @RequestBody DatabaseExportRequest request, HttpServletResponse response) { String fileName = Objects.isNull(request.getSchemaName()) ? request.getDatabaseName() : request.getSchemaName(); response.setContentType("text/sql"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".sql"); response.setCharacterEncoding("utf-8"); DatabaseExportParam param = databaseConverter.request2param(request); try (PrintWriter printWriter = response.getWriter()) { String sql = databaseService.exportDatabase(param); printWriter.println(sql); } catch (Exception e) { throw new RuntimeException(e); } } @PostMapping("/export_data") public DataResult exportData(@Valid @RequestBody DatabaseExportDataRequest request) { DatabaseExportDataParam databaseExportDataParam = databaseConverter.request2param(request); return databaseDataService.doExportAsync(databaseExportDataParam); } @GetMapping("/export_data_status/{taskId}") public DataResult exportDataStatus(@PathVariable("taskId") Long taskId) { TaskState task = TaskManager.getTask(taskId); String state = task.getState(); if (Objects.equals(state, TaskStatusEnum.FINISH.name()) || Objects.equals(state, TaskStatusEnum.ERROR.name())) { TaskManager.removeTask(taskId); } return DataResult.of(task.getExportStatus()); } /** * Query the database_user_list contained in the database * * @return username list */ @GetMapping("/database_username_list") public ListResult databaseUsernameList(@Valid DataSourceBaseRequest dataSourceBaseRequest) { return databaseService.getUsernameList(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.FunctionService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.FunctionConverter; import ai.chat2db.server.web.api.controller.rdb.request.FunctionDetailRequest; import ai.chat2db.server.web.api.controller.rdb.request.FunctionPageRequest; import ai.chat2db.server.web.api.controller.rdb.request.FunctionUpdateRequest; import ai.chat2db.spi.model.Function; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @ConnectionInfoAspect @RequestMapping("/api/rdb/function") @RestController public class FunctionController { @Autowired private FunctionService functionService; @Autowired private FunctionConverter functionConverter; @GetMapping("/list") public WebPageResult list(@Valid FunctionPageRequest request) { ListResult functionListResult = functionService.functions(request.getDatabaseName(), request.getSchemaName()); return WebPageResult.of(functionListResult.getData(), Long.valueOf(functionListResult.getData().size()), 1, functionListResult.getData().size()); } @GetMapping("/detail") public DataResult detail(@Valid FunctionDetailRequest request) { return functionService.detail(request.getDatabaseName(), request.getSchemaName(), request.getFunctionName()); } @PostMapping("/delete") public ActionResult delete(@Valid FunctionUpdateRequest request) { Function function = functionConverter.request2param(request); return functionService.delete(request.getDatabaseName(), request.getSchemaName(), function); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.ProcedureService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.ProcedureConverter; import ai.chat2db.server.web.api.controller.rdb.request.ProcedureDetailRequest; import ai.chat2db.server.web.api.controller.rdb.request.ProcedurePageRequest; import ai.chat2db.server.web.api.controller.rdb.request.ProcedureUpdateRequest; import ai.chat2db.spi.model.Procedure; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.sql.SQLException; @ConnectionInfoAspect @RequestMapping("/api/rdb/procedure") @RestController public class ProcedureController { @Autowired private ProcedureService procedureService; @Autowired private ProcedureConverter procedureConverter; @GetMapping("/list") public WebPageResult list(@Valid ProcedurePageRequest request) { ListResult procedureListResult = procedureService.procedures(request.getDatabaseName(), request.getSchemaName()); return WebPageResult.of(procedureListResult.getData(), Long.valueOf(procedureListResult.getData().size()), 1, procedureListResult.getData().size()); } @GetMapping("/detail") public DataResult detail(@Valid ProcedureDetailRequest request) { return procedureService.detail(request.getDatabaseName(), request.getSchemaName(), request.getProcedureName()); } @PostMapping("/update") public ActionResult update(@Valid ProcedureUpdateRequest request) throws SQLException { Procedure procedure = procedureConverter.request2param(request); return procedureService.update(request.getDatabaseName(), request.getSchemaName(), procedure); } @PostMapping("/delete") public ActionResult delete(@Valid ProcedureUpdateRequest request) { Procedure procedure = procedureConverter.request2param(request); return procedureService.delete(request.getDatabaseName(), request.getSchemaName(), procedure); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.EmbeddingController; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.*; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import com.google.common.collect.Lists; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * mysql table operation and maintenance class * * @author moji * @version MysqlTableManageController.java, v 0.1 September 16, 2022 17:41 moji Exp $ * @date 2022/09/16 */ @ConnectionInfoAspect @RequestMapping("/api/rdb/ddl") @RestController @Slf4j @Deprecated public class RdbDdlController extends EmbeddingController { @Autowired private TableService tableService; @Autowired private DlTemplateService dlTemplateService; @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DatabaseService databaseService; public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); /** * Query the table list under the current DB * * @param request * @return */ @GetMapping("/list") public WebPageResult list(@Valid TableBriefQueryRequest request) { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); tableSelector.setIndexList(false); PageResult

tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); // ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); // singleThreadExecutor.submit(() -> { // try { // Chat2DBContext.putContext(connectInfo); // syncTableVector(request); //// syncTableEs(request); // } catch (Exception e) { // log.error("sync table vector error", e); // } finally { // Chat2DBContext.removeContext(); // } // log.info("sync table vector finish"); // }); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } /** * Query the schema_list contained in the database * * @param request * @return */ @GetMapping("/schema_list") public ListResult schemaList(@Valid DataSourceBaseRequest request) { SchemaQueryParam queryParam = SchemaQueryParam.builder().dataBaseName(request.getDatabaseName()).build(); ListResult tableColumns = databaseService.querySchema(queryParam); List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); return ListResult.of(tableVOS); } /** * Query the database_schema_list contained in the database * * @param request * @return */ @GetMapping("/database_schema_list") public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).refresh( request.isRefresh()).build(); DataResult result = databaseService.queryDatabaseSchema(queryParam); MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); return DataResult.of(schemaDto2vo); } /** * Query the table columns under the current DB * * @param request * @return */ @GetMapping("/column_list") public ListResult columnList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableColumns = tableService.queryColumns(queryParam); List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); return ListResult.of(tableVOS); } /** * Query the table index under the current DB * * @param request * @return */ @GetMapping("/index_list") public ListResult indexList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableIndices = tableService.queryIndexes(queryParam); List indexVOS = rdbWebConverter.indexDto2vo(tableIndices); return ListResult.of(indexVOS); } /** * Query the table key under the current DB * * @param request * @return */ @GetMapping("/key_list") public ListResult keyList(@Valid TableDetailQueryRequest request) { // TODO Add query key implementation return ListResult.of(Lists.newArrayList()); } /** * Export table creation statement * * @param request * @return */ @GetMapping("/export") public DataResult export(@Valid DdlExportRequest request) { ShowCreateTableParam param = rdbWebConverter.ddlExport2showTableCreate(request); return tableService.showCreateTable(param); } /** * Table creation statement example * * @param request * @return */ @GetMapping("/create/example") public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { return tableService.createTableExample(request.getDbType()); } /** * Update table statement example * * @param request * @return */ @GetMapping("/update/example") public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { return tableService.alterTableExample(request.getDbType()); } /** * Get information such as table columns and indexes * * @param request * @return */ @GetMapping("/query") public DataResult query(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); DataResult
tableDTODataResult = tableService.query(queryParam, tableSelector); TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); return DataResult.of(tableVO); } /** * Get the sql statement that modifies the table * * @param request * @return */ @GetMapping("/modify/sql") public ListResult modifySql(@Valid TableModifySqlRequest request) { return tableService.buildSql( rdbWebConverter.tableRequest2param(request.getOldTable()), rdbWebConverter.tableRequest2param(request.getNewTable())) .map(rdbWebConverter::dto2vo); } /** * Delete table * * @param request * @return */ @PostMapping("/delete") public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); return tableService.drop(dropParam); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import java.sql.Connection; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.param.GroupByParam; import ai.chat2db.server.domain.api.param.OrderByParam; import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.request.SqlExecuteHistoryCreateRequest; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.Chat2DBContext; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * mysql data operation and maintenance class * * @author moji * @version MysqlDataManageController.java, v 0.1 September 16, 2022 17:37 moji Exp $ * @date 2022/09/16 */ @ConnectionInfoAspect @RequestMapping("/api/rdb/dml") @RestController public class RdbDmlController { @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DlTemplateService dlTemplateService; @Autowired private GatewayClientService gatewayClientService; public static ExecutorService executorService = Executors.newFixedThreadPool(10); /** * Data operation and maintenance such as addition, deletion, modification and query * * @param request * @return */ @RequestMapping(value = "/execute", method = {RequestMethod.POST, RequestMethod.PUT}) public ListResult manage(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); return ListResult.of(resultVOS); } /** * query chat2db apikey * * @return */ private String getClientId() { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { return ConfigUtils.getClientId(); } return keyConfig.getContent(); } /** * Query table structure information * * @param request * @return */ @RequestMapping(value = "/execute_table", method = {RequestMethod.POST, RequestMethod.PUT}) public ListResult executeTable(@RequestBody DmlTableRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); return dlTemplateService.executeSelectTable(param) .map(rdbWebConverter::dto2vo); } /** * update search result * * @param request * @return */ @RequestMapping(value = "/execute_update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult executeSelectResultUpdate(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); DataResult result = dlTemplateService.executeUpdate(param); if (!result.success()) { return DataResult.error(result.getErrorCode(), result.getErrorMessage()); } ExecuteResultVO executeResultVO = rdbWebConverter.dto2vo(result.getData()); return DataResult.of(executeResultVO); } @RequestMapping(value = "/get_update_sql", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult getUpdateSelectResultSql(@RequestBody SelectResultUpdateRequest request) { UpdateSelectResultParam param = rdbWebConverter.request2param(request); return dlTemplateService.updateSelectResult(param); } @RequestMapping(value = "/get_group_by_sql", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult getGroupBySql(@RequestBody GroupByRequest request) { GroupByParam param = rdbWebConverter.request2param(request); return dlTemplateService.getGroupBySql(param); } @RequestMapping(value = "/get_order_by_sql", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult getOrderBySql(@RequestBody OrderByRequest request) { OrderByParam param = rdbWebConverter.request2param(request); return dlTemplateService.getOrderBySql(param); } /** * Data operation and maintenance such as addition, deletion, modification and query * * @param request * @return */ @RequestMapping(value = "/execute_ddl", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult executeDDL(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); Connection connection = Chat2DBContext.getConnection(); if (connection != null) { try { boolean flag = true; ExecuteResultVO executeResult = null; //connection.setAutoCommit(false); ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); if (!CollectionUtils.isEmpty(resultVOS)) { for (ExecuteResultVO resultVO : resultVOS) { if (!resultVO.getSuccess()) { flag = false; executeResult = resultVO; break; } } } if (flag) { //connection.commit(); return DataResult.of(resultVOS.get(0)); } else { //connection.rollback(); return DataResult.of(executeResult); } } catch (Exception e) { throw new RuntimeException(e); } } else { return DataResult.error("connection error", ""); } } /** * Number of statistics rows * * @param request * @return */ @RequestMapping(value = "/count", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult count(@RequestBody DdlCountRequest request) { return dlTemplateService.count(rdbWebConverter.request2param(request)); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.SQLUtils.FormatOption; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; import com.alibaba.druid.sql.ast.statement.SQLInsertStatement.ValuesClause; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.visitor.VisitorFeature; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.metadata.WriteSheet; import ai.chat2db.server.domain.api.enums.ExportSizeEnum; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.SqlUtils; import cn.hutool.core.date.DatePattern; import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * Export Database Exclusive * * @author Jiaju Zhuang */ @ConnectionInfoAspect @RequestMapping("/api/rdb/dml") @Controller @Slf4j public class RdbDmlExportController { /** * Format insert statement */ private static final FormatOption INSERT_FORMAT_OPTION = new FormatOption(true, false); static { INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); } /** * export data * * @param request * @return */ @PostMapping("/export") public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws IOException { ExportSizeEnum exportSize = EasyEnumUtils.getEnum(ExportSizeEnum.class, request.getExportSize()); ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); String sql; if (exportSize == ExportSizeEnum.CURRENT_PAGE) { sql = request.getOriginalSql() + " LIMIT " + request.getPageSize() + " OFFSET " + (request.getPageNo() - 1) * request.getPageSize(); } else { sql = request.getOriginalSql(); } if (StringUtils.isBlank(sql)) { throw new ParamBusinessException("exportSize"); } DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); String tableName; if (dbType != null) { SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if (!(sqlStatement instanceof SQLSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } tableName = SqlUtils.getTableName(sql, dbType); } else { tableName = StringUtils.join(Lists.newArrayList(request.getDatabaseName(), request.getSchemaName()), "_"); } response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode( tableName + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); if (exportType == ExportTypeEnum.CSV) { doExportCsv(sql, response, fileName); } else { doExportInsert(sql, response, fileName, dbType, tableName); } } private void doExportCsv(String sql, HttpServletResponse response, String fileName) throws IOException { response.setContentType("text/csv"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".csv"); ExcelWrapper excelWrapper = new ExcelWrapper(); try { ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream()) .charset(StandardCharsets.UTF_8) .excelType(ExcelTypeEnum.CSV); excelWrapper.setExcelWriterBuilder(excelWriterBuilder); SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { excelWriterBuilder.head( EasyCollectionUtils.toList(headerList, header -> Lists.newArrayList(header.getName()))); excelWrapper.setExcelWriter(excelWriterBuilder.build()); excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); }, dataList -> { List> writeDataList = Lists.newArrayList(); writeDataList.add(dataList); excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); }, jdbcDataValue -> valueProcessor.getJdbcValue(jdbcDataValue), false); } finally { if (excelWrapper.getExcelWriter() != null) { excelWrapper.getExcelWriter().finish(); } } } private void doExportInsert(String sql, HttpServletResponse response, String fileName, DbType dbType, String tableName) throws IOException { response.setContentType("text/sql"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".sql"); ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); SqlBuilder sqlBuilder = Chat2DBContext.getMetaData().getSqlBuilder(); try (PrintWriter printWriter = response.getWriter()) { List headerColumns = Lists.newArrayList(); List headers = new ArrayList<>(); InsertWrapper insertWrapper = new InsertWrapper(); SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { headerList.forEach(sqlIdentifierExpr -> headerColumns.add(sqlIdentifierExpr.getName())); } , dataList -> { for (String header : headerColumns) { SQLIdentifierExpr expr = new SQLIdentifierExpr(header); expr.setName(header); headers.add(expr); } insertWrapper.setHeaderList(headers); SQLInsertStatement sqlInsertStatement = new SQLInsertStatement(); sqlInsertStatement.setDbType(dbType); sqlInsertStatement.setTableSource(new SQLExprTableSource(tableName)); sqlInsertStatement.getColumns().addAll(insertWrapper.getHeaderList()); ValuesClause valuesClause = new ValuesClause(); for (String s : dataList) { valuesClause.addValue(s); } sqlInsertStatement.setValues(valuesClause); String sqls = sqlBuilder.buildSingleInsertSql(null, null, tableName, headerColumns, dataList); printWriter.println(sqls + ";"); }, jdbcDataValue -> valueProcessor.getJdbcSqlValueString(jdbcDataValue), false); } } @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public static class InsertWrapper { private List headerList; } @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public static class ExcelWrapper { private ExcelWriterBuilder excelWriterBuilder; private ExcelWriter excelWriter; private WriteSheet writeSheet; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.event.TemplateEvent; import ai.chat2db.server.web.api.controller.rdb.factory.ExportServiceFactory; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.Table; import cn.hutool.core.date.DatePattern; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import java.lang.reflect.Constructor; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.List; /** * RdbDocController * * @author lzy **/ @ConnectionInfoAspect @RequestMapping("/api/rdb/doc") @Controller @Slf4j public class RdbDocController { @Autowired private TableService tableService; @Autowired private RdbWebConverter rdbWebConverter; /** * export data * * @param request */ @PostMapping("/export") public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws Exception { //Copy template ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode( request.getDatabaseName() + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); queryParam.setPageNo(1); queryParam.setPageSize(Integer.MAX_VALUE); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); TableQueryParam param = rdbWebConverter.tableRequest2param(request); for (TableVO tableVO: tableVOS) { param.setTableName(tableVO.getName()); tableVO.setColumnList(tableService.queryColumns(param)); tableVO.setIndexList(tableService.queryIndexes(param)); } Class targetClass = ExportServiceFactory.get(exportType.getCode()); Constructor constructor = targetClass.getDeclaredConstructor(); DatabaseExportService databaseExportService = (DatabaseExportService) constructor.newInstance(); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + databaseExportService.getSuffix()); response.setContentType(databaseExportService.getContentType()); // Set up data collection databaseExportService.setExportList(tableVOS); databaseExportService.generate(request.getDatabaseName(), response.getOutputStream(), new ExportOptions()); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import java.util.List; import ai.chat2db.server.domain.api.param.SchemaOperationParam; import ai.chat2db.server.domain.api.param.SchemaQueryParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.SchemaCreateRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateSchemaRequest; import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Sql; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * shema controller */ @ConnectionInfoAspect @RequestMapping("/api/rdb/schema") @RestController public class SchemaController { @Autowired private TableService tableService; @Autowired private DlTemplateService dlTemplateService; @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DatabaseService databaseService; /** * Query the schema_list contained in the database * * @param request * @return */ @GetMapping("/list") public ListResult list(@Valid DataSourceBaseRequest request) { SchemaQueryParam queryParam = SchemaQueryParam.builder().dataSourceId(request.getDataSourceId()).dataBaseName( request.getDatabaseName()).refresh(request.isRefresh()).build(); ListResult tableColumns = databaseService.querySchema(queryParam); List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); return ListResult.of(tableVOS); } /** * Delete schema * * @param request * @return */ @PostMapping("/delete_schema") public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).build(); return databaseService.deleteSchema(param); } /** * Create schema * * @param request * @return */ @PostMapping("/create_schema_sql") public DataResult createSchema(@Valid @RequestBody SchemaCreateRequest request) { Schema schema = Schema.builder().databaseName(request.getDatabaseName()) .name(request.getSchemaName()) .owner(request.getOwner()) .comment(request.getComment()) .build(); return databaseService.createSchema(schema); } /** * Modify schema * * @param request * @return */ @PostMapping("/modify_schema") public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); return databaseService.modifySchema(param); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SequenceController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.SequenceService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.SequenceVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.spi.model.*; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; /** * sequence controller * * @author Sylphy */ @Slf4j @RestController @ConnectionInfoAspect @RequiredArgsConstructor @RequestMapping("/api/rdb/sequence") public class SequenceController { private final RdbWebConverter rdbWebConverter; private final SequenceService sequenceService; /** * Query the sequence list under the current DB * * @param request * @return */ @GetMapping("/list") public ListResult list(@Valid SequenceBriefQueryRequest request) { SequencePageQueryParam queryParam = rdbWebConverter.sequencePageRequest2param(request); return sequenceService.pageQuery(queryParam); } /** * Export sequence creation statement * * @param request * @return */ @GetMapping("/export") public DataResult export(@Valid DdlExportRequest request) { ShowCreateSequenceParam param = rdbWebConverter.ddlExport2showSequenceCreate(request); return sequenceService.showCreateSequence(param); } /** * Get a SQL statement that modifies or creates a new sequence * * @param request * @return */ @PostMapping("/modify/sql") public ListResult modifySql(@Valid @RequestBody SequenceModifySqlRequest request) { Sequence sequence = rdbWebConverter.sequenceRequest2param(request.getNewSequence()); return sequenceService.buildSql(rdbWebConverter.sequenceRequest2param(request.getOldSequence()), sequence) .map(rdbWebConverter::dto2vo); } /** * Delete sequence * * @param request * @return */ @PostMapping("/delete") public ActionResult delete(@Valid @RequestBody SequenceDeleteRequest request){ DropParam dropParam = rdbWebConverter.sequenceDelete2dropParam(request); return sequenceService.drop(dropParam); } /** * Get information such as table columns and indexes * * @param request * @return */ @GetMapping("/query") public DataResult query(@Valid SequenceDetailQueryRequest request) { SequenceQueryParam queryParam = rdbWebConverter.sequenceRequest2param(request); DataResult sequenceDTODataResult = sequenceService.query(queryParam); SequenceVO sequenceVO = rdbWebConverter.sequenceDto2vo(sequenceDTODataResult.getData()); return DataResult.of(sequenceVO); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.EmbeddingController; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import com.google.common.collect.Lists; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j @ConnectionInfoAspect @RequestMapping("/api/rdb/table") @RestController public class TableController extends EmbeddingController { @Autowired private TableService tableService; @Autowired private DlTemplateService dlTemplateService; @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DatabaseService databaseService; public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); /** * Query the table list under the current DB * * @param request * @return */ @GetMapping("/list") public WebPageResult list(@Valid TableBriefQueryRequest request) { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); tableSelector.setIndexList(false); PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); // ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); // singleThreadExecutor.submit(() -> { // try { // Chat2DBContext.putContext(connectInfo); // syncTableVector(request); //// syncTableEs(request); // } catch (Exception e) { // log.error("sync table vector error", e); // } finally { // Chat2DBContext.removeContext(); // } // log.info("sync table vector finish"); // }); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } /** * Query the table list under the current DB * * @param request * @return */ @GetMapping("/table_list") public ListResult tableList(@Valid TableBriefQueryRequest request) { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); return tableService.queryTables(queryParam); } /** * Query the table columns under the current DB * * @param request * @return */ @GetMapping("/column_list") public ListResult columnList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableColumns = tableService.queryColumns(queryParam); List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); return ListResult.of(tableVOS); } @GetMapping("/copy_dml_sql") public DataResult copyDmlSql(@Valid DmlSqlCopyRequest request) { DmlSqlCopyParam queryParam = rdbWebConverter.dmlRequest2param(request); return tableService.copyDmlSql(queryParam); } /** * Query the table index under the current DB * * @param request * @return */ @GetMapping("/index_list") public ListResult indexList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableIndices = tableService.queryIndexes(queryParam); List indexVOS = rdbWebConverter.indexDto2vo(tableIndices); return ListResult.of(indexVOS); } /** * Query the table key under the current DB * * @param request * @return */ @GetMapping("/key_list") public ListResult keyList(@Valid TableDetailQueryRequest request) { // TODO Add query key implementation return ListResult.of(Lists.newArrayList()); } /** * Export table creation statement * * @param request * @return */ @GetMapping("/export") public DataResult export(@Valid DdlExportRequest request) { ShowCreateTableParam param = rdbWebConverter.ddlExport2showTableCreate(request); return tableService.showCreateTable(param); } /** * Table creation statement example * * @param request * @return */ @GetMapping("/create/example") public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { return tableService.createTableExample(request.getDbType()); } /** * Update table statement example * * @param request * @return */ @GetMapping("/update/example") public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { return tableService.alterTableExample(request.getDbType()); } /** * Get information such as table columns and indexes * * @param request * @return */ @GetMapping("/query") public DataResult
query(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); return tableService.query(queryParam, tableSelector); //TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); //return DataResult.of(tableVO); } /** * Get the sql statement that modifies the table * * @param request * @return */ @PostMapping("/modify/sql") public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { Table table = rdbWebConverter.tableRequest2param(request.getNewTable()); table.setSchemaName(request.getSchemaName()); table.setDatabaseName(request.getDatabaseName()); for (TableColumn tableColumn : table.getColumnList()) { tableColumn.setSchemaName(request.getSchemaName()); tableColumn.setTableName(table.getName()); tableColumn.setDatabaseName(request.getDatabaseName()); } for (TableIndex tableIndex : table.getIndexList()) { tableIndex.setSchemaName(request.getSchemaName()); tableIndex.setTableName(table.getName()); tableIndex.setDatabaseName(request.getDatabaseName()); } return tableService.buildSql(rdbWebConverter.tableRequest2param(request.getOldTable()),table) .map(rdbWebConverter::dto2vo); } /** * Data types supported by the database * * @param request * @return */ @GetMapping("/type_list") public ListResult types(@Valid TypeQueryRequest request) { TypeQueryParam typeQueryParam = TypeQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); List types = tableService.queryTypes(typeQueryParam); return ListResult.of(types); } @GetMapping("/table_meta") public DataResult tableMeta(@Valid TypeQueryRequest request) { TypeQueryParam typeQueryParam = TypeQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); TableMeta tableMeta = tableService.queryTableMeta(typeQueryParam); return DataResult.of(tableMeta); } /** * Delete table * * @param request * @return */ @PostMapping("/delete") public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); return tableService.drop(dropParam); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.TriggerService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.TriggerDetailRequest; import ai.chat2db.server.web.api.controller.rdb.request.TriggerPageRequest; import ai.chat2db.spi.model.Trigger; import jakarta.validation.Valid; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @ConnectionInfoAspect @RequestMapping("/api/rdb/trigger") @RestController public class TriggerController { @Autowired private TriggerService triggerService; @GetMapping("/list") public WebPageResult list(@Valid TriggerPageRequest request) { ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); Long total = CollectionUtils.isNotEmpty(listResult.getData()) ? Long.valueOf(listResult.getData().size()) : 0L; Integer pageSize = listResult.getData() != null ? listResult.getData().size() : 0; return WebPageResult.of(listResult.getData(), total, 1, pageSize); } @GetMapping("/detail") public DataResult detail(@Valid TriggerDetailRequest request) { return triggerService.detail(request.getDatabaseName(), request.getSchemaName(), request.getTriggerName()); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java ================================================ package ai.chat2db.server.web.api.controller.rdb; import java.util.List; import ai.chat2db.server.domain.api.param.DropParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.api.service.ViewService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @ConnectionInfoAspect @RequestMapping("/api/rdb/view") @RestController public class ViewController { @Autowired private ViewService viewService; @Autowired private TableService tableService; @Autowired private RdbWebConverter rdbWebConverter; @GetMapping("/list") public WebPageResult list(@Valid TableBriefQueryRequest request) { ListResult
tableDTOPageResult = viewService.views(request.getDatabaseName(), request.getSchemaName()); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); Integer pageSize = tableDTOPageResult.getData() != null ? tableDTOPageResult.getData().size() : 0; return WebPageResult.of(tableVOS, Long.valueOf(tableVOS.size()), 1, pageSize); } @GetMapping("/column_list") public ListResult columnList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableColumns = tableService.queryColumns(queryParam); List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); return ListResult.of(tableVOS); } @GetMapping("/detail") public DataResult detail(@Valid TableDetailQueryRequest request) { DataResult
dataResult = viewService.detail(request.getDatabaseName(),request.getSchemaName(),request.getTableName()); TableVO tableVO = rdbWebConverter.tableDto2vo(dataResult.getData()); return DataResult.of(tableVO); } @PostMapping("/delete") public ActionResult delete(@Valid TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); return tableService.drop(dropParam); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DatabaseConverter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.converter; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportParam; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseCreateRequest; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseExportDataRequest; import ai.chat2db.server.web.api.controller.rdb.request.DatabaseExportRequest; import ai.chat2db.spi.model.Database; import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public abstract class DatabaseConverter { public abstract Database request2param(DatabaseCreateRequest request); public abstract DatabaseExportParam request2param(DatabaseExportRequest request); public abstract DatabaseExportDataParam request2param(DatabaseExportDataRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/FunctionConverter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.converter; import ai.chat2db.server.web.api.controller.rdb.request.FunctionUpdateRequest; import ai.chat2db.spi.model.Function; import org.mapstruct.Mapper; /** * @author Juechen * @version : FunctionConverter.java */ @Mapper(componentModel = "spring") public abstract class FunctionConverter { public abstract Function request2param(FunctionUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/ProcedureConverter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.converter; import ai.chat2db.server.web.api.controller.rdb.request.ProcedureUpdateRequest; import ai.chat2db.spi.model.Procedure; import org.mapstruct.Mapper; /** * @author: zgq * @date: February 24, 2024 13:39 */ @Mapper(componentModel = "spring") public abstract class ProcedureConverter { public abstract Procedure request2param(ProcedureUpdateRequest request); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.converter; import java.util.List; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.*; import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.spi.model.*; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; /** * @author moji * @version MysqlDataConverter.java, v 0.1 October 14, 2022 14:04 moji Exp $ * @date 2022/10/14 */ @Mapper(componentModel = "spring") public abstract class RdbWebConverter { /** * Parameter conversion * * @param request * @return */ public abstract DlExecuteParam request2param(DmlRequest request); public abstract GroupByParam request2param(GroupByRequest request); /** * Parameter conversion * * @param request * @return */ public abstract OrderByParam request2param(OrderByRequest request); /** * Parameter conversion * * @param request * @return */ public abstract DlExecuteParam request2param(DmlTableRequest request); /** * Parameter conversion * * @param request * @return */ public abstract DlExecuteParam tableManageRequest2param(DdlRequest request); /** * Parameter conversion * * @param request * @return */ public abstract DlCountParam request2param(DdlCountRequest request); /** * Parameter conversion * * @param request * @return */ public abstract TableQueryParam tableRequest2param(TableDetailQueryRequest request); /** * Parameter conversion * * @param request * @return */ public abstract SequenceQueryParam sequenceRequest2param(SequenceDetailQueryRequest request); /** * Parameter conversion * * @param request * @return */ public abstract Table tableRequest2param(TableRequest request); /** * Parameter conversion * * @param request * @return */ public abstract Sequence sequenceRequest2param(SequenceRequest request); /** * Parameter conversion * * @param dto * @return */ public abstract SqlVO dto2vo(Sql dto); /** * Parameter conversion * * @param request * @return */ public abstract TablePageQueryParam tablePageRequest2param(TableBriefQueryRequest request); /** * Parameter conversion * * @param request * @return */ public abstract TablePageQueryParam tablePageRequest2param(DataExportRequest request); /** * Parameter conversion * * @param request * @return */ public abstract SequencePageQueryParam sequencePageRequest2param(SequenceBriefQueryRequest request); /** * Parameter conversion * * @param request * @return */ public abstract TableQueryParam tableRequest2param(DataExportRequest request); /** * Parameter conversion * * @param request * @return */ @Mapping(source = "name", target = "tableName") public abstract ShowCreateTableParam ddlExport2showTableCreate(DdlExportRequest request); /** * Parameter conversion * * @param request * @return */ @Mapping(source = "name", target = "sequenceName") public abstract ShowCreateSequenceParam ddlExport2showSequenceCreate(DdlExportRequest request); /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "tableName", target = "name"), @Mapping(source = "schemaName", target = "schema") }) public abstract DropParam tableDelete2dropParam(TableDeleteRequest request); /** * Parameter conversion * * @param request * @return */ @Mappings({ @Mapping(source = "sequenceName", target = "name"), @Mapping(source = "schemaName", target = "schema") }) public abstract DropParam sequenceDelete2dropParam(SequenceDeleteRequest request); /** * Model conversion * * @param dto * @return */ public abstract ExecuteResultVO dto2vo(ExecuteResult dto); /** * Model conversion * * @param dtos * @return */ public abstract List dto2vo(List dtos); /** * Model conversion * * @param dto * @return */ public abstract ColumnVO columnDto2vo(TableColumn dto); /** * Model conversion * * @param dtos * @return */ public abstract List columnDto2vo(List dtos); /** * Model conversion * * @param dto * @return */ @Mappings({ @Mapping(source = "columnList", target = "columnList") }) public abstract IndexVO indexDto2vo(TableIndex dto); /** * Model conversion * * @param dtos * @return */ public abstract List indexDto2vo(List dtos); /** * Model conversion * * @param dto * @return */ @Mappings({ @Mapping(source = "columnList", target = "columnList"), @Mapping(source = "indexList", target = "indexList"), }) public abstract TableVO tableDto2vo(Table dto); /** * Model conversion * * @param dto * @return */ public abstract SequenceVO sequenceDto2vo(Sequence dto); /** * Model conversion * * @param dtos * @return */ public abstract List tableDto2vo(List
dtos); /** * Model conversion * @param tableColumns * @return */ public abstract List schemaDto2vo(List tableColumns); /** * Model conversion * @param dto * @return */ public abstract SchemaVO schemaDto2vo(Schema dto); /** * Model conversion * @param dto * @return */ public abstract DatabaseVO databaseDto2vo(Database dto); /** * Model conversion * @param dto * @return */ public abstract List databaseDto2vo(List dto); public abstract MetaSchemaVO metaSchemaDto2vo(MetaSchema data); public abstract UpdateSelectResultParam request2param(SelectResultUpdateRequest request); public abstract TableMilvusQueryRequest request2request(TableBriefQueryRequest request); @Mappings({ @Mapping(source = "databaseName", target = "database"), @Mapping(source = "schemaName", target = "schema"), }) public abstract TableVectorParam param2param(TableBriefQueryRequest request); public abstract EsTableSchemaRequest req2req(TableBriefQueryRequest request); public abstract TablePageQueryParam schemaReq2page(EsTableSchemaRequest request); public abstract DmlSqlCopyParam dmlRequest2param(DmlSqlCopyRequest request) ; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/BaseDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.web.api.util.StringUtils; import ai.chat2db.spi.sql.Chat2DBContext; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import java.io.*; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @author: zgq * @date: 2024年06月04日 10:51 */ @Slf4j public abstract class BaseDataExporter implements DataExportStrategy { protected String contentType; protected String suffix; public static int BATCH_SIZE = 1000; @Override public void doExport(DatabaseExportDataParam databaseExportDataParam, File file) throws IOException, SQLException { List tableNames = databaseExportDataParam.getTableNames(); if (CollectionUtils.isEmpty(tableNames)) { throw new IllegalArgumentException("tableNames should not be null or empty"); } try (Connection connection = Chat2DBContext.getConnection()) { if (tableNames.size() == 1) { String tableName = tableNames.get(0); if (StringUtils.isEmpty(tableName)) { throw new IllegalArgumentException("tableName should not be null or empty"); } singleExport(connection, databaseExportDataParam,file); } else { multiExport(databaseExportDataParam, connection, file); } } } private void multiExport(DatabaseExportDataParam databaseExportDataParam, Connection connection, File file) throws IOException { try (OutputStream outputStream = new FileOutputStream(file); ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { List tableNames = databaseExportDataParam.getTableNames(); for (String tableName : tableNames) { String fileName = tableName + suffix; zipOutputStream.putNextEntry(new ZipEntry(fileName)); try (ByteArrayOutputStream byteArrayOutputStream = multiExport(connection, databaseExportDataParam, tableName)) { byteArrayOutputStream.writeTo(zipOutputStream); zipOutputStream.closeEntry(); } } } } protected abstract void singleExport(Connection connectionInfo, DatabaseExportDataParam databaseExportDataParam, File file) throws IOException, SQLException; protected abstract ByteArrayOutputStream multiExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, String tableName); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/BaseDataImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; /** * @author: zgq * @date: 2024年06月04日 10:52 */ public abstract class BaseDataImporter implements DataImportStrategy{ } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/BaseExcelExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; import ai.chat2db.server.domain.api.enums.TaskStatusEnum; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskManager; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.ResultSetUtils; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; import lombok.extern.slf4j.Slf4j; import java.io.*; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * @author: zgq * @date: 2024年06月04日 10:56 */ @Slf4j public abstract class BaseExcelExporter extends BaseDataExporter { @Override protected void singleExport(Connection connection, DatabaseExportDataParam exportParam, File outputFile) { ExcelTypeEnum excelType = getExcelType(); try (OutputStream outputStream = new FileOutputStream(outputFile)) { String tableName = exportParam.getTableNames().get(0); String querySql = getQuerySql(exportParam, tableName); log.info("开始导出:{}表数据,导出类型:{}", tableName, excelType); SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> writeExcelData(resultSet, excelType, outputStream, tableName, exportParam.getContainsHeader())); } catch (IOException e) { TaskManager.updateStatus(TaskStatusEnum.ERROR); throw new RuntimeException(e); } } @Override protected ByteArrayOutputStream multiExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, String tableName) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ExcelTypeEnum excelType = getExcelType(); log.info("开始导出:{}表数据,导出类型:{}", tableName, excelType); String querySql = getQuerySql(databaseExportDataParam, tableName); SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> { writeExcelData(resultSet, excelType, byteArrayOutputStream, tableName, databaseExportDataParam.getContainsHeader()); }); return byteArrayOutputStream; } private void writeExcelData(ResultSet resultSet, ExcelTypeEnum excelType, OutputStream outputStream, String sheetName, Boolean containsHeader) { try { ExcelWriterSheetBuilder excelWriterSheetBuilder = EasyExcel.write(outputStream).excelType(excelType).sheet(sheetName); ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); List> dataList = new ArrayList<>(); if (containsHeader) { List header = ResultSetUtils.getRsHeader(resultSet); excelWriterSheetBuilder.head(header.stream().map(Collections::singletonList).collect(Collectors.toList())); } while (resultSet.next()) { List rowDataList = new ArrayList<>(); for (int i = 1; i <= columnCount; i++) { JDBCDataValue jdbcDataValue = new JDBCDataValue(resultSet, metaData, i, false); rowDataList.add(valueProcessor.getJdbcValue(jdbcDataValue)); } dataList.add(rowDataList); } excelWriterSheetBuilder.doWrite(dataList); TaskManager.increaseCurrent(); } catch (SQLException e) { TaskManager.updateStatus(TaskStatusEnum.ERROR); log.error("Error writing Excel data", e); throw new RuntimeException(e); } } private String getQuerySql(DatabaseExportDataParam databaseExportDataParam, String tableName) { String databaseName = databaseExportDataParam.getDatabaseName(); String schemaName = databaseExportDataParam.getSchemaName(); return Chat2DBContext.getSqlBuilder().buildTableQuerySql(databaseName, schemaName, tableName); } protected abstract ExcelTypeEnum getExcelType(); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/BaseExcelImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; /** * 功能描述 * * @author: zgq * @date: 2024年06月04日 10:57 */ public abstract class BaseExcelImporter extends BaseDataImporter{ } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/DataExportStrategy.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import java.io.File; import java.io.IOException; import java.sql.SQLException; public interface DataExportStrategy { void doExport(DatabaseExportDataParam databaseExportDataParam, File file) throws IOException, SQLException; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/DataImportStrategy.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data; public interface DataImportStrategy { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/csv/CsvDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.csv; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelExporter; import com.alibaba.excel.support.ExcelTypeEnum; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:05 */ @Component("csvExporter") public class CsvDataExporter extends BaseExcelExporter { public CsvDataExporter() { this.contentType = "text/csv"; this.suffix = ExportFileSuffix.CSV.getSuffix(); } @Override protected ExcelTypeEnum getExcelType() { return ExcelTypeEnum.CSV; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/csv/CsvDataImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.csv; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelImporter; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:04 */ @Component("csvImporter") public class CsvDataImporter extends BaseExcelImporter { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/factory/DataExportFactory.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.factory; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.rdb.data.DataExportStrategy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Map; import java.util.Objects; /** * @author: zgq * @date: 2024年06月04日 10:26 */ @Component public class DataExportFactory { public static final String BEAN_SUFFIX = "Exporter"; private final Map exports; @Autowired public DataExportFactory(Map exports) { this.exports = exports; } public DataExportStrategy getExporter(String type) { DataExportStrategy dataExportStrategy = exports.get(type.toLowerCase() + BEAN_SUFFIX); if (Objects.isNull(dataExportStrategy)) { throw new ParamBusinessException(type); } return dataExportStrategy; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/factory/DataImportFactory.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.factory; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.rdb.data.DataImportStrategy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Map; import java.util.Objects; /** * @author: zgq * @date: 2024年06月04日 10:07 */ @Component public class DataImportFactory { private static final String BEAN_SUFFIX = "Importer"; private final Map imports; @Autowired public DataImportFactory(Map imports) { this.imports = imports; } public DataImportStrategy getImporter(String type) { DataImportStrategy dataImportStrategy = imports.get(type.toLowerCase() + BEAN_SUFFIX); if (Objects.isNull(dataImportStrategy)) { throw new ParamBusinessException(type); } return dataImportStrategy; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/json/JsonDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.json; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.web.api.controller.rdb.data.BaseDataExporter; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskManager; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.*; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @author: zgq * @date: 2024年06月04日 10:33 */ @Component("jsonExporter") @Slf4j public class JsonDataExporter extends BaseDataExporter { public JsonDataExporter() { this.suffix = ExportFileSuffix.JSON.getSuffix(); this.contentType = "application/json"; } @Override protected void singleExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, File file) { String tableName = databaseExportDataParam.getTableNames().get(0); String querySql = getQuerySql(databaseExportDataParam, tableName); log.info("开始导出:{}表数据,导出类型:json", tableName); try (PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8);) { writeJsonData(connection, querySql, writer); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected ByteArrayOutputStream multiExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, String tableName) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); log.info("开始导出:{}表数据,导出类型:json", tableName); try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8))) { String querySql = getQuerySql(databaseExportDataParam, tableName); writeJsonData(connection, querySql, writer); } return byteArrayOutputStream; } private void writeJsonData(Connection connection, String querySql, PrintWriter writer) { SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> { List> dataBatch = new ArrayList<>(); ResultSetMetaData metaData = resultSet.getMetaData(); ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); writer.println("["); boolean firstBatch = true; while (resultSet.next()) { Map row = new LinkedHashMap<>(); for (int i = 1; i <= metaData.getColumnCount(); i++) { row.put(metaData.getColumnName(i), valueProcessor.getJdbcValue(new JDBCDataValue(resultSet, metaData, i, false))); } dataBatch.add(row); if (dataBatch.size() >= BATCH_SIZE || resultSet.isLast()) { if (!firstBatch) { writer.println(","); } writeBatch(writer, objectMapper, dataBatch); firstBatch = false; } } writer.println("]"); }); TaskManager.increaseCurrent(); } private void writeBatch(PrintWriter writer, ObjectMapper objectMapper, List> dataBatch) { try { String jsonBatch = objectMapper.writeValueAsString(dataBatch); writer.println(jsonBatch.substring(1, jsonBatch.length() - 1)); writer.flush(); dataBatch.clear(); } catch (JsonProcessingException e) { throw new BusinessException("data.export.json.error", null, e); } } private String getQuerySql(DatabaseExportDataParam databaseExportDataParam, String tableName) { String databaseName = databaseExportDataParam.getDatabaseName(); String schemaName = databaseExportDataParam.getSchemaName(); return Chat2DBContext.getSqlBuilder().buildTableQuerySql(databaseName, schemaName, tableName); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/json/JsonDataImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.json; import ai.chat2db.server.web.api.controller.rdb.data.BaseDataImporter; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:33 */ @Component("jsonImporter") public class JsonDataImporter extends BaseDataImporter { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/service/DatabaseDataService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.service; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.tools.base.wrapper.result.DataResult; /** * @author: zgq * @date: 2024年06月08日 10:32 */ public interface DatabaseDataService { DataResult doExportAsync(DatabaseExportDataParam databaseExportDataParam); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/service/impl/DatabaseDataImpl.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.service.impl; import ai.chat2db.server.domain.api.enums.TaskStatusEnum; import ai.chat2db.server.domain.api.enums.TaskTypeEnum; import ai.chat2db.server.domain.api.param.TaskCreateParam; import ai.chat2db.server.domain.api.param.TaskUpdateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.rdb.data.factory.DataExportFactory; import ai.chat2db.server.web.api.controller.rdb.data.factory.DataImportFactory; import ai.chat2db.server.web.api.controller.rdb.data.service.DatabaseDataService; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskManager; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskState; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import cn.hutool.core.date.DatePattern; import cn.hutool.core.io.FileUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.CompletableFuture; /** * @author: zgq * @date: 2024年06月08日 10:32 */ @Service @Slf4j public class DatabaseDataImpl implements DatabaseDataService { public static final String EXPORT_DATA_TASK_TEMPLATE = "export_%s_data"; public static final String IMPORT_DATA_TASK_TEMPLATE = "import_%s_data"; @Autowired private DataExportFactory dataExportFactory; @Autowired private DataImportFactory dataImportFactory; @Autowired private TaskService taskService; @Override public DataResult doExportAsync(DatabaseExportDataParam databaseExportDataParam) { List tableNames = databaseExportDataParam.getTableNames(); String databaseName = databaseExportDataParam.getDatabaseName(); String schemaName = databaseExportDataParam.getSchemaName(); Long dataSourceId = databaseExportDataParam.getDataSourceId(); String taskName = buildTaskName(tableNames, databaseName, schemaName); String fileName = URLEncoder.encode( taskName + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), StandardCharsets.UTF_8); String suffix = "."; int size = tableNames.size(); if (size > 1) { suffix += "zip"; } else { suffix += databaseExportDataParam.getExportType().toLowerCase(); } File file = FileUtil.createTempFile(fileName, suffix, true); file.deleteOnExit(); LoginUser loginUser = ContextUtils.getLoginUser(); ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); DataResult dataResult = createTask(tableNames.get(0), databaseName, schemaName, dataSourceId, taskName); Long taskId = dataResult.getData(); CompletableFuture.runAsync(() -> { buildContext(loginUser, connectInfo); TaskManager.addTask(taskId, TaskState.builder().state(TaskStatusEnum.PROCESSING.name()).total(size) .current(0).build()); try { dataExportFactory.getExporter(databaseExportDataParam.getExportType()).doExport(databaseExportDataParam, file); } catch (IOException | SQLException e) { throw new RuntimeException(e); } }).whenComplete((v, ex) -> { updateStatus(taskId, file, ex); removeContext(); TaskManager.removeTaskId(); }); return dataResult; } private void updateStatus(Long id, File file, Throwable throwable) { TaskUpdateParam updateParam = new TaskUpdateParam(); updateParam.setId(id); updateParam.setTaskProgress("1"); updateParam.setDownloadUrl(file.getAbsolutePath()); if (throwable != null) { log.error("export error", throwable); updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); } else { updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); } taskService.updateStatus(updateParam); } private void removeContext() { Dbutils.removeSession(); ContextUtils.removeContext(); Chat2DBContext.removeContext(); } private DataResult createTask(String tableName, String databaseName, String schemaName, Long datasourceId, String taskName) { TaskCreateParam param = new TaskCreateParam(); param.setTaskName(taskName); param.setTaskType(TaskTypeEnum.DOWNLOAD_TABLE_DATA.name()); param.setDatabaseName(databaseName); param.setSchemaName(schemaName); param.setTableName(tableName); param.setDataSourceId(datasourceId); param.setUserId(ContextUtils.getUserId()); param.setTaskProgress("0.1"); return taskService.create(param); } private void buildContext(LoginUser loginUser, ConnectInfo connectInfo) { ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); Dbutils.setSession(); Chat2DBContext.putContext(connectInfo); } private String buildTaskName(List tableNames, String databaseName, String schemaName) { StringBuilder taskNameBuilder = new StringBuilder(); if (StringUtils.isNotBlank(databaseName)) { taskNameBuilder.append(databaseName).append("_"); } if (StringUtils.isNotBlank(schemaName)) { taskNameBuilder.append(schemaName).append("_"); } if (tableNames.size() == 1) { taskNameBuilder.append(StringUtils.join(tableNames, "_")); } return String.format(EXPORT_DATA_TASK_TEMPLATE, taskNameBuilder); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/sql/SqlDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.sql; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.param.datasource.DatabaseExportDataParam; import ai.chat2db.server.web.api.controller.rdb.data.BaseDataExporter; import ai.chat2db.server.web.api.controller.rdb.data.task.TaskManager; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.ResultSetUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import java.io.*; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author: zgq * @date: 2024年06月04日 10:33 */ @Component("sqlExporter") @Slf4j public class SqlDataExporter extends BaseDataExporter { public SqlDataExporter() { this.suffix = ExportFileSuffix.SQL.getSuffix(); this.contentType = "text/sql"; } /** * @param connection * @param databaseExportDataParam * @param file */ @Override protected void singleExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, File file) { String tableName = databaseExportDataParam.getTableNames().get(0); log.info("开始导出:{}表数据,导出类型:sql", tableName); try (PrintWriter writer = new PrintWriter(file);) { exportSql(connection, databaseExportDataParam, tableName, writer); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } @Override protected ByteArrayOutputStream multiExport(Connection connection, DatabaseExportDataParam databaseExportDataParam, String tableName) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); log.info("开始导出:{}表数据,导出类型:sql", tableName); try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8))) { exportSql(connection, databaseExportDataParam, tableName, writer); } return byteArrayOutputStream; } private void exportSql(Connection connection, DatabaseExportDataParam databaseExportDataParam, String tableName, PrintWriter writer) { String databaseName = databaseExportDataParam.getDatabaseName(); String schemaName = databaseExportDataParam.getSchemaName(); Boolean containsHeader = databaseExportDataParam.getContainsHeader(); MetaData metaData = Chat2DBContext.getMetaData(); String querySql = metaData.getSqlBuilder().buildTableQuerySql(databaseName, schemaName, tableName); SqlBuilder sqlBuilder = metaData.getSqlBuilder(); ValueProcessor valueProcessor = metaData.getValueProcessor(); String sqyType = databaseExportDataParam.getSqyType(); switch (sqyType) { case "single" -> exportSingleInsert(connection, querySql, containsHeader, sqlBuilder, valueProcessor, databaseName, schemaName, tableName, writer); case "multi" -> exportMultiInsert(connection, querySql, containsHeader, sqlBuilder, valueProcessor, databaseName, schemaName, tableName, writer); case "update" -> exportUpdate(connection, querySql, sqlBuilder, valueProcessor, databaseName, schemaName, tableName, writer); default -> throw new IllegalArgumentException("Unsupported sqyType: " + sqyType); } } private void exportSingleInsert(Connection connection, String querySql, Boolean containsHeader, SqlBuilder sqlBuilder, ValueProcessor valueProcessor, String databaseName, String schemaName, String tableName, PrintWriter writer) { List sqlList = new ArrayList<>(BATCH_SIZE); SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> { List header = containsHeader ? ResultSetUtils.getRsHeader(resultSet) : null; while (resultSet.next()) { List rowData = extractRowData(resultSet, valueProcessor); String sql = sqlBuilder.buildSingleInsertSql(databaseName, schemaName, tableName, header, rowData); sqlList.add(sql+";"); if (sqlList.size() >= BATCH_SIZE) { writeSqlList(writer, sqlList); } } if(CollectionUtils.isNotEmpty(sqlList)){ writeSqlList(writer, sqlList); } }); TaskManager.increaseCurrent(); } private void exportMultiInsert(Connection connection, String querySql, Boolean containsHeader, SqlBuilder sqlBuilder, ValueProcessor valueProcessor, String databaseName, String schemaName, String tableName, PrintWriter writer) { SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> { List> dataList = new ArrayList<>(BATCH_SIZE); List header = containsHeader ? ResultSetUtils.getRsHeader(resultSet) : null; while (resultSet.next()) { dataList.add(extractRowData(resultSet, valueProcessor)); } String sql = sqlBuilder.buildMultiInsertSql(databaseName, schemaName, tableName, header, dataList); writer.println(sql+";"); writer.flush(); }); TaskManager.increaseCurrent(); } private void exportUpdate(Connection connection, String querySql, SqlBuilder sqlBuilder, ValueProcessor valueProcessor, String databaseName, String schemaName, String tableName, PrintWriter writer) { List sqlList = new ArrayList<>(BATCH_SIZE); SQLExecutor.getInstance().execute(connection, querySql, BATCH_SIZE, resultSet -> { Map primaryKeyMap = getPrimaryKeyMap(connection, databaseName, schemaName, tableName); while (resultSet.next()) { Map row = extractRowDataAsMap(resultSet, valueProcessor, primaryKeyMap); String sql = sqlBuilder.buildUpdateSql(databaseName, schemaName, tableName, row, primaryKeyMap); sqlList.add(sql); if (sqlList.size() >= BATCH_SIZE || resultSet.isLast()) { writeSqlList(writer, sqlList); } } }); TaskManager.increaseCurrent(); } private List extractRowData(ResultSet resultSet, ValueProcessor valueProcessor) throws SQLException { ResultSetMetaData metaData = resultSet.getMetaData(); List rowData = new ArrayList<>(metaData.getColumnCount()); for (int i = 1; i <= metaData.getColumnCount(); i++) { JDBCDataValue jdbcDataValue = new JDBCDataValue(resultSet, metaData, i, false); rowData.add(valueProcessor.getJdbcSqlValueString(jdbcDataValue)); } return rowData; } private Map extractRowDataAsMap(ResultSet resultSet, ValueProcessor valueProcessor, Map primaryKeyMap) throws SQLException { ResultSetMetaData metaData = resultSet.getMetaData(); Map row = new HashMap<>(metaData.getColumnCount()); for (int i = 1; i <= metaData.getColumnCount(); i++) { JDBCDataValue jdbcDataValue = new JDBCDataValue(resultSet, metaData, i, false); String columnName = metaData.getColumnName(i); String jdbcValueString = valueProcessor.getJdbcSqlValueString(jdbcDataValue); if (primaryKeyMap.containsKey(columnName)) { primaryKeyMap.put(columnName, jdbcValueString); } else { row.put(columnName, jdbcValueString); } } return row; } private Map getPrimaryKeyMap(Connection connection, String databaseName, String schemaName, String tableName) throws SQLException { Map primaryKeyMap = new HashMap<>(); try (ResultSet primaryKeys = connection.getMetaData().getPrimaryKeys(databaseName, schemaName, tableName)) { while (primaryKeys.next()) { primaryKeyMap.put(primaryKeys.getString("COLUMN_NAME"), ""); } } return primaryKeyMap; } private void writeSqlList(PrintWriter writer, List sqlList) { sqlList.forEach(writer::println); sqlList.clear(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/task/TaskManager.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.task; import ai.chat2db.server.domain.api.enums.TaskStatusEnum; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public class TaskManager { public static final ThreadLocal TASK_ID = new ThreadLocal<>(); public static final Map taskMap = new ConcurrentHashMap<>(); public static void increaseCurrent(int current) { TaskState task = getTask(); task.setCurrent(task.getCurrent() + current); if (task.getCurrent() >= task.getTotal()) { task.setState(TaskStatusEnum.FINISH.name()); } } public static void increaseCurrent() { TaskState task = getTask(); task.setCurrent(task.getCurrent() +1); if (task.getCurrent() >= task.getTotal()) { task.setState(TaskStatusEnum.FINISH.name()); } } public static void updateStatus(TaskStatusEnum status) { TaskState task = getTask(); task.setState(status.name()); } public static void addTask(Long taskId, TaskState taskState) { setTaskId(taskId); taskMap.put(taskId, taskState); } public static TaskState getTask(Long taskId) { TaskState taskState = taskMap.get(taskId); if (Objects.isNull(taskState)) { throw new IllegalArgumentException("taskId is not valid"); } return taskState; } public static TaskState getTask() { return getTask(getTaskId()); } public static void removeTask(Long taskId) { taskMap.remove(taskId); } public static void setTaskId(Long taskId) { TASK_ID.set(taskId); } public static Long getTaskId() { return TASK_ID.get(); } public static void removeTaskId() { TASK_ID.remove(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/task/TaskState.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.task; import lombok.Builder; import lombok.Data; /** * @author: zgq * @date: 2024年06月10日 15:51 */ @Data @Builder public class TaskState { private String taskId; private String state; private int total; private int current; public String getExportStatus() { StringBuilder statusBuilder = new StringBuilder(); statusBuilder.append("导出状态: ").append(state) .append(" 导出进度: ") .append(current).append("/") .append(total); return statusBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/xls/XlsDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.xls; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelExporter; import com.alibaba.excel.support.ExcelTypeEnum; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:34 */ @Component("xlsExporter") public class XlsDataExporter extends BaseExcelExporter { public XlsDataExporter() { this.suffix = ExportFileSuffix.XLS.getSuffix(); this.contentType="application/vnd.ms-excel"; } @Override protected ExcelTypeEnum getExcelType() { return ExcelTypeEnum.XLS; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/xls/XlsDataImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.xls; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelImporter; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:34 */ @Component("xlsImporter") public class XlsDataImporter extends BaseExcelImporter { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/xlsx/XlsxDataExporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.xlsx; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelExporter; import com.alibaba.excel.support.ExcelTypeEnum; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:34 */ @Component("xlsxExporter") public class XlsxDataExporter extends BaseExcelExporter { public XlsxDataExporter() { this.suffix = ExportFileSuffix.EXCEL.getSuffix(); this.contentType="application/vnd.ms-excel"; } @Override protected ExcelTypeEnum getExcelType() { return ExcelTypeEnum.XLSX; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/data/xlsx/XlsxDataImporter.java ================================================ package ai.chat2db.server.web.api.controller.rdb.data.xlsx; import ai.chat2db.server.web.api.controller.rdb.data.BaseExcelImporter; import org.springframework.stereotype.Component; /** * @author: zgq * @date: 2024年06月04日 10:34 */ @Component("xlsxImporter") public class XlsxDataImporter extends BaseExcelImporter { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.server.web.api.util.StringUtils; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; import lombok.val; import java.io.OutputStream; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * DatabaseExportService * * @author lzy **/ public class DatabaseExportService { protected ExportTypeEnum exportTypeEnum; @Getter public String suffix; @Getter public String contentType; /** * Export excel collection **/ @Setter @Getter public List exportList; /** * Export word and excel table information collection **/ public static Map> listMap = new LinkedHashMap<>(); /** * Export word index collection **/ public static Map> indexMap = new HashMap<>(0); /** * Joiner **/ public final static String JOINER = "---"; private void init() { CommonConstant.INDEX_HEAD_NAMES = new String[]{I18nUtils.getMessage("main.indexName"), I18nUtils.getMessage("main.indexFieldName"), I18nUtils.getMessage("main.indexType"), I18nUtils.getMessage("main.indexMethod"), I18nUtils.getMessage("main.indexNote")}; CommonConstant.COLUMN_HEAD_NAMES = new String[]{I18nUtils.getMessage("main.fieldNo"), I18nUtils.getMessage("main.fieldName"), I18nUtils.getMessage("main.fieldType"), I18nUtils.getMessage("main.fieldLength"), I18nUtils.getMessage("main.fieldIfEmpty"), I18nUtils.getMessage("main.fieldDefault"), I18nUtils.getMessage("main.fieldDecimalPlaces"), I18nUtils.getMessage("main.fieldNote")}; // index header StringBuilder mdIndex = new StringBuilder(PatternConstant.MD_SPLIT); StringBuilder htmlIndex = new StringBuilder(""); // column header StringBuilder mdColumn = new StringBuilder(PatternConstant.MD_SPLIT); StringBuilder htmlColumn = new StringBuilder(""); PatternConstant.ALL_INDEX_TABLE_HEADER = mdIndex.toString(); PatternConstant.HTML_INDEX_TABLE_HEADER = htmlIndex.toString(); PatternConstant.ALL_TABLE_HEADER = mdColumn.toString(); PatternConstant.HTML_TABLE_HEADER = htmlColumn.toString(); listMap.clear(); indexMap.clear(); } public void generate(String databaseName, OutputStream outputStream, ExportOptions exportOptions) { init(); exportList.forEach(item -> { dataAssemble(databaseName, exportOptions, item); }); try { export(outputStream, exportOptions); } catch (Exception e) { throw new RuntimeException("Export failed! Please contact the developer" + e); } init(); } /** * data processing * * @param exportOptions Configuration information **/ public void dataAssemble(String databaseName, ExportOptions exportOptions, TableVO item) { boolean isExportIndex = Optional.ofNullable(exportOptions.getIsExportIndex()).orElse(false); val t = new TableParameter(); t.setFieldName(item.getName() + "[" + StringUtils.isNull(item.getComment()) + "]"); List colForTable = new LinkedList<>(); for (TableColumn info : item.getColumnList()) { val p = new TableParameter(); p.setFieldName(info.getName()).setColumnDefault(info.getDefaultValue()) .setColumnComment(info.getComment()) .setColumnType(info.getColumnType()) .setLength(String.valueOf(info.getColumnSize())).setIsNullAble(String.valueOf(info.getNullable())) .setDecimalPlaces(String.valueOf(info.getDecimalDigits())); colForTable.add(p); } String key = databaseName + JOINER + t.getFieldName(); listMap.put(key, colForTable); if (isExportIndex) { int index = key.lastIndexOf("["); String str = key.substring(0, index); indexMap.put(str, vo2Info(item.getIndexList())); } //Assignment serial number for (Map.Entry> map : listMap.entrySet()) { //Assignment serial number List list = map.getValue(); IntStream.range(0, list.size()).forEach(x -> { list.get(x).setNo(String.valueOf(x + 1)); }); } } private List vo2Info(List indexList) { return indexList.stream().map(v -> { IndexInfo info = new IndexInfo(); info.setName(v.getName()); List columnList = v.getColumnList(); info.setColumnName(columnList.stream().map(TableIndexColumn::getColumnName).collect(Collectors.joining(","))); info.setIndexType(v.getType()); info.setComment(v.getComment()); return info; }).collect(Collectors.toList()); } /** * Export * * @param outputStream file stream **/ public void export(OutputStream outputStream, ExportOptions exportOptions) { } /** * Handle empty string or null character * * @param source source character * @return java.lang.String **/ public String dealWith(String source) { return StringUtils.isNullOrEmpty(source); } @SneakyThrows public Object[] getIndexValues(IndexInfo indexInfoVO) { Object[] values = new Object[IndexInfo.class.getDeclaredFields().length]; values[0] = dealWith(indexInfoVO.getName()); values[1] = dealWith(indexInfoVO.getColumnName()); values[2] = dealWith(indexInfoVO.getIndexType()); values[3] = dealWith(indexInfoVO.getIndexMethod()); values[4] = dealWith(indexInfoVO.getComment()); return values; } @SneakyThrows public Object[] getColumnValues(TableParameter tableParameter) { Object[] values = new Object[TableParameter.class.getDeclaredFields().length]; values[0] = StringUtils.isNull(tableParameter.getNo()); values[1] = StringUtils.isNull(tableParameter.getFieldName()); values[2] = StringUtils.isNull(tableParameter.getColumnType()); values[3] = StringUtils.isNull(tableParameter.getLength()); values[4] = StringUtils.isNull(tableParameter.getIsNullAble()); values[5] = StringUtils.isNull(tableParameter.getColumnDefault()); values[6] = StringUtils.isNull(tableParameter.getDecimalPlaces()); values[7] = StringUtils.isNull(tableParameter.getColumnComment()); return values; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteHeightConfig.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.adaptive; import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import java.util.Iterator; /** * CustomCellWriteHeightConfig * * @author lzy **/ public class CustomCellWriteHeightConfig extends AbstractRowHeightStyleStrategy { /** * Default height */ private static final Integer DEFAULT_HEIGHT = 300; @Override protected void setHeadColumnHeight(Row row, int relativeRowIndex) { } @Override protected void setContentColumnHeight(Row row, int relativeRowIndex) { Iterator cellIterator = row.cellIterator(); if (!cellIterator.hasNext()) { return; } // Default is 1 row height int maxHeight = 1; while (cellIterator.hasNext()) { Cell cell = cellIterator.next(); if (cell.getCellType() == CellType.STRING) { if (cell.getStringCellValue().contains("\n")) { int length = cell.getStringCellValue().split("\n").length; maxHeight = Math.max(maxHeight, length); } } } row.setHeight((short) (maxHeight * DEFAULT_HEIGHT)); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteWidthConfig.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.adaptive; import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.data.CellData; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy; import org.apache.commons.collections.CollectionUtils; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import java.util.HashMap; import java.util.List; import java.util.Map; /** * CustomCellWriteWidthConfig * * @author lzy **/ public class CustomCellWriteWidthConfig extends AbstractColumnWidthStyleStrategy { private Map> CACHE = new HashMap<>(); @Override protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) { boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList); if (needSetWidth) { Map maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetName(), k -> new HashMap<>(0)); Integer columnWidth = this.dataLength(cellDataList, cell, isHead); if (columnWidth >= 0) { if (columnWidth > 119) { columnWidth = 120; } Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex()); if (maxColumnWidth == null || columnWidth > maxColumnWidth) { maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth); Sheet sheet = writeSheetHolder.getSheet(); sheet.setColumnWidth(cell.getColumnIndex(), Math.min(Double.valueOf(columnWidth * 256 * 1.8).intValue(), 16384)); } } } } /** * Calculate length * @param cellDataList cell data * @param cell cell * @param isHead whether it is the title * @return */ private Integer dataLength(List> cellDataList, Cell cell, Boolean isHead) { if (isHead) { return cell.getStringCellValue().getBytes().length; } else { CellData cellData = cellDataList.get(0); CellDataTypeEnum type = cellData.getType(); if (type == null) { return -1; } else { switch (type) { case STRING: // Newline character (data needs to be parsed in advance) int index = cellData.getStringValue().indexOf("\n"); return index != -1 ? cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1; case BOOLEAN: return cellData.getBooleanValue().toString().getBytes().length; case NUMBER: return cellData.getNumberValue().toString().getBytes().length; default: return -1; } } } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.conf; import lombok.Data; /** * Build options * * @author lzy */ @Data public class ExportOptions { /** * Whether to export multiple sheets */ private Boolean isExportMoreSheet = Boolean.FALSE; /** * Whether to export the index */ private Boolean isExportIndex = Boolean.FALSE; /** * Export file suffix **/ private String fileSuffix; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.constant; /** * CommonConstant * * @author lzy **/ public final class CommonConstant { /** * Table head **/ public static String[] INDEX_HEAD_NAMES = {}; public static String[] COLUMN_HEAD_NAMES = {}; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.constant; /** * PatternConstant * * @author lzy **/ public final class PatternConstant { /** * public */ public static final String MD_SPLIT = "|"; /** * Markdown */ public static final String TITLE = "# %s"; public static final String CATALOG = "## %s"; public static String ALL_TABLE_HEADER = ""; public static String TABLE_BODY = "|%s|%s|%s|%s|%s|%s|%s|%s|"; public static String TABLE_SEPARATOR = "|:----:|----|----|----|----|----|----|----|"; public static String ALL_INDEX_TABLE_HEADER = ""; public static String INDEX_TABLE_BODY = "|%s|%s|%s|%s|"; public static String INDEX_TABLE_SEPARATOR = "|:----:|----|----|----|"; /** * Html */ public static final String HTML_TITLE = "

{0}

"; public static final String HTML_CATALOG = "

{1}

"; public static final String HTML_INDEX_ITEM = "{1}"; public static String HTML_TABLE_HEADER = ""; public static String HTML_TABLE_BODY = "
"; public static String HTML_INDEX_TABLE_HEADER = ""; public static String HTML_INDEX_TABLE_BODY = ""; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/event/TemplateEvent.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.event; import org.springframework.context.ApplicationEvent; /** * TemplateEvent * * @author lzy **/ public class TemplateEvent extends ApplicationEvent { public TemplateEvent(String key) { super(key); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.export; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteHeightConfig; import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteWidthConfig; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.merge.MyMergeExcel; import ai.chat2db.server.web.api.controller.rdb.doc.style.CustomExcelStyle; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import lombok.SneakyThrows; import lombok.val; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * ExportExcelService * * @author lzy **/ public class ExportExcelService extends DatabaseExportService { public ExportExcelService() { exportTypeEnum = ExportTypeEnum.EXCEL; suffix = ExportFileSuffix.EXCEL.getSuffix(); contentType = "text/csv"; } @SneakyThrows @Override public void export(OutputStream outputStream, ExportOptions exportOptions) { List export = new ArrayList<>(); for (Map.Entry> item : listMap.entrySet()) { val t = new TableParameter(); t.setNo(item.getKey()).setColumnComment(MyMergeExcel.NAME); export.add(t); export.addAll(item.getValue()); } EasyExcel.write(outputStream) .registerWriteHandler(new HorizontalCellStyleStrategy(CustomExcelStyle.getHeadStyle(), CustomExcelStyle.getContentWriteCellStyle())) .registerWriteHandler(new CustomCellWriteHeightConfig()) .registerWriteHandler(new CustomCellWriteWidthConfig()) .registerWriteHandler(new MyMergeExcel()) .sheet(I18nUtils.getMessage("main.sheetName")) .doWrite(export); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportHtmlService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.export; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.config.GlobalDict; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; import ai.chat2db.server.web.api.util.StringUtils; import lombok.SneakyThrows; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * ExportHtmlService * * @author lzy **/ public class ExportHtmlService extends DatabaseExportService { public ExportHtmlService() { exportTypeEnum = ExportTypeEnum.HTML; suffix = ExportFileSuffix.HTML.getSuffix(); contentType = "text/html"; } @SneakyThrows @Override public void export(OutputStream outputStream, ExportOptions exportOptions) { Map>>> allMap = listMap.entrySet() .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); StringBuilder htmlText = new StringBuilder(); StringBuilder catalogue = new StringBuilder(); try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (Map.Entry>>> myMap : allMap.entrySet()) { //Database name String database = myMap.getKey(); String title = MessageFormat.format(PatternConstant.HTML_TITLE, I18nUtils.getMessage("main.databaseText") + database); //Database name-directory catalogue.append("
  • ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, I18nUtils.getMessage("main.databaseText") + database, I18nUtils.getMessage("main.databaseText") + database)).append("
      "); htmlText.append(title).append("\n"); for (Map.Entry> parameterMap : myMap.getValue()) { //Table Name String tableName = parameterMap.getKey().split("---")[1]; //Table name-directory catalogue.append("
    1. ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, database + tableName, tableName)); htmlText.append(MessageFormat.format(PatternConstant.HTML_CATALOG, database + tableName, tableName)).append("\n

      "); //IndexTable if (!indexMap.isEmpty()) { htmlText.append("
  • "); for (int i = 0; i < CommonConstant.INDEX_HEAD_NAMES.length; i++) { mdIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); htmlIndex.append(CommonConstant.INDEX_HEAD_NAMES[i]).append(i == CommonConstant.INDEX_HEAD_NAMES.length - 1 ? "" : ""); } mdIndex.append(PatternConstant.MD_SPLIT); htmlIndex.append("
    "); for (int i = 0; i < CommonConstant.COLUMN_HEAD_NAMES.length; i++) { mdColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : PatternConstant.MD_SPLIT); htmlColumn.append(CommonConstant.COLUMN_HEAD_NAMES[i]).append(i == CommonConstant.COLUMN_HEAD_NAMES.length - 1 ? "" : ""); } mdColumn.append(PatternConstant.MD_SPLIT); htmlColumn.append("
    %s%s%s%s%s%s%s%s
    %s%s%s%s
    \n"); htmlText.append(PatternConstant.HTML_INDEX_TABLE_HEADER); String name = parameterMap.getKey().split("\\[")[0]; List indexInfoVOList = indexMap.get(name); for (IndexInfo indexInfo : indexInfoVOList) { htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(indexInfo))); } htmlText.append("
    \n"); htmlText.append("\n

    "); } else { htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(new IndexInfo()))); } //FieldTable htmlText.append("\n"); htmlText.append(PatternConstant.HTML_TABLE_HEADER); List exportList = parameterMap.getValue(); for (TableParameter tableParameter : exportList) { htmlText.append(String.format(PatternConstant.HTML_TABLE_BODY, getColumnValues(tableParameter))); } htmlText.append("
    \n"); } htmlText.append("

    "); catalogue.append(""); } catalogue.append(""); try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(0)); ByteArrayOutputStream result = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { result.write(buffer, 0, length); } String str = result.toString(String.valueOf(StandardCharsets.UTF_8)); str = str.replace("${data}", htmlText).replace("${catalogue}", catalogue); writer.write(str); } } } @Override public String dealWith(String source) { return StringUtils.isNullForHtml(source); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.export; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; import ai.chat2db.server.web.api.util.StringUtils; import lombok.SneakyThrows; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * ExportMarkdownService * * @author lzy **/ public class ExportMarkdownService extends DatabaseExportService { public ExportMarkdownService() { exportTypeEnum = ExportTypeEnum.MARKDOWN; suffix = ExportFileSuffix.MARKDOWN.getSuffix(); contentType = "text/plain"; } @SneakyThrows @Override public void export(OutputStream outputStream, ExportOptions exportOptions) { Map>>> allMap = listMap.entrySet() .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { for (Map.Entry>>> myMap : allMap.entrySet()) { //Database name String database = myMap.getKey(); String title = String.format(PatternConstant.TITLE, I18nUtils.getMessage("main.databaseText") + database); fileWriter.write(title); writeLineSeparator(fileWriter, 2); for (Map.Entry> parameterMap : myMap.getValue()) { //Table Name String tableName = parameterMap.getKey().split("---")[1]; fileWriter.write(String.format(PatternConstant.CATALOG, tableName)); writeLineSeparator(fileWriter, 1); //IndexTable if (!indexMap.isEmpty()) { fileWriter.write(PatternConstant.ALL_INDEX_TABLE_HEADER); writeLineSeparator(fileWriter, 1); fileWriter.write(PatternConstant.INDEX_TABLE_SEPARATOR); writeLineSeparator(fileWriter, 1); String name = parameterMap.getKey().split("\\[")[0]; List indexInfoVOList = indexMap.get(name); for (int j = 0; j < indexInfoVOList.size(); j++) { fileWriter.write(String.format(PatternConstant.INDEX_TABLE_BODY, getIndexValues(indexInfoVOList.get(j)))); writeLineSeparator(fileWriter, 1); } writeLineSeparator(fileWriter, 1); } writeLineSeparator(fileWriter, 2); fileWriter.write(PatternConstant.ALL_TABLE_HEADER); writeLineSeparator(fileWriter, 1); fileWriter.write(PatternConstant.TABLE_SEPARATOR); writeLineSeparator(fileWriter, 1); //FieldTable List exportList = parameterMap.getValue(); for (TableParameter tableParameter : exportList) { fileWriter.write(String.format(PatternConstant.TABLE_BODY, getColumnValues(tableParameter))); writeLineSeparator(fileWriter, 1); } writeLineSeparator(fileWriter, 2); } } } } private void writeLineSeparator(BufferedWriter fileWriter, int number) throws IOException { for (int i = 0; i < number; i++) { fileWriter.write(System.lineSeparator()); } } @Override public String dealWith(String source) { return StringUtils.isNullForHtml(source); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportPdfService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.export; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; import com.itextpdf.text.Document; import com.itextpdf.text.Font; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.BaseFont; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import lombok.SneakyThrows; import java.io.OutputStream; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * ExportPdfService * * @author lzy **/ public class ExportPdfService extends DatabaseExportService { public ExportPdfService() { exportTypeEnum = ExportTypeEnum.PDF; suffix = ExportFileSuffix.PDF.getSuffix(); contentType = "application/pdf"; } @SneakyThrows @Override public void export(OutputStream outputStream, ExportOptions exportOptions) { boolean isExportIndex = exportOptions.getIsExportIndex(); Map>>> allMap = listMap.entrySet() .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); Document document = new Document(); PdfWriter pdfWriter = PdfWriter.getInstance(document, outputStream); pdfWriter.setStrictImageSequence(true); // Font settings BaseFont baseFont =BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); // Create font object Font font = new Font(baseFont, 10, Font.NORMAL); Font headFont = new Font(baseFont, 12, Font.NORMAL); Font titleFont = new Font(baseFont, 14, Font.BOLD); document.open(); //Iterate over data for (Map.Entry>>> myMap : allMap.entrySet()) { //Database name String database = myMap.getKey(); String title = I18nUtils.getMessage("main.databaseText") + database; Paragraph p = new Paragraph(title, titleFont); document.add(p); for (Map.Entry> parameterMap : myMap.getValue()) { //Table Name String tableName = parameterMap.getKey().split("---")[1]; Paragraph tableParagraph = new Paragraph(tableName, font); document.add(tableParagraph); //IndexTable if (isExportIndex && !indexMap.isEmpty()) { PdfPTable table = new PdfPTable(CommonConstant.INDEX_HEAD_NAMES.length); process(table, CommonConstant.INDEX_HEAD_NAMES, font); String name = parameterMap.getKey().split("\\[")[0]; List indexInfoVOList = indexMap.get(name); for (IndexInfo indexInfo : indexInfoVOList) { process(table, getIndexValues(indexInfo), font); } table.setPaddingTop(5); document.add(table); } document.add(new Paragraph()); //FieldTable List exportList = parameterMap.getValue(); PdfPTable table = new PdfPTable(CommonConstant.COLUMN_HEAD_NAMES.length); //title content process(table, CommonConstant.COLUMN_HEAD_NAMES, headFont); for (TableParameter tableParameter : exportList) { process(table, getColumnValues(tableParameter), font); } // Set the blank space above the table, that is, the effect of moving downwards table.setSpacingBefore(10f); table.setSpacingAfter(20f); //Align left table.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); document.add(table); //Pagination //document.newPage(); } } document.close(); } //Set table content public static void process(PdfPTable table, T[] line, Font font) { for (T s : line) { if (Objects.isNull(s)) { return; } PdfPCell cell = new PdfPCell(new Paragraph(s.toString(), font)); cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); cell.setPaddingTop(5); cell.setPaddingBottom(5); table.addCell(cell); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportWordSuperService.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.export; import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; import ai.chat2db.server.tools.common.config.GlobalDict; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; import ai.chat2db.server.web.api.util.AddToTopic; import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.data.Includes; import com.deepoove.poi.data.RowRenderData; import com.deepoove.poi.data.Rows; import com.deepoove.poi.data.Tables; import lombok.SneakyThrows; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * WordSuperActionListener * * @author lzy **/ public class ExportWordSuperService extends DatabaseExportService { public ExportWordSuperService() { exportTypeEnum = ExportTypeEnum.WORD; suffix = ExportFileSuffix.WORD.getSuffix(); contentType = "application/msword"; } /** * Word export **/ @SneakyThrows @Override public void export(OutputStream outputStream, ExportOptions exportOptions) { boolean isExportIndex = exportOptions.getIsExportIndex(); InputStream filePath = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(1)); InputStream subFile = this.getClass().getClassLoader().getResourceAsStream("template/" + GlobalDict.TEMPLATE_FILE.get(2)); Map>>> allMap = listMap.entrySet() .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); List> list = new ArrayList<>(); Map myDataMap = new HashMap<>(2); //Index header RowRenderData indexHeaderRow = Rows.of(CommonConstant.INDEX_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); //Field header RowRenderData tableHeaderRow = Rows.of(CommonConstant.COLUMN_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); for (Map.Entry>>> myMap : allMap.entrySet()) { //Database name String database = myMap.getKey(); int i = 1; for (Map.Entry> parameterMap : myMap.getValue()) { //Initial capacity 3/0.75 + 1 Map tableData = new HashMap<>(8); //IndexTable if (isExportIndex) { String name = parameterMap.getKey().split("\\[")[0]; List indexInfoVOList = indexMap.get(name); List rowList = getIndexValues(indexInfoVOList, indexHeaderRow); tableData.put("indexTable", Tables.create(rowList.toArray(new RowRenderData[0]))); } if (i == 1) { Map map = new HashMap<>(2); map.put("dataBase", database); tableData.put("ifDatabase", map); } //Table Name String tableName = parameterMap.getKey().split("---")[1]; tableData.put("number", i); tableData.put("name", tableName); List tableParameterList = parameterMap.getValue(); List rowList = getColumnValues(tableParameterList, tableHeaderRow); tableData.put("table", Tables.create(rowList.toArray(new RowRenderData[0]))); i++; list.add(tableData); } } myDataMap.put("mydata", Includes.ofStream(subFile).setRenderModel(list).create()); /*Generate documents based on template*/ XWPFTemplate template = XWPFTemplate.compile(filePath).render(myDataMap); AddToTopic.generateTOC(template.getXWPFDocument(), outputStream); } @SneakyThrows public List getColumnValues(List list, RowRenderData tableHeaderRow) { List rowRenderDataList = new ArrayList<>(); rowRenderDataList.add(tableHeaderRow); for (TableParameter tableParameter : list) { String[] values = Arrays.stream(getColumnValues(tableParameter)).toArray(String[]::new); rowRenderDataList.add(Rows.of(values).center().create()); } return rowRenderDataList; } @SneakyThrows public List getIndexValues(List list, RowRenderData tableHeaderRow) { List rowRenderDataList = new ArrayList<>(); rowRenderDataList.add(tableHeaderRow); if (list.isEmpty()) { String[] values = Arrays.stream(getIndexValues(new IndexInfo())).toArray(String[]::new); rowRenderDataList.add(Rows.of(values).center().create()); return rowRenderDataList; } for (IndexInfo indexInfo : list) { String[] values = Arrays.stream(getIndexValues(indexInfo)).toArray(String[]::new); rowRenderDataList.add(Rows.of(values).center().create()); } return rowRenderDataList; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/merge/MyMergeExcel.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.merge; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.merge.AbstractMergeStrategy; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; /** * MyMergeExcel * * @author lzy **/ public class MyMergeExcel extends AbstractMergeStrategy { public static final String NAME = "isTableNameBlank"; @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { if (NAME.equals(cell.getStringCellValue())) { CellRangeAddress region = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), 0, 7); sheet.addMergedRegion(region); Row row = sheet.getRow(cell.getRowIndex()); cell = row.getCell(0); Workbook workbook = sheet.getWorkbook(); // generate a style CellStyle style = workbook.createCellStyle(); // Set these styles style.setBorderBottom(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); style.setBorderTop(BorderStyle.THIN); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); //Set up filling scheme style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //Set custom fill color style.setFillForegroundColor(IndexedColors.GREEN.getIndex()); // generate a font Font font = workbook.createFont(); font.setBold(true); font.setFontHeightInPoints((short) 14); // Apply font to current style style.setFont(font); cell.setCellStyle(style); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/style/CustomExcelStyle.java ================================================ package ai.chat2db.server.web.api.controller.rdb.doc.style; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; /** * CustomExcelStyle * * @author lzy **/ public class CustomExcelStyle { public static WriteCellStyle getContentWriteCellStyle() { //Content style strategy WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); //Center vertically, center horizontally contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); contentWriteCellStyle.setBorderLeft(BorderStyle.THIN); contentWriteCellStyle.setBorderTop(BorderStyle.THIN); contentWriteCellStyle.setBorderRight(BorderStyle.THIN); contentWriteCellStyle.setBorderBottom(BorderStyle.THIN); //Set automatic line wrapping contentWriteCellStyle.setWrapped(true); // Font strategy WriteFont contentWriteFont = new WriteFont(); // font size contentWriteFont.setFontHeightInPoints((short) 11); contentWriteFont.setFontName("宋体"); contentWriteFont.setColor(IndexedColors.BLACK.getIndex()); contentWriteCellStyle.setWriteFont(contentWriteFont); return contentWriteCellStyle; } public static WriteCellStyle getHeadStyle() { //Header policy uses default settings for font size WriteCellStyle headWriteCellStyle = new WriteCellStyle(); headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); headWriteCellStyle.setBorderLeft(BorderStyle.THIN); headWriteCellStyle.setBorderTop(BorderStyle.THIN); headWriteCellStyle.setBorderRight(BorderStyle.THIN); headWriteCellStyle.setBorderBottom(BorderStyle.THIN); headWriteCellStyle.setWrapped(false); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 14); headWriteFont.setBold(true); headWriteFont.setFontName("宋体"); headWriteCellStyle.setWriteFont(headWriteFont); headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); return headWriteCellStyle; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/factory/ExportServiceFactory.java ================================================ package ai.chat2db.server.web.api.controller.rdb.factory; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.web.api.controller.rdb.doc.export.*; import lombok.SneakyThrows; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * ExportServiceFactory * * @author lzy **/ public class ExportServiceFactory { /** * Export implementation class cache pool */ private static final Map> REPORT_POOL = new ConcurrentHashMap<>(8); static { REPORT_POOL.put(ExportTypeEnum.EXCEL.name(), ExportExcelService.class); REPORT_POOL.put(ExportTypeEnum.WORD.name(), ExportWordSuperService.class); REPORT_POOL.put(ExportTypeEnum.MARKDOWN.name(), ExportMarkdownService.class); REPORT_POOL.put(ExportTypeEnum.HTML.name(), ExportHtmlService.class); REPORT_POOL.put(ExportTypeEnum.PDF.name(), ExportPdfService.class); } /** * Get the corresponding interface * * @param type dashboard type * @return Class */ @SneakyThrows public static Class get(String type) { Class dataResult = REPORT_POOL.get(type); if (dataResult == null) { throw new ClassNotFoundException("no ExportUI was found"); } else { return dataResult; } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ColumnRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Column * * @author Shi Yi */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ColumnRequest { /** * The old column name, this parameter is needed when modifying the column * You can send it without modification */ private String oldName; /** * name */ private String name; /** * Column type * For example, varchar(100), double(10,6) */ private String columnType; /** * Is it empty */ private Integer nullable; /** * Is it a primary key? */ private Boolean primaryKey; /** * default value */ private String defaultValue; /** * Whether to increment automatically */ private Boolean autoIncrement; /** * comment */ private String comment; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DataExportRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.domain.api.enums.ExportSizeEnum; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DataExportRequest extends DataSourceBaseRequest { /** * Executed SQL */ private String sql; /** * Original SQL without pagination */ private String originalSql; private Integer pageNo; private Integer pageSize; /** * export type * * @see ExportTypeEnum */ @NotNull private String exportType; /** * How much data is currently needed at the beginning * * @see ExportSizeEnum */ @NotNull private String exportSize; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; @Data public class DatabaseCreateRequest extends DataSourceBaseRequest { private String name; private String comment; private String charset; private String collation; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseExportDataRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * @author: zgq * @date: 2024年03月24日 12:36 */ @Data @AllArgsConstructor @NoArgsConstructor public class DatabaseExportDataRequest extends DataSourceBaseRequest { @NotNull private String exportType; @NotEmpty private List tableNames; /** * single:单行插入,multi:多行插入,update:更新语句 */ private String sqyType; private Boolean containsHeader; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseExportRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author: zgq * @date: February 27, 2024 22:03 */ @Data @AllArgsConstructor @NoArgsConstructor public class DatabaseExportRequest extends DataSourceBaseRequest { private Boolean containData; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DdlCountRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import lombok.Data; /** * total number * * @author Jiaju Zhuang */ @Data public class DdlCountRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * sql statement */ @NotNull private String sql; /** * console id */ @NotNull private Long consoleId; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DdlExportRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class DdlExportRequest extends DataSourceBaseRequest { /** * Name */ @NotNull private String name; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DdlRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import lombok.Data; /** * @author moji * @version TableManageRequest.java, v 0.1 September 16, 2022 17:55 moji Exp $ * @date 2022/09/16 */ @Data public class DdlRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * sql statement */ @NotNull private String sql; /** * console id */ @NotNull private Long consoleId; /** * Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Return all data * Only available for select statements */ private Boolean pageSizeAll; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import lombok.Data; /** * @author moji * @version TableManageRequest.java, v 0.1 September 16, 2022 17:55 moji Exp $ * @date 2022/09/16 */ @Data public class DmlRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * sql statement */ @NotNull private String sql; /** * console id */ @NotNull private Long consoleId; /** *Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Return all data * Only available for select statements */ private Boolean pageSizeAll; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlSqlCopyRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class DmlSqlCopyRequest extends DataSourceBaseRequest { @NotNull private String tableName; private String type; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DmlTableRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version TableManageRequest.java, v 0.1 September 16, 2022 17:55 moji Exp $ * @date 2022/09/16 */ @Data public class DmlTableRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * table noun */ @NotNull private String tableName; /** * console id */ @NotNull private Long consoleId; /** *Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Return all data * Only available for select statements */ private Boolean pageSizeAll; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionDetailRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class FunctionDetailRequest implements DataSourceBaseRequestInfo { /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * function name */ private String functionName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionPageRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class FunctionPageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * Fuzzy search terms */ private String searchKey; /** * function name */ private String functionName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author Juechen * @version : FunctionUpdateRequest.java */ @Data @AllArgsConstructor @NoArgsConstructor public class FunctionUpdateRequest extends DataSourceBaseRequest { private String functionName; private String functionBody; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/GroupByRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.spi.model.OrderBy; import lombok.Data; import java.util.List; @Data public class GroupByRequest extends DataSourceBaseRequest implements DataSourceBaseRequestInfo { /** * origin sql */ private String originSql; /** * group by field */ private List groupByList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/IndexRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.util.List; import ai.chat2db.spi.enums.IndexTypeEnum; import ai.chat2db.spi.model.TableIndexColumn; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * index * * @author Shi Yi */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class IndexRequest { /** * Index name */ private String name; /** * all types * * @see IndexTypeEnum */ private String type; /** * Comment */ private String comment; /** * Columns included in the index */ private List columnList; private String editStatus; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/NewTableSqlRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class NewTableSqlRequest extends DataSourceBaseRequest { /** * new table structure */ @NotNull private TableRequest newTable; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/OrderByRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.spi.model.OrderBy; import lombok.Data; import java.util.List; @Data public class OrderByRequest extends DataSourceBaseRequest implements DataSourceBaseRequestInfo { /** * origin sql */ private String originSql; /** * sort field */ private List orderByList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureDetailRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class ProcedureDetailRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * procedure name */ private String procedureName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedurePageRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class ProcedurePageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * Fuzzy search terms */ private String searchKey; /** * procedure name */ private String procedureName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author: zgq * @date: February 24, 2024 13:21 */ @Data @AllArgsConstructor @NoArgsConstructor public class ProcedureUpdateRequest extends DataSourceBaseRequest { private String procedureName; private String procedureBody; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.Data; @Data public class SchemaCreateRequest extends DataSourceBaseRequest { /** * Data name */ @JsonAlias({"TABLE_SCHEM"}) private String name; private String comment; private String owner; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author jipengfei * @version : SchemaQueryRequest.java */ @Data public class SchemaQueryRequest extends DataSourceBaseRequest { } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.domain.api.param.SelectResultOperation; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import ai.chat2db.spi.model.Header; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.List; @Data public class SelectResultUpdateRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { /** * List of display headers */ private List
    headerList; /** * List of modified data */ @NotEmpty private List operations; /** * Table Name */ private String tableName; /** * console id */ @NotNull private Long consoleId; @Override public Long getConsoleId() { return consoleId; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SequenceBriefQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.io.Serial; /** * Query sequence brief request * * @author Sylphy */ @Data public class SequenceBriefQueryRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -1324577112324436332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the sequence is located is required by pg and oracle, but not by mysql. */ private String schemaName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SequenceDeleteRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Delete sequence sql request * * @author Sylphy */ @Data public class SequenceDeleteRequest extends DataSourceBaseRequest { /** * Sequence Name */ @NotNull private String sequenceName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SequenceDetailQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Query sequence detail request * * @author Sylphy */ @Data public class SequenceDetailQueryRequest extends DataSourceBaseRequest { /** * Sequence Name */ @NotNull private String sequenceName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SequenceModifySqlRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * Modify sequence sql request * * @author Sylphy */ @Data public class SequenceModifySqlRequest extends DataSourceBaseRequest { /** * Old sequence structure * Empty means creating a new sequence */ private SequenceRequest oldSequence; /** * new sequence structure */ @NotNull private SequenceRequest newSequence; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SequenceRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Modify sequence sql request * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SequenceRequest { /** * Schema name */ private String nspname; /** * Sequence name */ private String relname; /** * Sequence data type */ private String typname; /** * Sequence cache */ private String seqcache; /** * Sequence owner */ private String rolname; /** * Sequence comment */ private String comment; /** * Sequence start value */ private String seqstart; /** * Sequence step value */ private String seqincrement; /** * Sequence max value */ private String seqmax; /** * Sequence min value */ private String seqmin; /** * Sequence cycle */ private Boolean seqcycle; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class TableBriefQueryRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * Fuzzy search terms */ private String searchKey; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableCreateDdlQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version TableCreateDdlQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class TableCreateDdlQueryRequest { /** * DB type * @see ai.chat2db.server.domain.support.enums.DbTypeEnum */ @NotNull private String dbType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDeleteRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version TableManageRequest.java, v 0.1 September 16, 2022 17:55 moji Exp $ * @date 2022/09/16 */ @Data public class TableDeleteRequest extends DataSourceBaseRequest { /** * Table Name */ @NotNull private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDetailQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class TableDetailQueryRequest extends DataSourceBaseRequest { /** * Table Name */ @NotNull private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableMilvusQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import lombok.Data; @Data public class TableMilvusQueryRequest extends TableBriefQueryRequest { private String apikey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableModifySqlRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * Modify table sql request * * @author Shi Yi */ @Data public class TableModifySqlRequest extends DataSourceBaseRequest { /** * Old table structure * Empty means creating a new table */ private TableRequest oldTable; /** * new table structure */ @NotNull private TableRequest newTable; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import java.util.List; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import lombok.Data; /** * @author jipengfei * @version : TableColumnQueryRequest.java */ @Data public class TableQueryRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = 5794716286491282784L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ @NotNull private String databaseName; /** * Table Name */ private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.util.List; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * 修改表sql请求 * * @author Shi Yi */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableRequest { /** * Table Name */ private String name; /** * Table description */ private String comment; /** * Column */ private List columnList; /** * index */ private List indexList; /** * Space name */ private String schemaName; /** * Database name */ private String databaseName; private String engine; private String charset; private String collate; private Long incrementValue; private String partition; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableUpdateDdlQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import jakarta.validation.constraints.NotNull; import lombok.Data; /** * @author moji * @version TableUpdateDdlQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class TableUpdateDdlQueryRequest { /** * DB type * @see ai.chat2db.server.domain.support.enums.DbTypeEnum */ @NotNull private String dbType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerDetailRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class TriggerDetailRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * trigger name */ private String triggerName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerPageRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class TriggerPageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { @Serial private static final long serialVersionUID = -364547173428396332L; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; /** * The space where the table is located is required by pg and oracle, but not by mysql. */ private String schemaName; /** * Fuzzy search terms */ private String searchKey; /** * trigger name */ private String triggerName; /** * if true, refresh the cache */ private boolean refresh; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TypeQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class TypeQueryRequest implements DataSourceBaseRequestInfo { @NotNull private Long dataSourceId; /** * DB name */ private String databaseName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateDatabaseRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author jipengfei * @version : UpdateDatasourceRequest.java */ @Data public class UpdateDatabaseRequest extends DataSourceBaseRequest { private String newDatabaseName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateSchemaRequest.java ================================================ package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author jipengfei * @version : UpdateSchemaRequest.java */ @Data public class UpdateSchemaRequest extends DataSourceBaseRequest { private String newSchemaName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author moji * @version TableVO.java, v 0.1 September 16, 2022 17:16 moji Exp $ * @date 2022/09/16 */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ColumnVO { /** * The old column name, this parameter is needed when modifying the column * oldName=name when returning */ private String oldName; /** * Column name */ private String name; /** * Table Name */ private String tableName; /** * Column type * For example, varchar(100), double(10,6) */ private String columnType; /** * Column data type * For example, varchar, double */ private Integer dataType; /** * default value */ private String defaultValue; /** * Whether to increase automatically * Empty means there is no value. The actual semantics of the database are false. */ private Boolean autoIncrement; /** * Comment */ private String comment; /** * Is it a primary key? */ private Boolean primaryKey; /** * Space name */ private String schemaName; /** * Database name */ private String databaseName; /** * Data source dependent type name, for a UDT the type name is fully qualified */ private String typeName; /** * column size. */ private Integer columnSize; /** * is not used. */ private Integer bufferLength; /** * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. */ private Integer decimalDigits; /** * Radix (typically either 10 or 2) */ private Integer numPrecRadix; /** * is NULL allowed. * columnNoNulls - might not allow NULL values * columnNullable - definitely allows NULL values * columnNullableUnknown - nullability unknown */ private Integer nullableInt; /** * unused */ private Integer sqlDataType; /** * unused */ private Integer sqlDatetimeSub; /** * for char types the maximum number of bytes in the column */ private Integer charOctetLength; /** * index of column in table (starting at 1) */ private Integer ordinalPosition; /** * ISO rules are used to determine the nullability for a column. */ private Integer nullable; /** * String => Indicates whether this is a generated column * * YES --- if this a generated column * * NO --- if this not a generated column */ private Boolean generatedColumn; private String extent; private String editStatus; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import java.util.List; import ai.chat2db.spi.model.Header; import lombok.Data; /** * @author moji * @version ExecuteResultVO.java, v 0.1 October 23, 2022 11:20 moji Exp $ * @date 2022/10/23 */ @Data public class ExecuteResultVO { /** * executed sql */ private String sql; /** * Original SQL without pagination */ private String originalSql; /** * description */ private String description; /** * Failure message prompt */ private String message; /** * success flag */ private Boolean success; /** * Modify the number of rows and query sql will not return */ private Integer updateCount; /** * List of display headers */ private List
    headerList; /** * list of data */ private List> dataList; /** * sql type * */ private String sqlType; /** * Whether there is a next page * Only available for select statements */ private Boolean hasNextPage; /** * Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Total number of fuzzy rows * Only select statements have */ private String fuzzyTotal; /** * execution duration */ private Long duration; /** * Whether the returned result can be edited */ private boolean canEdit; /** * Table Name */ private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/IndexColumnVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Column information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class IndexColumnVO { /** * Index name */ private String indexName; /** * Table Name */ private String tableName; /** * index type * * @see */ private String type; /** * Comment */ private String comment; /** * Column name */ private String columnName; /** * order */ private Short ordinalPosition; /** * sort * */ private String collation; /** * The schema to which the index belongs */ private String schemaName; /** * Database name */ private String databaseName; /** * Is it unique? */ private Boolean nonUnique; /** * index catalog (may be null); null when TYPE is tableIndexStatistic */ private String indexQualifier; /** * ASC_OR_DESC String => column sort sequence, "A" => ascending, "D" => descending, may be null if sort sequence is not supported; null when TYPE is tableIndexStatistic */ private String ascOrDesc; /** * CARDINALITY long => When TYPE is tableIndexStatistic, then this is the number of rows in the table; otherwise, it is the number of unique values in the index. */ private Long cardinality; /** * When TYPE is tableIndexStatistic then this is the number of pages used for the table, otherwise it is the number of pages used for the current index. */ private Long pages; /** * Filter condition, if any. (may be null) */ private String filterCondition; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/IndexVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import java.util.List; import ai.chat2db.spi.enums.IndexTypeEnum; import lombok.Data; /** * @author moji * @version IndexVO.java, v 0.1 September 16, 2022 17:47 moji Exp $ * @date 2022/09/16 */ @Data public class IndexVO { /** * Contains columns */ private String columns; /** * Index name */ private String name; /** * all types * * @see IndexTypeEnum */ private String type; /** * Comment */ private String comment; /** * Columns included in the index */ private List columnList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/KeyVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import java.util.List; import lombok.Data; /** * @author moji * @version IndexVO.java, v 0.1 September 16, 2022 17:47 moji Exp $ * @date 2022/09/16 */ @Data public class KeyVO { /** * Contains columns */ private String columns; /** * Index name */ private String name; /** * Comment */ private String comment; /** * Columns included in the index */ private List columnList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/MetaSchemaVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Schema; import lombok.Data; import java.util.List; @Data public class MetaSchemaVO { /** * database list */ private List databases; /** * schema list */ private List schemas; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.Data; /** * @author jipengfei * @version : SchemaVO.java */ @Data public class SchemaVO { /** * Data name */ private String name; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SequenceVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.Data; /** * Sequence detail VO * * @author Sylphy */ @Data public class SequenceVO { /** * Schema name */ private String nspname; /** * Sequence name */ private String relname; /** * Sequence data type */ private String typname; /** * Sequence cache */ private String seqcache; /** * Sequence owner */ private String rolname; /** * Sequence comment */ private String comment; /** * Sequence start value */ private String seqstart; /** * Sequence step value */ private String seqincrement; /** * Sequence max value */ private String seqmax; /** * Sequence min value */ private String seqmin; /** * Sequence cycle */ private Boolean seqcycle; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SqlVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * sql object * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SqlVO { /** * sql */ private String sql; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java ================================================ package ai.chat2db.server.web.api.controller.rdb.vo; import java.util.List; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import lombok.Data; /** * @author moji * @version TableVO.java, v 0.1 September 16, 2022 17:16 moji Exp $ * @date 2022/09/16 */ @Data public class TableVO { /** * Table Name */ private String name; /** * Table description */ private String comment; /** * Column */ private List columnList; /** * index */ private List indexList; /** * Has it been fixed? */ private boolean pinned; /** * ddl */ private String ddl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ViewVO.java ================================================ //package ai.chat2db.server.web.api.controller.rdb.vo; // //public class ViewVO extends TableVO{ // // /** // * view script // */ // private String script; //} ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyManageController.java ================================================ package ai.chat2db.server.web.api.controller.redis; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.controller.redis.request.KeyCreateRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyDeleteRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyQueryRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyUpdateRequest; import ai.chat2db.server.web.api.controller.redis.vo.KeyVO; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * redis key operation and maintenance class * * @author moji * @version MysqlTableManageController.java, v 0.1 September 16, 2022 17:41 moji Exp $ * @date 2022/09/16 */ @RequestMapping("/api/redis/key") @RestController public class RedisKeyManageController { /** * Query the key list under the current DB * * @param request * @return */ @GetMapping("/list") public ListResult list(KeyQueryRequest request) { return null; } /** * Add Key * * @param request * @return */ @PostMapping("/create") public ActionResult create(@RequestBody KeyCreateRequest request) { return null; } /** * Modify key information * * @param request * @return */ @RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody KeyUpdateRequest request) { return null; } /** * Delete key * * @param request * @return */ @DeleteMapping("/delete") public ActionResult delete(@RequestBody KeyDeleteRequest request) { return null; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/RedisKeyValueManageController.java ================================================ package ai.chat2db.server.web.api.controller.redis; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.controller.redis.request.KeyQueryRequest; import ai.chat2db.server.web.api.controller.redis.request.KeyValueManageRequest; import ai.chat2db.server.web.api.controller.redis.request.ValueUpdateRequest; import ai.chat2db.server.web.api.controller.redis.vo.KeyVO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * redis data operation and maintenance class * * @author moji * @version MysqlDataManageController.java, v 0.1 September 16, 2022 17:37 moji Exp $ * @date 2022/09/16 */ @RequestMapping("/api/redis/kv") @RestController public class RedisKeyValueManageController { /** * redis ddl command execution * * @param request * @return */ @RequestMapping(value = "/manage",method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult manage(@RequestBody KeyValueManageRequest request) { return null; } /** * Get cache key details * * @param request * @return */ @GetMapping("/query") public DataResult query(KeyQueryRequest request) { return null; } /** * Update key value * * @param request * @return */ @RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody ValueUpdateRequest request) { return null; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version TableVO.java, v 0.1 September 16, 2022 17:16 moji Exp $ * @date 2022/09/16 */ @Data public class KeyCreateRequest extends DataSourceBaseRequest { /** * key name */ @NotNull private String name; /** * key value */ private Object value; /** * Expiration */ private Long ttl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyDeleteRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class KeyDeleteRequest extends DataSourceBaseRequest { /** * key name */ private String keyName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class KeyQueryRequest extends DataSourceBaseRequest { /** * Cache key name */ private String keyName; /** * search keyword */ private String searchKey; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class KeyUpdateRequest extends DataSourceBaseRequest { /** * key name */ @NotNull private String originalKey; /** * Key name after update */ private String updateKey; /** * Original ttl value */ private Long originalTtl; /** * ttl value after update */ private Object updateTtl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/KeyValueManageRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version TableManageRequest.java, v 0.1 September 16, 2022 17:55 moji Exp $ * @date 2022/09/16 */ @Data public class KeyValueManageRequest extends DataSourceBaseRequest { /** * redis ddl statement */ @NotNull private String ddl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/request/ValueUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.redis.request; import jakarta.validation.constraints.NotNull; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import lombok.Data; /** * @author moji * @version ConnectionQueryRequest.java, v 0.1 September 16, 2022 14:23 moji Exp $ * @date 2022/09/16 */ @Data public class ValueUpdateRequest extends DataSourceBaseRequest { /** * key name */ @NotNull private String key; /** * Original key value */ @NotNull private Object originalValue; /** * Key value after update */ @NotNull private Object updateValue; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/redis/vo/KeyVO.java ================================================ package ai.chat2db.server.web.api.controller.redis.vo; import lombok.Data; /** * @author moji * @version Table.java, v 0.1 September 16, 2022 17:16 moji Exp $ * @date 2022/09/16 */ @Data public class KeyVO { /** * key name */ private String name; /** * key value */ private Object value; /** * key type */ private String type; /** * Expiration */ private Long ttl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java ================================================ package ai.chat2db.server.web.api.controller.sql; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.sql.request.SqlFormatRequest; import com.github.vertical_blank.sqlformatter.SqlFormatter; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * SQL Controller */ @ConnectionInfoAspect @RequestMapping("/api/sql") @RestController public class SqlController { /** * SQL Format * * @param sqlFormatRequest * @return */ @GetMapping("/format") public DataResult list(@Valid SqlFormatRequest sqlFormatRequest) { String sql = sqlFormatRequest.getSql(); try { sql = SqlFormatter.format(sql); } catch (Exception e) { // ignore } return DataResult.of(sql); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFormatRequest.java ================================================ package ai.chat2db.server.web.api.controller.sql.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SqlFormatRequest { private String sql; private String dbType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java ================================================ /** * Alipay.com Inc. * Copyright (c) 2004-2022 All Rights Reserved. */ package ai.chat2db.server.web.api.controller.system; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.tools.common.enums.ModeEnum; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; import ai.chat2db.server.web.api.controller.system.vo.SystemVO; import ai.chat2db.spi.ssh.SSHManager; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.*; /** * @author jipengfei * @version : HomeController.java, v 0.1 September 18, 2022 14:52 jipengfei Exp $ */ @RestController @RequestMapping("/api/system") @Slf4j public class SystemController { @Autowired private ApplicationContext applicationContext; @Autowired private Chat2dbProperties chat2dbProperties; @Autowired private ConfigService configService; /** * Check if the test is successful * * @return */ @GetMapping public DataResult get() { String clientVersion = System.getProperty("client.version"); String version = ConfigUtils.getLatestLocalVersion(); log.error("clientVersion:{},version:{}", clientVersion, version); if (!StringUtils.equals(clientVersion, version) && !StringUtils.isEmpty(clientVersion)) { stop(); return null; } else { ConfigJson configJson = ConfigUtils.getConfig(); return DataResult.of(SystemVO.builder() .systemUuid(configJson.getSystemUuid()) .build()); } } private static final String UPDATE_TYPE = "client_update_type"; @GetMapping("/get_latest_version") public DataResult getLatestVersion(String currentVersion) { ModeEnum mode = EasyEnumUtils.getEnum(ModeEnum.class, System.getProperty("chat2db.mode")); if (mode != ModeEnum.DESKTOP) { // In this mode, no user login is required, so only local access is available return DataResult.of(null); } String user = ""; DataResult dataResult = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); if (dataResult.getData() != null) { user = dataResult.getData().getContent(); } AppVersionVO appVersionVO = SystemUtils.getLatestVersion(currentVersion, "manual", user); if (appVersionVO == null) { appVersionVO = new AppVersionVO(); appVersionVO.setVersion(currentVersion); appVersionVO.setType("manual"); } DataResult updateType = configService.find(UPDATE_TYPE); if (updateType.getData() != null) { appVersionVO.setType(updateType.getData().getContent()); } // In this mode, no user login is required, so only local access is available appVersionVO.setDesktop(true); return DataResult.of(appVersionVO); } @PostMapping("/update_desktop_version") public DataResult updateDesktopVersion(@RequestBody AppVersionVO version) { new Thread(() -> { SystemUtils.upgrade(version); }).start(); return DataResult.of(version.getVersion()); } @GetMapping("/is_update_success") public DataResult isUpdateSuccess(String version) { String localVersion = ConfigUtils.getLocalVersion(); if (StringUtils.isEmpty(localVersion)) { return DataResult.of(false); } return DataResult.of(localVersion.equals(version)); } @PostMapping("/set_update_type") public ActionResult setUpdateType(@RequestBody String updateType) { SystemConfigParam systemConfigParam = new SystemConfigParam(); systemConfigParam.setCode(UPDATE_TYPE); systemConfigParam.setContent(updateType); systemConfigParam.setSummary("client update type"); configService.createOrUpdate(systemConfigParam); return ActionResult.isSuccess(); } /** * Get the current version number * * @return */ @GetMapping("/get-version-a") public DataResult getVersion() { return DataResult.of(chat2dbProperties.getVersion()); } /** * Exit service */ @RequestMapping("/stop") public DataResult stop(boolean forceQuit) { log.info("Exit application"); if (forceQuit) { stop(); } else { // String clientVersion = System.getProperty("client.version"); // String version = ConfigUtils.getLatestLocalVersion(); // log.error("clientVersion:{},version:{}", clientVersion, version); // if (!StringUtils.equals(clientVersion, version)) { stop(); //} } return DataResult.of("ok"); } private void stop() { new Thread(() -> { // Will exit the background after 100ms try { Thread.sleep(200L); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("Start exiting Spring application"); SSHManager.close(); try { SpringApplication.exit(applicationContext); } catch (Exception ignore) { } // It is possible that SpringApplication.exit will fail to exit // Direct system exit log.info("Start exiting system applications"); CacheManage.close(); try { System.exit(0); } catch (Exception ignore) { } }).start(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java ================================================ package ai.chat2db.server.web.api.controller.system.util; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; import ai.chat2db.spi.ssh.SSHManager; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.ZipUtil; import cn.hutool.http.HttpUtil; import com.dtflys.forest.Forest; import com.dtflys.forest.utils.TypeReference; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.time.Duration; /** * System Toolkit * * @author Jiaju Zhuang */ @Slf4j public class SystemUtils { /** * Stop current application */ public static void stop() { new Thread(() -> { log.info("Exit the application after 1 second"); // Automatically exit the application after 1 second try { Thread.sleep(1000L); } catch (InterruptedException e) { throw new RuntimeException(e); } // Direct system exit log.info("Start exiting system applications"); SSHManager.close(); try { System.exit(0); } catch (Exception ignore) { } }).start(); } private static final OkHttpClient client = new OkHttpClient(); private static final String VERSION_URL = "https://sqlgpt.cn/api/version.json"; private static final String ZIP_FILE_PATH = ConfigUtils.APP_PATH + File.separator + "versions" + File.separator; public static void upgrade(AppVersionVO appVersion) { String appPath = ConfigUtils.APP_PATH; log.info("appPath: {}", appPath); if (StringUtils.isBlank(appPath) || !appPath.contains("app")) { return; } try { String zipPath = ZIP_FILE_PATH + appVersion.getVersion() + ".zip"; HttpUtil.downloadFile(appVersion.getHotUpgradeUrl(), ZIP_FILE_PATH + appVersion.getVersion() + ".zip"); ZipUtil.unzip(zipPath); FileUtil.del(zipPath); ConfigUtils.updateVersion(appVersion.getVersion()); } catch (Exception e) { log.error("checkVersionUpdates error", e); } } private static final String LATEST_VERSION_URL = "http://test.sqlgpt.cn/gateway/api/client/version/check/v3?version=%s&type=%s&userId=%s"; public static AppVersionVO getLatestVersion(String version, String type, String userId) { String url = String.format(LATEST_VERSION_URL, version, type, userId); DataResult result = Forest.get(url) .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .execute(new TypeReference<>() { }); return result.getData(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/AppVersionVO.java ================================================ package ai.chat2db.server.web.api.controller.system.vo; import lombok.Data; import java.time.LocalDateTime; @Data public class AppVersionVO { /** * primary key */ private Long id; /** * new version */ private String version; // /** // * Which versions can be upgraded to this version // */ // private String versionUse; // /** // * state // */ // private String status; /** * downloadLink */ private String downloadLink; /** * Manual update, automatic forced update */ private String type; // /** // * Whitelist, for testing // */ // private String whiteList; /** * Hot update package address */ private String hotUpgradeUrl; /** * updateLog */ private String updateLog; /** * desktop */ private boolean desktop; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/SystemVO.java ================================================ package ai.chat2db.server.web.api.controller.system.vo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * system * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SystemVO { /** * The unique ID of the system */ private String systemUuid; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/ExportController.java ================================================ package ai.chat2db.server.web.api.controller.task; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.task.biz.TaskBizService; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @ConnectionInfoAspect @RequestMapping("/api/export") @Controller @Slf4j public class ExportController { @Autowired private TaskBizService taskBizService; /** * export data * * @param request * @return */ @PostMapping("/export_data") public DataResult export(@Valid @RequestBody DataExportRequest request) { return taskBizService.exportResultData(request); } @PostMapping("/export_doc") public DataResult exportDoc(@Valid @RequestBody DataExportRequest request) { return taskBizService.exportSchemaDoc(request); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/TaskController.java ================================================ package ai.chat2db.server.web.api.controller.task; import ai.chat2db.server.domain.api.model.Task; import ai.chat2db.server.domain.api.param.TaskPageParam; import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import java.io.*; @ConnectionInfoAspect @RequestMapping("/api/task") @Controller @Slf4j public class TaskController { @Autowired private TaskService taskService; @GetMapping("/list") public WebPageResult list() { TaskPageParam taskPageParam = new TaskPageParam(); taskPageParam.setPageNo(1); taskPageParam.setPageSize(10); taskPageParam.setUserId(ContextUtils.getUserId()); PageResult task = taskService.page(taskPageParam); return WebPageResult.of(task.getData(), 100L, 1, 10); } @GetMapping("/download/{id}") public void download(@PathVariable Long id, HttpServletResponse response) { DataResult task = taskService.get(id); Task data = task.getData(); if (data == null) { log.error("task is null"); throw new RuntimeException("task is null"); } if (!ContextUtils.getUserId().equals(data.getUserId())) { log.error("task is not belong to user"); throw new RuntimeException("task is not belong to user"); } File file = new File(data.getDownloadUrl()); if (!file.exists() || !file.isFile()) { log.error("File not found or is not a file: {}", file.getAbsolutePath()); throw new RuntimeException("File not found or accessible"); } response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\""); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); try (InputStream inputStream = new FileInputStream(file)) { OutputStream outputStream = response.getOutputStream(); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); outputStream.flush(); } } catch (IOException e) { log.error("Error occurred while processing file download", e); throw new RuntimeException("Error in file download", e); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/task/biz/TaskBizService.java ================================================ package ai.chat2db.server.web.api.controller.task.biz; import ai.chat2db.server.domain.api.enums.*; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.api.service.TaskService; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.web.api.controller.rdb.RdbDmlExportController; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.factory.ExportServiceFactory; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.SqlUtils; import cn.hutool.core.date.DatePattern; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.visitor.VisitorFeature; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.metadata.WriteSheet; import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.CompletableFuture; @Slf4j @Component public class TaskBizService { /** * Format insert statement */ private static final SQLUtils.FormatOption INSERT_FORMAT_OPTION = new SQLUtils.FormatOption(true, false); static { INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); } @Autowired private TaskService taskService; @Autowired private TableService tableService; @Autowired private RdbWebConverter rdbWebConverter; public DataResult exportResultData(DataExportRequest request) { String sql = ExportSizeEnum.CURRENT_PAGE.getCode().equals(request.getExportSize()) ? request.getSql() : request.getOriginalSql(); Assert.notBlank(sql, "dataSource.sqlEmpty"); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); String tableName = getTableName(request, sql, dbType); File file = createTempFile(tableName, request.getExportType()); DataResult dataResult = createTask(tableName, request.getDatabaseName(), request.getSchemaName(), request.getDataSourceId(), tableName); LoginUser loginUser = ContextUtils.getLoginUser(); ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); CompletableFuture.runAsync(() -> { buildContext(loginUser, connectInfo); doExport(sql, file, dbType, tableName, request.getExportType()); }).whenComplete((aVoid, throwable) -> { updateStatus(dataResult.getData(), file, throwable); removeContext(); }); return dataResult; } public DataResult exportSchemaDoc(DataExportRequest request) { File file = createTempFile(request.getDatabaseName(), request.getExportType()); DataResult dataResult = createTask(null, request.getDatabaseName(), request.getSchemaName(), request.getDataSourceId(), "schema_doc"); LoginUser loginUser = ContextUtils.getLoginUser(); ConnectInfo connectInfo = Chat2DBContext.getConnectInfo().copy(); CompletableFuture.runAsync(() -> { buildContext(loginUser, connectInfo); doExportDoc(request, file); }).whenComplete((aVoid, throwable) -> { updateStatus(dataResult.getData(), file, throwable); removeContext(); }); return dataResult; } private void doExportDoc(DataExportRequest request, File file) { try { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); queryParam.setPageNo(1); queryParam.setPageSize(Integer.MAX_VALUE); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); PageResult tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); TableQueryParam param = rdbWebConverter.tableRequest2param(request); for (TableVO tableVO : tableVOS) { param.setTableName(tableVO.getName()); tableVO.setColumnList(tableService.queryColumns(param)); tableVO.setIndexList(tableService.queryIndexes(param)); } Class targetClass = ExportServiceFactory.get(request.getExportType()); Constructor constructor = targetClass.getDeclaredConstructor(); DatabaseExportService databaseExportService = (DatabaseExportService) constructor.newInstance(); // Set up data collection databaseExportService.setExportList(tableVOS); databaseExportService.generate(request.getDatabaseName(), new FileOutputStream(file), new ExportOptions()); } catch (Exception e) { log.error("export error", e); throw new BusinessException("dataSource.exportError"); } } private void removeContext() { Dbutils.removeSession(); ContextUtils.removeContext(); Chat2DBContext.removeContext(); } private void buildContext(LoginUser loginUser, ConnectInfo connectInfo) { ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); Dbutils.setSession(); Chat2DBContext.putContext(connectInfo); } private DataResult createTask(String tableName, String databaseName, String schemaName, Long datasourceId, String taskName) { TaskCreateParam param = new TaskCreateParam(); param.setTaskName("export_" + taskName); param.setTaskType(TaskTypeEnum.DOWNLOAD_TABLE_DATA.name()); param.setDatabaseName(databaseName); param.setSchemaName(schemaName); param.setTableName(tableName); param.setDataSourceId(datasourceId); param.setUserId(ContextUtils.getUserId()); param.setTaskProgress("0.1"); return taskService.create(param); } private void updateStatus(Long id, File file, Throwable throwable) { TaskUpdateParam updateParam = new TaskUpdateParam(); updateParam.setId(id); updateParam.setTaskProgress("1"); updateParam.setDownloadUrl(file.getAbsolutePath()); if (throwable != null) { log.error("export error", throwable); updateParam.setTaskStatus(TaskStatusEnum.ERROR.name()); } else { updateParam.setTaskStatus(TaskStatusEnum.FINISH.name()); } taskService.updateStatus(updateParam); } private void doExport(String sql, File file, DbType dbType, String tableName, String exportType) { try { if (ExportTypeEnum.CSV.getCode().equals(exportType)) { doExportCsv(sql, file); } else { doExportInsert(sql, file, dbType, tableName); } } catch (Exception e) { log.error("export error", e); throw new BusinessException("dataSource.exportError"); } } private File createTempFile(String tableName, String exportType) { String fileName = URLEncoder.encode( tableName + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); if (ExportTypeEnum.CSV.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ".csv", true); } else if (ExportTypeEnum.INSERT.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ".sql", true); } else if (ExportTypeEnum.EXCEL.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.EXCEL.getSuffix(), true); } else if (ExportTypeEnum.MARKDOWN.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.MARKDOWN.getSuffix(), true); } else if (ExportTypeEnum.WORD.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.WORD.getSuffix(), true); } else if (ExportTypeEnum.PDF.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.PDF.getSuffix(), true); } else if (ExportTypeEnum.HTML.getCode().equals(exportType)) { return FileUtil.createTempFile(fileName, ExportFileSuffix.HTML.getSuffix(), true); } return FileUtil.createTempFile(fileName, ".txt", true); } private String getTableName(DataExportRequest request, String sql, DbType dbType) { String tableName = null; if (dbType != null) { SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if (!(sqlStatement instanceof SQLSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } tableName = SqlUtils.getTableName(sql, dbType); } else { tableName = StringUtils.join(Lists.newArrayList(request.getDatabaseName(), request.getSchemaName()), "_"); } return tableName; } private void doExportCsv(String sql, File file) { RdbDmlExportController.ExcelWrapper excelWrapper = new RdbDmlExportController.ExcelWrapper(); try { ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(file) .charset(StandardCharsets.UTF_8) .excelType(ExcelTypeEnum.CSV); excelWrapper.setExcelWriterBuilder(excelWriterBuilder); ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { excelWriterBuilder.head( EasyCollectionUtils.toList(headerList, header -> Lists.newArrayList(header.getName()))); excelWrapper.setExcelWriter(excelWriterBuilder.build()); excelWrapper.setWriteSheet(EasyExcel.writerSheet(0).build()); }, dataList -> { List> writeDataList = Lists.newArrayList(); writeDataList.add(dataList); excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); }, jdbcDataValue -> valueProcessor.getJdbcValue(jdbcDataValue),false); } finally { if (excelWrapper.getExcelWriter() != null) { excelWrapper.getExcelWriter().finish(); } } } private void doExportInsert(String sql, File file, DbType dbType, String tableName) throws IOException { try (PrintWriter printWriter = new PrintWriter(file, StandardCharsets.UTF_8.name())) { RdbDmlExportController.InsertWrapper insertWrapper = new RdbDmlExportController.InsertWrapper(); ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String databaseName = Chat2DBContext.getConnectInfo().getDatabaseName(); String schemaName = Chat2DBContext.getConnectInfo().getSchemaName(); List headerColumns = Lists.newArrayList(); SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(), sql, headerList -> { headerList.forEach(header -> headerColumns.add(header.getName())); } , dataList -> { String insertSql = sqlBuilder.buildSingleInsertSql(databaseName, schemaName, tableName, headerColumns, dataList); printWriter.println(insertSql + ";"); }, jdbcDataValue -> valueProcessor.getJdbcSqlValueString(jdbcDataValue), false); } } @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public static class InsertWrapper { private List headerList; } @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public static class ExcelWrapper { private ExcelWriterBuilder excelWriterBuilder; private ExcelWriter excelWriter; private WriteSheet writeSheet; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java ================================================ package ai.chat2db.server.web.api.controller.user; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author jipengfei * @version : UserController.java */ @RequestMapping("/api/user") @RestController public class UserController { // //@Autowired //private UserService userService; // //@Autowired //private UserWebConverter userWebConverter; // //@GetMapping("/{id}") //public DataResult query(@PathVariable("id") Long id) { // return DataResult.of(userWebConverter.dto2vo(userService.query(id).getData())); //} // //@GetMapping("/list") //public WebPageResult list(UserQueryRequest request) { // UserQueryParam userQueryParam = new UserQueryParam(); // userQueryParam.setKeyWord(request.getKeyWord()); // userQueryParam.setPageNo(request.getPageNo()); // userQueryParam.setPageSize(request.getPageSize()); // PageResult pageResult = userService.queryPage(userQueryParam); // return WebPageResult.of(userWebConverter.dto2vo(pageResult.getData()), pageResult.getTotal(), request); //} // ///** // * Add Key // * // * @param request // * @return // */ //@PostMapping("/create") //public DataResult create(@RequestBody UserCreateRequest request) { // return userService.create(userWebConverter.createRequest2dto(request)); //} // ///** // * Update my save // * // * @param request // * @return // */ //@RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) //public ActionResult update(@RequestBody UserUpdateRequest request) { // DataResult result = userService.update(userWebConverter.updateRequest2dto(request)); // return ActionResult.isSuccess(); //} // ///** // * delete my save // * // * @param id // * @return // */ //@DeleteMapping("/{id}") //public ActionResult delete(@PathVariable("id") Long id) { // userService.delete(id); // return ActionResult.isSuccess(); //} } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/converter/UserWebConverter.java ================================================ package ai.chat2db.server.web.api.controller.user.converter; import java.util.List; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.web.api.controller.user.request.UserCreateRequest; import ai.chat2db.server.web.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.web.api.controller.user.vo.UserVO; import org.mapstruct.Mapper; /** * @author jipengfei * @version : UserWebConverter.java */ @Mapper(componentModel = "spring") public abstract class UserWebConverter { /** * Convert * * @param user * @return */ public abstract UserVO dto2vo(User user); /** * * @param user * @return */ public abstract List dto2vo(List user); /** * * @param createRequest * @return */ public abstract User createRequest2dto(UserCreateRequest createRequest); /** * * @param updateRequest * @return */ public abstract User updateRequest2dto(UserUpdateRequest updateRequest); } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserCreateRequest.java ================================================ package ai.chat2db.server.web.api.controller.user.request; import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : UserCreateRequest.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserCreateRequest implements Serializable { @Serial private static final long serialVersionUID = 353710386092262213L; /** * userName */ private String userName; /** * password */ private String password; /** * nickName */ private String nickName; /** * email */ private String email; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserQueryRequest.java ================================================ package ai.chat2db.server.web.api.controller.user.request; import java.io.Serial; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : UserQueryRequest.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserQueryRequest extends PageQueryParam { @Serial private static final long serialVersionUID = 5663790872812326134L; /** * Username magic search */ private String keyWord; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserUpdateRequest.java ================================================ package ai.chat2db.server.web.api.controller.user.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : UserUpdateRequest.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserUpdateRequest { /** * primary key */ private Long id; /** * userName */ private String userName; /** * password */ private String password; /** * nickName */ private String nickName; /** * email */ private String email; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/vo/UserVO.java ================================================ package ai.chat2db.server.web.api.controller.user.vo; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : UserVO.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class UserVO implements Serializable { private static final long serialVersionUID = 502943167829222727L; /** * primary key */ private Long id; /** * userName */ private String userName; /** * password */ private String password; /** * nickName */ private String nickName; /** * email */ private String email; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java ================================================ package ai.chat2db.server.web.api.http; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.web.api.http.request.*; import ai.chat2db.server.web.api.http.response.*; import com.dtflys.forest.Forest; import com.dtflys.forest.utils.TypeReference; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.time.Duration; /** * Gateway http service * * @author Jiaju Zhuang */ //@BaseRequest( // baseURL = "{gatewayBaseUrl}" //) @Service public class GatewayClientService { @Resource private Chat2dbProperties chat2dbProperties; /** * Get the QR code of the official account * * @return */ public DataResult getLoginQrCode() { DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/loginQrCode") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .execute(new TypeReference<>() { }); return result; } /** * Refresh login * * @param token * @return */ public DataResult getLoginStatus(String token) { DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/loginStatus") .connectTimeout(Duration.ofMillis(5000)) .addQuery("token", token) .readTimeout(Duration.ofMillis(10000)) .execute(new TypeReference<>() { }); return result; } /** * Return the remaining times * * @param key * @return */ public DataResult remaininguses(String key) { DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/remaininguses/" + key) .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .execute(new TypeReference<>() { }); return result; } /** * Obtain invitation QR code * * @param apiKey * @return */ public DataResult getInviteQrCode(String apiKey) { DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/inviteQrCode") .connectTimeout(Duration.ofMillis(5000)) .addQuery("apiKey", apiKey) .readTimeout(Duration.ofMillis(10000)) .execute(new TypeReference<>() { }); return result; } /** * save knowledge vector * * @param request * @return */ public ActionResult knowledgeVectorSave(KnowledgeRequest request) { ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/knowledge/save") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(request) .execute(new TypeReference<>() { }); return result; } /** * save table schema vector * * @param request * @return */ public ActionResult schemaVectorSave(TableSchemaRequest request) { ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/schema/save") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(request) .execute(new TypeReference<>() { }); return result; } /** * save table schema vector * * @param request * @return */ public ActionResult schemaEsSave(EsTableSchemaRequest request) { ActionResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/es/schema/save") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(request) .execute(new TypeReference<>() { }); return result; } /** * save knowledge vector * * @param searchVectors * @return */ public DataResult knowledgeVectorSearch(KnowledgeRequest searchVectors) { DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/knowledge/search") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(searchVectors) .execute(new TypeReference<>() { }); return result; } /** * save table schema vector * * @param request * @return */ public DataResult schemaVectorSearch(TableSchemaRequest request) { DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/milvus/schema/search") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(request) .execute(new TypeReference<>() { }); return result; } /** * save table schema vector * * @param request * @return */ public DataResult schemaEsSearch(EsTableSchemaRequest request) { DataResult result = Forest.post(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/es/schema/search") .connectTimeout(Duration.ofMillis(5000)) .readTimeout(Duration.ofMillis(10000)) .contentType("application/json") .addBody(request) .execute(new TypeReference<>() { }); return result; } /** * check in white list * * @param whiteListRequest * @return */ public DataResult checkInWhite(WhiteListRequest whiteListRequest) { // Remove whitelist return DataResult.of(false); // DataResult result = Forest.get(chat2dbProperties.getGateway().getBaseUrl() + "/api/client/whitelist/check") // .connectTimeout(Duration.ofMillis(5000)) // .readTimeout(Duration.ofMillis(10000)) // .addQuery(whiteListRequest) // .execute(new TypeReference<>() { // }); // return result; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/EsTableSchema.java ================================================ package ai.chat2db.server.web.api.http.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class EsTableSchema { private String dataSourceId; private String databaseName; private String apiKey; private String schemaName; private String tableName; private String tableSchemaContent; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/Knowledge.java ================================================ package ai.chat2db.server.web.api.http.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Knowledge { private Long id; private String content; private String contentVector; private Integer wordCount; public Knowledge(Long id, String content, Integer wordCount) { this.id = id; this.content = content; this.wordCount = wordCount; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/TableSchema.java ================================================ package ai.chat2db.server.web.api.http.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableSchema { private Long id; private Long dataSourceId; private String tableSchema; private String tableSchemaVector; private Integer wordCount; public TableSchema(Long id, Long dataSourceId, String tableSchema, Integer wordCount) { this.id = id; this.dataSourceId = dataSourceId; this.tableSchema = tableSchema; this.wordCount = wordCount; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/EsTableSchemaRequest.java ================================================ package ai.chat2db.server.web.api.http.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class EsTableSchemaRequest { private Long dataSourceId; private String databaseName; private String apiKey; private String schemaName; private String tableName; private String tableSchemaContent; private String searchKey; private Boolean deleteBeforeInsert; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/KnowledgeRequest.java ================================================ package ai.chat2db.server.web.api.http.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.math.BigDecimal; import java.util.List; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class KnowledgeRequest { private List> contentVector; private List sentenceList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/SqlExecuteHistoryCreateRequest.java ================================================ package ai.chat2db.server.web.api.http.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** *

    * sql execution history *

    * * @author chat2db * @since 2023-12-25 */ @Data @NoArgsConstructor @AllArgsConstructor @SuperBuilder public class SqlExecuteHistoryCreateRequest implements Serializable { private static final long serialVersionUID = 1L; /** * Database type */ private String databaseType; /** * Execute SQL */ private String sqlContent; /** * Client ID */ private String clientId; /** * state */ private String executeStatus; /** * wrong information */ private String errorMessage; /** * sql type */ private String sqlType; /** * execution duration */ private Long duration; /** * Table Name */ private String tableName; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java ================================================ package ai.chat2db.server.web.api.http.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.math.BigDecimal; import java.util.List; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableSchemaRequest { private Long dataSourceId; private String databaseName; private String apiKey; private String dataSourceSchema; private List> schemaVector; private List schemaList; private Boolean deleteBeforeInsert = false; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/WhiteListRequest.java ================================================ package ai.chat2db.server.web.api.http.request; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class WhiteListRequest { /** * api key */ private String apiKey; /** * Whitelist type, such as vector * @see ai.chat2db.server.tools.base.enums.WhiteListTypeEnum */ private String whiteType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/ApiKeyResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * apikey * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ApiKeyResponse { /** * key */ private String key; /** * Expiration */ private Long expiry; /** * return */ private Long remainingUses; /** * WeChat public account url */ private String wechatMpUrl; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/EsTableSchemaResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import ai.chat2db.server.web.api.http.model.EsTableSchema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.util.List; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class EsTableSchemaResponse { private List tableSchemas; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/InviteQrCodeResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import lombok.Data; @Data public class InviteQrCodeResponse { private String wechatQrCodeUrl; private String tip; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/KnowledgeResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import ai.chat2db.server.web.api.http.model.Knowledge; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.util.List; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class KnowledgeResponse { private List knowledgeList; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/QrCodeResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class QrCodeResponse { /** * When logging in for the first time, the token will be returned, and subsequent services need to poll the token to * check if the user is logged in */ private String token; /** * Return the QR code used by the user to log in */ private String wechatQrCodeUrl; /** * If the user logs in successfully, it will return apiKey. If the front-end detects the presence of apiKey, it * indicates successful login */ private String apiKey; private String tip; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/TableSchemaResponse.java ================================================ package ai.chat2db.server.web.api.http.response; import ai.chat2db.server.web.api.http.model.TableSchema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.util.List; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableSchemaResponse { private List tableSchemas; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/AddToTopic.java ================================================ package ai.chat2db.server.web.api.util; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField; import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff; import java.io.IOException; import java.io.OutputStream; /** * AddToTopic * * @author lzy **/ public class AddToTopic { public static void generateTOC(XWPFDocument document, OutputStream out) throws IOException { String findText = "directory"; String replaceText = ""; for (XWPFParagraph p : document.getParagraphs()) { for (XWPFRun r : p.getRuns()) { int pos = r.getTextPosition(); String text = r.getText(pos); if (text != null && text.contains(findText)) { text = text.replace(findText, replaceText); r.setText(text, 0); addField(p); // addField(p, "TOC \\h"); break; } } } document.write(out); } private static void addField(XWPFParagraph paragraph) { CTSimpleField ctSimpleField = paragraph.getCTP().addNewFldSimple(); ctSimpleField.setInstr("TOC \\o \"1-3\" \\h \\z \\u"); ctSimpleField.setDirty(STOnOff.TRUE); ctSimpleField.addNewR().addNewT().setStringValue("<>"); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/ApplicationContextUtil.java ================================================ package ai.chat2db.server.web.api.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; /** * @author jipengfei * @version : ApplicationContextUtil.java */ @Component @Lazy(false) public class ApplicationContextUtil implements ApplicationContextAware { /** * context object instance */ private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextUtil.applicationContext = applicationContext; } /** * Get applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * Get bean by name * * @param name * @return */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * Get Bean through class. * * @param clazz * @param * @return */ public static T getBean(Class clazz) { return getApplicationContext().getBean(clazz); } /** * Return the specified Bean through name and Clazz * * @param name * @param clazz * @param * @return */ public static T getBean(String name, Class clazz) { return getApplicationContext().getBean(name, clazz); } /** * Get the value in the configuration file * @param key * @return */ public static String getProperty(String key) { return getApplicationContext().getEnvironment().getProperty(key); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java ================================================ package ai.chat2db.server.web.api.util; /** * FileUtil * * @author lzy **/ public class FileUtils { public enum ConfigFile { // navicat connection information file NCX, //dbeaver connection information file DBP } public static String getFileExtension(String fileName) { int dotIndex = fileName.lastIndexOf("."); if (dotIndex > 0) { return fileName.substring(dotIndex + 1).toLowerCase(); } else { return ""; } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/StringUtils.java ================================================ package ai.chat2db.server.web.api.util; import java.util.function.BiFunction; import java.util.function.Function; /** * Add string tool class. In order to be compatible with various JB products, try not to use third-party toolkits. * * @author lzy */ @SuppressWarnings("WeakerAccess") public class StringUtils { private static final String EMPTY_STR = "null"; /** * How to deal with initial letters */ private static final BiFunction, String> FIRST_CHAR_HANDLER_FUN = (str, firstCharFun) -> { int strLen; if (str == null || (strLen = str.length()) == 0) { return str; } final int firstCodepoint = str.codePointAt(0); final int newCodePoint = firstCharFun.apply(firstCodepoint); if (firstCodepoint == newCodePoint) { // already capitalized return str; } // cannot be longer than the char array final int[] newCodePoints = new int[strLen]; int outOffset = 0; // copy the first codepoint newCodePoints[outOffset++] = newCodePoint; for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; ) { final int codepoint = str.codePointAt(inOffset); // copy the remaining ones newCodePoints[outOffset++] = codepoint; inOffset += Character.charCount(codepoint); } return new String(newCodePoints, 0, outOffset); }; public static String isNullOrEmpty(String str) { if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { return ""; } return str; } public static String isNull(String str) { if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { return "--"; } return str; } public static String isNullForHtml(String str) { if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { return "
    "; } return str; } /** * Determine if it is an empty string * * @param cs string * @return whether it is empty */ public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } /** * Capitalize the first letter * * @param str string * @return capitalize the first letter of the result */ public static String capitalize(final String str) { return FIRST_CHAR_HANDLER_FUN.apply(str, Character::toTitleCase); } /** * Lowercase first letter * * @param str string * @return the first letter of the result is lowercase */ public static String uncapitalize(final String str) { return FIRST_CHAR_HANDLER_FUN.apply(str, Character::toLowerCase); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/XMLUtils.java ================================================ /* * DBeaver - Universal Database Manager * Copyright (C) 2010-2023 DBeaver Corp and others * * Licensed 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. */ package ai.chat2db.server.web.api.util; import org.apache.batik.xml.XMLException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Common XML utils */ public class XMLUtils { public static Document parseDocument(String fileName) throws XMLException { return parseDocument(new java.io.File(fileName)); } public static Document parseDocument(java.io.File file) throws XMLException { try (InputStream is = new FileInputStream(file)) { return parseDocument(new InputSource(is)); } catch (IOException e) { throw new XMLException("Error opening file '" + file + "'", e); } } public static Document parseDocument(InputStream is) throws XMLException { return parseDocument(new InputSource(is)); } public static Document parseDocument(java.io.Reader is) throws XMLException { return parseDocument(new InputSource(is)); } public static Document parseDocument(InputSource source) throws XMLException { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder xmlBuilder = dbf.newDocumentBuilder(); return xmlBuilder.parse(source); } catch (Exception er) { throw new XMLException("Error parsing XML document", er); } } public static Document createDocument() throws XMLException { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder xmlBuilder = dbf.newDocumentBuilder(); return xmlBuilder.newDocument(); } catch (Exception er) { throw new XMLException("Error creating XML document", er); } } public static Element getChildElement(Element element, String childName) { if (element == null) { return null; } for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && ((Element) node).getTagName().equals(childName)) { return (Element) node; } } return null; } public static String getChildElementBody(Element element, String childName) { if (element == null) { return null; } for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && ((Element) node).getTagName().equals(childName)) { return getElementBody((Element) node); } } return null; } public static String getElementBody( Element element) { return element.getTextContent(); } // Get list of all child elements of specified node public static List getChildElementList( Element parent, String nodeName) { List list = new ArrayList<>(); if (parent != null) { for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && nodeName.equals(node.getNodeName())) { list.add((Element) node); } } } return list; } // Get list of all child elements of specified node public static Collection getChildElementListNS( Element parent, String nsURI) { List list = new ArrayList<>(); if (parent != null) { for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && node.getNamespaceURI().equals(nsURI)) { list.add((Element) node); } } } return list; } // Get list of all child elements of specified node public static Collection getChildElementListNS( Element parent, String nodeName, String nsURI) { List list = new ArrayList<>(); for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && node.getLocalName().equals(nodeName) && node.getNamespaceURI().equals(nsURI)) { list.add((Element) node); } } return list; } // Get list of all child elements of specified node public static Collection getChildElementList( Element parent, String[] nodeNameList) { List list = new ArrayList<>(); for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE) { for (int i = 0; i < nodeNameList.length; i++) { if (node.getNodeName().equals(nodeNameList[i])) { list.add((Element) node); } } } } return list; } // Find one child element with specified name public static Element findChildElement( Element parent) { for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE) { return (Element) node; } } return null; } public static Object escapeXml(Object obj) { if (obj == null) { return null; } else if (obj instanceof CharSequence) { return escapeXml((CharSequence) obj); } else { return obj; } } public static String escapeXml(CharSequence str) { if (str == null) { return null; } StringBuilder res = null; int strLength = str.length(); for (int i = 0; i < strLength; i++) { char c = str.charAt(i); String repl = encodeXMLChar(c); if (repl == null) { if (res != null) { res.append(c); } } else { if (res == null) { res = new StringBuilder(str.length() + 5); for (int k = 0; k < i; k++) { res.append(str.charAt(k)); } } res.append(repl); } } return res == null ? str.toString() : res.toString(); } public static boolean isValidXMLChar(char c) { return (c >= 32 || c == '\n' || c == '\r' || c == '\t'); } /** * Encodes a char to XML-valid form replacing &,',",<,> with special XML encoding. * * @param ch char to convert * @return XML-encoded text */ public static String encodeXMLChar(char ch) { switch (ch) { case '&': return "&"; case '\"': return """; case '\'': return "'"; case '<': return "<"; case '>': return ">"; default: return null; } } public static XMLException adaptSAXException(Exception toCatch) { if (toCatch instanceof XMLException) { return (XMLException) toCatch; } else if (toCatch instanceof org.xml.sax.SAXException) { String message = toCatch.getMessage(); Exception embedded = ((org.xml.sax.SAXException) toCatch).getException(); if (embedded != null && embedded.getMessage() != null && embedded.getMessage().equals(message)) { // Just SAX wrapper - skip it return adaptSAXException(embedded); } else { return new XMLException( message, embedded != null ? adaptSAXException(embedded) : null); } } else { return new XMLException(toCatch.getMessage(), toCatch); } } public static Collection getChildElementList(Element element) { List children = new ArrayList<>(); if (element != null) { for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE) { children.add((Element) node); } } } return children; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsConfig.java ================================================ package ai.chat2db.server.web.api.ws; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WsConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsMessage.java ================================================ package ai.chat2db.server.web.api.ws; import com.alibaba.fastjson2.JSONObject; import lombok.Data; @Data public class WsMessage { /** * message id */ private String uuid; /** * message content */ private JSONObject message; /** * message type */ private String actionType; public static class ActionType { public static final String EXECUTE = "execute"; public static final String LOGIN = "login"; public static final String PING = "ping"; public static final String OPEN_SESSION = "open_session"; public static final String ERROR = "error"; public static final String MESSAGE = "message"; } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsResult.java ================================================ package ai.chat2db.server.web.api.ws; import ai.chat2db.server.tools.base.wrapper.Result; import lombok.Data; @Data public class WsResult { /** * message id */ private String uuid; /** * message content */ private Result message; /** * message type */ private String actionType; } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsServer.java ================================================ package ai.chat2db.server.web.api.ws; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import com.alibaba.fastjson2.JSONObject; import com.jcraft.jsch.JSchException; import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.context.support.SpringBeanAutowiringSupport; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; @Slf4j @Component @ServerEndpoint("/api/ws/{token}") public class WsServer { private Session session; private static final AtomicInteger OnlineCount = new AtomicInteger(0); // The thread-safe Set of the concurrent package is used to store the Session object corresponding to each client. private static CopyOnWriteArraySet SessionSet = new CopyOnWriteArraySet(); private static int num = 0; private Timer timer = new Timer(); private Map connectInfoMap = new ConcurrentHashMap<>(); private LoginUser loginUser; private WsService wsService; /** * Method called when connection is established successfully */ @OnOpen public void onOpen(Session session, @PathParam("token") String token) throws IOException { SessionSet.add(session); this.session = session; int cnt = OnlineCount.incrementAndGet(); // Add 1 to the online number log.info("There are connections added, and the current number of connections is: {}", cnt); heartBeat(session); this.wsService = ApplicationContextUtil.getBean(WsService.class); Dbutils.setSession(); this.loginUser = wsService.doLogin(token); if (this.loginUser == null) { ActionResult actionResult = new ActionResult(); actionResult.setSuccess(false); actionResult.setErrorCode("LOGIN_FAIL"); WsResult wsMessage = new WsResult(); wsMessage.setActionType(WsMessage.ActionType.OPEN_SESSION); wsMessage.setUuid(token); wsMessage.setMessage(actionResult); SendMessage(this.session, wsMessage); onClose(); }else { ActionResult actionResult = new ActionResult(); actionResult.setSuccess(true); WsResult wsMessage = new WsResult(); wsMessage.setActionType(WsMessage.ActionType.OPEN_SESSION); wsMessage.setUuid(token); wsMessage.setMessage(actionResult); SendMessage(this.session, wsMessage); } Dbutils.removeSession(); } /** * Method called on connection close */ @OnClose public void onClose() throws IOException { if (SessionSet.contains(session)) { SessionSet.remove(this.session); session.close(); for (Map.Entry entry : connectInfoMap.entrySet()) { ConnectInfo connectInfo = entry.getValue(); if (connectInfo != null) { Connection connection = connectInfo.getConnection(); try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (SQLException e) { log.error("close connection error", e); } com.jcraft.jsch.Session session = connectInfo.getSession(); if (session != null && session.isConnected() && connectInfo.getSsh() != null && connectInfo.getSsh().isUse()) { try { session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); } catch (JSchException e) { } } } } int cnt = OnlineCount.decrementAndGet(); log.info("A connection was closed, session:{},{}", session, this); log.info("A connection is closed, the current number of connections is: {}", cnt); } } /** * Method called after receiving client message * * @param message message sent by the client */ @OnMessage(maxMessageSize = 1024000) public void onMessage(String message, Session session) { CompletableFuture.runAsync(() -> { WsMessage wsMessage = JSONObject.parseObject(message, WsMessage.class); // Process your messages here try { String actionType = wsMessage.getActionType(); if (WsMessage.ActionType.PING.equalsIgnoreCase(actionType)) { WsResult wsResult = new WsResult(); ActionResult actionResult = new ActionResult(); actionResult.setSuccess(true); wsResult.setActionType(WsMessage.ActionType.PING); wsResult.setUuid(wsMessage.getUuid()); wsResult.setMessage(actionResult); SendMessage(session, wsResult); timer.cancel(); heartBeat(session); } else { ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); Dbutils.setSession(); JSONObject jsonObject = wsMessage.getMessage(); Long dataSourceId = jsonObject.getLong("dataSourceId"); String databaseName = jsonObject.getString("databaseName"); String schemaName = jsonObject.getString("schemaName"); Long consoleId = jsonObject.getLong("consoleId"); String key = connectInfoKey(dataSourceId, databaseName, schemaName, consoleId); ConnectInfo connectInfo = connectInfoMap.get(key); if (connectInfo == null) { connectInfo = wsService.toInfo(dataSourceId, databaseName, consoleId, schemaName); connectInfoMap.put(key, connectInfo); } Chat2DBContext.putContext(connectInfo); if (WsMessage.ActionType.EXECUTE.equalsIgnoreCase(actionType)) { DmlRequest request = jsonObject.toJavaObject(DmlRequest.class); ListResult result = wsService.execute(request); WsResult resultMessage = new WsResult(); resultMessage.setUuid(wsMessage.getUuid()); resultMessage.setActionType(wsMessage.getActionType()); resultMessage.setMessage(result); SendMessage(session, resultMessage); } } } catch (Exception e) { WsResult wsResult = new WsResult(); ActionResult actionResult = new ActionResult(); actionResult.setSuccess(false); actionResult.setErrorCode(e.getMessage()); wsResult.setActionType(WsMessage.ActionType.ERROR); wsResult.setUuid(wsMessage.getUuid()); wsResult.setMessage(actionResult); SendMessage(session, wsResult); } finally { Chat2DBContext.removeContext(); ContextUtils.removeContext(); Dbutils.removeSession(); } }); } private String connectInfoKey(Long dataSourceId, String databaseName, String schemaName, Long consoleId) { return dataSourceId + "_" + databaseName + "_" + schemaName + "_" + consoleId; } /** * An error occurred * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("An error occurred:{},Session ID: {}", error.getMessage(), session.getId(), error); } /** * heartbeat * * @param session */ private void heartBeat(Session session) { timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { try { onClose(); } catch (IOException e) { log.error("Error sending message:{}", e.getMessage(), e); } } }, 600000); } /** * Sending a message, practice shows that every time the browser refreshes, the session will change. * * @param session * @param wsResult */ public static void SendMessage(Session session, WsResult wsResult) { try { if (session.isOpen()) { session.getBasicRemote().sendText(JSONObject.toJSONString(wsResult)); } } catch (IOException e) { log.error("Error sending message:{}", e.getMessage()); } } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/ws/WsService.java ================================================ package ai.chat2db.server.web.api.ws; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.DlExecuteParam; import ai.chat2db.server.domain.api.service.*; import ai.chat2db.server.domain.core.cache.CacheKey; import ai.chat2db.server.domain.core.cache.MemoryCacheManage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.ConnectInfo; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Component public class WsService { @Autowired private UserService userService; @Autowired private DataSourceService dataSourceService; @Autowired private DataSourceAccessBusinessService dataSourceAccessBusinessService; @Autowired private RdbWebConverter rdbWebConverter; @Autowired private DlTemplateService dlTemplateService; public static ExecutorService executorService = Executors.newFixedThreadPool(10); public ListResult execute(DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); return ListResult.of(resultVOS); } public LoginUser doLogin(String token) { Long userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { User user = userService.query(userId).getData(); if (user == null) { return null; } boolean admin = RoleCodeEnum.ADMIN.getCode().equals(user.getRoleCode()); return LoginUser.builder() .id(user.getId()) .nickName(user.getNickName()) .admin(admin) .roleCode(user.getRoleCode()) .build(); }); loginUser.setToken(userId.toString()); return loginUser; } public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); if (!result.success() || dataSource == null) { throw new ParamBusinessException("dataSourceId"); } // Verify permissions dataSourceAccessBusinessService.checkPermission(dataSource); ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setAlias(dataSource.getAlias()); connectInfo.setUser(dataSource.getUserName()); connectInfo.setConsoleId(consoleId); connectInfo.setDataSourceId(dataSourceId); connectInfo.setPassword(dataSource.getPassword()); connectInfo.setDbType(dataSource.getType()); connectInfo.setUrl(dataSource.getUrl()); connectInfo.setDatabase(database); connectInfo.setSchemaName(schemaName); connectInfo.setConsoleOwn(false); connectInfo.setDriver(dataSource.getDriver()); connectInfo.setSsh(dataSource.getSsh()); connectInfo.setSsl(dataSource.getSsl()); connectInfo.setJdbc(dataSource.getJdbc()); connectInfo.setExtendInfo(dataSource.getExtendInfo()); connectInfo.setUrl(dataSource.getUrl()); connectInfo.setPort(StringUtils.isNotBlank(dataSource.getPort()) ? Integer.parseInt(dataSource.getPort()) : null); connectInfo.setHost(dataSource.getHost()); DriverConfig driverConfig = dataSource.getDriverConfig(); if (driverConfig != null && driverConfig.notEmpty()) { connectInfo.setDriverConfig(driverConfig); } return connectInfo; } private String getApiKey() { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { return null; } return keyConfig.getContent(); } } ================================================ FILE: chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/resources/template/template.html ================================================
      ${catalogue}
    ${data} ================================================ FILE: chat2db-server/chat2db-server-web/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-web pom chat2db-server-web-api chat2db-server-admin-api chat2db-server-common-api ================================================ FILE: chat2db-server/chat2db-server-web-start/pom.xml ================================================ ai.chat2db chat2db-server-parent ${revision} ../pom.xml 4.0.0 chat2db-server-web-start jar chat2db-server-web-start org.springframework.boot spring-boot-starter-web log4j-api org.apache.logging.log4j ai.chat2db chat2db-server-web-api ai.chat2db chat2db-server-admin-api ai.chat2db chat2db-server-domain-core org.slf4j jcl-over-slf4j org.slf4j log4j-over-slf4j ch.qos.logback logback-classic com.h2database h2 org.flywaydb flyway-core org.flywaydb flyway-mysql cn.dev33 sa-token-spring-boot3-starter cn.dev33 sa-token-jwt org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-test test org.freemarker freemarker test com.baomidou mybatis-plus-generator test com.dtflys.forest forest-spring com.dtflys.forest forest-core org.zalando logbook-spring-boot-starter chat2db-server-web-start org.springframework.boot spring-boot-maven-plugin ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/Application.java ================================================ package ai.chat2db.server.web.start; import ai.chat2db.server.tools.common.enums.ModeEnum; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import cn.hutool.core.lang.UUID; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Indexed; /** * Startup class * * @author Jiaju Zhuang */ @SpringBootApplication @ComponentScan(value = {"ai.chat2db.server"}) @Indexed @EnableCaching @EnableScheduling @EnableAsync @Slf4j public class Application { public static void main(String[] args) { long s1 = System.currentTimeMillis(); String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); // The unique ID of the entire system will not change after multiple starts if (StringUtils.isBlank(configJson.getSystemUuid())) { configJson.setSystemUuid(UUID.fastUUID().toString(true)); ConfigUtils.setConfig(configJson); } // Represents that the current version has been successfully launched if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { // Flyway doesn't need to start every time to increase startup speed //args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); log.info("The current version {} has been successfully launched once and will no longer load Flyway.", currentVersion); } ModeEnum mode = EasyEnumUtils.getEnum(ModeEnum.class, System.getProperty("chat2db.mode")); if (mode == ModeEnum.DESKTOP) { // In this mode, no user login is required, so only local access is available args = ArrayUtils.add(args, "--server.address=0.0.0.0"); } String jwtSecretKey = System.getProperty("sa-token.jwt-secret-key"); // The user did not specify the jws key if (StringUtils.isBlank(jwtSecretKey)) { if (StringUtils.isBlank(configJson.getJwtSecretKey())) { configJson.setJwtSecretKey(UUID.fastUUID().toString(true)); ConfigUtils.setConfig(configJson); } // Ensure that the jwt Secret Key for each application is unique args = ArrayUtils.add(args, "--sa-token.jwt-secret-key=" + configJson.getJwtSecretKey()); } System.out.println("启动耗时:" + (System.currentTimeMillis() - s1) + "ms"); SpringApplication.run(Application.class, args); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/StdinReader.java ================================================ package ai.chat2db.server.web.start.config;//package ai.chat2db.server.start.config; // //import jakarta.annotation.PostConstruct; //import org.springframework.boot.context.event.ApplicationReadyEvent; //import org.springframework.context.ApplicationListener; //import org.springframework.stereotype.Component; // //import java.util.Scanner; // //@Component //public class StdinReader implements ApplicationListener { // // // @Override // public void onApplicationEvent(ApplicationReadyEvent event) { // // Start a thread that reads stdin // new Thread(() -> readStdin()).start(); // } // // private void readStdin() { // Scanner scanner = new Scanner(System.in); // while (scanner.hasNextLine()) { // String line = scanner.nextLine(); // // Process the received data // System.out.println("data received: " + line); // // Call other services or logic here // } // } // //} ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/AsyncContextRefreshedListener.java ================================================ package ai.chat2db.server.web.start.config.config; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; /** * Execute tasks after startup is completed * * @author Jiaju Zhuang */ @Component @Slf4j public class AsyncContextRefreshedListener implements ApplicationListener { @Override @Async public void onApplicationEvent(ContextRefreshedEvent event) { // Successfully set up startup String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); if (StringUtils.isNotBlank(currentVersion) && !StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { configJson.setLatestStartupSuccessVersion(currentVersion); ConfigUtils.setConfig(configJson); } Dbutils.init(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbForestConfiguration.java ================================================ package ai.chat2db.server.web.start.config.config; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import com.dtflys.forest.Forest; import jakarta.annotation.Resource; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Configuration; /** * forest config * * @author Jiaju Zhuang */ @Configuration public class Chat2dbForestConfiguration implements InitializingBean { @Resource private Chat2dbProperties chat2dbProperties; @Override public void afterPropertiesSet() throws Exception { Forest.config() .setVariableValue("gatewayBaseUrl", chat2dbProperties.getGateway().getBaseUrl()); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/Chat2dbWebMvcConfigurer.java ================================================ package ai.chat2db.server.web.start.config.config; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.cache.CacheKey; import ai.chat2db.server.domain.core.cache.MemoryCacheManage; import ai.chat2db.server.domain.repository.Dbutils; import ai.chat2db.server.tools.base.constant.SymbolConstant; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.tools.common.enums.ModeEnum; import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; import ai.chat2db.server.tools.common.exception.RedirectBusinessException; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.I18nUtils; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaFoxUtil; import com.alibaba.fastjson2.JSON; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.io.IOException; import java.util.Enumeration; /** * web project configuration * * @author Shi Yi */ @Configuration @Slf4j public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer { /** * api prefix */ private static final String API_PREFIX = "/api/"; /** * Globally released url */ private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**", "/api/system", "/login", "/api/system/get_latest_version"}; @Resource private UserService userService; @Resource private TeamUserService teamUserService; @Resource private Chat2dbProperties chat2dbProperties; @Override public void addInterceptors(InterceptorRegistry registry) { // All requests try to add user information registry.addInterceptor(new AsyncHandlerInterceptor() { @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) { Dbutils.setSession(); String userIdString = (String)StpUtil.getLoginIdDefaultNull(); Long userId; // Not logged in if (!StringUtils.isNumeric(userIdString)) { if (chat2dbProperties.getMode() == ModeEnum.DESKTOP) { userId = RoleCodeEnum.DESKTOP.getDefaultUserId(); } else { return true; } } else { userId = Long.parseLong(userIdString); } Long finalUserId = userId; LoginUser loginUser = MemoryCacheManage.computeIfAbsent(CacheKey.getLoginUserKey(userId), () -> { User user = userService.query(finalUserId).getData(); if (user == null) { return null; } if (!ValidStatusEnum.VALID.getCode().equals(user.getStatus())) { StpUtil.logout(); throw new BusinessException("oauth.invalidUserName"); } boolean admin = RoleCodeEnum.ADMIN.getCode().equals(user.getRoleCode()); return LoginUser.builder() .id(user.getId()) .nickName(user.getNickName()) .admin(admin) .roleCode(user.getRoleCode()) .build(); }); if (loginUser == null) { // Indicates that the user may have been deleted return true; } loginUser.setToken(StpUtil.getTokenValue()); ContextUtils.setContext(Context.builder() .loginUser(loginUser) .build()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Remove login information ContextUtils.removeContext(); Dbutils.removeSession(); } }) .order(1) .addPathPatterns("/**") .excludePathPatterns(FRONT_PERMIT_ALL); // Verify login information registry.addInterceptor(new AsyncHandlerInterceptor() { @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws IOException { Context context = ContextUtils.queryContext(); // Verify login information if (context == null) { log.info("Login is required to access {},{}", buildHeaderString(request), SaHolder.getRequest().getUrl()); String path = SaHolder.getRequest().getRequestPath(); // if(path.startsWith("/login")){ // return true; // } if (path.startsWith(API_PREFIX)) { response.getWriter().println(JSON.toJSONString( ActionResult.fail("common.needLoggedIn", I18nUtils.getMessage("common.needLoggedIn"), ""))); return false; } else { throw new RedirectBusinessException( "/login?callback=" + SaFoxUtil.joinParam(request.getRequestURI(), request.getQueryString())); } } return true; } }) .order(2) .addPathPatterns("/**") // Links that need to be released on the front end .excludePathPatterns(FRONT_PERMIT_ALL) // Uniform release ending in -a .excludePathPatterns("/**/*-a") // Uniform release of endings in _a .excludePathPatterns("/**/*_a"); // Verify permissions registry.addInterceptor(new AsyncHandlerInterceptor() { @Override public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws IOException { LoginUser loginUser = ContextUtils.getLoginUser(); if (BooleanUtils.isNotTrue(loginUser.getAdmin())) { throw new PermissionDeniedBusinessException(); } return true; } }) .order(3) .addPathPatterns("/api/admin/**") .addPathPatterns("/admin/**") ; } private String buildHeaderString(HttpServletRequest request) { StringBuilder stringBuilder = new StringBuilder(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); stringBuilder.append(headerName); stringBuilder.append(SymbolConstant.COLON); stringBuilder.append(request.getHeader(headerName)); stringBuilder.append(SymbolConstant.COMMA); } return stringBuilder.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/JarDownloadTask.java ================================================ package ai.chat2db.server.web.start.config.config; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.JdbcJarUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; /** * @author jipengfei * @version : JarDownloadTask.java */ @Component @Slf4j public class JarDownloadTask implements CommandLineRunner { @Override public void run(String... args) throws Exception { List urls = new ArrayList<>(); Chat2DBContext.PLUGIN_MAP.forEach((k, v) -> { v.getDBConfig().getDriverConfigList().forEach(driverConfig -> { if (driverConfig != null && !CollectionUtils.isEmpty(driverConfig.getDownloadJdbcDriverUrls()) && ( "MYSQL".equals(driverConfig.getDbType()))) { urls.addAll(driverConfig.getDownloadJdbcDriverUrls()); } }); }); JdbcJarUtils.asyncDownload(urls); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/config/WebLogConfiguration.java ================================================ package ai.chat2db.server.web.start.config.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.zalando.logbook.BodyFilter; /** * log config * * @author Jiaju Zhuang */ @Configuration public class WebLogConfiguration { @Bean public BodyFilter bodyFilter() { return BodyFilter.none(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/i18n/I18nConfig.java ================================================ package ai.chat2db.server.web.start.config.i18n; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.i18n.CookieLocaleResolver; import java.util.Locale; /** * Internationalized configuration * * @author Jiaju Zhuang */ @Configuration public class I18nConfig { @Bean public CookieLocaleResolver localeResolver() { CookieLocaleResolver resolver = new CookieLocaleResolver("CHAT2DB.LOCALE"); resolver.setDefaultLocale(Locale.US); return resolver; } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/interceptor/CorsFilter.java ================================================ package ai.chat2db.server.web.start.config.interceptor; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import java.io.IOException; /** * Cors cross-domain interceptor, allowing cross-domain under any circumstances * * There will be a problem when logging in across domains through the CorsRegistry policy, but it does not happen locally. The possible reason is: the loading order of the beans. * Temporarily solved through CorsFilter, you can study it later: CorsRegistry * * @author Shi Yi */ @Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse)res; HttpServletRequest request = (HttpServletRequest)req; response.setHeader("Access-Control-Allow-Origin", request.getHeader(HttpHeaders.ORIGIN)); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, DBHUB, uid"); chain.doFilter(req, res); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/listener/DbhubTomcatConnectorCustomizer.java ================================================ package ai.chat2db.server.web.start.config.listener;//package ai.chat2db.server.start.config.listener; // //import ai.chat2db.server.web.api.controller.system.util.SystemUtils; //import lombok.extern.slf4j.Slf4j; //import org.apache.catalina.LifecycleState; //import org.apache.catalina.connector.Connector; //import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; //import org.springframework.stereotype.Component; // ///** // * Custom tomcat parameters // * // * @author Jiaju Zhuang // */ //@Component //@Slf4j //public class DbhubTomcatConnectorCustomizer implements TomcatConnectorCustomizer { // @Override // public void customize(Connector connector) { // connector.addLifecycleListener(event -> { // // Exit the system directly after receiving the shutdown event, because sometimes the system will not exit. // if (LifecycleState.STOPPING == event.getLifecycle().getState()) { // SystemUtils.stop(); // } // }); // } //} ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/listener/FailedEventApplicationListener.java ================================================ package ai.chat2db.server.web.start.config.listener; import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.context.ApplicationListener; /** * Listener for application startup failure * The application failed to start. It just stopped tomcat and did not stop the application. Stop xia here. * * @author Jiaju Zhuang */ @Slf4j public class FailedEventApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationFailedEvent event) { log.error("Failed to start, stop application", event.getException()); SystemUtils.stop(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/listener/ManageApplicationListener.java ================================================ package ai.chat2db.server.web.start.config.listener;//package ai.chat2db.server.start.config.listener; // //import com.alibaba.fastjson2.JSON; //import com.alibaba.fastjson2.TypeReference; // //import ai.chat2db.server.tools.base.enums.SystemEnvironmentEnum; //import ai.chat2db.server.tools.base.wrapper.result.DataResult; //import cn.hutool.http.HttpUtil; //import lombok.extern.slf4j.Slf4j; //import org.apache.commons.lang3.BooleanUtils; //import org.apache.commons.lang3.StringUtils; //import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; //import org.springframework.context.ApplicationListener; //import org.springframework.util.Assert; // ///** // * Used to manage startup // * Prevent starting multiple // * // * @author zhuangjiaju // * @date 2023/05/08 // */ //@Slf4j //public class ManageApplicationListener implements ApplicationListener { // // @Override // public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { // Integer serverPort = event.getEnvironment().getProperty("server.port", Integer.class); // Assert.notNull(serverPort, "server.port configuration information"); // log.info("The startup port is: {}", serverPort); // String environment = event.getEnvironment().getProperty("spring.profiles.active", String.class); // // // Try to access to confirm whether the application has been started // DataResult dataResult; // try { // String body = HttpUtil.get("http://127.0.0.1:" + serverPort + "/api/system/get-version-a", 10); // dataResult = JSON.parseObject(body, new TypeReference<>() {}); // } catch (Exception e) { // // Throwing an exception means that there is no old startup or the old one is unreliable. // log.info("Attempts to access old applications failed. This exception is not important. It will be output during normal startup, so please ignore it." + e.getMessage()); // // // Try killing the old process // killOldIfNecessary(environment); // return; // } // // if (dataResult == null || BooleanUtils.isNotTrue(dataResult.getSuccess())) { // // Try killing the old process // killOldIfNecessary(environment); // return; // } // // // Indicates that the old process is available // log.info("There is already a started application on the current interface, and this application is no longer started."); // System.exit(0); // } // // private void killOldIfNecessary(String environment) { // try { // ProcessHandle.allProcesses().forEach(process -> { // String command = process.info().command().orElse(null); // // Not a java application // boolean isJava = StringUtils.endsWithIgnoreCase(command, "java") || StringUtils.endsWithIgnoreCase( // command, // "java.exe"); // if (!isJava) { // return; // } // String[] arguments = process.info().arguments().orElse(null); // // no parameters // if (arguments == null) { // return; // } // // Is it dbhub? // boolean isDbhub = false; // String environmentArgument = null; // for (String argument : arguments) { // if (StringUtils.equals("chat2db-server-start.jar", argument)) { // isDbhub = true; // } // if (StringUtils.startsWith(argument, "-Dspring.profiles.active=")) { // environmentArgument = StringUtils.substringAfter(argument, "-Dspring.profiles.active="); // } // } // // Not dbhub // if (!isDbhub) { // return; // } // // Determine whether it is a formal environment // if (StringUtils.equals(SystemEnvironmentEnum.RELEASE.getCode(), environment) && StringUtils.equals( // SystemEnvironmentEnum.RELEASE.getCode(), environmentArgument)) { // log.info("The formal environment requires closing the process"); // destroyProcess(process, command, arguments); // return; // } // // // Determine whether it is a test environment // if (StringUtils.equals(SystemEnvironmentEnum.TEST.getCode(), environment) && StringUtils.equals( // SystemEnvironmentEnum.TEST.getCode(), environmentArgument)) { // log.info("The test environment needs to shut down the process"); // destroyProcess(process, command, arguments); // return; // } // // // Determine whether it is a local environment // boolean devDestroy = StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environment) && ( // environmentArgument == null // || StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environmentArgument)); // if (devDestroy) { // log.info("The local environment needs to close the process"); // destroyProcess(process, command, arguments); // } // }); // } catch (Throwable t) { // log.warn("Attempts to close redundant processes failed and did not affect normal startup.", t); // } // // } // // private void destroyProcess(ProcessHandle process, String command, String[] arguments) { // log.info("Checked that there are processes that need to be shut down:{},{}", JSON.toJSONString(command), JSON.toJSONString(arguments)); // try { // process.destroy(); // } catch (Exception e) { // log.error("Failed to end process", e); // } // } //} ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/listener/manage/ManageMessage.java ================================================ package ai.chat2db.server.web.start.config.listener.manage; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serial; import java.io.Serializable; /** * Administrative messages * * @author Jiaju Zhuang */ @Data @SuperBuilder @AllArgsConstructor @NoArgsConstructor public class ManageMessage implements Serializable { @Serial private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Message type * * @see MessageTypeEnum */ private MessageTypeEnum messageTypeEnum; } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/listener/manage/MessageTypeEnum.java ================================================ package ai.chat2db.server.web.start.config.listener.manage; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Message type enum * * @author Jiaju Zhuang */ @Getter public enum MessageTypeEnum implements BaseEnum { /** * Check if it works properly */ HEARTBEAT, ; @Override public String getCode() { return this.name(); } @Override public String getDescription() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/mybatis/MyBatisPlusConfig.java ================================================ package ai.chat2db.server.web.start.config.mybatis; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author moji * @version MyBatisPlusConfig.java, v 0.1 September 29, 2022 17:38 moji Exp $ * @date 2022/09/29 */ @Configuration public class MyBatisPlusConfig { /** * myBatisPlus Pagination plugin */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mybatisPlusInterceptor; } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/oauth/SaLogForSlf4j.java ================================================ package ai.chat2db.server.web.start.config.oauth; import cn.dev33.satoken.log.SaLog; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * satoken Log printing * * @author Shi Yi */ @Slf4j @Component public class SaLogForSlf4j implements SaLog { @Override public void trace(String str, Object... args) { log.trace(str, args); } @Override public void debug(String str, Object... args) { log.debug(str, args); } @Override public void info(String str, Object... args) { log.info(str, args); } @Override public void warn(String str, Object... args) { log.trace(str, args); } @Override public void error(String str, Object... args) { log.error(str, args); } @Override public void fatal(String str, Object... args) { log.error(str, args); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/config/oauth/SaTokenConfigure.java ================================================ package ai.chat2db.server.web.start.config.oauth; import cn.dev33.satoken.jwt.StpLogicJwtForStateless; import cn.dev33.satoken.stp.StpLogic; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * satoken placement * * @author Shi Yi */ @Configuration public class SaTokenConfigure { @Bean public StpLogic ttpLogic() { // Login display is stateless, so there is no need to rely on redis or the like. // Can it be changed to ehcahe storage disk later? return new StpLogicJwtForStateless(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/OauthController.java ================================================ package ai.chat2db.server.web.start.controller.oauth; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.web.start.controller.oauth.request.LoginRequest; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.hutool.crypto.digest.DigestUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.Objects; /** * Login authorization service * * @author Jiaju Zhuang */ @RestController @RequestMapping("/api/oauth") @Slf4j public class OauthController { @Resource private UserService userService; /** * Login with username and password * * @param request * @return */ @PostMapping("login_a") public DataResult login(@Validated @RequestBody LoginRequest request) { // Query user User user = userService.query(request.getUserName()).getData(); this.validateUser(user); // Successfully logged in without modifying the administrator password if (this.validateAdmin(user)) { return DataResult.of(doLogin(user)); } if (!DigestUtil.bcryptCheck(request.getPassword(), user.getPassword())) { throw new BusinessException("oauth.passwordIncorrect"); } return DataResult.of(doLogin(user)); } private boolean validateAdmin(final @NotNull User user) { return RoleCodeEnum.ADMIN.getDefaultUserId().equals(user.getId()) && RoleCodeEnum.ADMIN.getPassword().equals( user.getPassword()); } private void validateUser(final User user) { if (Objects.isNull(user)) { throw new BusinessException("oauth.userNameNotExits"); } if (!ValidStatusEnum.VALID.getCode().equals(user.getStatus())) { throw new BusinessException("oauth.invalidUserName"); } if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(user.getId())) { throw new BusinessException("oauth.IllegalUserName"); } } private Object doLogin(User user) { StpUtil.login(user.getId()); return SaHolder.getStorage().get(SaTokenConsts.JUST_CREATED_NOT_PREFIX); } /** * Sign out * * @return */ @PostMapping("logout_a") public ActionResult logout() { StpUtil.logout(); return ActionResult.isSuccess(); } /** * user * * @return */ @GetMapping("user") public DataResult user() { return DataResult.of(getLoginUser()); } /** * user * * @return */ @GetMapping("user_a") public DataResult usera() { return DataResult.of(getLoginUser()); } private LoginUser getLoginUser() { return ContextUtils.queryLoginUser(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/oauth/request/LoginRequest.java ================================================ package ai.chat2db.server.web.start.controller.oauth.request; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Log in * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class LoginRequest { /** * userName */ @NotNull(message = "Username can not be empty") private String userName; /** * password */ @NotNull(message = "password can not be blank") private String password; } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/controller/thymeleaf/ThymeleafController.java ================================================ package ai.chat2db.server.web.start.controller.thymeleaf; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Template engine configuration * * @author Jiaju Zhuang */ @Controller @Slf4j @Order(Integer.MIN_VALUE) public class ThymeleafController { /** * Front-end template settings * * @return */ @GetMapping(value = {"/", "/web/", "/web/**","/login","/workspace","/dashboard","/connections","/team"}) public String index() { return "index"; } @RequestMapping(value = "/chat.html", method={RequestMethod.GET}, produces="text/html;charset=utf-8") public String chat(){ return "chat"; } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/EasyControllerExceptionHandler.java ================================================ package ai.chat2db.server.web.start.exception; import ai.chat2db.server.web.start.exception.convertor.*; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.excption.SystemException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.exception.NeedLoggedInBusinessException; import ai.chat2db.server.tools.common.exception.RedirectBusinessException; import com.alibaba.fastjson2.JSON; import com.google.common.collect.Maps; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.ModelAndView; import java.util.Map; /** * Intercepting Controller exceptions * * @author Shi Yi */ @ControllerAdvice @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) public class EasyControllerExceptionHandler { /** * All exception handling converters */ public static final Map, ExceptionConvertor> EXCEPTION_CONVERTOR_MAP = Maps.newHashMap(); static { EXCEPTION_CONVERTOR_MAP.put(MethodArgumentNotValidException.class, new MethodArgumentNotValidExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(BindException.class, new BindExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(BusinessException.class, new BusinessExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(NeedLoggedInBusinessException.class, new BusinessExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MissingServletRequestParameterException.class, new ParamExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(IllegalArgumentException.class, new ParamExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MethodArgumentTypeMismatchException.class, new MethodArgumentTypeMismatchExceptionConvertor()); EXCEPTION_CONVERTOR_MAP.put(MaxUploadSizeExceededException.class, new MaxUploadSizeExceededExceptionConvertor()); } /** * Default converter */ public static ExceptionConvertor DEFAULT_EXCEPTION_CONVERTOR = new DefaultExceptionConvertor(); /** * Business abnormality * * @param request request * @param exception exception * @return return */ @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, IllegalArgumentException.class, MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class, BusinessException.class, MaxUploadSizeExceededException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MultipartException.class, MissingRequestHeaderException.class, HttpMediaTypeNotSupportedException.class, NeedLoggedInBusinessException.class}) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handleBusinessException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.info("Business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } /** * Business abnormality * * @param request request * @param exception exception * @return return */ @ExceptionHandler({RedirectBusinessException.class}) public ModelAndView handleModelAndViewBizException(HttpServletRequest request, Exception exception) { ModelAndView result = translateModelAndView(exception); log.info("ModelAndView business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } public ModelAndView translateModelAndView(Throwable exception) { // Parameter exception if (exception instanceof RedirectBusinessException) { RedirectBusinessException e = (RedirectBusinessException)exception; return dealResponseModelAndView(null, e.getMessage(), e.getRedirect(), null, null); } // Jump to homepage by default return new ModelAndView("redirect:/"); } private ModelAndView dealResponseModelAndView(String title, String errorMessage, String redirect, String href, String buttonText) { // If there is redirection information, jump if (StringUtils.isNotBlank(redirect)) { return new ModelAndView("redirect:" + redirect); } // Jump to homepage by default return new ModelAndView("redirect:/"); // synchronous request //return ModelAndViewUtils.error(title, errorMessage,href,buttonText); } /** * System exception * * @param request request * @param exception exception * @return return */ @ExceptionHandler({SystemException.class}) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handleSystemException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.error("Business exception occurred{}:{}", request.getRequestURI(), result, exception); return result; } /** * Unknown exception requires manual intervention to view logs * * @param request request * @param exception exception * @return return */ @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.OK) @ResponseBody public ActionResult handledException(HttpServletRequest request, Exception exception) { ActionResult result = convert(exception); log.error("An unknown exception occurred {}:{}, request parameters:{}", request.getRequestURI(), result, JSON.toJSONString(request.getParameterMap()), exception); return result; } public ActionResult convert(Throwable exception) { ExceptionConvertor exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(exception.getClass()); if (exceptionConvertor == null) { if (exception instanceof BusinessException) { exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(BusinessException.class); } else if (exception instanceof SystemException) { exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(SystemException.class); } else { exceptionConvertor = DEFAULT_EXCEPTION_CONVERTOR; } } return exceptionConvertor.convert(exception); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/BindExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.validation.BindException; /** * BindException * * @author Shi Yi */ public class BindExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(BindException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/BusinessExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; /** * BusinessException * * @author Shi Yi */ public class BusinessExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(BusinessException exception) { return ActionResult.fail(exception.getCode(), I18nUtils.getMessage(exception.getCode(), exception.getArgs()), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/DefaultExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; /** * Default exception handling * Throw system exception directly * * @author Shi Yi */ public class DefaultExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(Throwable exception) { return ActionResult.fail("common.systemError", I18nUtils.getMessage("common.systemError"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/ExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; /** * exception converter * * @author Shi Yi */ public interface ExceptionConvertor { /** * Conversion exception * * @param exception * @return */ ActionResult convert(T exception); } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/ExceptionConvertorUtils.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.constant.SymbolConstant; import ai.chat2db.server.tools.common.util.I18nUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import java.util.List; /** * Conversion tool class * * @author Shi Yi */ public class ExceptionConvertorUtils { /** * Extract error message from BindingResult * * @param result * @return */ public static String buildMessage(BindingResult result) { List errors = result.getAllErrors(); if (CollectionUtils.isEmpty(errors)) { return null; } int index = 1; StringBuilder msg = new StringBuilder(); msg.append(I18nUtils.getMessage("common.paramCheckError")); for (ObjectError e : errors) { msg.append(index++); // got error message msg.append(SymbolConstant.DOT); if (e instanceof FieldError fieldError) { msg.append(fieldError.getField()); msg.append(" : "); } msg.append(e.getDefaultMessage()); msg.append(SymbolConstant.SEMICOLON); } return msg.toString(); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.multipart.MaxUploadSizeExceededException; /** * MaxUploadSizeExceededException * * @author Shi Yi */ public class MaxUploadSizeExceededExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MaxUploadSizeExceededException exception) { return ActionResult.fail("common.maxUploadSize", I18nUtils.getMessage("common.maxUploadSize"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.bind.MethodArgumentNotValidException; /** * MethodArgumentNotValidException * * @author Shi Yi */ public class MethodArgumentNotValidExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MethodArgumentNotValidException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; /** * MethodArgumentTypeMismatchException * * @author Shi Yi */ public class MethodArgumentTypeMismatchExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(MethodArgumentTypeMismatchException exception) { return ActionResult.fail("common.paramError", I18nUtils.getMessage("common.paramError"), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/exception/convertor/ParamExceptionConvertor.java ================================================ package ai.chat2db.server.web.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.spi.util.ExceptionUtils; /** * Parameter exceptions currently include: * ConstraintViolationException * MissingServletRequestParameterException * IllegalArgumentException * * @author Shi Yi */ public class ParamExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(Throwable exception) { return ActionResult.fail("common.paramError", exception.getMessage(), ExceptionUtils.getErrorInfoFromException(exception)); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/log/EasyLogSink.java ================================================ package ai.chat2db.server.web.start.log; import ai.chat2db.server.tools.common.util.LogUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.thymeleaf.util.ContentTypeUtils; import org.zalando.logbook.*; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.ZoneId; /** * log * * @author Jiaju Zhuang */ @Slf4j @Component public class EasyLogSink implements Sink { @Override public void write(final Precorrelation precorrelation, final HttpRequest request) { } @Override public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response) { try { printLog(correlation, request, response); } catch (Exception e) { log.error("Log exceptions", e); } } public void printLog(final Correlation correlation, final HttpRequest request, final HttpResponse response) throws IOException { // Encapsulate log object WebLog webLog = new WebLog(); String method = request.getMethod(); // path String path = request.getPath(); webLog.setMethod(method); webLog.setPath(LogUtils.cutLog(path)); webLog.setQuery(LogUtils.cutLog(request.getQuery())); webLog.setDuration(correlation.getDuration().toMillis()); webLog.setStartTime(LocalDateTime.ofInstant(correlation.getStart(), ZoneId.systemDefault())); webLog.setEndTime(LocalDateTime.ofInstant(correlation.getEnd(), ZoneId.systemDefault())); try { webLog.setRequest(LogUtils.maskString(LogUtils.cutLog(new String(request.getBody(), StandardCharsets.UTF_8)))); if (ContentTypeUtils.isContentTypeJSON(response.getContentType()) || ContentTypeUtils.isContentTypeHTML( response.getContentType())) { webLog.setResponse(LogUtils.maskString(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8)))); } else { webLog.setResponse(response.getContentType() + ":[" + response.getBody().length + "]"); } } catch (IOException e) { log.warn("The request to obtain the log & returns an exception. Most likely, the user has closed the stream.", e); } webLog.setIp(LogUtils.getClientIp(request)); String pathAndQuery = path; if (StringUtils.isNotBlank(webLog.getQuery())) { pathAndQuery += "?" + webLog.getQuery(); } log.info("http : {}|{}|{}|{}|{}", webLog.getMethod(), pathAndQuery, webLog.getDuration(), webLog.getRequest(), webLog.getResponse()); } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/log/LogOncePerRequestFilter.java ================================================ package ai.chat2db.server.web.start.log; import ai.chat2db.server.tools.common.util.LogUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; /** * Intercept the log and put in the trace id * * @author Jiaju Zhuang */ @Order(Ordered.HIGHEST_PRECEDENCE) @Component @Slf4j public class LogOncePerRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { MDC.put(LogUtils.TRACE_ID, LogUtils.generateTraceId()); filterChain.doFilter(request, response); } finally { MDC.remove(LogUtils.TRACE_ID); } } } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/java/ai/chat2db/server/web/start/log/WebLog.java ================================================ package ai.chat2db.server.web.start.log; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; /** * log object * * @author Shi Yi */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class WebLog { /** * call method */ private String method; /** * path */ private String path; /** * Query conditions */ private String query; /** * Time consuming ms */ private Long duration; /** * Time consuming ms */ private LocalDateTime startTime; /** * Time consuming ms */ private LocalDateTime endTime; /** * request */ private String request; /** * response */ private String response; /** * IP address */ private String ip; } ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/META-INF/spring.factories ================================================ ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/application-dev.yml ================================================ #spring: # datasource: # # Configure the relative path of the built-in database # url: jdbc:h2:~/.chat2db/db/chat2db_dev;FILE_LOCK=NO;MODE=MYSQL # driver-class-name: org.h2.Driver # The port number server: port: 10821 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/application-release.yml ================================================ #spring: # datasource: # # Configure the relative path of the built-in database # url: jdbc:h2:~/.chat2db/db/chat2db;MODE=MYSQL;FILE_LOCK=NO # driver-class-name: org.h2.Driver # The port number server: port: 10824 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/application-test.yml ================================================ # port server: port: 10822 chat2db: gateway: base-url: http://test.sqlgpt.cn/gateway model-base-url: http://test.sqlgpt.cn/gateway ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/application.yml ================================================ spring: # Default development environment profiles: active: dev main: allow-bean-definition-overriding: true messages: basename: i18n/messages encoding: UTF-8 fallbackToSystemLocale: true jmx: enabled: false # thymeleaf thymeleaf: prefix: classpath:/thymeleaf/ check-template-location: true suffix: .html servlet: content-type: text/html mode: HTML5 # static files mvc: static-path-pattern: /static/** web: resources: static-locations[0]: classpath:/static/ # Used for database table structure version management flyway: enabled: false servlet: multipart: max-file-size: -1 max-request-size: -1 jackson: serialization: write-dates-as-timestamps: true chat2db: version: 1.0.0 # flywaydb outputs the log of executing sql logging: level: org: flywaydb: debug ai: chat2db: server: domain: repository: mapper: debug # Login function sa-token: # token name (also cookie name) token-name: CHAT2DB timeout: 2592000 # Whether to allow concurrent logins with the same account (when true, simultaneous logins are allowed, when false, new logins crowd out old logins) is-concurrent: true # When multiple people log in to the same account, whether to share a token (when true, all logins share a token, when false, a new token is created for each login) is-share: true # token style token-style: uuid # Whether to output operation logs is-log: true is-write-header: true chatgpt: apiKey: sk-xxxx apiHost: https://api.openai.com/ # You can choose GPT3 or GPT35 version: GPT35 context: length: 1 # Print the HTTP log logbook: include: - /api/** # Http Client forest: connect-timeout: 30000 log-response-content: true read-timeout: 30000 timeout: 30000 ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages.properties ================================================ # common common.businessError=Please try resubmitting or refreshing the page later common.systemError=An exception occurs, you can view the exception details in the log in the help menu. common.needLoggedIn=Login required common.redirect=Redirect common.paramError=The parameter is incorrect common.paramDetailError=The parameter: {0} is incorrect common.paramCheckError=The following parameters are not valid: common.maxUploadSize=The file exceeds the maximum limit common.permissionDenied=Permission denied common.dataNotFound=Data not found common.dataAlreadyExists=The data already exists in the database common.dataAlreadyExistsWithParam=The data already exists in the database,{0}:{1} oauth.userNameNotExits=The current account does not exist oauth.IllegalUserName=The current account cannot be logged in. Please change your account oauth.passwordIncorrect=The password you entered is incorrect oauth.invalidUserName=The current account is invalid # dataSource dataSource.sqlAnalysisError=Invalid statements # connection connection.error=Connection failed, please check the connection information connection.ssh.error=SSH connection failed, please check the connection information connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_en_US.properties ================================================ common.businessError=Please try resubmitting or refreshing the page later common.systemError=An exception occurs, you can view the exception details in the log in the help menu. common.needLoggedIn=Login required common.redirect=Redirect common.paramError=The parameter is incorrect common.paramDetailError=The parameter: {0} is incorrect common.paramCheckError=The following parameters are not valid common.maxUploadSize=The file exceeds the maximum limit common.permissionDenied=Permission denied common.dataNotFound=Data not found common.dataAlreadyExists=The data already exists in the database common.dataAlreadyExistsWithParam=The data already exists in the database,{0}:{1} oauth.userNameNotExits=The current account does not exist oauth.IllegalUserName=The current account cannot be logged in. Please change your account oauth.passwordError=The password you entered is incorrect oauth.invalidUserName=The current account is invalid dataSource.sqlAnalysisError=Invalid statements connection.error=Connection failed, please check the connection information connection.ssh.error=SSH connection failed, please check the connection information connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" main.indexName=Name main.indexFieldName=Column Name main.indexType=Index Type main.indexMethod=Index Method main.indexNote=Index Comment main.fieldNo=No main.fieldName=Field Name main.fieldType=Column Type main.fieldLength=Length main.fieldIfEmpty=Nullable main.fieldDefault=Column Default main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment main.databaseText=Database: main.sheetName=Table Structure ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/i18n/messages_zh_CN.properties ================================================ common.businessError=请尝试重新提交或者刷新页面 common.systemError=系统发生异常,可在帮助中点击日志查看异常详情。 common.needLoggedIn=需要登陆页面 common.redirect=重定向页面 common.paramError=您输入的参数异常 common.paramDetailError=您输入的参数:{0},存在异常 common.paramCheckError=请检查以下参数: common.maxUploadSize=您输入的文件超过最大限制 common.permissionDenied=您没有权限访问该页面 common.dataNotFound=您访问的数据不存在 common.dataAlreadyExists=数据库总已经存在该数据 common.dataAlreadyExistsWithParam=数据库总已经存在该数据,{0}:{1} oauth.userNameNotExits=当前账号不存在 oauth.IllegalUserName=当前账号无法登录,请换一个账号 oauth.passwordIncorrect=您输入的密码有误 oauth.invalidUserName=您输入的账号已经失效 dataSource.sqlAnalysisError=不合法的执行语句 connection.error=数据库链接异常,请检查数据库配置 connection.ssh.error=SSH 链接异常,请检查SSH配置 connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 # sqlResult sqlResult.rowNumber=行号 sqlResult.success=执行成功 user.canNotOperateSystemAccount=不能操作系统账号 execute.exportCsv=更多数据请点击导出csv settings.permissionDeniedForAiConfig=请联系管理员在 “设置->自定义AI” 里面设置ApiKey main.indexName=名称 main.indexFieldName=字段 main.indexType=索引类型 main.indexMethod=索引方法 main.indexNote=注释 main.fieldNo=序号 main.fieldName=字段名 main.fieldType=数据类型 main.fieldLength=长度 main.fieldIfEmpty=不是null main.fieldDefault=默认值 main.fieldDecimalPlaces=小数位 main.fieldNote=备注 main.databaseText=数据库: main.sheetName=表结构 ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/logback-spring.xml ================================================ ${LOG_FILE} ${EASY_FILE_LOG_PATTERN} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 1GB 10GB ${EASY_CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: chat2db-server/chat2db-server-web-start/src/main/resources/thymeleaf/template.html ================================================ Chat2DB ================================================ FILE: chat2db-server/chat2db-spi/pom.xml ================================================ chat2db-server-parent ai.chat2db ${revision} ../pom.xml 4.0.0 chat2db-spi ai.chat2db chat2db-server-tools-common com.alibaba druid com.h2database h2 org.springframework spring-jdbc com.github.mwiede jsch 0.2.9 org.bouncycastle bcprov-jdk18on 1.71 com.oracle.ojdbc orai18n 19.3.0.0 test mysql mysql-connector-java 8.0.30 test com.squareup.okhttp3 okhttp com.zaxxer HikariCP com.baomidou mybatis-plus commons-beanutils commons-beanutils com.github.jsqlparser jsqlparser 4.6 org.locationtech.jts jts-core 1.19.0 org.antlr antlr4 4.9.1 com.oceanbase ob-sql-parser 1.2.1 org.apache.tika tika-core 2.7.0 org.apache.maven.plugins maven-source-plugin 3.2.1 attach-sources jar ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.model.TableColumn; public interface ColumnBuilder { /** * Generate column sql * @param column * @return */ String buildCreateColumnSql(TableColumn column); /** * Build modify column sql * @param tableColumn * @return */ String buildModifyColumn(TableColumn tableColumn); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/CommandExecutor.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.model.Command; import ai.chat2db.spi.model.ExecuteResult; import java.sql.Connection; import java.sql.SQLException; import java.util.List; /** * Command executor *

    * The command executor is used to execute the command. *
    */ public interface CommandExecutor { /** * Execute command */ List execute(Command command); /** * Execute command */ ExecuteResult executeUpdate(String sql, Connection connection, int n)throws SQLException; /** * Execute command */ List executeSelectTable(Command command); /** * * */ ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count) throws SQLException; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.model.AsyncContext; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.ConnectInfo; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.sql.Connection; import java.sql.SQLException; /** * @author jipengfei * @version : DBManage.java */ public interface DBManage { /** * Create connection * * @param connectInfo */ Connection getConnection(ConnectInfo connectInfo); /** * @param database */ void connectDatabase(Connection connection, String database); /** * Modify database name * * @param databaseName * @param newDatabaseName */ void modifyDatabase(Connection connection, String databaseName, String newDatabaseName); /** * Create database * * @param databaseName */ void createDatabase(Connection connection, String databaseName); /** * Delete database * * @param databaseName */ void dropDatabase(Connection connection, String databaseName); /** * Create schema * * @param databaseName * @param schemaName */ void createSchema(Connection connection, String databaseName, String schemaName); /** * Delete schema * * @param databaseName * @param schemaName */ void dropSchema(Connection connection, String databaseName, String schemaName); /** * Modify schema * * @param databaseName * @param schemaName * @param newSchemaName */ void modifySchema(Connection connection, String databaseName, String schemaName, String newSchemaName); /** * Delete table structure * * @param databaseName * @param tableName * @return */ void dropTable(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Delete sequence structure * * @param databaseName * @param sequenceName * @return */ void dropSequence(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String sequenceName); /** * delete function * * @param databaseName * @param functionName * @return */ void dropFunction(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String functionName); /** * delete trigger * * @param databaseName * @param triggerName * @return */ void dropTrigger(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String triggerName); /** * Delete stored procedure * * @param databaseName * @param triggerName * @return */ void dropProcedure(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String triggerName); /** * Update stored procedure * @param connection * @param databaseName * @param schemaName * @param procedure */ void updateProcedure(Connection connection, @NotEmpty String databaseName, String schemaName, @NotNull Procedure procedure) throws SQLException; /** * Export database * * @param databaseName * @param schemaName * @return */ void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException; /** * Export database data * * @param databaseName * @param schemaName * @param tableName * @return */ void exportTable(Connection connection, String databaseName, String schemaName,String tableName,AsyncContext asyncContext) throws SQLException; /** * truncate table * @param connection * @param databaseName * @param schemaName * @param tableName * @throws SQLException */ void truncateTable(Connection connection, String databaseName, String schemaName, String tableName)throws SQLException; /** * copy table * * @param databaseName * @param schemaName * @param tableName * @param newTableName * @return */ void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException; /** * delete procedure * * @param databaseName * @param schemaName * @param procedure */ void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure); /** * delete function * * @param databaseName * @param schemaName * @param function */ void deleteFunction(Connection connection, String databaseName, String schemaName, Function function); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java ================================================ package ai.chat2db.spi; import java.sql.Connection; import java.util.List; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.spi.model.*; import jakarta.validation.constraints.NotEmpty; /** * Get database metadata information. * * @author jipengfei * @version : MetaData.java */ public interface MetaData { /** * Query all databases. * * @param connection * @return */ List databases(Connection connection); /** * Querying all schemas under a database * * @param connection * @param databaseName * @return */ List schemas(Connection connection, String databaseName); /** * Querying DDL information * * @param connection * @param databaseName * @param tableName * @return */ String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Querying all table under a schema. * * @param connection * @param databaseName * @param schemaName * @param tableName * @return */ List

    tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName); /** * Querying all table name under a schema. * @param connection * @param databaseName * @param schemaName * @param tableName * @return */ List tableNames(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName); /** * Querying all table under a schema. * * @param connection * @param databaseName * @param schemaName * @param tableNamePattern * @param pageNo * @param pageSize * @return */ PageResult
    tables(Connection connection, String databaseName, String schemaName, String tableNamePattern, int pageNo, int pageSize); /** * Querying view information. * * @param connection * @param databaseName * @param schemaName * @param viewName * @return */ Table view(Connection connection, @NotEmpty String databaseName, String schemaName, String viewName); /** query view names * @param connection * @param databaseName * @param schemaName * @return */ List viewNames(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all views under a schema. * * @param connection * @param databaseName * @param schemaName * @return */ List
    views(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all functions under a schema. * * @param connection * @param databaseName * @param schemaName * @return */ List functions(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all triggers under a schema. * * @param connection * @param databaseName * @param schemaName * @return */ List triggers(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all procedures under a schema. * * @param connection * @param schemaName * @param databaseName * @return */ List procedures(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all columns under a table. * * @param connection * @param databaseName * @param schemaName * @param tableName * @return */ List columns(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Querying all columns under a table. * * @param connection * @param databaseName * @param schemaName * @param tableName * @param columnName * @return */ List columns(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName, String columnName); /** * Querying all indexes under a table. * * @param connection * @param databaseName * @param databaseName * @return */ List indexes(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Querying function detail under a schema. * * @param connection * @param databaseName * @param schemaName * @param functionName * @return */ Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName); /** * Querying trigger under a schema. * * @param connection * @param databaseName * @param schemaName * @param triggerName * @return */ Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName); /** * Querying all procedures under a schema. * * @param connection * @param schemaName * @param databaseName * @param procedureName * @return */ Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName); /** * @param connection * @return */ List types(Connection connection); /** * Get sql builder. * * @return */ SqlBuilder getSqlBuilder(); /** * @param databaseName * @param schemaName * @param tableName * @return */ TableMeta getTableMeta(String databaseName, String schemaName, String tableName); /** * Get meta data name. * */ String getMetaDataName(String ...names); /** * Get column builder. * */ ValueProcessor getValueProcessor(); /** * Get command executor. */ CommandExecutor getCommandExecutor(); /** * Get system databases. * @return */ List getSystemDatabases(); /** * Get system schemas. * @return */ List getSystemSchemas(); /** * Querying DDL information * * @param connection * @param databaseName * @param tableName * @return */ String sequenceDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Querying sequences simple information * * @param connection * @param databaseName * @return */ List sequences(Connection connection, String databaseName, String schemaName); /** * Querying all sequence under a schema. * * @param connection * @param databaseName * @param schemaName * @param sequenceName * @return */ Sequence sequences(Connection connection, @NotEmpty String databaseName, String schemaName, String sequenceName); List usernames(Connection connection); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/Plugin.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.config.DBConfig; /** * @author jipengfei * @version : Plugin.java */ public interface Plugin { /** * Get DB configuration information. * * @return */ DBConfig getDBConfig(); /** * Query db metadata information. * * @return */ MetaData getMetaData(); /** * * @return */ DBManage getDBManage(); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.model.*; import java.util.List; import java.util.Map; public interface SqlBuilder { /** * Generate create table sql * * @param table * @return */ String buildCreateTableSql(T table); /** * Generate modify table sql * * @param newTable * @param oldTable * @return */ String buildModifyTaleSql(T oldTable, T newTable); /** * Generate page limit sql * * @param sql * @param offset * @param pageNo * @param pageSize * @return */ String pageLimit(String sql, int offset, int pageNo, int pageSize); /** * Generate create database sql * * @param database * @return */ String buildCreateDatabaseSql(Database database); /** * @param oldDatabase * @param newDatabase * @return */ String buildModifyDatabaseSql(Database oldDatabase, Database newDatabase); /** * @param schemaName * @return */ String buildCreateSchemaSql(Schema schemaName); /** * @param oldSchemaName * @param newSchemaName * @return */ String buildModifySchemaSql(String oldSchemaName, String newSchemaName); /** * @param originSql * @param orderByList * @return */ String buildOrderBySql(String originSql, List orderByList); /** * @param originSql * @param groupByList * @return */ String buildGroupBySql(String originSql, List groupByList); /** * generate sql based on results */ String buildSqlByQuery(QueryResult queryResult); /** * DML SQL * * @param table * @param type * @return */ String getTableDmlSql(T table, String type); /** * * @param databaseName * @param schemaName * @param tableName * @return */ String buildTableQuerySql(String databaseName, String schemaName, String tableName); /** * Generate create sequence sql * * @param sequence * @return */ String buildCreateSequenceSql(Sequence sequence); /** * Generate modify sequence sql * * @param newSequence * @param oldSequence * @return */ String buildModifySequenceSql(Sequence oldSequence, Sequence newSequence); String buildSingleInsertSql(String databaseName, String schemaName, String tableName, List columnList, List valueList); String buildMultiInsertSql(String databaseName, String schemaName, String tableName, List columnList, List> valueLists); String buildUpdateSql(String databaseName, String schemaName, String tableName, Map row,Map primaryKeyMap); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ValueHandler.java ================================================ //package ai.chat2db.spi; // //import java.sql.ResultSet; //import java.sql.SQLException; // //public interface ValueHandler { // // /** // * Process column values in the result set // * @param rs // * @param index // * @param limitSize // * @return // * @throws SQLException // */ // String getString(ResultSet rs, int index, boolean limitSize)throws SQLException; //} ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ValueProcessor.java ================================================ package ai.chat2db.spi; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; public interface ValueProcessor { /** * Converts a given value into a format suitable for use in an SQL statement *
    * Example: *
    * Input oracle DATE : '2024-05-29 11:35:20.0' *
    * Output for Oracle DATE: TO_DATE('2024-05-29 14:25:00', 'YYYY-MM-DD HH24:MI:SS') */ String getSqlValueString(SQLDataValue dataValue); /** * 将JDBC数据值对象转换为适合前端展示的字符串格式。 *

    * 它旨在处理包括但不限于数字、日期、字符串以及特殊的空数据,确保这些数据 * 在传递到前端用户界面时是格式化良好且可理解的。 * * @param dataValue ResultSetMetaData, ResultSet, columnIndex的组合对象,用于获取数据值。 * @return 一个格式化后的字符串,适配于前端展示。例如,日期可能会转换为"YYYY-MM-DD"格式,以方便用户直观理解。 */ String getJdbcValue(JDBCDataValue dataValue); /** * 将从JDBC ResultSet中获取的数据值转换并构造为适合DML语句的格式。 * * @param dataValue JDBC数据源中检索出的数据值对象,用于准备DML操作的值。 * * @return 一个格式化后的字符串,可以直接用于DML语句中,确保数据的正确插入或更新。 */ String getJdbcSqlValueString(JDBCDataValue dataValue); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java ================================================ package ai.chat2db.spi.config; import org.apache.commons.collections4.CollectionUtils; import java.util.List; /** * @author jipengfei * @version : DBConfig.java */ public class DBConfig { /** * MYSQL POSTGRESQL ... */ private String dbType; /** * Mysql PostgreSQL ... */ private String name; /** * defaultDriverConfig */ private DriverConfig defaultDriverConfig; /** * List of supported drivers */ private List driverConfigList; /** * Create table statement */ private String simpleCreateTable; /** * Modify table structure */ private String simpleAlterTable; private boolean supportDatabase; private boolean supportSchema; public boolean isSupportDatabase() { return supportDatabase; } public void setSupportDatabase(boolean supportDatabase) { this.supportDatabase = supportDatabase; } public boolean isSupportSchema() { return supportSchema; } public void setSupportSchema(boolean supportSchema) { this.supportSchema = supportSchema; } public String getDbType() { return dbType; } public void setDbType(String dbType) { this.dbType = dbType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public DriverConfig getDefaultDriverConfig() { if (this.defaultDriverConfig != null) { return this.defaultDriverConfig; } else { if (!CollectionUtils.isEmpty(driverConfigList)) { for (DriverConfig driverConfig : driverConfigList) { if (driverConfig.isDefaultDriver()) { return driverConfig; } } return driverConfigList.get(0); } } return null; } public void setDefaultDriverConfig(DriverConfig defaultDriverConfig) { this.defaultDriverConfig = defaultDriverConfig; } public List getDriverConfigList() { return driverConfigList; } public void setDriverConfigList(List driverConfigList) { this.driverConfigList = driverConfigList; if (!CollectionUtils.isEmpty(driverConfigList)) { for (DriverConfig driverConfig : driverConfigList) { if (driverConfig.isDefaultDriver()) { this.defaultDriverConfig = driverConfig; break; } } } } public String getSimpleCreateTable() { return simpleCreateTable; } public void setSimpleCreateTable(String simpleCreateTable) { this.simpleCreateTable = simpleCreateTable; } public String getSimpleAlterTable() { return simpleAlterTable; } public void setSimpleAlterTable(String simpleAlterTable) { this.simpleAlterTable = simpleAlterTable; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DriverConfig.java ================================================ package ai.chat2db.spi.config; import java.io.Serializable; import java.util.List; import ai.chat2db.spi.model.KeyValue; import lombok.Data; import org.apache.commons.lang3.StringUtils; /** * @author jipengfei * @version : DriverConfig.java */ @Data public class DriverConfig implements Serializable { private static final long serialVersionUID = 1L; /** * url */ private String url; /** * jdbcDriver */ private String jdbcDriver; /** * jdbcDriverClass */ private String jdbcDriverClass; /** * downloadJdbcDriverUrls */ private List downloadJdbcDriverUrls; /** * dbType */ private String dbType; /** * customize */ private boolean custom; /** * properties */ private List extendInfo; private boolean defaultDriver; public boolean notEmpty() { return StringUtils.isNotBlank(getJdbcDriver()) && StringUtils.isNotBlank( getJdbcDriverClass()); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/CellTypeEnum.java ================================================ package ai.chat2db.spi.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Driver class enumeration * * @author Jiaju Zhuang */ @Getter public enum CellTypeEnum implements BaseEnum { /** * string */ STRING("string"), /** * number */ BIG_DECIMAL("number"), /** * date */ DATE("date"), /** * binary stream */ BYTE("binary stream"), /** * empty data */ EMPTY("empty data"), ; final String description; CellTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/CollationEnum.java ================================================ package ai.chat2db.spi.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import com.alibaba.druid.sql.ast.SQLOrderingSpecification; import lombok.Getter; /** * Sorted enumeration * * @author Jiaju Zhuang */ @Getter public enum CollationEnum implements BaseEnum { /** * ASC */ ASC("asc", SQLOrderingSpecification.ASC), /** * DESC */ DESC("desc", SQLOrderingSpecification.DESC), ; final String description; final SQLOrderingSpecification sqlOrderingSpecification; CollationEnum(String description, SQLOrderingSpecification sqlOrderingSpecification) { this.description = description; this.sqlOrderingSpecification = sqlOrderingSpecification; } @Override public String getCode() { return this.name(); } public static boolean equals(String collation1, String collation2) { return equals(EasyEnumUtils.getEnum(CollationEnum.class, collation1), EasyEnumUtils.getEnum(CollationEnum.class, collation2)); } public static boolean equals(CollationEnum collation1, CollationEnum collation2) { // The same returns directly if (collation1 == collation2) { return true; } // One of them is in reverse order, which means they are different. The others are the same. return !(collation1 == CollationEnum.DESC || collation2 == CollationEnum.DESC); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java ================================================ package ai.chat2db.spi.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; import org.apache.commons.lang3.StringUtils; /** * Driver class enumeration * * @author Jiaju Zhuang */ @Getter public enum DataTypeEnum implements BaseEnum { /** * Boolean value */ BOOLEAN("Boolean value"), /** * number */ NUMERIC("number"), /** * string */ STRING("string"), /** * date */ DATETIME("date"), /** * binary */ BINARY("binary"), /** * content */ CONTENT("content"), /** * structure */ STRUCT("structure"), /** * document */ DOCUMENT("document"), /** * array */ ARRAY("array"), /** * object */ OBJECT("object"), /** * reference */ REFERENCE("reference"), /** * rowid */ ROWID("rowid"), /** * any */ ANY("any"), /** * unknow */ UNKNOWN("unknow"), /** * Row number */ CHAT2DB_ROW_NUMBER("Row number"), ; final String description; DataTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } public static DataTypeEnum getByCode(String code) { for (DataTypeEnum value : DataTypeEnum.values()) { if (value.getCode().equals(code)) { return value; } } return DataTypeEnum.UNKNOWN; } public String getSqlValue(String value) { if (this == DataTypeEnum.BOOLEAN) { if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { return value; } else { return "'" + value + "'"; } } if (this == DataTypeEnum.NUMERIC) { return value; } if (this == DataTypeEnum.STRING) { return getStringValue(value); } if (this == DataTypeEnum.DATETIME) { return "'" + value + "'"; } if (this == DataTypeEnum.BINARY) { return "''"; } if (this == DataTypeEnum.CONTENT) { return "'" + value + "'"; } if (this == DataTypeEnum.STRUCT) { return "'" + value + "'"; } if (this == DataTypeEnum.DOCUMENT) { return "'" + value + "'"; } if (this == DataTypeEnum.ARRAY) { return "'" + value + "'"; } if (this == DataTypeEnum.OBJECT) { return "'" + value + "'"; } if (this == DataTypeEnum.REFERENCE) { return "'" + value + "'"; } if (this == DataTypeEnum.ROWID) { return "'" + value + "'"; } if (this == DataTypeEnum.ANY) { return "'" + value + "'"; } if (this == DataTypeEnum.UNKNOWN) { return "'" + value + "'"; } return "'" + value + "'"; } public static String getStringValue(String value) { if (StringUtils.isBlank(value)) { return "'" + value + "'"; } value = value.replace("\\", "\\\\"); value = value.replace("'", "\\'"); value = value.replace("\"", "\\\""); return "'" + value + "'"; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DmlType.java ================================================ package ai.chat2db.spi.enums; public enum DmlType { INSERT, UPDATE, DELETE, SELECT } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/EditStatus.java ================================================ package ai.chat2db.spi.enums; public enum EditStatus { DELETE, ADD, MODIFY; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/IndexTypeEnum.java ================================================ package ai.chat2db.spi.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * Index type * * @author Jiaju Zhuang */ @Getter public enum IndexTypeEnum implements BaseEnum { /** * primary key */ PRIMARY_KEY("primary key"), /** * Ordinary index */ NORMAL("Ordinary index"), /** * unique index */ UNIQUE("unique index"), ; final String description; IndexTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/SqlTypeEnum.java ================================================ package ai.chat2db.spi.enums; import ai.chat2db.server.tools.base.enums.BaseEnum; import lombok.Getter; /** * sql type * * @author Jiaju Zhuang */ @Getter public enum SqlTypeEnum implements BaseEnum { /** * Check for phrases */ SELECT("Check for phrases"), /** * unknow */ UNKNOWN("unknow"), ; final String description; SqlTypeEnum(String description) { this.description = description; } @Override public String getCode() { return this.name(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/BaseValueProcessor.java ================================================ //package ai.chat2db.spi.jdbc; // //import ai.chat2db.server.tools.common.util.EasyStringUtils; //import ai.chat2db.spi.ValueProcessor; //import ai.chat2db.spi.model.JDBCDataValue; //import ai.chat2db.spi.model.SQLDataValue; //import org.apache.commons.lang3.StringUtils; // //import java.util.Objects; // ///** // * @author: zgq // * @date: 2024年05月30日 15:33 // */ //public abstract class BaseValueProcessor implements ValueProcessor { // // // // public abstract String convertSQLValueByType(SQLDataValue dataValue); // // public abstract String convertJDBCValueByType(JDBCDataValue dataValue); // // public abstract String convertJDBCValueStrByType(JDBCDataValue dataValue); //} ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java ================================================ package ai.chat2db.spi.jdbc; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.ssh.SSHManager; import ai.chat2db.spi.util.ResultSetUtils; import com.jcraft.jsch.Session; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import static ai.chat2db.server.tools.base.constant.SymbolConstant.DOT; /** * @author jipengfei * @version : DefaultDBManage.java */ public class DefaultDBManage implements DBManage { protected static final String DIVIDING_LINE = "-- ----------------------------"; protected static final String NEW_LINE = "\n"; protected static final String EXPORT_TITLE = DIVIDING_LINE + NEW_LINE + "-- Chat2DB export data , export time: %s" + NEW_LINE + DIVIDING_LINE; protected static final String TABLE_TITLE = DIVIDING_LINE + NEW_LINE + "-- Table structure for table %s" + NEW_LINE + DIVIDING_LINE; protected static final String VIEW_TITLE = DIVIDING_LINE + NEW_LINE + "-- View structure for view %s" + NEW_LINE + DIVIDING_LINE; protected static final String FUNCTION_TITLE = DIVIDING_LINE + NEW_LINE + "-- Function structure for function %s" + NEW_LINE + DIVIDING_LINE; protected static final String TRIGGER_TITLE = DIVIDING_LINE + NEW_LINE + "-- Trigger structure for trigger %s" + NEW_LINE + DIVIDING_LINE; protected static final String PROCEDURE_TITLE = DIVIDING_LINE + NEW_LINE + "-- Procedure structure for procedure %s" + NEW_LINE + DIVIDING_LINE; private static final String RECORD_TITLE = DIVIDING_LINE + NEW_LINE + "-- Records of %s" + NEW_LINE + DIVIDING_LINE; @Override public Connection getConnection(ConnectInfo connectInfo) { Connection connection = connectInfo.getConnection(); SSHInfo ssh = connectInfo.getSsh(); String url = connectInfo.getUrl(); String host = connectInfo.getHost(); String port = connectInfo.getPort() + ""; Session session = null; try { if (connection != null && !connection.isClosed()) { return connection; } ssh.setRHost(host); ssh.setRPort(port); session = getSession(ssh); if (session != null) { url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); } } catch (Exception e) { throw new ConnectionException("connection.ssh.error", null, e); } try { connection = IDriverManager.getConnection(url, connectInfo.getUser(), connectInfo.getPassword(), connectInfo.getDriverConfig(), connectInfo.getExtendMap()); } catch (Exception e1) { close(connection, session, ssh); throw new BusinessException("connection.error", null, e1); } connectInfo.setSession(session); connectInfo.setConnection(connection); if (StringUtils.isNotBlank(connectInfo.getDatabaseName()) || StringUtils.isNotBlank(connectInfo.getSchemaName())) { connectDatabase(connection, connectInfo.getDatabaseName()); } return connection; } private void close(Connection connection, Session session, SSHInfo ssh) { if (connection != null) { try { connection.close(); } catch (Exception e) { } } if (session != null) { try { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); } catch (Exception e) { } try { session.disconnect(); } catch (Exception e) { } } } private Session getSession(SSHInfo ssh) { Session session = null; if (ssh != null && ssh.isUse()) { session = SSHManager.getSSHSession(ssh); } return session; } @Override public void connectDatabase(Connection connection, String database) { } @Override public void modifyDatabase(Connection connection, String databaseName, String newDatabaseName) { } @Override public void createDatabase(Connection connection, String databaseName) { } @Override public void dropDatabase(Connection connection, String databaseName) { } @Override public void createSchema(Connection connection, String databaseName, String schemaName) { } @Override public void dropSchema(Connection connection, String databaseName, String schemaName) { } @Override public void modifySchema(Connection connection, String databaseName, String schemaName, String newSchemaName) { } @Override public void dropFunction(Connection connection, String databaseName, String schemaName, String functionName) { } @Override public void dropTrigger(Connection connection, String databaseName, String schemaName, String triggerName) { } @Override public void dropProcedure(Connection connection, String databaseName, String schemaName, String triggerName) { } @Override public void updateProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) throws SQLException { } @Override public void exportDatabase(Connection connection, String databaseName, String schemaName, AsyncContext asyncContext) throws SQLException { } @Override public void exportTable(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) throws SQLException { } @Override public void truncateTable(Connection connection, String databaseName, String schemaName, String tableName) throws SQLException { String sql = "TRUNCATE TABLE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName, boolean copyData) throws SQLException { String sql = ""; if (copyData) { sql = "CREATE TABLE " + newTableName + " AS SELECT * FROM " + tableName; } else { sql = "CREATE TABLE " + newTableName + " AS SELECT * FROM " + tableName + " WHERE 1=0"; } SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteProcedure(Connection connection, String databaseName, String schemaName, Procedure procedure) { String procedureNewName = getSchemaOrProcedureName(procedure.getProcedureBody(), schemaName, procedure); String sql = "DROP PROCEDURE " + procedureNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void deleteFunction(Connection connection, String databaseName, String schemaName, Function function) { String functionNewName = getSchemaOrFunctionName(function.getFunctionBody(), schemaName, function); String sql = "DROP FUNCTION " + functionNewName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } @Override public void dropSequence(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String sequenceName){ String sql = "DROP SEQUENCE " + schemaName + DOT + sequenceName; SQLExecutor.getInstance().execute(connection, sql, resultSet -> null); } public void exportTableData(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) { SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); String tableQuerySql = sqlBuilder.buildTableQuerySql(databaseName, schemaName, tableName); asyncContext.info("export table data sql: " + tableQuerySql); SQLExecutor.getInstance().execute(connection, tableQuerySql, 1000, resultSet -> { ResultSetMetaData metaData = resultSet.getMetaData(); List columnList = ResultSetUtils.getRsHeader(resultSet); List valueList = new ArrayList<>(); asyncContext.write(String.format(RECORD_TITLE, tableName)); while (resultSet.next()) { for (int i = 1; i <= metaData.getColumnCount(); i++) { ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); JDBCDataValue jdbcDataValue = new JDBCDataValue(resultSet, metaData, i, false); String valueString = valueProcessor.getJdbcSqlValueString(jdbcDataValue); valueList.add(valueString); } String insertSql = sqlBuilder.buildSingleInsertSql(null, null, tableName, columnList, valueList); asyncContext.write(insertSql + ";"); valueList.clear(); } }); } private static String getSchemaOrProcedureName(String procedureBody, String schemaName, Procedure procedure) { if (procedureBody.toLowerCase().contains(schemaName.toLowerCase())) { return procedure.getProcedureName(); } else { return schemaName + "." + procedure.getProcedureName(); } } private static String getSchemaOrFunctionName(String functionBody, String schemaName, Function function) { if (functionBody.toLowerCase().contains(schemaName.toLowerCase())) { return function.getFunctionName(); } else { return schemaName + "." + function.getFunctionName(); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java ================================================ package ai.chat2db.spi.jdbc; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.spi.*; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * @author jipengfei * @version : DefaultMetaService.java */ public class DefaultMetaService implements MetaData { @Override public List databases(Connection connection) { return SQLExecutor.getInstance().databases(connection); } @Override public List schemas(Connection connection, String databaseName) { List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); if (StringUtils.isNotBlank(databaseName) && CollectionUtils.isNotEmpty(schemas)) { for (Schema schema : schemas) { if (StringUtils.isBlank(schema.getDatabaseName())) { schema.setDatabaseName(databaseName); } } } return schemas; } @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { return null; } @Override public List

    tables(Connection connection, String databaseName, String schemaName, String tableName) { return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE", "SYSTEM TABLE"}); } @Override public List tableNames(Connection connection, String databaseName, String schemaName, String tableName) { return SQLExecutor.getInstance().tableNames(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE", "SYSTEM TABLE"}); } @Override public PageResult
    tables(Connection connection, String databaseName, String schemaName, String tableNamePattern, int pageNo, int pageSize) { List
    tables = tables(connection, databaseName, schemaName, tableNamePattern); if (CollectionUtils.isEmpty(tables)) { return PageResult.of(tables, 0L, pageNo, pageSize); } List result = tables.stream().skip((pageNo - 1) * pageSize).limit(pageSize).collect(Collectors.toList()); return PageResult.of(result, (long) tables.size(), pageNo, pageSize); } @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { return null; } @Override public List
    views(Connection connection, String databaseName, String schemaName) { return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, null, new String[]{"VIEW"}); } @Override public List viewNames(Connection connection, String databaseName, String schemaName) { return SQLExecutor.getInstance().tableNames(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, null, new String[]{"VIEW"}); } @Override public List functions(Connection connection, String databaseName, String schemaName) { List functions = SQLExecutor.getInstance().functions(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); if (CollectionUtils.isEmpty(functions)) { return functions; } return functions.stream().filter(function -> StringUtils.isNotBlank(function.getFunctionName())).map(function -> { String functionName = function.getFunctionName(); function.setFunctionName(functionName.trim()); return function; }).collect(Collectors.toList()); } @Override public List triggers(Connection connection, String databaseName, String schemaName) { return null; } @Override public List procedures(Connection connection, String databaseName, String schemaName) { List procedures = SQLExecutor.getInstance().procedures(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); if (CollectionUtils.isEmpty(procedures)) { return procedures; } return procedures.stream().filter(function -> StringUtils.isNotBlank(function.getProcedureName())).map(procedure -> { String procedureName = procedure.getProcedureName(); procedure.setProcedureName(procedureName.trim()); return procedure; }).collect(Collectors.toList()); } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { List columns = SQLExecutor.getInstance().columns(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, null); if (CollectionUtils.isNotEmpty(columns)) { for (TableColumn column : columns) { String columnType = SqlUtils.removeDigits(column.getColumnType()); column.setColumnType(columnType); } } return columns; } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName, String columnName) { return SQLExecutor.getInstance().columns(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, columnName); } @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { return SQLExecutor.getInstance().indexes(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName); } @Override public Function function(Connection connection, String databaseName, String schemaName, String functionName) { return null; } @Override public Trigger trigger(Connection connection, String databaseName, String schemaName, String triggerName) { return null; } @Override public Procedure procedure(Connection connection, String databaseName, String schemaName, String procedureName) { return null; } @Override public List types(Connection connection) { return SQLExecutor.getInstance().types(connection); } @Override public SqlBuilder getSqlBuilder() { return new DefaultSqlBuilder(); } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return null; } @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).collect(Collectors.joining(".")); } @Override public ValueProcessor getValueProcessor() { return new DefaultValueProcessor(); } @Override public CommandExecutor getCommandExecutor() { return SQLExecutor.getInstance(); } @Override public List getSystemDatabases() { return Lists.newArrayList(); } @Override public List getSystemSchemas() { return Lists.newArrayList(); } @Override public String sequenceDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName){ return null; } @Override public List sequences(Connection connection, String databaseName, String schemaName){ return Collections.emptyList(); } @Override public Sequence sequences(Connection connection, @NotEmpty String databaseName, String schemaName, String sequenceName){ return null; } @Override public List usernames(Connection connection){ return Collections.emptyList(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java ================================================ package ai.chat2db.spi.jdbc; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.enums.DmlType; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.GroupByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class DefaultSqlBuilder implements SqlBuilder
    { @Override public String buildTableQuerySql(String databaseName, String schemaName, String tableName) { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("SELECT * FROM "); buildTableName(databaseName, schemaName, tableName, sqlBuilder); return sqlBuilder.toString(); } @Override public String buildCreateTableSql(Table table) { return null; } @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { return null; } @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return null; } public static String CREATE_DATABASE_SQL = "CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET %s COLLATE %s"; @Override public String buildCreateDatabaseSql(Database database) { return null; } @Override public String buildModifyDatabaseSql(Database oldDatabase, Database newDatabase) { return null; } @Override public String buildCreateSchemaSql(Schema schema) { return null; } @Override public String buildModifySchemaSql(String oldSchemaName, String newSchemaName) { return null; } @Override public String buildOrderBySql(String originSql, List orderByList) { if (CollectionUtils.isEmpty(orderByList)) { return originSql; } try { Statement statement = CCJSqlParserUtil.parse(originSql); if (statement instanceof Select) { Select selectStatement = (Select) statement; PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody(); // Create a new ORDER BY clause List orderByElements = new ArrayList<>(); for (OrderBy orderBy : orderByList) { OrderByElement orderByElement = new OrderByElement(); orderByElement.setExpression(CCJSqlParserUtil.parseExpression(orderBy.getColumnName())); orderByElement.setAsc(orderBy.isAsc()); // Set to ascending order, use setAsc(false) to set to descending order orderByElements.add(orderByElement); } // Replace the original ORDER BY clause plainSelect.setOrderByElements(orderByElements); // Output the modified SQL return plainSelect.toString(); } } catch (Exception e) { } return originSql; } @Override public String buildGroupBySql(String originSql, List groupByList) { if (CollectionUtils.isEmpty(groupByList)) { return originSql; } try { Statement statement = CCJSqlParserUtil.parse(originSql); if (statement instanceof Select) { Select selectStatement = (Select) statement; PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody(); // Create a new GROUP BY clause // Replace the original GROUP BY clause GroupByElement grouByElement = new GroupByElement(); grouByElement.setGroupingSets(groupByList); plainSelect.setGroupByElement(grouByElement); // Output the modified SQL return plainSelect.toString(); } } catch (Exception e) { } return originSql; } @Override public String buildSqlByQuery(QueryResult queryResult) { List
    headerList = queryResult.getHeaderList(); List operations = queryResult.getOperations(); String tableName = queryResult.getTableName(); StringBuilder stringBuilder = new StringBuilder(); MetaData metaSchema = Chat2DBContext.getMetaData(); String dbType = Chat2DBContext.getDBConfig().getDbType(); List keyColumns = getPrimaryColumns(headerList); for (int i = 0; i < operations.size(); i++) { ResultOperation operation = operations.get(i); List row = operation.getDataList(); List odlRow = operation.getOldDataList(); String sql = ""; if ("UPDATE".equalsIgnoreCase(operation.getType())) { sql = getUpdateSql(tableName, headerList, row, odlRow, metaSchema, keyColumns, false); if("MYSQL".equalsIgnoreCase(dbType)){ sql = sql + " LIMIT 1"; } } else if ("CREATE".equalsIgnoreCase(operation.getType())) { sql = getInsertSql(tableName, headerList, row, metaSchema); } else if ("DELETE".equalsIgnoreCase(operation.getType())) { sql = getDeleteSql(tableName, headerList, odlRow, metaSchema, keyColumns); if("MYSQL".equalsIgnoreCase(dbType)){ sql = sql + " LIMIT 1"; } } else if ("UPDATE_COPY".equalsIgnoreCase(operation.getType())) { sql = getUpdateSql(tableName, headerList, row, row, metaSchema, keyColumns, true); } stringBuilder.append(sql + ";\n"); } return stringBuilder.toString(); } @Override public String getTableDmlSql(Table table, String type) { if (table == null || CollectionUtils.isEmpty(table.getColumnList()) || StringUtils.isBlank(type)) { return ""; } if (DmlType.INSERT.name().equalsIgnoreCase(type)) { return getInsertSql(table.getName(), table.getColumnList()); } else if (DmlType.UPDATE.name().equalsIgnoreCase(type)) { return getUpdateSql(table.getName(), table.getColumnList()); } else if (DmlType.DELETE.name().equalsIgnoreCase(type)) { return getDeleteSql(table.getName(), table.getColumnList()); } else if (DmlType.SELECT.name().equalsIgnoreCase(type)) { return getSelectSql(table.getName(), table.getColumnList()); } return ""; } private String getSelectSql(String name, List columnList) { StringBuilder script = new StringBuilder(); script.append("SELECT "); for (TableColumn column : columnList) { script.append(column.getName()) .append(","); } script.deleteCharAt(script.length() - 1); script.append(" FROM where").append(name); return script.toString(); } private String getDeleteSql(String name, List columnList) { StringBuilder script = new StringBuilder(); script.append("DELETE FROM ").append(name) .append(" where "); return script.toString(); } private String getUpdateSql(String name, List columnList) { StringBuilder script = new StringBuilder(); script.append("UPDATE ").append(name) .append(" set "); for (TableColumn column : columnList) { script.append(column.getName()) .append(" = ") .append(" ") .append(","); } script.deleteCharAt(script.length() - 1); script.append(" where "); return script.toString(); } private String getInsertSql(String name, List columnList) { StringBuilder script = new StringBuilder(); script.append("INSERT INTO ").append(name) .append(" ("); for (TableColumn column : columnList) { script.append(column.getName()) .append(","); } script.deleteCharAt(script.length() - 1); script.append(") VALUES ("); for (TableColumn column : columnList) { script.append(" ") .append(","); } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } /** * Generates the base part of the INSERT SQL statement. * Optionally includes column names if provided. * * @param databaseName * @param schemaName Name of the database schema. * @param tableName Name of the table to insert into. * @param columnList Optional list of column names. * @return The base part of the INSERT SQL statement. */ protected String buildBaseInsertSql(String databaseName, String schemaName, String tableName, List columnList) { StringBuilder script = new StringBuilder(); script.append("INSERT INTO "); buildTableName(databaseName, schemaName, tableName, script); buildColumns(columnList, script); script.append(" VALUES "); return script.toString(); } protected void buildColumns(List columnList, StringBuilder script) { if (CollectionUtils.isNotEmpty(columnList)) { script.append(" (") .append(String.join(",", columnList)) .append(") "); } } protected void buildTableName(String databaseName, String schemaName, String tableName, StringBuilder script) { if (StringUtils.isNotBlank(databaseName)) { script.append(databaseName).append('.'); } if (StringUtils.isNotBlank(schemaName)) { script.append(schemaName).append('.'); } script.append(tableName); } /** * Generates a single INSERT SQL statement for one record. * * @param schemaName Name of the database schema. * @param tableName Name of the table to insert into. * @param columnList Optional list of column names. * @param valueList List of values to be inserted. * @return The complete INSERT SQL statement for a single record. */ public String buildSingleInsertSql(String databaseName, String schemaName, String tableName, List columnList, List valueList) { String baseSql = buildBaseInsertSql(databaseName, schemaName, tableName, columnList); List list = valueList.stream().map(EasyStringUtils::escapeLineString).toList(); return baseSql + "(" + String.join(",", list) + ")"; } /** * Generates a multi-row INSERT SQL statement. * * @param schemaName Name of the database schema. * @param tableName Name of the table to insert into. * @param columnList Optional list of column names. * @param valueLists List of lists, each inner list represents values for a row. * @return The complete multi-row INSERT SQL statement. */ public String buildMultiInsertSql(String databaseName, String schemaName, String tableName, List columnList, List> valueLists) { String baseSql = buildBaseInsertSql(databaseName, schemaName, tableName, columnList); String valuesPart = valueLists.stream() .map(values -> "(" + String.join(",", values.stream().map(EasyStringUtils::escapeLineString).toList()) + ")") .collect(Collectors.joining(",\n")); return baseSql + valuesPart; } @Override public String buildUpdateSql(String databaseName, String schemaName, String tableName, Map row, Map primaryKeyMap) { StringBuilder script = new StringBuilder(); script.append("UPDATE "); buildTableName(databaseName, schemaName, tableName, script); script.append(" SET "); List setClauses = row.entrySet().stream() .map(entry -> entry.getKey() + " = " + entry.getValue()) .collect(Collectors.toList()); script.append(String.join(",", setClauses)); if (MapUtils.isNotEmpty(primaryKeyMap)) { script.append(" WHERE "); List whereClauses = primaryKeyMap.entrySet().stream() .map(entry -> entry.getKey() + " = " + entry.getValue()) .collect(Collectors.toList()); script.append(String.join(" AND ", whereClauses)); } return script.toString(); } @Override public String buildCreateSequenceSql(Sequence sequence) { return null; } @Override public String buildModifySequenceSql(Sequence oldSequence, Sequence newSequence) { return null; } private List getPrimaryColumns(List
    headerList) { if (CollectionUtils.isEmpty(headerList)) { return Lists.newArrayList(); } List keyColumns = Lists.newArrayList(); for (Header header : headerList) { if (header.getPrimaryKey() != null && header.getPrimaryKey()) { keyColumns.add(header.getName()); } } return keyColumns; } private String getDeleteSql(String tableName, List
    headerList, List row, MetaData metaSchema, List keyColumns) { StringBuilder script = new StringBuilder(); script.append("DELETE FROM ").append(tableName).append(""); script.append(buildWhere(headerList, row, metaSchema, keyColumns)); return script.toString(); } private String buildWhere(List
    headerList, List row, MetaData metaSchema, List keyColumns) { StringBuilder script = new StringBuilder(); script.append(" where "); if (CollectionUtils.isEmpty(keyColumns)) { for (int i = 1; i < row.size(); i++) { String oldValue = row.get(i); Header header = headerList.get(i); String value = SqlUtils.getSqlValue(oldValue, header.getDataType()); if (value == null) { script.append(metaSchema.getMetaDataName(header.getName())) .append(" is null and "); } else { script.append(metaSchema.getMetaDataName(header.getName())) .append(" = ") .append(value) .append(" and "); } } } else { for (int i = 1; i < row.size(); i++) { String oldValue = row.get(i); Header header = headerList.get(i); String columnName = header.getName(); if (keyColumns.contains(columnName)) { String value = SqlUtils.getSqlValue(oldValue, header.getDataType()); if (value == null) { script.append(metaSchema.getMetaDataName(columnName)) .append(" is null and "); } else { script.append(metaSchema.getMetaDataName(columnName)) .append(" = ") .append(value) .append(" and "); } } } } script.delete(script.length() - 4, script.length()); return script.toString(); } private String getInsertSql(String tableName, List
    headerList, List row, MetaData metaSchema) { if (CollectionUtils.isEmpty(row) || ObjectUtils.allNull(row.toArray())) { return ""; } StringBuilder script = new StringBuilder(); script.append("INSERT INTO ").append(tableName) .append(" ("); ValueProcessor valueProcessor = metaSchema.getValueProcessor(); for (int i = 1; i < row.size(); i++) { Header header = headerList.get(i); //String newValue = row.get(i); //if (newValue != null) { script.append(metaSchema.getMetaDataName(header.getName())) .append(","); // } } script.deleteCharAt(script.length() - 1); script.append(") VALUES ("); for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); //if (newValue != null) { Header header = headerList.get(i); SQLDataValue sqlDataValue = new SQLDataValue(); DataType dataType = new DataType(); dataType.setDataTypeName(header.getDataType()); dataType.setScale(header.getDecimalDigits()); dataType.setPrecision(header.getColumnSize()); sqlDataValue.setValue(newValue); sqlDataValue.setDataType(dataType); String value = valueProcessor.getSqlValueString(sqlDataValue); script.append(value) .append(","); //} } script.deleteCharAt(script.length() - 1); script.append(")"); return script.toString(); } private String getUpdateSql(String tableName, List
    headerList, List row, List odlRow, MetaData metaSchema, List keyColumns, boolean copy) { StringBuilder script = new StringBuilder(); if (CollectionUtils.isEmpty(row) || CollectionUtils.isEmpty(odlRow)) { return ""; } script.append("UPDATE ").append(tableName).append(" set "); for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); String oldValue = odlRow.get(i); if (StringUtils.equals(newValue, oldValue) && !copy) { continue; } Header header = headerList.get(i); String newSqlValue = SqlUtils.getSqlValue(newValue, header.getDataType()); script.append(metaSchema.getMetaDataName(header.getName())) .append(" = ") .append(newSqlValue) .append(","); } script.deleteCharAt(script.length() - 1); script.append(buildWhere(headerList, odlRow, metaSchema, keyColumns)); return script.toString(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultValueHandler.java ================================================ //package ai.chat2db.spi.jdbc; // //import ai.chat2db.server.tools.common.util.I18nUtils; //import ai.chat2db.spi.ValueHandler; //import cn.hutool.core.io.unit.DataSizeUtil; //import lombok.extern.slf4j.Slf4j; // //import java.math.BigDecimal; //import java.nio.charset.StandardCharsets; //import java.sql.*; //import java.time.LocalDateTime; //import java.time.format.DateTimeFormatter; // //@Slf4j //public class DefaultValueHandler implements ValueHandler { // // private static final long MAX_RESULT_SIZE = 10* 1024 * 1024; // // @Override // public String getString(ResultSet rs, int index, boolean limitSize) throws SQLException { // try { // Object obj = rs.getObject(index); // if (obj == null) { // return null; // } // if (obj instanceof BigDecimal bigDecimal) { // return bigDecimal.toPlainString(); // } else if (obj instanceof Double d) { // return BigDecimal.valueOf(d).toPlainString(); // } else if (obj instanceof Float f) { // return BigDecimal.valueOf(f).toPlainString(); // } else if (obj instanceof Clob) { // return largeString(rs, index, limitSize); // } else if (obj instanceof byte[]) { // return largeString(rs, index, limitSize); // } else if (obj instanceof Blob blob) { // return largeStringBlob(blob, limitSize); // } else if (obj instanceof Timestamp || obj instanceof LocalDateTime) { // return largeTime(obj); // } else if (obj instanceof SQLXML){ // return ((SQLXML) obj).getString(); // } else { // return obj.toString(); // } // } catch (Exception e) { // log.warn("Failed to parse number:{},", index, e); // return rs.getString(index); // } // } // // private String largeStringBlob(Blob blob, boolean limitSize) throws SQLException { // if (blob == null) { // return null; // } // int length = Math.toIntExact(blob.length()); // if (limitSize && length > MAX_RESULT_SIZE) { // length = Math.toIntExact(MAX_RESULT_SIZE); // } // byte[] data = blob.getBytes(1, length); // String result = new String(data, StandardCharsets.UTF_8); // // if (length > MAX_RESULT_SIZE) { // return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(length) // + " ," // + I18nUtils.getMessage("execute.exportCsv") + " ] " + result; // } // return result; // } // // private String largeTime(Object obj) throws SQLException { // Object timeField = obj; // Assuming a time field of type Object // // LocalDateTime localDateTime; // // if (obj instanceof Timestamp) { // // Convert a time field of type Object to a LocalDateTime object // localDateTime = ((Timestamp) timeField).toLocalDateTime(); // } else if(obj instanceof LocalDateTime){ // localDateTime = (LocalDateTime) timeField; // } else { // try { // localDateTime = LocalDateTime.parse(timeField.toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); // }catch (Exception e){ // localDateTime = LocalDateTime.parse(timeField.toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")); // } // } // // Create a DateTimeFormatter instance and specify the output date and time format // DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // // // Format date time // String formattedDateTime = dtf.format(localDateTime); // return formattedDateTime; // } // // private static String largeString(ResultSet rs, int index, boolean limitSize) throws SQLException { // String result = rs.getString(index); // if (result == null) { // return null; // // } // if (!limitSize) { // return result; // } // // if (result.length() > MAX_RESULT_SIZE) { // return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(result.length()) + " ," // + I18nUtils.getMessage("execute.exportCsv") + " ] " + result.substring(0, // Math.toIntExact(MAX_RESULT_SIZE)); // } // return result; // } //} ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultValueProcessor.java ================================================ package ai.chat2db.spi.jdbc; import ai.chat2db.server.tools.common.util.EasyStringUtils; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.model.JDBCDataValue; import ai.chat2db.spi.model.SQLDataValue; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import java.util.Objects; /** * @author: zgq * @date: 2024年05月24日 14:30 */ public class DefaultValueProcessor implements ValueProcessor { @Override public String getSqlValueString(SQLDataValue dataValue) { if (Objects.isNull(dataValue.getValue())) { return "NULL"; } return convertSQLValueByType(dataValue); } @Override public String getJdbcValue(JDBCDataValue dataValue) { // Object value = dataValue.getObject(); // if (Objects.isNull(dataValue.getObject())) { // return null; // } // if (value instanceof String emptySry) { // if (StringUtils.isBlank(emptySry)) { // return emptySry; // } // } return convertJDBCValueByType(dataValue); } @Override public String getJdbcSqlValueString(JDBCDataValue dataValue) { // Object value = dataValue.getObject(); // if (Objects.isNull(value)) { // return "NULL"; // } // if (value instanceof String stringValue) { // if (StringUtils.isBlank(stringValue)) { // return EasyStringUtils.quoteString(stringValue); // } // } return convertJDBCValueStrByType(dataValue); } public String convertSQLValueByType(SQLDataValue dataValue) { return getString(dataValue.getValue()); } public String convertJDBCValueByType(JDBCDataValue dataValue) { return dataValue.getString(); } public String convertJDBCValueStrByType(JDBCDataValue dataValue) { String value = dataValue.getString(); if (value == null) { return "NULL"; } return getString(value); } private boolean isNumber(String value) { return NumberUtils.isCreatable(value); } private String getString(String value) { // if (isNumber(value)) { // return value; // } return EasyStringUtils.escapeAndQuoteString(value); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/AsyncCall.java ================================================ package ai.chat2db.spi.model; import java.util.Map; public interface AsyncCall { void update(Map map); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/AsyncContext.java ================================================ package ai.chat2db.spi.model; import ai.chat2db.server.tools.common.model.Context; import ai.chat2db.server.tools.common.util.ContextUtils; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.PrintWriter; import java.util.Date; import java.util.HashMap; import java.util.Map; import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN; @Slf4j public class AsyncContext { private File writeFile; protected PrintWriter writer; protected boolean containsData; protected AsyncCall call; protected boolean finish; public File getWriteFile() { return writeFile; } public AsyncContext(AsyncCall call, Context context, File writeFile, boolean containsData) { this.call = call; this.writeFile = writeFile; this.progress = 5; this.containsData = containsData; createWriter(); asyncCallBack(context); info("start:" + DateUtil.format(new Date(), NORM_DATETIME_PATTERN)); } private void createWriter() { if (writeFile != null) { this.writer = FileUtil.getPrintWriter(writeFile, "UTF-8", false); } } private void asyncCallBack(Context context) { if (call != null && context != null) { new Thread(() -> { try { ContextUtils.setContext(context); int n = 1; while (!finish) { // 更新时间逐渐变长避免频繁更新 callUpdate(); Thread.sleep(2000 * n); if (n < 5) { n++; } } } catch (Exception e) { log.error("AsyncContext call error", e); } finally { ContextUtils.removeContext(); } }).start(); } } private void callUpdate() { if (call == null) { return; } Map map = new HashMap<>(); map.put("progress", progress); map.put("info", info.toString()); map.put("error", error.toString()); map.put("status", finish ? "FINISHED" : "RUNNING"); if (progress == 100 && writeFile != null) { map.put("downloadUrl", writeFile.getAbsolutePath()); } info = new StringBuffer(); error = new StringBuffer(); call.update(map); } public boolean isContainsData() { return containsData; } public void setProgress(Integer progress) { if (progress == null) { return; } if (progress >= 100) { progress = 99; } this.progress = progress; } public void info(String message) { info.append(message + "\n"); } public void error(String message) { error.append(message + "\n"); info.append(message + "\n"); } public void stop() { this.finish = true; } public void finish() { finish = true; this.progress = 100; info("finish:" + DateUtil.format(new Date(), NORM_DATETIME_PATTERN)); if (writeFile != null) { info("file:" + writeFile.getAbsolutePath()); } if (writer != null) { writer.flush(); writer.close(); } callUpdate(); } public void write(String message) { if (writer != null) { writer.write(message + "\n"); } } protected Integer progress; private StringBuffer info = new StringBuffer(); private StringBuffer error = new StringBuffer(); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Cell.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * cell type * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Cell implements Serializable { private static final long serialVersionUID = 1L; /** * cell type * * @see CellTypeEnum */ private String type; /** * string data */ private String stringValue; /** * number */ private BigDecimal bigDecimalValue; /** * date data */ private Long dateValue; /** * binary stream */ private byte[] byteValue; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Charset.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class Charset implements Serializable { private static final long serialVersionUID = 1L; private String charsetName; private String defaultCollationName; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Collation.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class Collation implements Serializable { private static final long serialVersionUID = 1L; private String collationName; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class ColumnType implements Serializable { private static final long serialVersionUID = 1L; private String typeName; private boolean supportLength; private boolean supportScale; private boolean supportNullable; private boolean supportAutoIncrement; private boolean supportCharset; private boolean supportCollation; private boolean supportComments; private boolean supportDefaultValue; private boolean supportExtent; private boolean supportValue; private boolean supportUnit; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Command.java ================================================ package ai.chat2db.spi.model; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.io.Serializable; @Data public class Command implements Serializable { private static final long serialVersionUID = 1L; /** * sql statement */ @NotNull private String script; /** * console id */ @NotNull private Long consoleId; /** * Data source id */ @NotNull private Long dataSourceId; /** * DB name */ @NotNull private String databaseName; /** * schema name */ private String schemaName; /** * */ private String tableName; /** *Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Return all data * Only available for select statements */ private Boolean pageSizeAll; /** * single SQL */ private boolean single; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/CreateTableSql.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * @author jipengfei * @version : CreateTableSql.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class CreateTableSql implements Serializable { private static final long serialVersionUID = 1L; public String tableName; public String sql; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataSourceConnect.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * Database connection object * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DataSourceConnect implements Serializable { private static final long serialVersionUID = 1L; /** * success flag */ private Boolean success; /** * Failure message prompt * Only in case of failure */ private String message; /** * description */ private String description; /** * error detail */ private String errorDetail; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataType.java ================================================ package ai.chat2db.spi.model; import lombok.Data; /** * @author: zgq * @date: 2024年05月31日 16:47 */ @Data public class DataType { /** * 数据类型的名称,如 "VARCHAR", "INTEGER", "DECIMAL", "DATE" 等。 * 这个名称反映了数据库中字段的确切数据类型,是根据`ResultSetMetaData.getColumnTypeName()`获取的, * 对理解和转换字段值至关重要,尤其是在处理数据库特定类型(如Oracle的NUMBER,MySQL的DATETIME)时。 */ private String dataTypeName; /** * 精度(Precision),通常用于数值类型和字符串类型,表示该类型能够存储的最大字符数量或数字的总位数。 * 对于数值类型,如`DECIMAL(5,2)`,精度5指的是整数部分加上小数部分的总位数。 * 在从`ResultSetMetaData.getPrecision()`获取时,它帮助确定如何格式化数值,以确保数据的完整性和准确性。 */ private Integer precision; /** * 小数位数(Scale),仅对数值类型有意义,表示小数点右侧的位数。 * 例如,在`DECIMAL(5,2)`中,比例2表示小数点后保留两位数。 * 通过`ResultSetMetaData.getScale()`获得,对于构造精确的数值字符串(特别是在财务和科学计算中)非常重要。 */ private Integer scale; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.List; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * database * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Database implements Serializable { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * Database name */ @JsonAlias({"TABLE_CAT"}) private String name; /** * schema name */ private List schemas; private String comment; private String charset; private String collation; private String owner; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DefaultValue.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class DefaultValue implements Serializable { private static final long serialVersionUID = 1L; private String defaultValue; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DriverEntry.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.sql.Driver; import ai.chat2db.spi.config.DriverConfig; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : DriverEntry.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class DriverEntry implements Serializable { private static final long serialVersionUID = 1L; private DriverConfig driverConfig; private Driver driver; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/EngineType.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class EngineType implements Serializable { private static final long serialVersionUID = 1L; private String name; private boolean supportTTL; private boolean supportSortOrder; private boolean supportSkippingIndices; private boolean supportDeduplication; private boolean supportSettings; private boolean supportParallelInsert; private boolean supportProjections; private boolean supportReplication; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.List; import java.util.Map; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Results of the * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ExecuteResult implements Serializable { private static final long serialVersionUID = 1L; /** * success flag */ private Boolean success; /** * Failure message prompt * Only in case of failure */ private String message; /** * executed sql */ private String sql; /** * Original SQL without pagination */ private String originalSql; /** * description */ private String description; /** * Modify the number of rows and query sql will not return */ private Integer updateCount; /** * List of display headers */ private List
    headerList; /** * list of data */ private List> dataList; /** * sql type * * @see ai.chat2db.spi.enums.SqlTypeEnum */ private String sqlType; /** * Whether there is a next page * Only available for select statements */ private Boolean hasNextPage; /** * Page coding * Only available for select statements */ private Integer pageNo; /** * Paging Size * Only available for select statements */ private Integer pageSize; /** * Total number of fuzzy rows * Only select statements have */ private String fuzzyTotal; /** * execution duration */ private Long duration; /** * Whether the returned result can be edited */ private boolean canEdit; /** * Table Name for the result */ private String tableName; /** * Extra information that can be used by the plugin */ private Map extra; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * @author jipengfei * @version : Function.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Function implements Serializable { private static final long serialVersionUID = 1L; //FUNCTION_CAT String => function catalog (may be null) //FUNCTION_SCHEME String => function schema (may be null) //FUNCTION_NAME String => function name. This is the name used to invoke the function //REMARKS String => explanatory comment on the function //FUNCTION_TYPE short => kind of function: //functionResultUnknown - Cannot determine if a return value or table will be returned //functionNoTable- Does not return a table //functionReturnsTable - Returns a table //SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. This is a user specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload functions // @JsonAlias({"FUNCTION_CAT"}) private String databaseName; @JsonAlias({"FUNCTION_SCHEM"}) private String schemaName; @JsonAlias({"FUNCTION_NAME"}) private String functionName; @JsonAlias({"REMARKS"}) private String remarks; @JsonAlias({"FUNCTION_TYPE"}) private Short functionType; @JsonAlias({"SPECIFIC_NAME"}) private String specificName; private String functionBody; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Header.java ================================================ package ai.chat2db.spi.model; import ai.chat2db.spi.enums.DataTypeEnum; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * cell header * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Header implements Serializable { private static final long serialVersionUID = 1L; /** * cell type * * @see DataTypeEnum */ private String dataType; /** * display name */ private String name; private Boolean primaryKey; private String comment; private String defaultValue; private Integer autoIncrement; private Integer nullable; private Integer columnSize; private Integer decimalDigits; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexType.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class IndexType implements Serializable { private static final long serialVersionUID = 1L; /** * */ private String typeName; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/JDBCDataValue.java ================================================ package ai.chat2db.spi.model; import ai.chat2db.spi.util.ResultSetUtils; import com.google.common.io.BaseEncoding; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import org.apache.commons.io.IOUtils; import org.apache.tika.Tika; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; import java.math.BigDecimal; import java.sql.*; import java.util.Objects; /** * @author: zgq * @date: 2024年05月30日 20:48 */ @Data @AllArgsConstructor public class JDBCDataValue { private static final Logger log = LoggerFactory.getLogger(JDBCDataValue.class); private ResultSet resultSet; private ResultSetMetaData metaData; private int columnIndex; private boolean limitSize; public Object getObject() { try { return resultSet.getObject(columnIndex); } catch (Exception e) { log.warn("Failed to retrieve object from database", e); try { return resultSet.getString(columnIndex); } catch (SQLException ex) { throw new RuntimeException(ex); } } } public String getString() { return ResultSetUtils.getString(resultSet, columnIndex); } public String getType() { return ResultSetUtils.getColumnDataTypeName(metaData, columnIndex); } public InputStream getBinaryStream() { return ResultSetUtils.getBinaryStream(resultSet, columnIndex); } public int getPrecision() { return ResultSetUtils.getColumnPrecision(metaData, columnIndex); } public byte[] getBytes() { return ResultSetUtils.getBytes(resultSet, columnIndex); } public boolean getBoolean() { return ResultSetUtils.getBoolean(resultSet, columnIndex); } public int getScale() { return ResultSetUtils.getColumnScale(metaData, columnIndex); } public int getInt() { return ResultSetUtils.getInt(resultSet, columnIndex); } public Date getDate() { return ResultSetUtils.getDate(resultSet, columnIndex); } public Timestamp getTimestamp() { return ResultSetUtils.getTimestamp(resultSet, columnIndex); } public Clob getClob() { return ResultSetUtils.getClob(resultSet, columnIndex); } public Blob getBlob() { return ResultSetUtils.getBlob(resultSet, columnIndex); } public String getBlobHexString() { byte[] bytes = getBytes(); if (Objects.isNull(bytes)) { return null; } return BaseEncoding.base16().encode(bytes); } public BigDecimal getBigDecimal() { return ResultSetUtils.getBigDecimal(resultSet, columnIndex); } public String getBigDecimalString() { BigDecimal bigDecimal = getBigDecimal(); return bigDecimal == null ? new String(getBytes()) : bigDecimal.toPlainString(); } public String getBlobString() { Blob blob = getBlob(); try (InputStream binaryStream = blob.getBinaryStream()) { long length = blob.length(); return converterBinaryData(length, binaryStream); } catch (SQLException | IOException e) { log.warn("Error while reading binary stream", e); return getString(); } } public String getClobString() { Clob clob = getClob(); try (Reader reader = clob.getCharacterStream()) { long length = clob.length(); LOBInfo cLobInfo = getLobInfo(length); double size = cLobInfo.getSize(); if (size == 0) { return ""; } String unit = cLobInfo.getUnit(); if (limitSize && isBigSize(unit)) { return String.format("[%s] %s", getType(), cLobInfo); } return IOUtils.toString(reader); } catch (IOException | SQLException e) { log.warn("Error while reading clob stream", e); return getStringValue(); } } private String handleImageType(InputStream imageStream, LOBInfo lobInfo) { if (limitSize) { try { BufferedImage bufferedImage = ImageIO.read(imageStream); return String.format("[%s] %dx%d JPEG image %s", getType(), bufferedImage.getWidth(), bufferedImage.getHeight(), lobInfo); } catch (IOException e) { log.warn("Error while reading image stream", e); return getStringValue(); } } else { return "0x" + getBlobHexString(); } } private String handleStringType(InputStream binaryStream, LOBInfo lobInfo) throws IOException { if (isBigSize(lobInfo.getUnit()) && limitSize) { return String.format("[%s] %s", getType(), lobInfo); } else { return new String(binaryStream.readAllBytes()); } } private boolean isBigSize(String unit) { return LobUnit.G.unit.equals(unit) || LobUnit.M.unit.equals(unit); } @NotNull private LOBInfo getLobInfo(long size) { if (size == 0) { return new LOBInfo(LobUnit.B.unit, 0); } return new LOBInfo(size); } public String getStringValue() { return ResultSetUtils.getStringValue(resultSet, columnIndex); } public String getBinaryDataString() { InputStream binaryStream = null; try { binaryStream = getBinaryStream(); if (Objects.isNull(binaryStream)) { return null; } // 检查流是否支持 mark 操作,不支持则用 BufferedInputStream 包装 if (!binaryStream.markSupported()) { binaryStream = new BufferedInputStream(binaryStream); } binaryStream.mark(Integer.MAX_VALUE); long size = 0; byte[] buffer = new byte[8192]; // 缓冲区 int bytesRead; while ((bytesRead = binaryStream.read(buffer)) != -1) { size += bytesRead; } binaryStream.reset(); // 重置流到标记的位置 return converterBinaryData(size, binaryStream); } catch (SQLException | IOException e) { log.warn("Error while reading binary stream", e); return getStringValue(); } finally { // 关闭流 if (binaryStream != null) { try { binaryStream.close(); } catch (IOException e) { log.warn("Error while closing binary stream", e); } } } } private String converterBinaryData(long size, InputStream binaryStream) throws IOException, SQLException { LOBInfo lobInfo = getLobInfo(size); String unit = lobInfo.unit; if (size == 0) { return ""; } Tika tika = new Tika(); String contentType = tika.detect(binaryStream); FileTypeEnum fileTypeEnum = FileTypeEnum.fromDescription(contentType); if (Objects.isNull(fileTypeEnum)) { if (isBigSize(unit) && limitSize) { return String.format("[%s] %s", getType(), lobInfo); } return "0x" + BaseEncoding.base16().encode(binaryStream.readAllBytes()); } return switch (fileTypeEnum) { case IMAGE -> handleImageType(binaryStream, lobInfo); case STRING -> handleStringType(binaryStream, lobInfo); default -> ""; }; } @Getter public enum LobUnit { B("B", 1L), K("KB", 1024L), M("MB", 1024L * 1024L), G("GB", 1024L * 1024L * 1024L); private final String unit; private final long size; LobUnit(String unit, long size) { this.unit = unit; this.size = size; } } @Getter public static class LOBInfo { private final String unit; private final double size; public LOBInfo(String unit, double size) { this.unit = unit; this.size = size; } public LOBInfo(long size) { if (size >= LobUnit.G.size) { this.unit = LobUnit.G.unit; this.size = (double) size / LobUnit.G.size; } else if (size >= LobUnit.M.size) { this.unit = LobUnit.M.unit; this.size = (double) size / LobUnit.M.size; } else if (size >= LobUnit.K.size) { this.unit = LobUnit.K.unit; this.size = (double) size / LobUnit.K.size; } else { this.unit = LobUnit.B.unit; this.size = (double) size; } } @Override public String toString() { return String.format("%.2f %s", size, unit); } } @Getter public enum FileTypeEnum { IMAGE("image/jpeg"), STRING("text/plain"), ; private final String description; FileTypeEnum(String description) { this.description = description; } public static FileTypeEnum fromDescription(String description) { for (FileTypeEnum fileType : values()) { if (fileType.description.equals(description)) { return fileType; } } return null; } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/KeyValue.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.List; import java.util.Map; import com.google.common.collect.Maps; import lombok.Data; import org.apache.commons.collections4.CollectionUtils; /** * @author jipengfei * @version : KeyValue.java */ @Data public class KeyValue implements Serializable { /** * attribute name */ private String key; /** * attribute value */ private String value; /** * Is it required? */ private boolean required; /** * Options */ private List choices; public static Map toMap(List keyValues) { if (CollectionUtils.isEmpty(keyValues)) { return Maps.newHashMap(); } else { Map map = Maps.newHashMap(); keyValues.forEach(keyValue -> map.put(keyValue.getKey(), String.valueOf(keyValue.getValue()))); return map; } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.List; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class MetaSchema implements Serializable { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * database list */ private List databases; /** * schema list */ private List schemas; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/OrderBy.java ================================================ package ai.chat2db.spi.model; import lombok.Data; import java.io.Serializable; @Data public class OrderBy implements Serializable { private static final long serialVersionUID = 1L; /** * sort field */ private String columnName; /** * sort by */ private boolean asc; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * @author jipengfei * @version : Procedure.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Procedure implements Serializable { private static final long serialVersionUID = 1L; //PROCEDURE_CAT String => procedure catalog (may be null) //PROCEDURE_SCHEME String => procedure schema (may be null) //PROCEDURE_NAME String => procedure name //REMARKS String => explanatory comment on the procedure //PROCEDURE_TYPE short => kind of procedure: //procedureResultUnknown - Cannot determine if a return value will be returned //procedureNoResult - Does not return a return value //procedureReturnsResult - Returns a return value //SPECIFIC_NAME String => the name which uniquely identifies this procedure within its schema. This is a user specified, or DBMS generated, name that may be different then the PROCEDURE_NAME for example with overload procedures // @JsonAlias({"PROCEDURE_CAT"}) private String databaseName; @JsonAlias({"PROCEDURE_SCHEM"}) private String schemaName; @JsonAlias({"PROCEDURE_NAME"}) private String procedureName; @JsonAlias({"REMARKS"}) private String remarks; @JsonAlias({"PROCEDURE_TYPE"}) private Short procedureType; @JsonAlias({"SPECIFIC_NAME"}) private String specificName; private String procedureBody; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/QueryResult.java ================================================ package ai.chat2db.spi.model; import lombok.Data; import java.io.Serializable; import java.util.List; import java.util.Map; @Data public class QueryResult implements Serializable { private String tableName; private List
    headerList; private List operations; private Map extra; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ResultOperation.java ================================================ package ai.chat2db.spi.model; import lombok.Data; import java.io.Serializable; import java.util.List; @Data public class ResultOperation implements Serializable { private static final long serialVersionUID = 1L; private String type; private List dataList; private List oldDataList; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SQLDataValue.java ================================================ package ai.chat2db.spi.model; import com.google.common.io.BaseEncoding; import lombok.Data; /** * @author: zgq * @date: 2024年05月30日 15:01 */ @Data public class SQLDataValue { private String value; private DataType dataType; public String getDateTypeName() { return dataType.getDataTypeName(); } public int getPrecision() { return dataType.getPrecision(); } public int getScale() { return dataType.getScale(); } public String getBlobHexString() { return BaseEncoding.base16().encode(value.getBytes()); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.Objects; import lombok.Data; /** * @author jipengfei * @version : SSHInfo.java */ @Data public class SSHInfo implements Serializable { private static final long serialVersionUID = 1L; /** * Whether to use ssh */ private boolean use; /** * ssh hostname */ private String hostName; /** * ssh port */ private String port; /** * ssh username */ private String userName; /** * ssh local port */ private String localPort; /** * ssh Certification type */ private String authenticationType; /** * ssh password */ private String password; /** * ssh key file */ private String keyFile; /** * ssh key file password */ private String passphrase; /** * ssh springboard target host */ private String rHost; /** * ssh springboard target port */ private String rPort; @Override public boolean equals(Object o) { if (this == o) {return true;} if (o == null || getClass() != o.getClass()) {return false;} SSHInfo sshInfo = (SSHInfo)o; return use == sshInfo.use && Objects.equals(hostName, sshInfo.hostName) && Objects.equals(port, sshInfo.port) && Objects.equals(userName, sshInfo.userName) && Objects.equals(localPort, sshInfo.localPort) && Objects.equals(authenticationType, sshInfo.authenticationType) && Objects.equals(password, sshInfo.password) && Objects.equals(keyFile, sshInfo.keyFile) && Objects.equals(passphrase, sshInfo.passphrase) && Objects.equals(rHost, sshInfo.rHost) && Objects.equals(rPort, sshInfo.rPort); } @Override public int hashCode() { return Objects.hash(use, hostName, port, userName, localPort, authenticationType, password, keyFile, passphrase, rHost, rPort); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java ================================================ package ai.chat2db.spi.model; import lombok.Data; import java.io.Serializable; /** * @author jipengfei * @version : SSLInfo.java */ @Data public class SSLInfo implements Serializable { private static final long serialVersionUID = 1L; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * @author jipengfei * @version : TableSchema.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Schema implements Serializable { private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * databaseName */ @JsonAlias({"TABLE_CATALOG","table_catalog"}) private String databaseName; /** * Data name */ @JsonAlias({"TABLE_SCHEM","table_schem"}) private String name; private String comment; private String owner; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Sequence.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * Sequence information * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Sequence { /** * Schema name */ private String nspname; /** * Sequence name */ private String relname; /** * Sequence data type */ private String typname; /** * Sequence cache */ private String seqcache; /** * Sequence owner */ private String rolname; /** * Sequence comment */ private String comment; /** * Sequence start value */ private String seqstart; /** * Sequence step value */ private String seqincrement; /** * Sequence max value */ private String seqmax; /** * Sequence min value */ private String seqmin; /** * Sequence cycle */ private Boolean seqcycle; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ShowDatabaseResult.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * @author jipengfei * @version : ShowDatabaseResult.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class ShowDatabaseResult implements Serializable { private static final long serialVersionUID = 1L; String database; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleColumn.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SimpleColumn implements Serializable { private static final long serialVersionUID = 1L; /** * Column name */ @JsonAlias({"COLUMN_NAME"}) private String name; @JsonAlias({"TYPE_NAME"}) private String columnType; /** * Comment */ @JsonAlias({"REMARKS"}) private String comment; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleSequence.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * Simple sequence information * * @author Sylphy */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SimpleSequence implements Serializable { private static final long serialVersionUID = 1L; /** * Sequence Name */ private String name; /** * description */ private String comment; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleTable.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class SimpleTable implements Serializable { private static final long serialVersionUID = 1L; /** * Table Name */ @JsonAlias({"TABLE_NAME"}) private String name; /** * description */ @JsonAlias({"REMARKS"}) private String comment; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Sql.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * sql object * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Sql implements Serializable { private static final long serialVersionUID = 1L; /** * sql */ private String sql; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; import java.util.List; /** * Table information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Table implements Serializable { private static final long serialVersionUID = 1L; /** * Table Name */ @JsonAlias({"TABLE_NAME"}) private String name; /** * description */ @JsonAlias({"REMARKS"}) private String comment; /** * DB name */ @JsonAlias({"TABLE_SCHEM"}) private String schemaName; /** * columnList */ private List columnList; /** * indexList */ private List indexList; /** * DB type */ private String dbType; /** * Database name */ @JsonAlias("TABLE_CAT") private String databaseName; /** * table type */ @JsonAlias("TABLE_TYPE") private String type; /** * Whether to pin it to the top */ private boolean pinned; /** * ddl */ private String ddl; /** * engine */ @JsonAlias("TYPE_NAME") private String engine; private String charset; private String collate; private Long incrementValue; private String partition; private String tablespace; private Long rows; private Long dataLength; private String createTime; private String updateTime; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java ================================================ package ai.chat2db.spi.model; import com.alibaba.fastjson2.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; import java.util.Objects; /** * Column information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableColumn implements Serializable { private static final long serialVersionUID = 1L; /** * Old column, when modifying a column, you need this parameter */ private TableColumn oldColumn; /** * The old column name, this parameter is needed when modifying the column * oldName=name when returning */ private String oldName; /** * Column name */ @JsonAlias({"COLUMN_NAME","column_name"}) private String name; /** * Table Name */ @JsonAlias({"TABLE_NAME","table_name"}) private String tableName; /** * Column type * For example, varchar(100), double(10,6) */ @JsonAlias({"TYPE_NAME","type_name"}) private String columnType; /** * Column data type * For example, varchar, double */ @JsonAlias({"DATA_TYPE","data_type"}) private Integer dataType; /** * default value */ @JsonAlias({"COLUMN_DEF","column_def"}) private String defaultValue; /** * Whether to increase automatically * Empty means there is no value. The actual semantics of the database are false. */ private Boolean autoIncrement; /** * Comment */ @JsonAlias({"REMARKS","remarks"}) private String comment; /** * Is it a primary key? */ private Boolean primaryKey; /** * primary key name */ private String primaryKeyName; /** * primaryKeyOrder */ private int primaryKeyOrder; /** * Space name */ @JsonAlias({"TABLE_SCHEM","table_schem"}) private String schemaName; /** * Database name */ @JsonAlias({"TABLE_CAT","table_cat"}) private String databaseName; // /** // * Data source dependent type name, for a UDT the type name is fully qualified // */ // private String typeName; /** * column size. */ @JsonAlias({"COLUMN_SIZE","column_size"}) private Integer columnSize; /** * is not used. */ private Integer bufferLength; /** * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. */ @JsonAlias({"DECIMAL_DIGITS","decimal_digits"}) private Integer decimalDigits; /** * Radix (typically either 10 or 2) */ @JsonAlias({"NUM_PREC_RADIX","num_prec_radix"}) private Integer numPrecRadix; /** * unused */ private Integer sqlDataType; /** * unused */ private Integer sqlDatetimeSub; /** * for char types the maximum number of bytes in the column */ private Integer charOctetLength; /** * index of column in table (starting at 1) */ @JsonAlias({"ORDINAL_POSITION","ordinal_position"}) private Integer ordinalPosition; /** * ISO rules are used to determine the nullability for a column. */ @JsonAlias({"nullable","NULLABLE"}) @JSONField(name = "nullable") private Integer nullable; /** * String => Indicates whether this is a generated column * * YES --- if this a generated column * * NO --- if this not a generated column */ private Boolean generatedColumn; private String extent; private String editStatus; private String charSetName; private String collationName; //Mysql private String value; //ORACLE private String unit; // sqlserver private Boolean sparse; // sqlserver private String defaultConstraintName; // DM seed private Integer seed; // DM increment private Integer increment; @Override public boolean equals(Object o) { if (this == o) { return true;} if (o == null || getClass() != o.getClass()) { return false;} TableColumn that = (TableColumn) o; return Objects.equals(name, that.name) && Objects.equals(tableName, that.tableName) && Objects.equals(columnType, that.columnType) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(autoIncrement, that.autoIncrement) && Objects.equals(comment, that.comment) && Objects.equals(columnSize, that.columnSize) && Objects.equals(decimalDigits, that.decimalDigits) && Objects.equals(numPrecRadix, that.numPrecRadix) && Objects.equals(sqlDataType, that.sqlDataType) && Objects.equals(ordinalPosition, that.ordinalPosition) && Objects.equals(nullable, that.nullable) && Objects.equals(extent, that.extent) && Objects.equals(charSetName, that.charSetName) && Objects.equals(collationName, that.collationName) && Objects.equals(value, that.value) && Objects.equals(unit, that.unit) && Objects.equals(sparse, that.sparse) && Objects.equals(defaultConstraintName, that.defaultConstraintName) && Objects.equals(seed, that.seed) && Objects.equals(increment, that.increment); } @Override public int hashCode() { return Objects.hash(name, tableName, columnType, defaultValue, autoIncrement, comment, columnSize, decimalDigits, numPrecRadix, sqlDataType, ordinalPosition, nullable, extent, charSetName, collationName, value, unit, sparse, defaultConstraintName, seed, increment); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java ================================================ package ai.chat2db.spi.model; import java.io.Serializable; import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** * Index information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableIndex implements Serializable { private static final long serialVersionUID = 1L; private String oldName; /** * Index name */ private String name; /** * Table Name */ private String tableName; /** * Index type * * @see IndexTypeEnum */ private String type; /** * Is it unique? */ private Boolean unique; /** * Comment */ private String comment; /** * The schema to which the index belongs */ private String schemaName; /** * Database name */ private String databaseName; /** * Columns included in the index */ private List columnList; private String editStatus; /** * Is it concurrent? */ private Boolean concurrently; /** * Index method */ private String method; /** * Foreign key points to schema */ private String foreignSchemaName; /** * Foreign key points to table name */ private String foreignTableName; /** * The column name pointed to by the foreign key */ private List foreignColumnNamelist; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * Column information * * @author Jiaju Zhuang */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class TableIndexColumn implements Serializable { private static final long serialVersionUID = 1L; /** * Index name */ @JsonAlias({"INDEX_NAME"}) private String indexName; /** * Table Name */ @JsonAlias ({"TABLE_NAME"}) private String tableName; /** * Index type * * @see */ private String type; /** * Comment */ private String comment; /** * columnName */ @JsonAlias({"COLUMN_NAME"}) private String columnName; /** * ordinalPosition */ @JsonAlias({"ORDINAL_POSITION"}) private Short ordinalPosition; /** * collation * */ private String collation; /** * The schema to which the index belongs */ @JsonAlias({"TABLE_SCHEM"}) private String schemaName; /** * Database name */ @JsonAlias({"TABLE_CAT"}) private String databaseName; /** * Is it unique? */ @JsonAlias({"NON_UNIQUE"}) private Boolean nonUnique; /** * index catalog (may be null); null when TYPE is tableIndexStatistic */ @JsonAlias({"INDEX_QUALIFIER"}) private String indexQualifier; /** * ASC_OR_DESC String => column sort sequence, "A" => ascending, "D" => descending, may be null if sort sequence is not supported; null when TYPE is tableIndexStatistic */ @JsonAlias({"ASC_OR_DESC"}) private String ascOrDesc; /** * CARDINALITY long => When TYPE is tableIndexStatistic, then this is the number of rows in the table; otherwise, it is the number of unique values in the index. */ @JsonAlias({"CARDINALITY"}) private Long cardinality; /** * When TYPE is tableIndexStatistic then this is the number of pages used for the table, otherwise it is the number of pages used for the current index. */ @JsonAlias({"PAGES"}) private Long pages; /** * Filter condition, if any. (may be null) */ @JsonAlias({"FILTER_CONDITION"}) private String filterCondition; private Long subPart; private String editStatus; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java ================================================ package ai.chat2db.spi.model; import lombok.Builder; import lombok.Data; import java.io.Serializable; import java.util.List; @Data @Builder public class TableMeta implements Serializable { private static final long serialVersionUID = 1L; private List columnTypes; private List charsets; private List collations; private List indexTypes; private List defaultValues; private List engineTypes; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java ================================================ package ai.chat2db.spi.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; /** * @author jipengfei * @version : Trigger.java */ @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Trigger implements Serializable { private static final long serialVersionUID = 1L; private String databaseName; private String schemaName; private String triggerName; private String eventManipulation; private String triggerBody; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java ================================================ package ai.chat2db.spi.model; import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import java.io.Serializable; @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor public class Type implements Serializable { private static final long serialVersionUID = 1L; @JsonAlias("TYPE_NAME") private String typeName; @JsonAlias("DATA_TYPE") private Integer dataType; @JsonAlias("PRECISION") private Integer precision; @JsonAlias("LITERAL_PREFIX") private String literalPrefix; @JsonAlias("LITERAL_SUFFIX") private String literalSuffix; @JsonAlias("CREATE_PARAMS") private String createParams; @JsonAlias("NULLABLE") private Short nullable; @JsonAlias("CASE_SENSITIVE") private Boolean caseSensitive; @JsonAlias("SEARCHABLE") private Short searchable; @JsonAlias("UNSIGNED_ATTRIBUTE") private Boolean unsignedAttribute; @JsonAlias("FIXED_PREC_SCALE") private Boolean fixedPrecScale; @JsonAlias("AUTO_INCREMENT") private Boolean autoIncrement; @JsonAlias("LOCAL_TYPE_NAME") private String localTypeName; @JsonAlias("MINIMUM_SCALE") private Short minimumScale; @JsonAlias("MAXIMUM_SCALE") private Short maximumScale; @JsonAlias("SQL_DATA_TYPE") private Integer sqlDataType; @JsonAlias("SQL_DATETIME_SUB") private Integer sqlDatetimeSub; @JsonAlias("NUM_PREC_RADIX") private Integer numPrecRadix; // TYPE_NAME String => Type name // DATA_TYPE int => SQL data type from java.sql.Types // PRECISION int => maximum precision // LITERAL_PREFIX String => prefix used to quote a literal (may be null) // LITERAL_SUFFIX String => suffix used to quote a literal (may be null) // CREATE_PARAMS String => parameters used in creating the type (may be null) // NULLABLE short => can you use NULL for this type. // typeNoNulls - does not allow NULL values // typeNullable - allows NULL values // typeNullableUnknown - nullability unknown // CASE_SENSITIVE boolean=> is it case sensitive. // SEARCHABLE short => can you use "WHERE" based on this type: // typePredNone - No support // typePredChar - Only supported with WHERE .. LIKE // typePredBasic - Supported except for WHERE .. LIKE // typeSearchable - Supported for all WHERE .. // UNSIGNED_ATTRIBUTE boolean => is it unsigned. // FIXED_PREC_SCALE boolean => can it be a money value. // AUTO_INCREMENT boolean => can it be used for an auto-increment value. // LOCAL_TYPE_NAME String => localized version of type name (may be null) // MINIMUM_SCALE short => minimum scale supported // MAXIMUM_SCALE short => maximum scale supported // SQL_DATA_TYPE int => unused // SQL_DATETIME_SUB int => unused // NUM_PREC_RADIX int => usually 2 or 10 } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java ================================================ package ai.chat2db.spi.sql; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; /** * @author jipengfei * @version : Chat2DBContext.java */ @Slf4j public class Chat2DBContext { private static final ThreadLocal CONNECT_INFO_THREAD_LOCAL = new ThreadLocal<>(); public static Map PLUGIN_MAP = new ConcurrentHashMap<>(); static { ServiceLoader s = ServiceLoader.load(Plugin.class); Iterator iterator = s.iterator(); while (iterator.hasNext()) { Plugin plugin = iterator.next(); PLUGIN_MAP.put(plugin.getDBConfig().getDbType(), plugin); } } public static DriverConfig getDefaultDriverConfig(String dbType) { return PLUGIN_MAP.get(dbType).getDBConfig().getDefaultDriverConfig(); } public static SqlBuilder getSqlBuilder() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getMetaData().getSqlBuilder(); } /** * Get the ContentContext of the current thread * * @return */ public static ConnectInfo getConnectInfo() { return CONNECT_INFO_THREAD_LOCAL.get(); } public static MetaData getMetaData() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getMetaData(); } public static MetaData getMetaData(String dbType) { if (StringUtils.isBlank(dbType)) { return getMetaData(); } return PLUGIN_MAP.get(dbType).getMetaData(); } public static DBConfig getDBConfig(String dbType) { return PLUGIN_MAP.get(dbType).getDBConfig(); } public static DBConfig getDBConfig() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBConfig(); } public static DBManage getDBManage() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBManage(); } public static Connection getConnection() { // ConnectInfo connectInfo = getConnectInfo(); // Connection connection = connectInfo.getConnection(); // try { // if (connection == null || connection.isClosed()) { // synchronized (connectInfo) { // connection = connectInfo.getConnection(); // try { // if (connection != null && !connection.isClosed()) { // log.info("get connection from cache"); // return connection; // } else { // log.info("get connection from db begin"); // connection = getDBManage().getConnection(connectInfo); // log.info("get connection from db end"); // } // } catch (SQLException e) { // log.error("get connection error", e); // log.info("get connection from db begin2"); // connection = getDBManage().getConnection(connectInfo); // log.info("get connection from db end2"); // } // connectInfo.setConnection(connection); // } // } // } catch (SQLException e) { // log.error("get connection error", e); // } return ConnectionPool.getConnection(getConnectInfo()); } public static String getDbVersion() { ConnectInfo connectInfo = getConnectInfo(); String dbVersion = connectInfo.getDbVersion(); if (dbVersion == null) { synchronized (connectInfo) { if (connectInfo.getDbVersion() != null) { return connectInfo.getDbVersion(); } else { dbVersion = SQLExecutor.getInstance().getDbVersion(getConnection()); connectInfo.setDbVersion(dbVersion); return connectInfo.getDbVersion(); } } } else { return dbVersion; } } /** * Set context * * @param info */ public static void putContext(ConnectInfo info) { DriverConfig config = info.getDriverConfig(); if (config == null) { config = getDefaultDriverConfig(info.getDbType()); info.setDriverConfig(config); } CONNECT_INFO_THREAD_LOCAL.set(info); } /** * Set context */ public static void removeContext() { ConnectInfo connectInfo = CONNECT_INFO_THREAD_LOCAL.get(); if (connectInfo != null) { // connectInfo.close(); CONNECT_INFO_THREAD_LOCAL.remove(); ConnectionPool.close(connectInfo); } } public static void close() { removeContext(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java ================================================ package ai.chat2db.spi.sql; import java.sql.Connection; import java.sql.SQLException; import java.time.LocalDateTime; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; /** * @author jipengfei * @version : ConnectInfo.java */ @Slf4j public class ConnectInfo { private String loginUser; /** * alias */ private String alias; /** * dataSourceId */ private Long dataSourceId; /** * creation time */ private LocalDateTime gmtCreate; /** * modified time */ private LocalDateTime gmtModified; /** * database */ private String databaseName; /** * schema */ private String schemaName; /** * console id */ private Long consoleId; /** * Database URL */ private String url; /** * user */ private String user; /** * password */ private String password; /** * The console independently owns the connection */ private Boolean consoleOwn = Boolean.FALSE; /** * Database type */ private String dbType; /** * port */ private Integer port; /** * */ private String urlWithOutDatabase; /** * host */ private String host; /** * ssh */ private SSHInfo ssh; /** * ssh */ private SSLInfo ssl; /** * sid */ private String sid; /** * driver */ private String driver; /** * jdbc version */ private String jdbc; /** * Extended Information */ private List extendInfo; public Connection connection; /** * Database version used for different database */ private String dbVersion; private DriverConfig driverConfig; private Date lastAccessTime; public String getDbVersion() { return dbVersion; } public void setDbVersion(String dbVersion) { this.dbVersion = dbVersion; } public DriverConfig getDriverConfig() { return driverConfig; } public void setDriverConfig(DriverConfig driverConfig) { this.driverConfig = driverConfig; } public Session getSession() { return session; } public void setSession(Session session) { this.session = session; } public Session session; public LinkedHashMap getExtendMap() { if (ObjectUtils.isEmpty(extendInfo)) { if (driverConfig != null) { extendInfo = driverConfig.getExtendInfo(); } else { return new LinkedHashMap<>(); } } if (ObjectUtils.isEmpty(extendInfo)) { return new LinkedHashMap<>(); } LinkedHashMap map = new LinkedHashMap<>(); for (KeyValue keyValue : extendInfo) { map.put(keyValue.getKey(), keyValue.getValue()); } return map; } public void setDatabase(String database) { this.databaseName = database; } public void setUrl(String url) { this.url = url; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ConnectInfo)) { return false; } ConnectInfo that = (ConnectInfo) o; return Objects.equals(dataSourceId, that.dataSourceId) && Objects.equals(gmtModified, that.gmtModified) ; } @Override public int hashCode() { return Objects.hash(dataSourceId, consoleId, databaseName); } public Long getDataSourceId() { return dataSourceId; } public void setDataSourceId(Long dataSourceId) { this.dataSourceId = dataSourceId; } public String getDatabaseName() { return databaseName; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public Long getConsoleId() { return consoleId; } public void setConsoleId(Long consoleId) { this.consoleId = consoleId; } public String getUrl() { return url; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } /** * Setter method for property password. * * @param password value to be assigned to property password */ public void setPassword(String password) { this.password = password; } /** * Getter method for property consoleOwn. * * @return property value of consoleOwn */ public Boolean getConsoleOwn() { return consoleOwn; } /** * Setter method for property consoleOwn. * * @param consoleOwn value to be assigned to property consoleOwn */ public void setConsoleOwn(Boolean consoleOwn) { this.consoleOwn = consoleOwn; } /** * Getter method for property dbType. * * @return property value of dbType */ public String getDbType() { return dbType; } /** * Setter method for property dbType. * * @param dbType value to be assigned to property dbType */ public void setDbType(String dbType) { this.dbType = dbType; } /** * Getter method for property port. * * @return property value of port */ public Integer getPort() { return port; } /** * Setter method for property port. * * @param port value to be assigned to property port */ public void setPort(Integer port) { this.port = port; } /** * Getter method for property urlWithOutDatabase. * * @return property value of urlWithOutDatabase */ public String getUrlWithOutDatabase() { return urlWithOutDatabase; } /** * Setter method for property urlWithOutDatabase. * * @param urlWithOutDatabase value to be assigned to property urlWithOutDatabase */ public void setUrlWithOutDatabase(String urlWithOutDatabase) { this.urlWithOutDatabase = urlWithOutDatabase; } /** * Getter method for property host. * * @return property value of host */ public String getHost() { return host; } /** * Setter method for property host. * * @param host value to be assigned to property host */ public void setHost(String host) { this.host = host; } /** * Getter method for property ssh. * * @return property value of ssh */ public SSHInfo getSsh() { return ssh; } /** * Setter method for property ssh. * * @param ssh value to be assigned to property ssh */ public void setSsh(SSHInfo ssh) { this.ssh = ssh; } /** * Getter method for property ssl. * * @return property value of ssl */ public SSLInfo getSsl() { return ssl; } /** * Setter method for property ssl. * * @param ssl value to be assigned to property ssl */ public void setSsl(SSLInfo ssl) { this.ssl = ssl; } /** * Getter method for property sid. * * @return property value of sid */ public String getSid() { return sid; } /** * Setter method for property sid. * * @param sid value to be assigned to property sid */ public void setSid(String sid) { this.sid = sid; } /** * Getter method for property driver. * * @return property value of driver */ public String getDriver() { return driver; } /** * Setter method for property driver. * * @param driver value to be assigned to property driver */ public void setDriver(String driver) { this.driver = driver; } /** * Getter method for property jdbc. * * @return property value of jdbc */ public String getJdbc() { return jdbc; } /** * Setter method for property jdbc. * * @param jdbc value to be assigned to property jdbc */ public void setJdbc(String jdbc) { this.jdbc = jdbc; } /** * Getter method for property extendInfo. * * @return property value of extendInfo */ public List getExtendInfo() { return extendInfo; } /** * Setter method for property extendInfo. * * @param extendInfo value to be assigned to property extendInfo */ public void setExtendInfo(List extendInfo) { this.extendInfo = extendInfo; } /** * Getter method for property connection. * * @return property value of connection */ public Connection getConnection() { return connection; } /** * Setter method for property connection. * * @param connection value to be assigned to property connection */ public void setConnection(Connection connection) { this.connection = connection; } /** * Getter method for property alias. * * @return property value of alias */ public String getAlias() { return alias; } /** * Setter method for property alias. * * @param alias value to be assigned to property alias */ public void setAlias(String alias) { this.alias = alias; } public LocalDateTime getGmtCreate() { return gmtCreate; } public void setGmtCreate(LocalDateTime gmtCreate) { this.gmtCreate = gmtCreate; } public LocalDateTime getGmtModified() { return gmtModified; } public void setGmtModified(LocalDateTime gmtModified) { this.gmtModified = gmtModified; } public String getSchemaName() { return schemaName; } public void setSchemaName(String schemaName) { this.schemaName = schemaName; } public ConnectInfo copy() { ConnectInfo copy = new ConnectInfo(); copy.setDbVersion(this.getDbVersion()); copy.setDbType(this.getDbType()); copy.setHost(this.getHost()); copy.setPort(this.getPort()); copy.setDatabaseName(this.getDatabaseName()); copy.setSchemaName(this.getSchemaName()); copy.setUser(this.getUser()); copy.setPassword(this.getPassword()); copy.setUrl(this.getUrl()); copy.setAlias(this.getAlias()); copy.setDataSourceId(this.getDataSourceId()); copy.setConsoleId(this.getConsoleId()); copy.setConsoleOwn(this.getConsoleOwn()); copy.setDriver(this.getDriver()); copy.setSsh(this.getSsh()); copy.setSsl(this.getSsl()); copy.setJdbc(this.getJdbc()); copy.setExtendInfo(this.getExtendInfo()); copy.setDriverConfig(this.getDriverConfig()); copy.setSid(this.getSid()); copy.setUrlWithOutDatabase(this.getUrlWithOutDatabase()); copy.setLastAccessTime(new Date()); return copy; } public void close() { if (this != null) { Connection connection = this.getConnection(); try { if (connection != null && !connection.isClosed()) { connection.close(); log.info("connection close success"); } } catch (SQLException e) { log.error("connection close error",e); } com.jcraft.jsch.Session session = this.getSession(); if (session != null && session.isConnected() && this.getSsh() != null && this.getSsh().isUse()) { try { session.delPortForwardingL(Integer.parseInt(this.getSsh().getLocalPort())); } catch (JSchException e) { log.error("ssh close error",e); } } } } public String getKey() { return "loginUser:"+loginUser + "_dataSourceId:" + dataSourceId + "_databaseName:" + databaseName + "_schemaName:" + schemaName + "_consoleId:" + consoleId; } public String getLoginUser() { return loginUser; } public void setLoginUser(String loginUser) { this.loginUser = loginUser; } public Date getLastAccessTime() { return lastAccessTime; } public void setLastAccessTime(Date lastAccessTime) { this.lastAccessTime = lastAccessTime; } private final AtomicInteger refCount = new AtomicInteger(0); public int incrementRefCount() { return refCount.incrementAndGet(); } public int decrementRefCount() { return refCount.decrementAndGet(); } public int getRefCount() { return refCount.get(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectionPool.java ================================================ package ai.chat2db.spi.sql; import lombok.extern.slf4j.Slf4j; import org.h2.engine.ConnectionInfo; import java.sql.Connection; import java.sql.SQLException; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j public class ConnectionPool { private static ConcurrentHashMap CONNECTION_MAP = new ConcurrentHashMap<>(); static { new Thread(() -> { while (true) { try { Thread.sleep(1000 * 60 * 10); log.info("CONNECTION_MAP size:{}",CONNECTION_MAP.size()); CONNECTION_MAP.forEach((k, v) -> { log.info("CONNECTION_key:{},value:{}",k,v.getRefCount()); if (v.getLastAccessTime().getTime() + 1000 * 60 * 20 < System.currentTimeMillis() && v.getRefCount() == 0) { try { Connection connection = v.getConnection(); if (connection != null) { connection.close(); CONNECTION_MAP.remove(k); } } catch (SQLException e) { log.error("close connection error", e); } } }); } catch (InterruptedException e) { log.error("close connection error", e); } } }).start(); } public static synchronized void removeConnection(Long datasourceId) { CONNECTION_MAP.forEach((k, v) -> { if (k.contains(String.valueOf(datasourceId))) { try { Connection connection = v.getConnection(); if (connection != null) { connection.close(); CONNECTION_MAP.remove(k); } } catch (SQLException e) { log.error("close connection error", e); } } }); } public static Connection getConnection(ConnectInfo connectInfo) { Connection connection = connectInfo.getConnection(); try { if (connection != null && !connection.isClosed()) { log.info("get connection from local"); return connection; } String key = connectInfo.getKey(); ConnectInfo lock = CONNECTION_MAP.computeIfAbsent(key, k -> connectInfo.copy()); try { synchronized (lock) { connection = connectInfo.getConnection(); if (connection != null && !connection.isClosed()) { log.info("get connection from local"); return connection; } int n = lock.incrementRefCount(); if (n == 1) { connection = lock.getConnection(); if (connection != null && !connection.isClosed()) { log.info("get connection from cache"); connectInfo.setConnection(connection); lock.setLastAccessTime(new Date()); return connection; } else { log.info("get connection from db begin"); connection = Chat2DBContext.getDBManage().getConnection(connectInfo); lock.setConnection(connection); lock.setLastAccessTime(new Date()); log.info("get connection from db end"); } connectInfo.setConnection(connection); return connection; } else { connection = Chat2DBContext.getDBManage().getConnection(connectInfo); connectInfo.setConnection(connection); return connection; } } } catch (SQLException e) { log.error("get connection error", e); if (connection != null && !connection.isClosed()) { connection.close(); } } } catch (SQLException e) { log.error("get connection error", e); try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (Exception e1) { log.error("", e1); } } return null; } public static void close(ConnectInfo connectInfo) { String key = connectInfo.getKey(); try { Connection currentConnection = connectInfo.getConnection(); // 如果当前连接已经关闭,则不需要重复关闭 if (currentConnection == null || currentConnection.isClosed()) { log.info("connection is already closed, key:{}, n:{}", connectInfo.getKey(), connectInfo.getRefCount()); return; } } catch (SQLException e) { log.error("connection close error",e); } ConnectInfo lock = CONNECTION_MAP.get(key); if (lock != null) { synchronized (lock) { int n = lock.decrementRefCount(); if (n == 0) { lock.setLastAccessTime(new Date()); lock.setConnection(connectInfo.getConnection()); } else { connectInfo.close(); } } } else { connectInfo.close(); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/DocumentUtils.java ================================================ package ai.chat2db.spi.sql; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.lang3.ClassUtils; /** * @author luojun * @version 1.0 * @description: 接口定义 * @date 2024/5/31 19:05 **/ public class DocumentUtils { public static LinkedHashMap convertToMap(Object obj) { LinkedHashMap map = new LinkedHashMap<>(); if (obj == null) { return map; } if (ClassUtils.isPrimitiveOrWrapper(obj.getClass()) || String.class.equals(obj.getClass())) { map.put("result", obj); return map; } for (Map.Entry entry : ((Map) obj).entrySet()) { Object value = entry.getValue(); if (value == null) { map.put(entry.getKey(), null); } else if (ClassUtils.isPrimitiveOrWrapper(value.getClass()) || String.class.equals(value.getClass())) { map.put(entry.getKey(), value); } else if (entry.getValue() instanceof Map) { LinkedHashMap mmp = convertToMap(entry.getValue()); map.put(entry.getKey(), mmp); } else { map.put(entry.getKey(), entry.getValue().toString()); } } return map; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java ================================================ package ai.chat2db.spi.sql; import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.DriverEntry; import ai.chat2db.spi.util.JdbcJarUtils; import com.alibaba.fastjson2.JSON; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import static ai.chat2db.spi.util.JdbcJarUtils.getFullPath; /** * @author jipengfei * @version : IsolationDriverManager.java */ public class IDriverManager { private static final Logger log = LoggerFactory.getLogger(IDriverManager.class); private static final Map CLASS_LOADER_MAP = new ConcurrentHashMap(); private static final Map DRIVER_ENTRY_MAP = new ConcurrentHashMap(); private static final String SQL_STATE_CODE = "08001"; public static Connection getConnection(String url, DriverConfig driver) throws SQLException { Properties info = new Properties(); return getConnection(url, info, driver); } public static Connection getConnection(String url, String user, String password, DriverConfig driver) throws SQLException { Properties info = new Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return getConnection(url, info, driver); } public static Connection getConnection(String url, String user, String password, DriverConfig driver, Map properties) throws SQLException { Properties info = new Properties(); if (StringUtils.isNotEmpty(user)) { info.put("user", user); } if (StringUtils.isNotEmpty(password)) { info.put("password", password); } if (properties != null && !properties.isEmpty()) { for (Map.Entry entry : properties.entrySet()) { if (entry.getKey() != null && entry.getValue() != null) { info.put(entry.getKey(), entry.getValue()); } } } return getConnection(url, info, driver); } public static Connection getConnection(String url, Properties info, DriverConfig driver) throws SQLException { if (Objects.isNull(url)) { throw new SQLException("The url cannot be null", SQL_STATE_CODE); } DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); if (Objects.isNull(driverEntry)) { driverEntry = getJDBCDriver(driver); } Connection connection; try { connection = driverEntry.getDriver().connect(url, info); if (Objects.isNull(connection)) { throw new SQLException(String.format("driver.connect return null , No suitable driver found for url %s", url), SQL_STATE_CODE); } return connection; } catch (SQLException sqlException) { Connection con = tryConnectionAgain(driverEntry, url, info); if (Objects.isNull(con)) { throw new SQLException(String.format("Cannot create connection (%s)", sqlException.getMessage()), SQL_STATE_CODE, sqlException); } return con; } } public static DriverPropertyInfo[] getProperty(DriverConfig driver) throws SQLException { if (Objects.isNull(driver)) { return null; } DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); try { if (driverEntry == null) { driverEntry = getJDBCDriver(driver); } String url = Objects.isNull(driver.getUrl()) ? "" : driver.getUrl(); return driverEntry.getDriver().getPropertyInfo(url, null); } catch (Exception var7) { return null; } } private static Connection tryConnectionAgain(DriverEntry driverEntry, String url, Properties info) throws SQLException { if (url.contains("mysql")) { if (!info.containsKey("useSSL")) { info.put("useSSL", "false"); } return driverEntry.getDriver().connect(url, info); } return null; } private static DriverEntry getJDBCDriver(DriverConfig driver) throws SQLException { synchronized (driver) { try { if (DRIVER_ENTRY_MAP.containsKey(driver.getJdbcDriver())) { return DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); } ClassLoader cl = getClassLoader(driver); Driver d = (Driver) cl.loadClass(driver.getJdbcDriverClass()).newInstance(); DriverEntry driverEntry = DriverEntry.builder().driverConfig(driver).driver(d).build(); DRIVER_ENTRY_MAP.put(driver.getJdbcDriver(), driverEntry); return driverEntry; } catch (Exception e) { throw new ConnectionException("connection.driver.load.error", null, e); } } } public static ClassLoader getClassLoader(DriverConfig driverConfig) throws MalformedURLException, ClassNotFoundException { String jarPath = driverConfig.getJdbcDriver(); if (CLASS_LOADER_MAP.containsKey(jarPath)) { return CLASS_LOADER_MAP.get(jarPath); } else { synchronized (jarPath) { if (CLASS_LOADER_MAP.containsKey(jarPath)) { return CLASS_LOADER_MAP.get(jarPath); } String[] jarPaths = jarPath.split(","); URL[] urls = new URL[jarPaths.length]; for (int i = 0; i < jarPaths.length; i++) { File driverFile = new File(getFullPath(jarPaths[i])); urls[i] = driverFile.toURI().toURL(); } //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); log.info("ClassLoader class:{}", cl.hashCode()); log.info("ClassLoader URLs:{}", JSON.toJSONString(cl.getURLs())); try { cl.loadClass(driverConfig.getJdbcDriverClass()); } catch (Exception e) { //If an error is reported, delete the directory and try again. for (int i = 0; i < jarPaths.length; i++) { File driverFile = new File(JdbcJarUtils.getNewFullPath(jarPaths[i])); urls[i] = driverFile.toURI().toURL(); } //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); cl.loadClass(driverConfig.getJdbcDriverClass()); } CLASS_LOADER_MAP.put(jarPath, cl); return cl; } } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/MongExtendedJsonObjectIdConverter.java ================================================ //package ai.chat2db.spi.sql; // //import org.bson.json.Converter; //import org.bson.json.StrictJsonWriter; //import org.bson.types.ObjectId; // //public class MongExtendedJsonObjectIdConverter implements Converter { // @Override // public void convert(final ObjectId value, final StrictJsonWriter writer) { // writer.writeStartObject(); // writer.writeString("", value.toHexString()); // writer.writeEndObject(); // } //} ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ResultSetConsumer.java ================================================ package ai.chat2db.spi.sql; import java.sql.ResultSet; import java.sql.SQLException; @FunctionalInterface public interface ResultSetConsumer { void accept(ResultSet resultSet) throws SQLException; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ResultSetFunction.java ================================================ package ai.chat2db.spi.sql; import java.sql.ResultSet; import java.sql.SQLException; public interface ResultSetFunction { R apply(ResultSet t) throws SQLException; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java ================================================ package ai.chat2db.spi.sql; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.enums.DataSourceTypeEnum; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.CommandExecutor; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.ValueProcessor; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.enums.SqlTypeEnum; import ai.chat2db.spi.model.*; import ai.chat2db.spi.util.JdbcUtils; import ai.chat2db.spi.util.ResultSetUtils; import ai.chat2db.spi.util.SqlUtils; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.core.util.ArrayUtil; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.util.Assert; import java.sql.*; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; /** * Dbhub unified database connection management * * @author jipengfei */ @Slf4j public class SQLExecutor implements CommandExecutor { /** * Singleton instance of SQLExecutor. */ private static final SQLExecutor INSTANCE = new SQLExecutor(); public SQLExecutor() { } public static SQLExecutor getInstance() { return INSTANCE; } public R execute(Connection connection, String sql, ResultSetFunction function) { try (Statement stmt = connection.createStatement();) { boolean query = stmt.execute(sql); // Represents the query if (query) { try (ResultSet rs = stmt.getResultSet();) { return function.apply(rs); } } } catch (Exception e) { log.error("execute:{}", sql, e); throw new RuntimeException(e); } return null; } public void execute(Connection connection, String sql, ResultSetConsumer consumer) { try (Statement stmt = connection.createStatement()) { boolean query = stmt.execute(sql); // Represents the query if (query) { try (ResultSet rs = stmt.getResultSet();) { consumer.accept(rs); } } } catch (Exception e) { log.error("execute:{}", sql, e); throw new RuntimeException(e); } } public void execute( Connection connection, String sql, Consumer> headerConsumer, Consumer> rowConsumer, java.util.function.Function valueFunction, boolean limitSize) { Assert.notNull(sql, "SQL must not be null"); try (Statement stmt = connection.createStatement();) { boolean query = stmt.execute(sql); // Represents the query if (query) { ResultSet rs = null; try { rs = stmt.getResultSet(); // Get how many columns ResultSetMetaData resultSetMetaData = rs.getMetaData(); int col = resultSetMetaData.getColumnCount(); // Get header information List
    headerList = generateHeaderList(resultSetMetaData); headerConsumer.accept(headerList); while (rs.next()) { List row = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { JDBCDataValue jdbcDataValue = new JDBCDataValue(rs, resultSetMetaData, i, limitSize); row.add(valueFunction.apply(jdbcDataValue)); } rowConsumer.accept(row); } } finally { JdbcUtils.closeResultSet(rs); } } } catch (SQLException e) { log.error("execute:{}", sql, e); throw new RuntimeException(e); } } public R preExecute(Connection connection, String sql, String[] args, ResultSetFunction function) { log.info("preExecute:{}", sql); try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { if (ArrayUtil.isNotEmpty(args)) { for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i + 1, args[i]); } } boolean query = preparedStatement.execute(); // Represents the query if (query) { try (ResultSet rs = preparedStatement.getResultSet()) { return function.apply(rs); } } } catch (Exception e) { log.error("execute:{}", sql, e); throw new RuntimeException(e); } return null; } // /** // * Execute SQL // * // * @param sql // * @return // * @throws SQLException // */ // public ExecuteResult execute(final String sql, Connection connection, ValueHandler valueHandler) // throws SQLException { // return execute(sql, connection, true, null, null, valueHandler); // } @Override public ExecuteResult executeUpdate(String sql, Connection connection, int n) throws SQLException { Assert.notNull(sql, "SQL must not be null"); ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); try (Statement stmt = connection.createStatement()) { int affectedRows = stmt.executeUpdate(sql); if (affectedRows != n) { log.info("Update error {} update affectedRows = {}", sql, affectedRows); } } return executeResult; } @Override public List executeSelectTable(Command command) { MetaData metaData = Chat2DBContext.getMetaData(); String tableName = metaData.getMetaDataName(command.getDatabaseName(), command.getSchemaName(), command.getTableName()); String sql = "select * from " + tableName; command.setScript(sql); return execute(command); } /** * Executes the given SQL query using the provided connection. * * @param sql The SQL query to be executed. * @param connection The database connection to use for the query. * @param limitRowSize Flag to indicate if row size should be limited. * @param offset The starting point of rows to fetch in the result set. * @param count The number of rows to fetch from the result set. * @return ExecuteResult containing the result of the execution. * @throws SQLException If there is any SQL related error. */ public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, Integer count) throws SQLException { Assert.notNull(sql, "SQL must not be null"); ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); try (Statement stmt = connection.createStatement()) { stmt.setFetchSize(EasyToolsConstant.MAX_PAGE_SIZE); if (offset != null && count != null) { stmt.setMaxRows(offset + count); } TimeInterval timeInterval = new TimeInterval(); boolean query = stmt.execute(sql); executeResult.setDescription(I18nUtils.getMessage("sqlResult.success")); // Represents the query if (query) { executeResult = generateQueryExecuteResult(stmt, limitRowSize, offset, count); } else { // Modification or other executeResult.setUpdateCount(stmt.getUpdateCount()); } executeResult.setDuration(timeInterval.interval()); } return executeResult; } private ExecuteResult generateQueryExecuteResult(Statement stmt, boolean limitRowSize, Integer offset, Integer count) throws SQLException { ExecuteResult executeResult = ExecuteResult.builder().success(Boolean.TRUE).build(); executeResult.setDescription(I18nUtils.getMessage("sqlResult.success")); ResultSet rs = null; try { rs = stmt.getResultSet(); // Get how many columns ResultSetMetaData resultSetMetaData = rs.getMetaData(); int col = resultSetMetaData.getColumnCount(); // Get header information List
    headerList = generateHeaderList(resultSetMetaData); int chat2dbAutoRowIdIndex = getChat2dbAutoRowIdIndex(headerList); // Get data information List> dataList = generateDataList(rs, col, chat2dbAutoRowIdIndex, limitRowSize, offset, count); executeResult.setHeaderList(headerList); executeResult.setDataList(dataList); } finally { JdbcUtils.closeResultSet(rs); } return executeResult; } private List> generateDataList(ResultSet rs, int col, int chat2dbAutoRowIdIndex, boolean limitRowSize, Integer offset, Integer count) throws SQLException { List> dataList = Lists.newArrayList(); if (offset == null || offset < 0) { offset = 0; } int rowNumber = 0; int rowCount = 1; while (rs.next()) { if (rowNumber++ < offset) { continue; } List row = Lists.newArrayListWithExpectedSize(col); dataList.add(row); for (int i = 1; i <= col; i++) { if (chat2dbAutoRowIdIndex == i) { continue; } ValueProcessor valueProcessor = Chat2DBContext.getMetaData().getValueProcessor(); row.add(valueProcessor.getJdbcValue(new JDBCDataValue(rs, rs.getMetaData(), i, limitRowSize))); } if (count != null && count > 0 && rowCount++ >= count) { break; } } return dataList; } private int getChat2dbAutoRowIdIndex(List
    headerList) { for (int i = 0; i < headerList.size(); i++) { Header header = headerList.get(i); if ("CAHT2DB_AUTO_ROW_ID".equals(header.getName())) { headerList.remove(i); return i + 1; } } return -1; } private List
    generateHeaderList(ResultSetMetaData resultSetMetaData) throws SQLException { int col = resultSetMetaData.getColumnCount(); List
    headerList = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() .dataType(JdbcUtils.resolveDataType( resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) .build()); } return headerList; } public ExecuteResult execute(Connection connection, String sql) throws SQLException { return execute(sql, connection, true, null, null); } /** * Get all databases * * @param connection * @return */ public List databases(Connection connection) { try (ResultSet resultSet = connection.getMetaData().getCatalogs();) { List databases = ResultSetUtils.toObjectList(resultSet, Database.class); if (CollectionUtils.isEmpty(databases)) { return databases; } return databases.stream().filter(database -> database.getName() != null).collect(Collectors.toList()); } catch (SQLException e) { throw new RuntimeException(e); } } /** * Retrieves the schema names available in this database. The results are ordered by TABLE_CATALOG and TABLE_SCHEM. * The schema columns are: TABLE_SCHEM String => schema name TABLE_CATALOG String => catalog name (may be null) * Params: catalog – a catalog name; must match the catalog name as it is stored in the database;"" retrieves those * without a catalog; null means catalog name should not be used to narrow down the search. schemaPattern – a schema * name; must match the schema name as it is stored in the database; null means schema name should not be used to * narrow down the search. Returns: a ResultSet object in which each row is a schema description Throws: * SQLException – if a database access error occurs Since: 1.6 See Also: getSearchStringEscape */ public List schemas(Connection connection, String databaseName, String schemaName) { if (StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)) { try (ResultSet resultSet = connection.getMetaData().getSchemas()) { return ResultSetUtils.toObjectList(resultSet, Schema.class); } catch (SQLException e) { throw new RuntimeException("Get schemas error", e); } } try (ResultSet resultSet = connection.getMetaData().getSchemas(databaseName, schemaName)) { return ResultSetUtils.toObjectList(resultSet, Schema.class); } catch (SQLException e) { throw new RuntimeException("Get schemas error", e); } } /** * Get all database tables * * @param connection * @param databaseName * @param schemaName * @param tableName * @param types * @return */ public List
    tables(Connection connection, String databaseName, String schemaName, String tableName, String types[]) { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, types)) { return ResultSetUtils.toObjectList(resultSet, Table.class); } catch (SQLException e) { throw new RuntimeException(e); } } /** * query table names * * @param connection * @param databaseName * @param schemaName * @param tableName * @param types * @return */ public List tableNames(Connection connection, String databaseName, String schemaName, String tableName, String[] types) { List tableNames = new ArrayList<>(); try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, types)) { while (resultSet.next()) { tableNames.add(resultSet.getString("TABLE_NAME")); } } catch (Exception e) { throw new RuntimeException(e); } return tableNames; } /** * Get all database table columns * * @param connection * @param databaseName * @param schemaName * @param tableName * @param columnName * @return */ public List columns(Connection connection, String databaseName, String schemaName, String tableName, String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, columnName)) { return ResultSetUtils.toObjectList(resultSet, TableColumn.class); } catch (Exception e) { throw new RuntimeException(e); } } /** * get all table index info * * @param connection connection * @param databaseName databaseName of the index * @param schemaName schemaName of the index * @param tableName tableName of the index * @return List table index list */ public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, false)) { List tableIndexColumns = ResultSetUtils.toObjectList(resultSet, TableIndexColumn.class); tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() .stream().forEach(entry -> { TableIndex tableIndex = new TableIndex(); TableIndexColumn column = entry.getValue().get(0); tableIndex.setName(entry.getKey()); tableIndex.setTableName(column.getTableName()); tableIndex.setSchemaName(column.getSchemaName()); tableIndex.setDatabaseName(column.getDatabaseName()); tableIndex.setUnique(!column.getNonUnique()); tableIndex.setColumnList(entry.getValue()); tableIndices.add(tableIndex); }); } catch (SQLException e) { throw new RuntimeException(e); } return tableIndices; } /** * Get all functions available in a catalog. * * @param connection connection * @param databaseName databaseName of the function * @param schemaName schemaName of the function * @return List */ public List functions(Connection connection, String databaseName, String schemaName) { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Function.class); } catch (Exception e) { throw new RuntimeException(e); } } /** * Retrieves a description of all the data types supported by this database. They are ordered by DATA_TYPE and then * by how closely the data type maps to the corresponding JDBC SQL type. If the database supports SQL distinct * types, then getTypeInfo() will return a single row with a TYPE_NAME of DISTINCT and a DATA_TYPE of * Types.DISTINCT. If the database supports SQL structured types, then getTypeInfo() will return a single row with a * TYPE_NAME of STRUCT and a DATA_TYPE of Types.STRUCT. If SQL distinct or structured types are supported, then * information on the individual types may be obtained from the getUDTs() method. * * @param connection connection * @return List */ public List types(Connection connection) { try (ResultSet resultSet = connection.getMetaData().getTypeInfo();) { return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Type.class); } catch (Exception e) { throw new RuntimeException(e); } } /** * procedure list * * @param connection connection * @param databaseName databaseName * @param schemaName schemaName * @return List */ public List procedures(Connection connection, String databaseName, String schemaName) { try (ResultSet resultSet = connection.getMetaData().getProcedures(databaseName, schemaName, null)) { return ResultSetUtils.toObjectList(resultSet, Procedure.class); } catch (Exception e) { throw new RuntimeException(e); } } public String getDbVersion(Connection connection) { try { String dbVersion = connection.getMetaData().getDatabaseProductVersion(); return dbVersion; } catch (Exception e) { log.error("get db version error", e); } return ""; } @Override public List execute(Command command) { if (StringUtils.isBlank(command.getScript())) { return Collections.emptyList(); } // parse sql String type = Chat2DBContext.getConnectInfo().getDbType(); DbType dbType = JdbcUtils.parse2DruidDbType(type); List sqlList = Lists.newArrayList(command.getScript()); if(!command.isSingle()) { sqlList = SqlUtils.parse(command.getScript(), dbType, true); } if (CollectionUtils.isEmpty(sqlList)) { throw new BusinessException("dataSource.sqlAnalysisError"); } List result = new ArrayList<>(); // Execute SQL for (String originalSql : sqlList) { ExecuteResult executeResult = executeSQL(originalSql, dbType, command); result.add(executeResult); } return result; } private ExecuteResult executeSQL(String originalSql, DbType dbType, Command param) { int pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); int pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); Integer offset = (pageNo - 1) * pageSize; Integer count = pageSize; SqlTypeEnum sqlType = getSqlType(dbType, originalSql); ExecuteResult executeResult = null; if (SqlTypeEnum.SELECT.equals(sqlType) && !SqlUtils.hasPageLimit(originalSql, dbType)) { String pageLimit = Chat2DBContext.getSqlBuilder().pageLimit(originalSql, offset, pageNo, pageSize); if (StringUtils.isNotBlank(pageLimit)) { executeResult = execute(pageLimit, 0, count); } } if (executeResult == null || !executeResult.getSuccess()) { executeResult = execute(originalSql, offset, count); } executeResult.setSqlType(sqlType.getCode()); executeResult.setOriginalSql(originalSql); SqlUtils.buildCanEditResult(originalSql, dbType, executeResult); // Add row number addRowNumber(executeResult, pageNo, pageSize); // Total number of fuzzy rows setPageInfo(executeResult, sqlType, pageNo, pageSize); return executeResult; } private SqlTypeEnum getSqlType(DbType dbType, String originalSql) { SqlTypeEnum sqlType = SqlTypeEnum.UNKNOWN; // parse sql String type = Chat2DBContext.getConnectInfo().getDbType(); boolean supportDruid = !DataSourceTypeEnum.MONGODB.getCode().equals(type); SQLStatement sqlStatement = null; if (supportDruid) { try { sqlStatement = SQLUtils.parseSingleStatement(originalSql, dbType); } catch (Exception e) { log.warn("Failed to parse sql: {}", originalSql, e); } } // Mongodb is currently unable to recognize it, so every time a page is transmitted if (!supportDruid || (sqlStatement instanceof SQLSelectStatement)) { sqlType = SqlTypeEnum.SELECT; } return sqlType; } private void setPageInfo(ExecuteResult executeResult, SqlTypeEnum sqlType, int pageNo, int pageSize) { if (SqlTypeEnum.SELECT.equals(sqlType)) { executeResult.setPageNo(pageNo); executeResult.setPageSize(pageSize); executeResult.setHasNextPage( CollectionUtils.size(executeResult.getDataList()) >= executeResult.getPageSize()); } else { executeResult.setPageNo(pageNo); executeResult.setPageSize(CollectionUtils.size(executeResult.getDataList())); executeResult.setHasNextPage(Boolean.FALSE); } executeResult.setFuzzyTotal(calculateFuzzyTotal(pageNo, pageSize, executeResult)); } private void addRowNumber(ExecuteResult executeResult, int pageNo, int pageSize) { List
    headers = executeResult.getHeaderList(); Header rowNumberHeader = Header.builder() .name(I18nUtils.getMessage("sqlResult.rowNumber")) .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER .getCode()).build(); executeResult.setHeaderList(EasyCollectionUtils.union(Arrays.asList(rowNumberHeader), headers)); // Add row number if (executeResult.getDataList() != null) { int rowNumberIncrement = 1 + Math.max(pageNo - 1, 0) * pageSize; for (int i = 0; i < executeResult.getDataList().size(); i++) { List row = executeResult.getDataList().get(i); List newRow = Lists.newArrayListWithExpectedSize(row.size() + 1); newRow.add(Integer.toString(i + rowNumberIncrement)); newRow.addAll(row); executeResult.getDataList().set(i, newRow); } } } private String calculateFuzzyTotal(int pageNo, int pageSize, ExecuteResult executeResult) { int dataSize = CollectionUtils.size(executeResult.getDataList()); if (pageSize <= 0) { return Integer.toString(dataSize); } int fuzzyTotal = Math.max(pageNo - 1, 0) * pageSize + dataSize; if (dataSize < pageSize) { return Integer.toString(fuzzyTotal); } return fuzzyTotal + "+"; } private ExecuteResult execute(String sql, Integer offset, Integer count) { ExecuteResult executeResult; try { executeResult = SQLExecutor.getInstance().execute(sql, Chat2DBContext.getConnection(), true, offset, count); } catch (SQLException e) { log.error("Execute sql: {} exception", sql, e); executeResult = ExecuteResult.builder() .sql(sql) .success(Boolean.FALSE) .message(e.getMessage()) .build(); } return executeResult; } /** * Formats the given table name by stripping off any schema or catalog prefixes. * If the table name contains a dot ('.'), it splits the string by the dot * and returns the last part, which is generally the actual table name. * If the table name is blank (null, empty, or only whitespace), it returns the original table name. * * @param tableName the original table name, potentially including schema or catalog prefixes. * @return the formatted table name, or the original table name if it's blank or contains no dot. */ public static String formatTableName(String tableName) { // Check if the table name is blank (null, empty, or only whitespace) if (StringUtils.isBlank(tableName)) { return tableName; } // Check if the table name contains a dot ('.') if (tableName.contains(".")) { // Split the table name by the dot and return the last part String[] split = tableName.split("\\."); return split[split.length - 1]; } // Return the original table name if it contains no dot return tableName; } public void execute(Connection connection, String sql, int batchSize, ResultSetConsumer consumer) { try (Statement stmt = connection.createStatement()) { stmt.setFetchSize(batchSize); boolean query = stmt.execute(sql); // Represents the query if (query) { try (ResultSet rs = stmt.getResultSet()) { consumer.accept(rs); } } } catch (Exception e) { log.error("execute error:{}", sql, e); throw new RuntimeException(e); } } public void executeBatchInsert(Connection connection, List sqlCacheList) { try (Statement stmt = connection.createStatement()) { for (String sql : sqlCacheList) { stmt.addBatch(sql); } stmt.executeBatch(); stmt.clearBatch(); } catch (SQLException e) { throw new RuntimeException(e); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SqlParseUtils.java ================================================ package ai.chat2db.spi.sql; import java.util.List; import com.alibaba.druid.sql.parser.SQLParserUtils; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; /** * @author luojun * @version 1.0 * @description: 用于解析sql,输出语句不会改变关键字的大小写 * @date 2024/6/3 10:57 **/ @Slf4j public class SqlParseUtils { public static List parseSql(String sql) { List list = Lists.newArrayList(); try { return SQLParserUtils.splitAndRemoveComment(sql, null); } catch (Exception e) { list.add(SQLParserUtils.removeComment(sql, null)); log.error("parse sql error", e); } return list; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/MyUserInfo.java ================================================ package ai.chat2db.spi.ssh; import com.jcraft.jsch.UserInfo; public class MyUserInfo implements UserInfo { private String passphrase; public MyUserInfo(String passphrase) { this.passphrase = passphrase; } @Override public String getPassphrase() { return passphrase; } @Override public String getPassword() { return null; } @Override public boolean promptPassword(String s) { return true; } @Override public boolean promptPassphrase(String s) { return true; } @Override public boolean promptYesNo(String s) { return true; } @Override public void showMessage(String s) { System.out.println(s); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java ================================================ package ai.chat2db.spi.ssh; import java.security.Security; import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.model.SSHInfo; import cn.hutool.core.net.NetUtil; import cn.hutool.extra.ssh.JschUtil; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * @author jipengfei * @version : SSHSessionManager.java */ @Slf4j public class SSHManager { static { try { Security.insertProviderAt(new BouncyCastleProvider(), 1); JSch.setConfig("kex", JSch.getConfig("kex") + ",diffie-hellman-group1-sha1"); JSch.setConfig("server_host_key", JSch.getConfig("server_host_key") + ",ssh-rsa,ssh-dss"); }catch (Exception e){ log.error("SSHManager init error",e); } } public static Session getSSHSession(SSHInfo ssh) { Session session = null; try { if (StringUtils.isNotBlank(ssh.getKeyFile())) { byte[] passphrase = StringUtils.isNotBlank(ssh.getPassphrase()) ? StringUtils.getBytes( ssh.getPassphrase(), "UTF-8") : null; session = JschUtil.getSession(ssh.getHostName(), Integer.parseInt(ssh.getPort()), ssh.getUserName(), ssh.getKeyFile(), passphrase); } else if (StringUtils.isNotBlank(ssh.getUserName())) { session = JschUtil.getSession(ssh.getHostName(), Integer.parseInt(ssh.getPort()), ssh.getUserName(), ssh.getPassword()); } } catch (Exception e) { log.info("getSSHSession error,sshinfo:{}",ssh.toString(), e); throw new ConnectionException("connection.ssh.error", null, e); } if (session != null && StringUtils.isNotBlank(ssh.getRHost()) && StringUtils.isNotBlank(ssh.getRPort())) { try { String[] portForwardingL = session.getPortForwardingL(); if (portForwardingL != null && portForwardingL.length > 0) { return session; } int localPort = !StringUtils.isBlank(ssh.getLocalPort()) ? Integer.parseInt(ssh.getLocalPort()) : NetUtil.getUsableLocalPort(); ssh.setLocalPort(String.valueOf(localPort)); session.setPortForwardingL(localPort, ssh.getRHost(), Integer.parseInt(ssh.getRPort())); } catch (Exception e) { log.info("getSSHSession setPortForwardingL error,sshinfo:{}",ssh.toString(), e); if (session != null && session.isConnected()) { session.disconnect(); } throw new ConnectionException("connection.ssh.error", null, e); } } return session; } public static void close() { JschUtil.closeAll(); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java ================================================ package ai.chat2db.spi.util; import java.io.PrintWriter; import java.io.StringWriter; import lombok.extern.slf4j.Slf4j; /** * exception utils */ @Slf4j public class ExceptionUtils { /** * print stack trace * * @param throwable * @return */ public static String getErrorInfoFromException(Throwable throwable) { try (StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter)) { throwable.printStackTrace(printWriter); return stringWriter.toString(); } catch (Exception e) { log.error("ErrorInfoFromException", e); return "ErrorInfoFromException"; } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java ================================================ package ai.chat2db.spi.util; import ai.chat2db.spi.config.DBConfig; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class FileUtils { public static T readJsonValue(Class loaderClass, String path, Class clazz) { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); T value = null; try { value = mapper.readValue(loaderClass.getResourceAsStream(path), clazz); // Use data in obj } catch (IOException e) { return null; } return value; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/Holder.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; public class Holder { private T value; public Holder() {} public Holder(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java ================================================ package ai.chat2db.spi.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.concurrent.Executors; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Dispatcher; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * @author jipengfei * @version : JdbcJarUtils.java */ public class JdbcJarUtils { private static final OkHttpClient async_client = new OkHttpClient.Builder() .dispatcher(new Dispatcher(Executors.newFixedThreadPool(20))) // 设定线程池大小 .build(); private static final OkHttpClient client = new OkHttpClient(); public static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + File.separator + "jdbc-lib" + File.separator; static { File file = new File(PATH); if (!file.exists()) { file.mkdirs(); } } public static void asyncDownload(List urls) throws Exception { for (String url : urls) { String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); File file = new File(outputPath); if (file.exists()) { continue; } asyncDownload(url); } } public static void asyncDownload(String url) throws Exception { String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); File file = new File(outputPath); if (file.exists()) { file.delete(); } Request request = new Request.Builder() .url(url) .build(); async_client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } try (InputStream is = response.body().byteStream(); FileOutputStream fos = new FileOutputStream(outputPath)) { byte[] buffer = new byte[2048]; int length; while ((length = is.read(buffer)) != -1) { fos.write(buffer, 0, length); } fos.flush(); } } }); } public static void download(String url) throws IOException { String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); File file = new File(outputPath); if (file.exists()) { file.delete(); } Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } try (InputStream is = response.body().byteStream(); FileOutputStream fos = new FileOutputStream(outputPath)) { byte[] buffer = new byte[2048]; int length; while ((length = is.read(buffer)) != -1) { fos.write(buffer, 0, length); } fos.flush(); } } } public static String getNewFullPath(String jarPath) { String path = PATH + jarPath; File file = new File(path); if (file.exists()) { file.delete(); } return getFullPath(jarPath); } public static String getFullPath(String jarPath) { String path = PATH + jarPath; File file = new File(path); if (!file.exists()) { String url = getDownloadUrl(jarPath); try { download(url); } catch (IOException e) { try { download(url); } catch (IOException ex) { throw new RuntimeException(ex); } } } return path; } public static final String DOWNLOAD_URL_HOST = "https://cdn.chat2db-ai.com/lib/"; private static String getDownloadUrl(String jarPath) { return DOWNLOAD_URL_HOST+jarPath; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcUtils.java ================================================ package ai.chat2db.spi.util; import java.sql.*; import java.text.Collator; import java.util.*; import ai.chat2db.spi.model.KeyValue; import com.alibaba.druid.DbType; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.ssh.SSHManager; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.lang.Nullable; /** * jdbc tool class * * @author Jiaju Zhuang */ @Slf4j public class JdbcUtils { private static final long MAX_RESULT_SIZE = 256 * 1024; /** * Get the database type of Druid * * @param dbType * @return */ public static DbType parse2DruidDbType(String dbType) { if (dbType == null) { return null; } if("SUNDB".equalsIgnoreCase(dbType)){ return DbType.oracle; } try { return DbType.valueOf(dbType.toLowerCase()); } catch (Exception e) { return null; } } /** * Parse field type * * @param typeName * @param type * @return */ public static DataTypeEnum resolveDataType(String typeName, int type) { switch (getTypeByTypeName(typeName, type)) { case Types.BOOLEAN: return DataTypeEnum.BOOLEAN; case Types.CHAR: case Types.VARCHAR: case Types.NVARCHAR: case Types.LONGVARCHAR: case Types.LONGNVARCHAR: return DataTypeEnum.STRING; case Types.BIGINT: case Types.DECIMAL: case Types.DOUBLE: case Types.FLOAT: case Types.INTEGER: case Types.NUMERIC: case Types.REAL: case Types.SMALLINT: return DataTypeEnum.NUMERIC; case Types.BIT: case Types.TINYINT: if (typeName.toLowerCase().contains("bool")) { // Declared as numeric but actually it's a boolean return DataTypeEnum.BOOLEAN; } return DataTypeEnum.NUMERIC; case Types.DATE: case Types.TIME: case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: return DataTypeEnum.DATETIME; case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: return DataTypeEnum.BINARY; case Types.BLOB: case Types.CLOB: case Types.NCLOB: case Types.SQLXML: return DataTypeEnum.CONTENT; case Types.STRUCT: return DataTypeEnum.STRUCT; case Types.ARRAY: return DataTypeEnum.ARRAY; case Types.ROWID: return DataTypeEnum.ROWID; case Types.REF: return DataTypeEnum.REFERENCE; case Types.OTHER: return DataTypeEnum.OBJECT; default: return DataTypeEnum.UNKNOWN; } } private static int getTypeByTypeName(String typeName, int type) { // [JDBC: SQLite driver uses VARCHAR value type for all LOBs] if (type == Types.OTHER || type == Types.VARCHAR) { if ("BLOB".equalsIgnoreCase(typeName)) { return Types.BLOB; } else if ("CLOB".equalsIgnoreCase(typeName)) { return Types.CLOB; } else if ("NCLOB".equalsIgnoreCase(typeName)) { return Types.NCLOB; } } else if (type == Types.BIT) { // Workaround for MySQL (and maybe others) when TINYINT(1) == BOOLEAN if ("TINYINT".equalsIgnoreCase(typeName)) { return Types.TINYINT; } } return type; } /** * Test database connection * * @param url database connection * @param userName username * @param password password * @param dbType database type * @return */ public static DataSourceConnect testConnect(String url, String host, String port, String userName, String password, String dbType, DriverConfig driverConfig, SSHInfo ssh, Map properties) { DataSourceConnect dataSourceConnect = DataSourceConnect.builder() .success(Boolean.TRUE) .build(); Session session = null; Connection connection = null; // Load driver try { if (ssh.isUse()) { ssh.setRHost(host); ssh.setRPort(port); session = SSHManager.getSSHSession(ssh); url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); } // Create connection connection = IDriverManager.getConnection(url, userName, password, driverConfig, properties); } catch (Exception e) { log.error("connection fail:", e); dataSourceConnect.setSuccess(Boolean.FALSE); // Get the last exception information to the front end Throwable t = e; while (t.getCause() != null) { t = t.getCause(); } dataSourceConnect.setMessage(t.getMessage()); dataSourceConnect.setErrorDetail(ExceptionUtils.getErrorInfoFromException(t)); return dataSourceConnect; } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { // ignore } } if (session != null) { try { if (StringUtils.isNotBlank(ssh.getLocalPort())) { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); } session.disconnect(); } catch (Exception e) { } } } dataSourceConnect.setDescription("成功"); return dataSourceConnect; } public static void closeResultSet(@Nullable ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException var2) { log.trace("Could not close JDBC ResultSet", var2); } catch (Throwable var3) { log.trace("Unexpected exception on closing JDBC ResultSet", var3); } } } public static void setDriverDefaultProperty(DriverConfig driverConfig) { if(driverConfig == null){ return; } List defaultKeyValues = driverConfig.getExtendInfo(); Map valueMap = Maps.newHashMap(); if (!CollectionUtils.isEmpty(defaultKeyValues)) { for (KeyValue keyValue : defaultKeyValues) { if (keyValue == null || StringUtils.isBlank(keyValue.getKey())) { continue; } valueMap.put(keyValue.getKey(), keyValue); } } try { DriverPropertyInfo[] propertyInfos = IDriverManager.getProperty(driverConfig); if (propertyInfos == null) { return; } for (int i = 0; i < propertyInfos.length; i++) { DriverPropertyInfo propertyInfo = propertyInfos[i]; if (propertyInfo == null) { continue; } KeyValue keyValue = valueMap.get(propertyInfo.name); if (keyValue != null) { String[] choices = propertyInfo.choices; if (CollectionUtils.isEmpty(keyValue.getChoices()) && choices != null && choices.length > 0) { keyValue.setChoices(Lists.newArrayList(choices)); } } else { keyValue = new KeyValue(); keyValue.setKey(propertyInfo.name); keyValue.setValue(propertyInfo.value); keyValue.setRequired(propertyInfo.required); String[] choices = propertyInfo.choices; if (choices != null && choices.length > 0) { keyValue.setChoices(Lists.newArrayList(choices)); } valueMap.put(keyValue.getKey(), keyValue); } } if (!valueMap.isEmpty()) { Comparator comparator = Collator.getInstance(Locale.ENGLISH); List result = new ArrayList<>(valueMap.values()); Collections.sort(result, (o1, o2) -> comparator.compare(o1.getKey(), o2.getKey())); driverConfig.setExtendInfo(result); } } catch (SQLException e) { log.error("get property error:", e); } } public static void removePropertySameAsDefault(DriverConfig driverConfig) { if(driverConfig == null){ return; } List customValue = driverConfig.getExtendInfo(); if (CollectionUtils.isEmpty(customValue)) { return ; } Map map = Maps.newHashMap(); List result = new ArrayList<>(); try { DriverPropertyInfo[] propertyInfos = IDriverManager.getProperty(driverConfig); if (propertyInfos == null) { return ; } for (int i = 0; i < propertyInfos.length; i++) { DriverPropertyInfo propertyInfo = propertyInfos[i]; if (propertyInfo == null) { continue; } map.put(propertyInfo.name, propertyInfo.value); } for (KeyValue keyValue : customValue) { if (keyValue == null || StringUtils.isBlank(keyValue.getKey())) { continue; } String value = map.get(keyValue.getKey()); if (!StringUtils.equals(value, keyValue.getValue())) { result.add(keyValue); } } Comparator comparator = Collator.getInstance(Locale.ENGLISH); Collections.sort(result, (o1, o2) -> comparator.compare(o1.getKey(), o2.getKey())); driverConfig.setExtendInfo(result); } catch (SQLException e) { } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/LexerFactories.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oboracle.PLLexer; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import org.antlr.v4.runtime.Lexer; public class LexerFactories { private static final LexerFactory OB_ORACLE_LEXER_FACTORY = new OBOraclePLLexerFactory(); private static final LexerFactory ORACLE_LEXER_FACTORY = new OracleLexerFactory(); public static LexerFactory of(Class lexerType) { if (PLLexer.class == lexerType) { return OB_ORACLE_LEXER_FACTORY; } if (PlSqlLexer.class == lexerType) { return ORACLE_LEXER_FACTORY; } throw new RuntimeException("LexerType not supported, lexerType=" + lexerType.getName()); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/LexerFactory.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; interface LexerFactory { Lexer create(CharStream input); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/LexerTokenDefinition.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; /** * SQL 语法 Token 定义。
    * 如果某个 Token 不支持,对应方法返回 1 {@link org.antlr.v4.runtime.Token#MIN_USER_TOKEN_TYPE} */ interface LexerTokenDefinition { /** * 除号 */ int DIV(); /** * 空白字符,包括空格、制表符、换行符等 */ int SPACES(); /** * OB 语法文件不区分单行注释和多行注释,统一为 ANTLR_SKIP */ int ANTLR_SKIP(); /** * 单行注释 */ int SINGLE_LINE_COMMENT(); /** * 多行注释 */ int MULTI_LINE_COMMENT(); /** * DECLARE 关键字 */ int DECLARE(); /** * BEGIN 关键字 */ int BEGIN(); /** * END 关键字 */ int END(); /** * CREATE 关键字 */ int CREATE(); /** * OR 关键字 */ int OR(); /** * REPLACE 关键字 */ int REPLACE(); /** * EDITIONABLE 关键字 */ int EDITIONABLE(); /** * NONEDITIONABLE 关键字 */ int NONEDITIONABLE(); /** * PROCEDURE 关键字 */ int PROCEDURE(); /** * FUNCTION 关键字 */ int FUNCTION(); /** * PACKAGE 关键字 */ int PACKAGE(); /** * TYPE 关键字 */ int TYPE(); /** * TRIGGER 关键字 */ int TRIGGER(); /** * BODY 关键字 */ int BODY(); /** * IDENT, may {@link #REGULAR_ID} OR {@link #DELIMITED_ID} */ int IDENT(); int REGULAR_ID(); int DELIMITED_ID(); int FOR(); /** * LOOP 关键字 */ int LOOP(); /** * IF 关键字 */ int IF(); /** * CASE 关键字 */ int CASE(); /** * LANGUAGE 关键字 */ int LANGUAGE(); /** * EXTERNAL 关键字 */ int EXTERNAL(); /** * IS 关键字 */ int IS(); /** * AS 关键字 */ int AS(); /** * MEMBER 关键字 */ int MEMBER(); /** * STATIC 关键字 */ int STATIC(); int SEMICOLON(); int ELSE(); int THEN(); int RIGHTBRACKET(); int LEFTBRACKET(); int GREATER_THAN_OP(); int WHILE(); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/LexerTokenDefinitions.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oboracle.PLLexer; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import org.antlr.v4.runtime.Lexer; class LexerTokenDefinitions { private static final LexerTokenDefinition OB_PL_LEXER_DEFINITION = new OBOraclePLLexerDefinition(); private static final LexerTokenDefinition ORACLE_LEXER_DEFINITION = new OracleLexerDefinition(); public static LexerTokenDefinition of(Class lexerType) { if (PLLexer.class == lexerType) { return OB_PL_LEXER_DEFINITION; } if (PlSqlLexer.class == lexerType) { return ORACLE_LEXER_DEFINITION; } throw new RuntimeException("LexerType not supported, lexerType=" + lexerType.getName()); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/OBOraclePLLexerDefinition.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oboracle.PLLexer; import org.antlr.v4.runtime.Token; class OBOraclePLLexerDefinition implements LexerTokenDefinition { @Override public int DIV() { return PLLexer.Div; } @Override public int SPACES() { return PLLexer.Blank; } @Override public int ANTLR_SKIP() { return PLLexer.ANTLR_SKIP; } @Override public int SINGLE_LINE_COMMENT() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int MULTI_LINE_COMMENT() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int DECLARE() { return PLLexer.DECLARE; } @Override public int BEGIN() { return PLLexer.BEGIN_KEY; } @Override public int END() { return PLLexer.END_KEY; } @Override public int CREATE() { return PLLexer.CREATE; } @Override public int OR() { return PLLexer.OR; } @Override public int REPLACE() { return PLLexer.REPLACE; } @Override public int EDITIONABLE() { return PLLexer.EDITIONABLE; } @Override public int NONEDITIONABLE() { return PLLexer.NONEDITIONABLE; } @Override public int PROCEDURE() { return PLLexer.PROCEDURE; } @Override public int FUNCTION() { return PLLexer.FUNCTION; } @Override public int PACKAGE() { return PLLexer.PACKAGE_P; } @Override public int TYPE() { return PLLexer.TYPE; } @Override public int TRIGGER() { return PLLexer.TRIGGER; } @Override public int BODY() { return PLLexer.BODY; } @Override public int IDENT() { return PLLexer.IDENT; } @Override public int REGULAR_ID() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int DELIMITED_ID() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int FOR() { return PLLexer.FOR; } @Override public int LOOP() { return PLLexer.LOOP; } @Override public int IF() { return PLLexer.IF; } @Override public int CASE() { return PLLexer.CASE; } @Override public int LANGUAGE() { return PLLexer.LANGUAGE; } @Override public int EXTERNAL() { return PLLexer.EXTERNAL; } @Override public int IS() { return PLLexer.IS; } @Override public int AS() { return PLLexer.AS; } @Override public int MEMBER() { return PLLexer.MEMBER; } @Override public int STATIC() { return PLLexer.STATIC; } @Override public int SEMICOLON() { return PLLexer.DELIMITER; } @Override public int ELSE() { return PLLexer.ELSE; } @Override public int THEN() { return PLLexer.THEN; } @Override public int RIGHTBRACKET() { return PLLexer.RightParen; } @Override public int LEFTBRACKET() { return PLLexer.LeftParen; } @Override public int GREATER_THAN_OP() { return PLLexer.LABEL_RIGHT; } @Override public int WHILE() { return PLLexer.WHILE; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/OBOraclePLLexerFactory.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oboracle.PLLexer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; class OBOraclePLLexerFactory implements LexerFactory { @Override public Lexer create(CharStream input) { return new PLLexer(input); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/OracleLexerDefinition.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import org.antlr.v4.runtime.Token; class OracleLexerDefinition implements LexerTokenDefinition { @Override public int DIV() { return PlSqlLexer.SOLIDUS; } @Override public int SPACES() { return PlSqlLexer.SPACES; } @Override public int ANTLR_SKIP() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int SINGLE_LINE_COMMENT() { return PlSqlLexer.SINGLE_LINE_COMMENT; } @Override public int MULTI_LINE_COMMENT() { return PlSqlLexer.MULTI_LINE_COMMENT; } @Override public int DECLARE() { return PlSqlLexer.DECLARE; } @Override public int BEGIN() { return PlSqlLexer.BEGIN; } @Override public int END() { return PlSqlLexer.END; } @Override public int CREATE() { return PlSqlLexer.CREATE; } @Override public int OR() { return PlSqlLexer.OR; } @Override public int REPLACE() { return PlSqlLexer.REPLACE; } @Override public int EDITIONABLE() { return PlSqlLexer.EDITIONABLE; } @Override public int NONEDITIONABLE() { return PlSqlLexer.NONEDITIONABLE; } @Override public int PROCEDURE() { return PlSqlLexer.PROCEDURE; } @Override public int FUNCTION() { return PlSqlLexer.FUNCTION; } @Override public int PACKAGE() { return PlSqlLexer.PACKAGE; } @Override public int TYPE() { return PlSqlLexer.TYPE; } @Override public int TRIGGER() { return PlSqlLexer.TRIGGER; } @Override public int BODY() { return PlSqlLexer.BODY; } @Override public int IDENT() { return Token.MIN_USER_TOKEN_TYPE; } @Override public int REGULAR_ID() { return PlSqlLexer.REGULAR_ID; } @Override public int DELIMITED_ID() { return PlSqlLexer.DELIMITED_ID; } @Override public int FOR() { return PlSqlLexer.FOR; } @Override public int LOOP() { return PlSqlLexer.LOOP; } @Override public int IF() { return PlSqlLexer.IF; } @Override public int CASE() { return PlSqlLexer.CASE; } @Override public int LANGUAGE() { return PlSqlLexer.LANGUAGE; } @Override public int EXTERNAL() { return PlSqlLexer.EXTERNAL; } @Override public int IS() { return PlSqlLexer.IS; } @Override public int AS() { return PlSqlLexer.AS; } @Override public int MEMBER() { return PlSqlLexer.MEMBER; } @Override public int STATIC() { return PlSqlLexer.STATIC; } @Override public int SEMICOLON() { return PlSqlLexer.SEMICOLON; } @Override public int ELSE() { return PlSqlLexer.ELSE; } @Override public int THEN() { return PlSqlLexer.THEN; } @Override public int RIGHTBRACKET() { return PlSqlLexer.RIGHT_PAREN; } @Override public int LEFTBRACKET() { return PlSqlLexer.LEFT_PAREN; } @Override public int GREATER_THAN_OP() { return PlSqlLexer.GREATER_THAN_OP; } @Override public int WHILE() { return PlSqlLexer.WHILE; } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/OracleLexerFactory.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import com.oceanbase.tools.sqlparser.util.CaseChangingCharStream; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Lexer; public class OracleLexerFactory implements LexerFactory { @Override public Lexer create(CharStream input) { CaseChangingCharStream caseChangingCharStream = new CaseChangingCharStream(input, true); return new PlSqlLexer(caseChangingCharStream); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java ================================================ package ai.chat2db.spi.util; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author jipengfei * @version : ResultSetUtils.java */ @Slf4j public class ResultSetUtils { public static List getRsHeader(ResultSet rs) { try { ResultSetMetaData resultSetMetaData = rs.getMetaData(); int col = resultSetMetaData.getColumnCount(); List headerList = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { headerList.add(getColumnName(resultSetMetaData, i)); } return headerList; } catch (SQLException e) { throw new RuntimeException(e); } } /** * * @param rs * @param clazz * @return * @param */ public static List toObjectList(ResultSet rs, Class clazz) { try { if (rs == null || clazz == null) { return Lists.newArrayList(); } List list = Lists.newArrayList(); ResultSetMetaData rsMetaData = rs.getMetaData(); int col = rsMetaData.getColumnCount(); List headerList = getRsHeader(rs); ObjectMapper mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); while (rs.next()) { Map map = new HashMap<>(); for (int i = 1; i <= col; i++) { map.put(headerList.get(i-1), rs.getObject(i)); } T obj = mapper.convertValue(map, clazz); list.add(obj); } return list; } catch (SQLException e) { throw new RuntimeException(e); } } public static String getColumnName(ResultSetMetaData resultSetMetaData, int column) throws SQLException { String columnLabel = resultSetMetaData.getColumnLabel(column); if (columnLabel != null) { return columnLabel; } return resultSetMetaData.getColumnName(column); } public static String getColumnDataTypeName(ResultSetMetaData resultSetMetaData, int columnIndex) { try { return resultSetMetaData.getColumnTypeName(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static int getColumnPrecision(ResultSetMetaData resultSetMetaData, int columnIndex){ try { return resultSetMetaData.getPrecision(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static int getColumnScale(ResultSetMetaData resultSetMetaData, int columnIndex){ try { return resultSetMetaData.getScale(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static String getString(ResultSet rs, int columnIndex){ try { Object obj = rs.getObject(columnIndex); if (obj == null) { return null; } if(obj instanceof String){ return (String) obj; }else if (obj instanceof BigDecimal bigDecimal) { return bigDecimal.toPlainString(); } else if (obj instanceof Double d) { return BigDecimal.valueOf(d).toPlainString(); } else if (obj instanceof Float f) { return BigDecimal.valueOf(f).toPlainString(); } else if (obj instanceof Clob) { return largeString(rs, columnIndex); } else if (obj instanceof byte[]) { return largeString(rs, columnIndex); } else if (obj instanceof Blob blob) { return largeStringBlob(blob); } else if (obj instanceof Timestamp || obj instanceof LocalDateTime) { return largeTime(obj); } else if (obj instanceof SQLXML){ return ((SQLXML) obj).getString(); } else { return obj.toString(); } } catch (Exception e) { log.warn("Failed to parse number:{},", columnIndex, e); try { return rs.getString(columnIndex); } catch (SQLException ex) { throw new RuntimeException(ex); } } } private static String largeStringBlob(Blob blob) throws SQLException { if (blob == null) { return null; } int length = Math.toIntExact(blob.length()); byte[] data = blob.getBytes(1, length); String result = new String(data, StandardCharsets.UTF_8); return result; } private static String largeTime(Object obj) throws SQLException { Object timeField = obj; // Assuming a time field of type Object LocalDateTime localDateTime; if (obj instanceof Timestamp) { // Convert a time field of type Object to a LocalDateTime object localDateTime = ((Timestamp) timeField).toLocalDateTime(); } else if(obj instanceof LocalDateTime){ localDateTime = (LocalDateTime) timeField; } else { try { localDateTime = LocalDateTime.parse(timeField.toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); }catch (Exception e){ localDateTime = LocalDateTime.parse(timeField.toString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")); } } // Create a DateTimeFormatter instance and specify the output date and time format DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // Format date time String formattedDateTime = dtf.format(localDateTime); return formattedDateTime; } private static String largeString(ResultSet rs, int index) throws SQLException { String result = rs.getString(index); if (result == null) { return null; } return result; } public static InputStream getBinaryStream(ResultSet rs, int columnIndex) { try { return rs.getBinaryStream(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static byte[] getBytes(ResultSet rs, int columnIndex) { try { return rs.getBytes(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static boolean getBoolean(ResultSet rs, int columnIndex) { try { return rs.getBoolean(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static int getInt(ResultSet resultSet, int columnIndex) { try { return resultSet.getInt(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static Date getDate(ResultSet resultSet, int columnIndex) { try { return resultSet.getDate(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static Timestamp getTimestamp(ResultSet resultSet, int columnIndex) { try { return resultSet.getTimestamp(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static Clob getClob(ResultSet resultSet, int columnIndex) { try { return resultSet.getClob(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static Blob getBlob(ResultSet resultSet, int columnIndex) { try { return resultSet.getBlob(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static BigDecimal getBigDecimal(ResultSet resultSet, int columnIndex) { try { return resultSet.getBigDecimal(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } public static String getStringValue(ResultSet resultSet, int columnIndex) { try { return resultSet.getString(columnIndex); } catch (SQLException e) { throw new RuntimeException(e); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SortUtils.java ================================================ package ai.chat2db.spi.util; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Schema; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class SortUtils { public static List sortDatabase(List databases, List list, Connection connection) { if (CollectionUtils.isEmpty(databases)) { return databases; } List databaseList = new ArrayList<>(); List systemDatabases = databases.stream() .filter(database -> list.contains(database.getName())).collect(Collectors.toList()); List userDatabases = databases.stream() .filter(database -> !list.contains(database.getName())).collect(Collectors.toList()); if (CollectionUtils.isEmpty(userDatabases)) { databaseList = databases; }else if (CollectionUtils.isEmpty(systemDatabases)) { databaseList = userDatabases; }else { databaseList = Stream.concat(userDatabases.stream(), systemDatabases.stream()) .collect(Collectors.toList()); } // If the database name contains the name of the current database, the current database is placed in the first place String ulr; try { ulr = connection.getMetaData().getURL(); } catch (SQLException e) { return databaseList; } // If the database name contains the name of the current database, the current database is placed in the first place int no = -1; for (int i = 0; i < databases.size(); i++) { if (StringUtils.isNotBlank(ulr) && StringUtils.isNotBlank(databases.get(i).getName()) && ulr.contains(databases.get(i).getName()) && !"mysql".equalsIgnoreCase(databases.get(i).getName())) { no = i; break; } } if (no != -1 && no != 0) { Collections.swap(databaseList, no, 0); } return databaseList; } public static List sortSchema(List schemas, List systemSchemas) { if (CollectionUtils.isEmpty(schemas)) { return schemas; } List systemSchema = schemas.stream() .filter(schema -> systemSchemas.contains(schema.getName()) || "APEX_".startsWith(schema.getName())).collect(Collectors.toList()); List userSchema = schemas.stream() .filter(schema -> !systemSchemas.contains(schema.getName()) && !"APEX_".startsWith(schema.getName())).collect(Collectors.toList()); if (CollectionUtils.isEmpty(userSchema)) { return schemas; } if (CollectionUtils.isEmpty(systemSchema)) { return userSchema; } return Stream.concat(userSchema.stream(), systemSchema.stream()) .collect(Collectors.toList()); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SplitSqlString.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class SplitSqlString { private int offset; private String str; } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlSplitProcessor.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.alibaba.druid.DbType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** */ public class SqlSplitProcessor { private static final String DELIMITER_NAME = "delimiter"; /** * 是否保留格式 */ private boolean preserveFormat = false; private String delimiter = ";"; @Getter private boolean mlComment = false; private char inString = '\0'; private DbType dialectType; private char escapeString = '\0'; private boolean inNormalSql = false; /** * 是否保留单行注释 */ @Getter private boolean preserveSingleComments = false; /** * 是否保留多行注释 */ @Getter private boolean preserveMultiComments = false; private static Pattern pattern = Pattern.compile("\\r\\n|\\r|\\n"); public SqlSplitProcessor(boolean preserveFormat, String delimiter) { this.delimiter = delimiter; this.preserveFormat = preserveFormat; } public SqlSplitProcessor(DbType dialectType, boolean preserveSingleComments, boolean preserveMultiComments) { this.preserveFormat = true; this.dialectType = dialectType; this.preserveSingleComments = preserveSingleComments; this.preserveMultiComments = preserveMultiComments; } public SqlSplitProcessor(DbType dialectType, boolean preserveFormat, boolean preserveSingleComments, boolean preserveMultiComments) { this.preserveFormat = preserveFormat; this.dialectType = dialectType; this.preserveSingleComments = preserveSingleComments; this.preserveMultiComments = preserveMultiComments; } public SqlSplitProcessor(DbType dialectType, String delimiter) { this.preserveFormat = true; this.dialectType = dialectType; this.delimiter = delimiter; } public SqlSplitProcessor() {} public static SqlStatementIterator iterator(InputStream in, Charset charset, SqlSplitProcessor processor) { return new SqlCommentProcessorIterator(in, charset, processor); } public static List removeSqlComments(String originalSql, String delimiter, DbType dbMode, boolean preserveFormat) { SqlSplitProcessor sqlCommentProcessor = new SqlSplitProcessor(preserveFormat, delimiter); StringBuffer buffer = new StringBuffer(); List offsetStrings = new ArrayList<>(); List> lines = splitLine(originalSql); Holder bufferOrder = new Holder<>(0); for (List item : lines) { if (Objects.nonNull(dbMode) && DbType.mysql.equals(dbMode)) { sqlCommentProcessor.addLineMysql(offsetStrings, buffer, bufferOrder, item); } else { sqlCommentProcessor.addLineOracle(offsetStrings, buffer, bufferOrder, item); } } String bufferStr = buffer.toString(); if (bufferStr.trim().length() != 0) { while (true) { if (bufferStr.endsWith("\n")) { /** * remove all \n from sqls */ bufferStr = bufferStr.substring(0, bufferStr.length() - 1); } else { break; } } if (offsetStrings.size() == 0) { offsetStrings.add(new SplitSqlString(0, bufferStr)); } else { offsetStrings.add(new SplitSqlString( offsetStrings.get(offsetStrings.size() - 1).getOffset() + offsetStrings.get(offsetStrings.size() - 1).getStr().length(), bufferStr)); } } return offsetStrings; } public synchronized List split(StringBuffer buffer, String sqlScript) { if (StringUtils.isBlank(sqlScript)) { return new ArrayList<>(); } try { List offsetStrings = new ArrayList<>(); List> lines = splitLine(sqlScript); Holder bufferOrder = new Holder<>(0); int i = 0; for (List item : lines) { if (Objects.nonNull(this.dialectType) && DbType.mysql.equals(this.dialectType)) { addLineMysql(offsetStrings, buffer, bufferOrder, item); } else if (Objects.nonNull(this.dialectType) && DbType.mariadb.equals(this.dialectType)) { addLineMysql(offsetStrings, buffer, bufferOrder, item); } else if (Objects.nonNull(this.dialectType) && DbType.oracle.equals(this.dialectType)) { addLineOracle(offsetStrings, buffer, bufferOrder, item); } else if (Objects.nonNull(this.dialectType) && DbType.oceanbase.equals(this.dialectType)) { addLineMysql(offsetStrings, buffer, bufferOrder, item); } else { throw new IllegalArgumentException("dialect type is illegal"); } i++; } return offsetStrings; } finally { mlComment = false; inString = '\0'; inNormalSql = false; } } private synchronized void addLineMysql(List sqls, StringBuffer buffer, Holder bufferOrder, List line) { int pos, out; boolean needSpace = false; // 标识量,用于标识当前是否处于HINT,CONDITIONAL中 SSC ssComment = SSC.NONE; boolean isSameLine = false; int lineLength = line.size(); OrderChar[] lines = line.toArray(new OrderChar[lineLength + 1]); if ((lines.length == 0 || lines[0] == null || lines[0].getCh() == 0) && buffer.length() == 0) { return; } lines[lineLength] = new OrderChar((char) 0, lineLength); for (pos = out = 0; pos < lineLength; pos++) { OrderChar inOrderChar = lines[pos]; char inChar = inOrderChar.getCh(); // 去掉每一行SQL语句最开始的空格 if (inChar == ' ' && out == 0 && buffer.length() == 0 && !preserveFormat) { continue; } int delimiterBegin = 0; if (preserveFormat) { for (; delimiterBegin < out && (lines[delimiterBegin].getCh() == ' ' || lines[delimiterBegin].getCh() == '\t'); delimiterBegin++) { } } if (equalsIgnoreCase((DELIMITER_NAME + " ").toCharArray(), lines, delimiterBegin, (out - delimiterBegin))) { // 检测到"delimiter "字符串,且不在多行注释以及多行字符串中,说明有设定分隔符的语句 StringBuilder newDelimiter = new StringBuilder(); for (; pos < lineLength; pos++) { char tempChar = lines[pos].getCh(); if (tempChar != ' ') { newDelimiter.append(tempChar); } else if (newDelimiter.length() != 0) { break; } } out = 0; this.delimiter = newDelimiter.toString(); continue; } // 扫描到转义字符,可能出现指令 if ((!mlComment && inChar == '\\')) { inOrderChar = lines[++pos]; inChar = inOrderChar.getCh(); if (inChar == 0) { break; } if (inString != '\0' || inChar == 'N') { lines[out++] = OrderChar.newOrderChar(lines[pos - 1]); if (inChar == '`' && inString == inChar) { pos--; } else { lines[out++] = OrderChar.newOrderChar(lines[pos]); } continue; } // 非mysql model或没有检索到正确的命令,直接将转义符号及转义字符放入缓冲 lines[out++] = OrderChar.newOrderChar(lines[pos - 1]); lines[out++] = OrderChar.newOrderChar(lines[pos]); } else if (!mlComment && inString == '\0' && ssComment != SSC.HINT && isPrefix(lines, pos, delimiter)) { // 不是多行注释,未在字符串中,不是hint且以delimiter开头,通常是扫描到了sql的末尾 pos += delimiter.length(); if (out != 0) { if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; } // buffer.append(";").append('\n'); sqls.add(new SplitSqlString(bufferOrder.getValue(), buffer.toString())); bufferOrder.setValue(bufferOrder.getValue() + buffer.length()); pos--; buffer.setLength(0); isSameLine = true; inNormalSql = false; } else if (!mlComment && (inString == '\0' && (inChar == '#' || (inChar == '-' && lines[pos + 1].getCh() == '-' && ((lines[pos + 2].getCh() == ' ' || lines[pos + 2].getCh() == '\0')))))) { // 处于单行注释中 if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (preserveSingleComments) { // 如果保留单行注释则需要将注释完整地拷贝到缓冲中不能丢弃 for (; pos < lineLength; pos++) { lines[out++] = OrderChar.newOrderChar(lines[pos]); } if (isOnlyWhiteSpace(buffer)) { // 缓冲中全部是空格,或者缓冲为空说明注释要么处于第一行要么处于个已经完结的sql语句之后 if (sqls.size() != 0) { if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } // 说明注释处于一个已经完结的sql之后,且该sql已经被加入到sql集合中,此处的注释需要追加到最后一句sql中 append(buffer, lines, 0, out); int lastIndex = sqls.size() - 1; String lastSql = sqls.get(lastIndex).getStr(); if (!isSameLine) { lastSql += '\n'; } lastSql += buffer + "\n"; sqls.set(lastIndex, new SplitSqlString(sqls.get(lastIndex).getOffset(), lastSql)); buffer.setLength(0); } else { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out - 1); } } else { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out - 1); } out = 0; } break; } else if (inString == '\0' && (inChar == '/' && lines[pos + 1].getCh() == '*') // 此处注意,Oracle模式下没有Conditional,故这里要做规避。Mysql模式下的Conditional在Oracle模式在要识别为注释去掉 && lines[pos + 2].getCh() != '!' && lines[pos + 2].getCh() != '+' && ssComment != SSC.HINT) { // 处于多行注释中,注意规避了HINT和CONDITIONAL,Oracle模式下没有conditional if (preserveMultiComments) { lines[out++].setCh('/'); lines[out++].setCh('*'); } pos++; mlComment = true; } else if (mlComment && ssComment == SSC.NONE && inChar == '*' && lines[pos + 1].getCh() == '/') { // 多行注释结束 pos++; mlComment = false; if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (preserveMultiComments) { lines[out++].setCh('*'); lines[out++].setCh('/'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (sqls.size() != 0 && !inNormalSql) { int lastIndex = sqls.size() - 1; String lastSql = sqls.get(lastIndex).getStr() + buffer; sqls.set(lastIndex, new SplitSqlString(sqls.get(lastIndex).getOffset(), lastSql)); buffer.setLength(0); } } needSpace = true; } else { if (inString == '\0' && inChar == '/' && lines[pos + 1].getCh() == '*') { if (lines[pos + 2].getCh() == '!') { // 处于CONDITIONAL中 ssComment = SSC.CONDITIONAL; } else if (lines[pos + 2].getCh() == '+') { // 处于HINT中 ssComment = SSC.HINT; } } else if (inString == '\0' && ssComment != SSC.NONE && inChar == '*' && lines[pos + 1].getCh() == '/') { // HINT或CONDITIONAL结束 ssComment = SSC.NONE; } if (inChar == inString) { // 字符指针出字符串或表达式 inString = '\0'; } else if (!mlComment && inString == '\0' && ssComment != SSC.HINT && (inChar == '\'' || inChar == '"' || inChar == '`')) { // 字符指针进入字符串或者表达式 inString = inChar; } if (!mlComment) { if (needSpace && inChar == ' ') { lines[out++].setCh(' '); } needSpace = false; // 正常的SQL语句,将其放入line缓冲当中,在合适的实际flush如buffer缓存 lines[out++] = OrderChar.newOrderChar(inOrderChar); if (inChar != ' ') { inNormalSql = true; } } else if (preserveMultiComments) { // 保留多行注释 lines[out++] = OrderChar.newOrderChar(inOrderChar); } } } // 拦截性的处理,如果out指针没有为0,说明lines中还有内容没有被刷入到buffer,在这里进行flush if (out != 0 || buffer.length() != 0) { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); } } private boolean isOnlyWhiteSpace(StringBuffer buffer) { if (buffer == null) { return false; } int length = buffer.length(); for (int i = 0; i < length; i++) { if (buffer.charAt(i) != ' ') { return false; } } return true; } public synchronized void addLineOracle(List sqls, StringBuffer buffer, Holder bufferOrder, List line) { int pos, out; boolean needSpace = false; // 标识量,用于标识当前是否处于HINT,CONDITIONAL中 SSC ssComment = SSC.NONE; boolean isSameLine = false; int lineLength = line.size(); OrderChar[] lines = line.toArray(new OrderChar[lineLength + 1]); if ((lines.length == 0 || lines[0] == null || lines[0].getCh() == 0) && buffer.length() == 0) { return; } lines[lineLength] = new OrderChar((char) 0, lineLength); for (pos = out = 0; pos < lineLength; pos++) { OrderChar inOrderChar = lines[pos]; char inChar = inOrderChar.getCh(); // 去掉每一行SQL语句最开始的空格 if (inChar == ' ' && out == 0 && buffer.length() == 0 && !preserveFormat) { continue; } int delimiterBegin = 0; if (preserveFormat) { for (; delimiterBegin < out && (lines[delimiterBegin].getCh() == ' ' || lines[delimiterBegin].getCh() == '\t'); delimiterBegin++) { } } if (equalsIgnoreCase((DELIMITER_NAME + " ").toCharArray(), lines, delimiterBegin, (out - delimiterBegin))) { // 检测到"delimiter "字符串,且不在多行注释以及多行字符串中,说明有设定分隔符的语句 StringBuilder newDelimiter = new StringBuilder(); for (; pos < lineLength; pos++) { char tempChar = lines[pos].getCh(); if (tempChar != ' ') { newDelimiter.append(tempChar); } else if (newDelimiter.length() != 0) { break; } } out = 0; this.delimiter = newDelimiter.toString(); continue; } if (!mlComment && inString == '\0' && ssComment != SSC.HINT && isPrefix(lines, pos, delimiter)) { // 不是多行注释,未在字符串中,不是hint且以delimiter开头,通常是扫描到了sql的末尾 pos += delimiter.length(); if (out != 0) { if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; } // buffer.append(";").append('\n'); sqls.add(new SplitSqlString(bufferOrder.getValue(), buffer.toString())); bufferOrder.setValue(bufferOrder.getValue() + buffer.length()); pos--; buffer.setLength(0); isSameLine = true; inNormalSql = false; } else if (!mlComment && (inString == '\0' && (inChar == '-' && lines[pos + 1].getCh() == '-' && (lines[pos + 2].getCh() != '+' || (lines[pos + 2].getCh() == ' ' || lines[pos + 2].getCh() == '\0'))))) { // 处于单行注释中,注意规避单行HINT if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (preserveSingleComments) { // 如果保留单行注释则需要将注释完整地拷贝到缓冲中不能丢弃 for (; pos < lineLength; pos++) { lines[out++] = OrderChar.newOrderChar(lines[pos]); } if (isOnlyWhiteSpace(buffer)) { // 缓冲中全部是空格,或者缓冲为空说明注释要么处于第一行要么处于个已经完结的sql语句之后 if (sqls.size() != 0) { if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } // 说明注释处于一个已经完结的sql之后,且该sql已经被加入到sql集合中,此处的注释需要追加到最后一句sql中 append(buffer, lines, 0, out); int lastIndex = sqls.size() - 1; String lastSql = sqls.get(lastIndex).getStr(); if (!isSameLine) { lastSql += '\n'; } lastSql += buffer + "\n"; sqls.set(lastIndex, new SplitSqlString(sqls.get(lastIndex).getOffset(), lastSql)); buffer.setLength(0); } else { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out - 1); } } else { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out - 1); } out = 0; } break; } else if (inString == '\0' && (inChar == '/' && lines[pos + 1].getCh() == '*') && lines[pos + 2].getCh() != '+' && ssComment != SSC.HINT) { // 处于多行注释中,注意规避了HINT和CONDITIONAL,Oracle模式下没有conditional if (preserveMultiComments) { lines[out++].setCh('/'); lines[out++].setCh('*'); } pos++; mlComment = true; } else if (mlComment && ssComment == SSC.NONE && inChar == '*' && lines[pos + 1].getCh() == '/') { // 多行注释结束 pos++; mlComment = false; if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (preserveMultiComments) { lines[out++].setCh('*'); lines[out++].setCh('/'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); out = 0; if (sqls.size() != 0 && !inNormalSql) { int lastIndex = sqls.size() - 1; String lastSql = sqls.get(lastIndex).getStr() + buffer; sqls.set(lastIndex, new SplitSqlString(sqls.get(lastIndex).getOffset(), lastSql)); buffer.setLength(0); } } needSpace = true; } else { if (inString == '\0' && inChar == '/' && lines[pos + 1].getCh() == '*') { if (lines[pos + 2].getCh() == '+') { // 处于HINT中 ssComment = SSC.HINT; } } else if (inString == '\0' && ssComment != SSC.NONE && inChar == '*' && lines[pos + 1].getCh() == '/') { // HINT或CONDITIONAL结束 ssComment = SSC.NONE; } else if (inString == '\0' && inChar == '-' && lines[pos + 1].getCh() == '-' && lines[pos + 2].getCh() == '+') { // 在Oracle模式下Hint有单行Hint和多行Hint之分,这里处理Oracle模式下的单行Hint ssComment = SSC.HINT; } if (inChar == inString) { // 字符指针出字符串或表达式 if (escapeString == '\0') { inString = '\0'; } else if (pos >= 1 && matchQEscape(lines[pos - 1].getCh())) { inString = '\0'; escapeString = '\0'; } } else if (!mlComment && inString == '\0' && ssComment != SSC.HINT && (inChar == '\'' || inChar == '"' || inChar == '`')) { // 字符指针进入字符串或者表达式 inString = inChar; if (pos >= 1 && (lines[pos - 1].getCh() == 'q' || lines[pos - 1].getCh() == 'Q')) { // oracle 特有语法,Q 转义 escapeString = lines[pos + 1].getCh(); } } if (!mlComment) { if (needSpace && inChar == ' ') { lines[out++].setCh(' '); } needSpace = false; // 正常的SQL语句,将其放入line缓冲当中,在合适的实际flush如buffer缓存 lines[out++] = new OrderChar(inOrderChar.getCh(), inOrderChar.getOrder()); if (inChar != ' ') { inNormalSql = true; } } else if (preserveMultiComments) { // 保留多行注释 lines[out++] = new OrderChar(inOrderChar.getCh(), inOrderChar.getOrder()); } } } // 拦截性的处理,如果out指针没有为0,说明lines中还有内容没有被刷入到buffer,在这里进行flush if (out != 0 || buffer.length() != 0) { lines[out++].setCh('\n'); if (buffer.length() == 0) { bufferOrder.setValue(lines[0].getOrder()); } append(buffer, lines, 0, out); } } private boolean equalsIgnoreCase(char[] src, OrderChar[] dest, int begin, int count) { if (src == null && dest == null) { return true; } else if (src != null && dest != null) { if (src.length != count) { return false; } for (int i = 0; i < count; i++) { char c1 = src[i]; char c2 = dest[begin + i].getCh(); if (c1 == c2) { continue; } char u1 = Character.toUpperCase(c1); char u2 = Character.toUpperCase(c2); if (u1 == u2) { continue; } if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { continue; } return false; } return true; } return false; } /** * 当前SQL是否是以分隔符开头 */ private boolean isPrefix(OrderChar[] line, int pos, String delim) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < line.length - pos; i++) { builder.append(line[pos + i].getCh()); } boolean res = builder.toString().startsWith(delim); if (!res || !"/".equals(delim) || line.length <= 1) { return res; } // 匹配到分隔符,分隔符为正斜杠且当前行的大小大于 1,需要注意规避多行注释 if (pos == 0) { return !(line[pos + 1].getCh() == '*'); } else if (line.length - 1 == pos) { return !(line[pos - 1].getCh() == '*'); } return !(line[pos + 1].getCh() == '*' || line[pos - 1].getCh() == '*'); } private boolean matchQEscape(char escapeChar) { if (this.escapeString == '\0') { return false; } switch (this.escapeString) { case '<': return escapeChar == '>'; case '{': return escapeChar == '}'; case '[': return escapeChar == ']'; case '(': return escapeChar == ')'; default: return this.escapeString == escapeChar; } } private void append(StringBuffer buffer, OrderChar[] chars, int begin, int count) { for (int i = begin; i < count; i++) { buffer.append(chars[i].getCh()); } } private static List> splitLine(String sqlScript) { List> lines = new ArrayList<>(); List currentList = new ArrayList<>(); Matcher matcher = pattern.matcher(sqlScript); int start = 0; while (matcher.find()) { int end = matcher.start(); for (int i = start; i < end; i++) { OrderChar orderChar = new OrderChar(sqlScript.charAt(i), i); currentList.add(orderChar); } lines.add(currentList); currentList = new ArrayList<>(); start = matcher.end(); } if (start < sqlScript.length()) { for (int i = start; i < sqlScript.length(); i++) { OrderChar orderChar = new OrderChar(sqlScript.charAt(i), i); currentList.add(orderChar); } } if (!currentList.isEmpty()) { lines.add(currentList); } return lines; } public String getDelimiter() { return delimiter; } public void setDelimiter(String delimiter) { this.delimiter = delimiter; } private enum SSC { /** * 不处于HINT或CONDITIONAL中 */ NONE(0), /** * 当前SQL字符指针处于CONDITIONAL中 */ CONDITIONAL(1), /** * 当前处于HINT中 */ HINT(2); private final int value; SSC(int value) { this.value = value; } public int getValue() { return value; } } private static class SqlCommentProcessorIterator implements SqlStatementIterator { private final BufferedReader reader; private final StringBuffer buffer = new StringBuffer(); private final LinkedList holder = new LinkedList<>(); private final Holder bufferOrder = new Holder<>(0); private final SqlSplitProcessor processor; private SplitSqlString current; private int lastLineOrder = 0; private long iteratedBytes = 0; public SqlCommentProcessorIterator(InputStream input, Charset charset, SqlSplitProcessor processor) { this.reader = new BufferedReader(new InputStreamReader(input, charset)); this.processor = processor; } @Override public boolean hasNext() { if (current == null) { current = parseNext(); } return current != null; } @Override public SplitSqlString next() { SplitSqlString next = current; current = null; if (next == null) { next = parseNext(); if (next == null) { throw new NoSuchElementException("No more available sql."); } } return next; } @Override public long iteratedBytes() { return iteratedBytes; } private SplitSqlString parseNext() { try { if (!holder.isEmpty()) { return holder.poll(); } String line; while (holder.isEmpty() && (line = reader.readLine()) != null) { if ( DbType.mysql.equals(processor.dialectType)) { processor.addLineMysql(holder, buffer, bufferOrder, line.chars() .mapToObj(c -> new OrderChar((char) c, lastLineOrder++)) .collect(Collectors.toList())); } else if (DbType.oracle.equals(processor.dialectType)) { processor.addLineOracle(holder, buffer, bufferOrder, line.chars() .mapToObj(c -> new OrderChar((char) c, lastLineOrder++)) .collect(Collectors.toList())); } else if (DbType.oceanbase.equals(processor.dialectType)) { processor.addLineMysql(holder, buffer, bufferOrder, line.chars() .mapToObj(c -> new OrderChar((char) c, lastLineOrder++)) .collect(Collectors.toList())); } // consider \n in the end of each line lastLineOrder++; iteratedBytes += line.getBytes(StandardCharsets.UTF_8).length + 1; } if (!holder.isEmpty()) { return holder.poll(); } if (buffer.toString().trim().isEmpty()) { return null; } String sql = buffer.toString(); buffer.setLength(0); return new SplitSqlString(0, sql); } catch (Exception e) { throw new RuntimeException("Failed to parse input. reason: " + e.getMessage(), e); } } } @Data @AllArgsConstructor @NoArgsConstructor static class OrderChar { private char ch; private int order; static OrderChar newOrderChar(OrderChar orderChar) { return new OrderChar(orderChar.getCh(), orderChar.getOrder()); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlSplitter.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import com.google.common.collect.ImmutableMap; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.antlr.v4.runtime.*; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.*; import java.util.stream.Collectors; /** * currently only for oracle mode
    * TODO: support mysql mode */ @Slf4j public class SqlSplitter { private static final int MAX_PL_PATTEN_TYPE_SIZE = 3; private static final String DEFAULT_SQL_DELIMITER = ";"; private static final char[] SPACES_CHARS = "\r\n\t ".toCharArray(); private final LexerTokenDefinition tokenDefinition; private final LexerFactory lexerFactory; private final InnerUtils innerUtils; private final int DEFAULT_PL_END_DELIMITER; private final int SQL_DELIMITER; private final int PL_ELSE; private final int PL_THEN; private final int PL_RIGHTPAREN; private final int PL_LEFTPAREN; private final int PL_GREATER_THAN_OP; /** * PL 块开始判断 token 清单 */ private final int[][] ALL_PL_START_PATTERNS; /** * 用于移除 SQL 语句前缀注释部分 */ private final int[] BLANK_OR_COMMENT_TYPES; /** * 用于判断 PL 块开始的 token type 忽略清单 */ private final int[] PL_START_PATTERN_IGNORE_TYPES; /** * 用于判断 IDENT Token */ private final int[] PL_IDENT_TYPES; /** * 缓存 Token 类型 List, 用于判断是否进入 PL Block
    * 为了保证性能,最多只使用 MAX_PL_PATTEN_TYPE_SIZE 个 token 判断,超出后则肯定不是 PL Block */ private final List cacheTokenTypes = new ArrayList<>(); /** * 当前语句缓冲区,确定一个语句结束时会清空缓冲区用于下一个语句 */ private final StringBuilder currentStmtBuilder = new StringBuilder(); /** * 拆句结果 */ private final List stmts = new ArrayList<>(); private final boolean addDelimiter; /** * 当前语句状态,初始为 SQL_STMT,进去 PL BLock 后切换到 PL_STMT 状态 */ private State state = State.SQL_STMT; /** * 当前拆句关键字,初始为连接会话的 delimiter,拆词结束后回写到连接会话的 delimiter */ private String delimiter; /** * 为避免每次匹配拆句关键字重复解析,每次 delimiter 变化时同步计算对应的 tokens,和 delimiter 一致 */ private Token[] delimiterTokens; /** * 是否移除 注释前缀,用于绕过部分 OB 版本 PL 语句带注释前缀报错的问(OB 3.1.x)
    */ @Getter @Setter private boolean removeCommentPrefix = false; /** * 用于记录在PL块中的子代码块嵌套情况 */ private Stack subPLStack = new Stack(); /** * 用于缓存PL块中 Token 类型 List, 用于判断是否进入子 PL Block
    * 为了保证性能,最多只使用 MAX_PL_PATTEN_TYPE_SIZE 个 token 判断,超出后则肯定不是 PL Block */ private List plCacheTokenTypes = new ArrayList<>(); /** * 用于记录 `ALL_PL_START_PATTERNS` 中PL块开始标志index和其枚举类型的映射关系 */ private Map INDEX_2_START_SYMBOL; private String sql; private Boolean whileForLoopFlag = false; private Holder currentOffset = new Holder<>(0); public SqlSplitter(Class lexerType) { this(lexerType, DEFAULT_SQL_DELIMITER); } public SqlSplitter(Class lexerType, String delimiter) { this(lexerType, delimiter, true); } public SqlSplitter(Class lexerType, String delimiter, boolean addDelimiter) { this.tokenDefinition = LexerTokenDefinitions.of(lexerType); this.lexerFactory = LexerFactories.of(lexerType); this.innerUtils = new InnerUtils(); this.delimiter = delimiter; this.addDelimiter = addDelimiter; LexerTokenDefinition definition = this.tokenDefinition; this.DEFAULT_PL_END_DELIMITER = definition.DIV(); this.SQL_DELIMITER = definition.SEMICOLON(); this.PL_ELSE = definition.ELSE(); this.PL_THEN = definition.THEN(); this.PL_RIGHTPAREN = definition.RIGHTBRACKET(); this.PL_LEFTPAREN = definition.LEFTBRACKET(); this.PL_GREATER_THAN_OP = definition.GREATER_THAN_OP(); int[] ANONYMOUS_BLOCK_DECLARE_START = new int[] {definition.DECLARE()}; int[] ANONYMOUS_BLOCK_BEGIN_START = new int[] {definition.BEGIN()}; int[] CREATE_FUNCTION_START = new int[] {definition.CREATE(), definition.FUNCTION()}; int[] CREATE_PROCEDURE_START = new int[] {definition.CREATE(), definition.PROCEDURE()}; int[] CREATE_TRIGGER_START = new int[] {definition.CREATE(), definition.TRIGGER()}; int[] CREATE_PACKAGE_START = new int[] {definition.CREATE(), definition.PACKAGE()}; int[] CREATE_PACKAGE_BODY_START = new int[] {definition.CREATE(), definition.PACKAGE(), definition.BODY()}; int[] CREATE_TYPE_START = new int[] {definition.CREATE(), definition.TYPE()}; int[] CREATE_TYPE_BODY_START = new int[] {definition.CREATE(), definition.TYPE(), definition.BODY()}; int[] FUNCTION_START = new int[] {definition.FUNCTION()}; int[] PROCEDURE_START = new int[] {definition.PROCEDURE()}; int[] TRIGGER_START = new int[] {definition.TRIGGER()}; int[] PACKAGE_START = new int[] {definition.PACKAGE()}; int[] PACKAGE_BODY_START = new int[] {definition.PACKAGE(), definition.BODY()}; int[] LOOP_START = new int[] {definition.LOOP()}; int[] FOR_START = new int[] {definition.FOR()}; int[] IF_START = new int[] {definition.IF()}; int[] CASE_START = new int[] {definition.CASE()}; int[] WHILE_START = new int[] {definition.WHILE()}; this.ALL_PL_START_PATTERNS = new int[][] {ANONYMOUS_BLOCK_DECLARE_START, ANONYMOUS_BLOCK_BEGIN_START, CREATE_FUNCTION_START, CREATE_PROCEDURE_START, CREATE_TRIGGER_START, CREATE_PACKAGE_START, CREATE_PACKAGE_BODY_START, CREATE_TYPE_START, CREATE_TYPE_BODY_START, FUNCTION_START, PROCEDURE_START, TRIGGER_START, PACKAGE_START, PACKAGE_BODY_START, LOOP_START, FOR_START, IF_START, CASE_START, WHILE_START}; this.INDEX_2_START_SYMBOL = ImmutableMap.builder().put(0, PLStartSymbol.DECLARE) .put(1, PLStartSymbol.BEGIN) .put(2, PLStartSymbol.CREATE_FUNCTION) .put(3, PLStartSymbol.CREATE_PROCEDURE) .put(4, PLStartSymbol.CREATE_TRIGGER) .put(5, PLStartSymbol.CREATE_PACKAGE) .put(6, PLStartSymbol.CREATE_PACKAGE_BODY) .put(7, PLStartSymbol.CREATE_TYPE) .put(8, PLStartSymbol.CREATE_TYPE_BODY) .put(9, PLStartSymbol.FUNCTION) .put(10, PLStartSymbol.PROCEDURE) .put(11, PLStartSymbol.TRIGGER) .put(12, PLStartSymbol.PACKAGE) .put(13, PLStartSymbol.PACKAGE_BODY) .put(14, PLStartSymbol.LOOP) .put(15, PLStartSymbol.FOR) .put(16, PLStartSymbol.IF) .put(17, PLStartSymbol.CASE) .put(18, PLStartSymbol.WHILE).build(); this.BLANK_OR_COMMENT_TYPES = definition.ANTLR_SKIP() > Token.MIN_USER_TOKEN_TYPE ? new int[] {definition.SPACES(), definition.ANTLR_SKIP()} : new int[] {definition.SPACES(), definition.SINGLE_LINE_COMMENT(), definition.MULTI_LINE_COMMENT()}; this.PL_START_PATTERN_IGNORE_TYPES = new int[] {definition.OR(), definition.REPLACE(), definition.EDITIONABLE(), definition.NONEDITIONABLE(), definition.BODY(), definition.AS(), definition.IS()}; this.PL_IDENT_TYPES = definition.IDENT() > Token.MIN_USER_TOKEN_TYPE ? new int[] {definition.IDENT()} : new int[] {definition.REGULAR_ID(), definition.DELIMITED_ID()}; this.delimiterTokens = innerUtils.extractDelimiterTokens(delimiter); } public String getDelimiter() { return delimiter; } public List split(String sql) { if (StringUtils.isBlank(sql)) { return new ArrayList<>(); } clear(); this.sql = sql; /** * Antlr Lexer 拆词后的 token 列表 */ Token[] tokens = innerUtils.initTokens(sql); int tokenCount = tokens.length; int labelRightCount = 0; for (int pos = 0; pos < tokenCount; pos++) { Token token = tokens[pos]; int type = token.getType(); if (type < Token.MIN_USER_TOKEN_TYPE) { // invalid token type continue; } String text = token.getText(); int offset = token.getStartIndex(); if (">".equals(text)) { labelRightCount++; } else { labelRightCount = 0; } if (this.removeCommentPrefix && 0 == currentStmtBuilder.length() && innerUtils.isBlankOrComment(type)) { continue; } if (innerUtils.isPLStartPatternIgnoreTypes(type)) { if (StringUtils.isBlank(currentStmtBuilder.toString()) && type != tokenDefinition.SPACES()) { currentOffset.setValue(offset); } // skip analysis blank, comment and other PL block start math pattern ignore types currentStmtBuilder.append(text); continue; } if (this.state == State.SQL_STMT) { if (cacheTokenTypes.size() < MAX_PL_PATTEN_TYPE_SIZE) { cacheTokenTypes.add(type); } if (cacheTokenTypes.size() == 1 && innerUtils.isDelimiterCommand(token)) { pos = executeDelimiterCommand(tokens, pos); continue; } if (isPLBlockStart()) { pushToStack(cacheTokenTypes); if (StringUtils.isBlank(currentStmtBuilder.toString()) && type != tokenDefinition.SPACES()) { currentOffset.setValue(offset); } currentStmtBuilder.append(text); this.state = State.PL_STMT; cacheTokenTypes.clear(); } else if (isStmtEnd(tokens, pos)) { pos = addStmtWhileStmtEnd(tokens, pos); } else { if (StringUtils.isBlank(currentStmtBuilder.toString()) && type != tokenDefinition.SPACES()) { currentOffset.setValue(offset); } currentStmtBuilder.append(text); } } else if (this.state == State.PL_STMT) { // sql statement inside PL block end if (SQL_DELIMITER == type || PL_ELSE == type || PL_THEN == type || PL_RIGHTPAREN == type || (labelRightCount == 2 && PL_GREATER_THAN_OP == type) || PL_LEFTPAREN == type) { plCacheTokenTypes.clear(); labelRightCount = 0; } else if (plCacheTokenTypes.size() < MAX_PL_PATTEN_TYPE_SIZE) { plCacheTokenTypes.add(type); } if (!subPLStack.empty() && (type == tokenDefinition.EXTERNAL() || type == tokenDefinition.LANGUAGE())) { subPLStack.peek().matchExternalOrLanguage = true; } else if (!subPLStack.empty() && (type == tokenDefinition.IS() || type == tokenDefinition.AS())) { // `IS` may run into case like `cursor cur1 is select col from for_loop_cursor_t;` // in this case, it does not have parent pl block subPLStack.peek().matchIsOrAs = true; } else if (!subPLStack.empty() && subPLStack.peek().startSymbol == PLStartSymbol.CREATE_TYPE && (type == tokenDefinition.MEMBER() || type == tokenDefinition.STATIC())) { // temporarily set matchMemberOrStatic in parent subPLLevel // when encounters sub Function / Procedure in create type // set sub Function / Procedure's matchMemberOrStatic // and recover parent subPLLevel matchMemberOrStatic value to false subPLStack.peek().matchMemberOrStatic = true; } if (isStmtEnd(tokens, pos)) { pos = addStmtWhileStmtEnd(tokens, pos); this.state = State.SQL_STMT; } else { if (isSubPLBlockStart()) { pushToStack(plCacheTokenTypes); plCacheTokenTypes.clear(); } int posShift = isPLBlockEnd(tokens, pos); if (posShift >= 0) { pos += posShift; subPLStack.pop(); plCacheTokenTypes.clear(); } if (StringUtils.isBlank(currentStmtBuilder.toString()) && type != tokenDefinition.SPACES()) { currentOffset.setValue(offset); } currentStmtBuilder.append(text); // add additional tokens in which may contains in pl block ending tokens // like end[;] / end [object_name;] / end [loop;] / end [if;] / end [case;] if (posShift > 0) { for (int index = 1; index <= posShift; index++) { if (StringUtils.isBlank(currentStmtBuilder.toString()) && type != tokenDefinition.SPACES()) { currentOffset.setValue(offset); } currentStmtBuilder.append(tokens[pos - posShift + index].getText()); } } } } } addStmtWhileStmtEnd(tokens, tokenCount); return stmts; } public static SqlStatementIterator iterator(InputStream in, Charset charset, String delimiter) { return iterator(in, charset, delimiter, true); } public static SqlStatementIterator iterator(InputStream in, Charset charset, String delimiter, boolean addDelimiter) { return new SqlSplitterIterator(in, charset, delimiter, addDelimiter); } private void clear() { this.stmts.clear(); this.cacheTokenTypes.clear(); this.currentStmtBuilder.setLength(0); this.state = State.SQL_STMT; } private int addStmtWhileStmtEnd(Token[] tokens, int pos) { String currentStmt = currentStmtBuilder.toString(); boolean notDefaultSqlDelimiter = false; if (StringUtils.isNotBlank(currentStmt)) { if (addDelimiter) { for (int cursor = pos - 1; cursor > 0; cursor--) { Token token = tokens[cursor]; if (innerUtils.isEOF(token.getType()) || innerUtils.isBlankOrComment(token.getType())) { continue; } notDefaultSqlDelimiter = !DEFAULT_SQL_DELIMITER.equals(token.getText()); if (notDefaultSqlDelimiter) { currentStmt += DEFAULT_SQL_DELIMITER; } break; } } this.stmts.add(new SplitSqlString(currentOffset.getValue(), currentStmt.trim())); if (notDefaultSqlDelimiter) { this.currentOffset.setValue(this.currentOffset.getValue() + DEFAULT_SQL_DELIMITER.length()); } } this.cacheTokenTypes.clear(); this.currentStmtBuilder.setLength(0); return pos + delimiterTokens.length - 1; } private int executeDelimiterCommand(Token[] tokens, int pos) { // delimiter command identified, will ignore built-in pl delimiter logic, // examples: // - delimiter $$ // - delimiter / if (pos + 2 >= tokens.length) { // invalid syntax throw new IllegalArgumentException("Invalid delimiter command syntax"); } pos++; Token expectBlank = tokens[pos]; if (expectBlank.getType() != tokenDefinition.SPACES()) { throw new IllegalArgumentException( "Invalid delimiter command syntax, expect blank after 'delimiter'"); } List delimiterTokensToSet = new ArrayList<>(); StringBuilder delimiterBuilder = new StringBuilder(); // ignore multiple blanks between delimiter keyword and value of delimiter boolean hasDelimiterValue = false; while (++pos < tokens.length) { Token delimiterToken = tokens[pos]; int delimiterTokenType = delimiterToken.getType(); if (delimiterTokenType > Token.MIN_USER_TOKEN_TYPE && delimiterTokenType != tokenDefinition.SPACES()) { hasDelimiterValue = true; break; } } if (hasDelimiterValue) { --pos; } else { throw new IllegalArgumentException( "Invalid delimiter command syntax, no delimiter value set"); } // extract value of delimiter, may multiple tokens while (++pos < tokens.length) { Token delimiterToken = tokens[pos]; int delimiterTokenType = delimiterToken.getType(); if (delimiterTokenType > Token.MIN_USER_TOKEN_TYPE && delimiterTokenType != tokenDefinition.SPACES()) { delimiterTokensToSet.add(delimiterToken); delimiterBuilder.append(delimiterToken.getText()); } else { break; } } if (delimiterTokensToSet.isEmpty()) { throw new IllegalArgumentException( "Invalid delimiter command syntax, no delimiter value set"); } this.delimiterTokens = delimiterTokensToSet.toArray(new Token[0]); this.delimiter = delimiterBuilder.toString(); cacheTokenTypes.clear(); return pos; } private boolean isPLBlockStart() { if (cacheTokenTypes.size() > MAX_PL_PATTEN_TYPE_SIZE) { return false; } int[] cacheTokenTypeArray = cacheTokenTypes.stream().mapToInt(i -> i).toArray(); for (int[] pattern : ALL_PL_START_PATTERNS) { if (Arrays.equals(pattern, cacheTokenTypeArray)) { return true; } } return false; } /** *
         * sub PL block start rule is different from the first PL block start
         * cache token types are stored in a queue because when we run into a new symbol,
         * we always add to the tail of queue and try to remove head of queue when it it full
         *
         * when trying to match ALL_PL_START_PATTERNS, we always try to match the longer start pattern,
         * for example, match `create type body` but not `create type`
         * 
    */ private boolean isSubPLBlockStart() { if (plCacheTokenTypes.size() > MAX_PL_PATTEN_TYPE_SIZE) { return false; } int[] plCacheTokenTypeArray = plCacheTokenTypes.stream().mapToInt(i -> i).toArray(); for (int[] pattern : ALL_PL_START_PATTERNS) { if (Arrays.equals(pattern, plCacheTokenTypeArray)) { return true; } } return false; } private void pushToStack(Collection sourceCache) { PLStartSymbol currentSymbol = recognizeStartSymbol(sourceCache); if (Objects.nonNull(currentSymbol) && (currentSymbol == PLStartSymbol.WHILE || currentSymbol == PLStartSymbol.FOR)) { whileForLoopFlag = true; } if (Objects.nonNull(currentSymbol) && whileForLoopFlag && currentSymbol == PLStartSymbol.LOOP) { whileForLoopFlag = false; return; } // declare does not need to push into stack // because declare ... begin ... end block can be recognized by begin ... end // declare does not have an explicit ending if (Objects.nonNull(currentSymbol) && currentSymbol != PLStartSymbol.DECLARE) { SubPLLevel subPLLevel = new SubPLLevel(currentSymbol); // begin symbol does not need to push into stack when its wrapped in [create] function / procedure / // trigger / package [body] / type [body] // because in this case, it is always like `create function ... begin ... end` which can be // recognized by create function ... end if (!subPLStack.empty() && currentSymbol == PLStartSymbol.BEGIN) { switch (subPLStack.peek().startSymbol) { case FUNCTION: case PROCEDURE: case TRIGGER: case PACKAGE: case PACKAGE_BODY: case CREATE_FUNCTION: case CREATE_PROCEDURE: case CREATE_TRIGGER: case CREATE_PACKAGE: case CREATE_PACKAGE_BODY: case CREATE_TYPE: case CREATE_TYPE_BODY: return; } } else if (!subPLStack.empty() && subPLStack.peek().startSymbol == PLStartSymbol.CREATE_TYPE && (currentSymbol == PLStartSymbol.FUNCTION || currentSymbol == PLStartSymbol.PROCEDURE)) { // inherit matchMemberOrStatic from parent `CREATE_TYPE` sentence to sub Function or procedure // declare subPLLevel.matchMemberOrStatic = subPLStack.peek().matchMemberOrStatic; subPLStack.peek().matchMemberOrStatic = false; } subPLStack.push(subPLLevel); } } private PLStartSymbol recognizeStartSymbol(Collection sourceCache) { if (sourceCache.size() > MAX_PL_PATTEN_TYPE_SIZE) { return null; } int[] cacheTokenTypeArray = sourceCache.stream().mapToInt(i -> i).toArray(); for (int i = 0; i < ALL_PL_START_PATTERNS.length; i++) { int[] pattern = ALL_PL_START_PATTERNS[i]; if (Arrays.equals(pattern, cacheTokenTypeArray)) { return INDEX_2_START_SYMBOL.get(i); } } return PLStartSymbol.UNKNOWN; } /** *
         * PL block ending judgement may involve pos moving forward.
         * For example in case LOOP / IF / CASE, ending push pos to pos + 2.
         * The result of this function means position shift
         * 0 means is end and pos does not need any move
         * value < 0 means is not end
         * value > 0 means is end and pos needs moving forward according to value
         * 
    */ private int isPLBlockEnd(Token[] tokens, int pos) { if (pos >= tokens.length || subPLStack.empty()) { return -1; } boolean isEnd; int posShift = 0; /** *
             * `subPLStack` in this function must not be empty because this function must be called when state in PL_STMT
             * `subPLStack` must have been pushed at least once before enter PL_STMT
             * 
    */ SubPLLevel peekLevel = subPLStack.peek(); switch (peekLevel.startSymbol) { case CREATE_TYPE: isEnd = matchDelimiterTokens(tokens, pos); break; case BEGIN: case CREATE_TYPE_BODY: case TRIGGER: case CREATE_TRIGGER: isEnd = tokens[pos].getType() == this.tokenDefinition.END(); break; case FUNCTION: case PROCEDURE: // member or static function && procedure declare in type which does not contain IS or AS // should end with `)` or `,` if (peekLevel.matchMemberOrStatic && !peekLevel.matchIsOrAs) { isEnd = tokens[pos].getText().equals(")") || tokens[pos].getText().equals(","); break; } case PACKAGE: case PACKAGE_BODY: case CREATE_FUNCTION: case CREATE_PROCEDURE: case CREATE_PACKAGE: case CREATE_PACKAGE_BODY: boolean matchDelimiter = matchDelimiterTokens(tokens, pos); if (matchDelimiter) { // in only two cases, delimiter can be the end of pl create sentence // 1. `IS` or `AS` is not matched // 2. `IS` or `AS` is matched but `EXTERNAL` or `LANGUAGE` is also matched isEnd = !peekLevel.matchIsOrAs || peekLevel.matchExternalOrLanguage; } else { isEnd = tokens[pos].getType() == this.tokenDefinition.END(); } break; case FOR: case LOOP: case WHILE: isEnd = matchPLBlockEnd(tokens, pos, this.tokenDefinition.LOOP()); posShift = isEnd ? 2 : 0; break; case IF: isEnd = matchPLBlockEnd(tokens, pos, this.tokenDefinition.IF()); posShift = isEnd ? 2 : 0; break; case CASE: isEnd = matchPLBlockEnd(tokens, pos, this.tokenDefinition.CASE()); posShift = isEnd ? 2 : 0; break; case UNKNOWN: default: throw new RuntimeException( String.format("Unsupported pl start symbol: %s", peekLevel.startSymbol)); } if (isEnd) { return posShift; } return -1; } private boolean matchPLBlockEnd(Token[] tokens, int pos, Integer endObjectType) { boolean match = tokens[pos].getType() == this.tokenDefinition.END(); int tokensLength = tokens.length; if (Objects.nonNull(endObjectType)) { if (endObjectType != Token.MIN_USER_TOKEN_TYPE) { // use MIN_USER_TOKEN_TYPE means place holder here // in which we can recognize `end object_name;` as pl block ending match &= (pos + 2 < tokensLength) && tokens[pos + 2].getType() == endObjectType; } } return match; } private boolean isStmtEnd(Token[] tokens, int pos) { // only use Div `/` as while in PL stmt and use `;` as delimiter if (pos >= tokens.length) { return false; } if (!subPLStack.empty()) { return false; } if (this.state == State.PL_STMT) { if (!DEFAULT_SQL_DELIMITER.equals(delimiter)) { return matchDelimiterTokens(tokens, pos); } return tokens[pos].getType() == DEFAULT_PL_END_DELIMITER; } return matchDelimiterTokens(tokens, pos); } private boolean matchDelimiterTokens(Token[] tokens, int pos) { Token[] dt = delimiterTokens; if (this.state == State.PL_STMT && !subPLStack.empty()) { dt = innerUtils.extractDelimiterTokens(DEFAULT_SQL_DELIMITER); } int delimiterLength = dt.length; int tokensLength = tokens.length; if (pos + delimiterLength > tokensLength) { return false; } for (int i = 0; i < delimiterLength; i++) { if (!innerUtils.isTokenEquals(dt[i], tokens[pos + i])) { return false; } } return true; } enum State { SQL_STMT, PL_STMT } enum PLStartSymbol { BEGIN, DECLARE, CREATE_FUNCTION, CREATE_PROCEDURE, CREATE_TRIGGER, CREATE_PACKAGE, CREATE_PACKAGE_BODY, CREATE_TYPE, CREATE_TYPE_BODY, FUNCTION, PROCEDURE, TRIGGER, PACKAGE, PACKAGE_BODY, FOR, LOOP, IF, CASE, WHILE, UNKNOWN } class SubPLLevel { private PLStartSymbol startSymbol; /** * 用于记录PL对象DDL语句[如create package / function 等]中是否出现了 EXTERNAL 或者 LANGUAGE 关键字 * 如果有,则当前DDL语句的结束符只能为delimiter */ private boolean matchExternalOrLanguage = false; /** * 用于记录PL对象DDL语句[如create package / function 等]中是否出现了 IS 或者 AS 关键字 */ private boolean matchIsOrAs = false; /** * 用于记录Type对象中是否出现了 MEMBER 或者 STATIC 关键字 */ private boolean matchMemberOrStatic = false; private SubPLLevel(PLStartSymbol startSymbol) { this.startSymbol = startSymbol; } } class InnerUtils { Token[] initTokens(String sql) { return tokens(sql).toArray(new Token[0]); } Token[] extractDelimiterTokens(String delimiter) { return tokens(delimiter).stream() .filter(token -> token.getType() > Token.MIN_USER_TOKEN_TYPE) .toArray(Token[]::new); } /** * Oracle 词法文件识别 IDENT 关键字必须是字母开头,对于形如 delimiter $$ 语句,其中的 $$ 会被认为是错误的词法,
    * 这里对不识别的词法转换为 IDENT 和 SPACES 类型的 Token,使得上层可以一致化处理 */ private List tokens(String sql) { List tokens = initTokenStream(sql).getTokens(); int length = sql.codePointCount(0, sql.length()); int size = tokens.size(); Token firstToken = size > 0 ? tokens.get(0) : null; Token lastToken = size > 0 ? tokens.get(size - 1) : null; List allTokens = new ArrayList<>(tokens.size()); if (firstToken != null) { if (firstToken.getStartIndex() > 0) { allTokens.addAll(generateInvalidTokens(sql, 0, firstToken.getStartIndex())); } allTokens.add(firstToken); } for (int i = 1; i < size; i++) { Token current = tokens.get(i); Token previous = tokens.get(i - 1); if (current.getStartIndex() - previous.getStopIndex() > 1) { allTokens.addAll(generateInvalidTokens(sql, previous.getStopIndex() + 1, current.getStartIndex())); } allTokens.add(current); } if (lastToken != null && lastToken.getStopIndex() < length - 1) { allTokens.addAll(generateInvalidTokens(sql, lastToken.getStopIndex() + 1, length)); } return allTokens; } private List generateInvalidTokens(String sql, int start, int end) { String invalidStr = StringUtils.substring(sql, start, end); char[] chars = invalidStr.toCharArray(); if (chars.length == 1) { Token token = new CommonToken(PL_IDENT_TYPES[0], invalidStr); return Collections.singletonList(token); } List invalidTokens = new ArrayList<>(); boolean lastCharSpace = ArrayUtils.contains(SPACES_CHARS, chars[0]); boolean currentCharSpace; int charProcessPos = 0; for (int i = 1; i < chars.length; i++) { currentCharSpace = ArrayUtils.contains(SPACES_CHARS, chars[i]); if (lastCharSpace != currentCharSpace) { invalidTokens.add(invalidToken(lastCharSpace, invalidStr, charProcessPos, i)); charProcessPos = i; } lastCharSpace = currentCharSpace; } if (charProcessPos <= chars.length - 1) { invalidTokens.add(invalidToken(lastCharSpace, invalidStr, charProcessPos, chars.length)); } return invalidTokens; } private Token invalidToken(boolean spaces, String str, int start, int end) { String text = StringUtils.substring(str, start, end); Token token = spaces ? new CommonToken(tokenDefinition.SPACES(), text) : new CommonToken(PL_IDENT_TYPES[0], text); return token; } private CommonTokenStream initTokenStream(String sql) { CharStream input = CharStreams.fromString(sql); Lexer lexer = lexerFactory.create(input); CommonTokenStream tokenStream = new CommonTokenStream(lexer); tokenStream.fill(); return tokenStream; } boolean isDelimiterCommand(Token token) { int type = token.getType(); String text = token.getText(); return isIdent(type) && StringUtils.equalsIgnoreCase("delimiter", text); } boolean isTokenEquals(Token left, Token right) { return left.getType() == right.getType() && StringUtils.equalsIgnoreCase(left.getText(), right.getText()); } boolean isPLStartPatternIgnoreTypes(int tokenType) { if (isBlankOrComment(tokenType)) { return true; } return ArrayUtils.contains(PL_START_PATTERN_IGNORE_TYPES, tokenType); } boolean isBlankOrComment(int tokenType) { return ArrayUtils.contains(BLANK_OR_COMMENT_TYPES, tokenType); } boolean isEOF(int tokenType) { return tokenType == -1; } private boolean isIdent(int tokenType) { return ArrayUtils.contains(PL_IDENT_TYPES, tokenType); } } private static class SqlSplitterIterator implements SqlStatementIterator { private final BufferedReader reader; private final StringBuilder buffer = new StringBuilder(); private final LinkedList holder = new LinkedList<>(); private final boolean addDelimiter; private SplitSqlString current; private String delimiter; private boolean firstLine = true; private List sqls = new ArrayList<>(); private long iteratedBytes = 0; private int offset = 0; private static final Character SQL_SEPARATOR_CHAR = '/'; private static final Character LINE_SEPARATOR_CHAR = '\n'; private static final String SQL_MULTI_LINE_COMMENT_PREFIX = "/*"; private static final Set DELIMITER_CHARACTERS = new HashSet<>(Arrays.asList(';', '/', '$')); public SqlSplitterIterator(InputStream input, Charset charset, String delimiter, boolean addDelimiter) { this.reader = new BufferedReader(new InputStreamReader(input, charset)); this.delimiter = delimiter; this.addDelimiter = addDelimiter; } @Override public boolean hasNext() { if (this.current == null) { this.current = parseNext(); } return this.current != null; } @Override public SplitSqlString next() { SplitSqlString next = this.current; this.current = null; if (next == null) { next = parseNext(); if (next == null) { throw new NoSuchElementException("No more available sql."); } } return next; } @Override public long iteratedBytes() { return this.iteratedBytes; } private SplitSqlString parseNext() { try { if (!this.holder.isEmpty()) { return this.holder.poll(); } String line; while (this.holder.isEmpty() && (line = this.reader.readLine()) != null) { this.iteratedBytes += line.getBytes(Charset.defaultCharset()).length + 1; addLineToBuffer(line); SqlSplitProcessor processor = new SqlSplitProcessor(); LinkedList innerHolder = new LinkedList<>(); StringBuffer innerBuffer = new StringBuffer(); Holder bufferOrder = new Holder<>(0); processor.addLineOracle(innerHolder, innerBuffer, bufferOrder, line.chars().mapToObj(c -> new SqlSplitProcessor.OrderChar((char) c, -1)).collect(Collectors.toList())); while (processor.isMlComment() && (line = reader.readLine()) != null) { this.iteratedBytes += line.getBytes(Charset.defaultCharset()).length + 1; addLineToBuffer(line); processor.addLineOracle(innerHolder, innerBuffer, bufferOrder, line.chars().mapToObj(c -> new SqlSplitProcessor.OrderChar((char) c, -1)).collect(Collectors.toList())); } // SqlSplitter is non-reentrant, so we need to create a new one for each loop SqlSplitter splitter = createSplitter(); this.sqls = splitter.split(this.buffer.toString()).stream().map(SplitSqlString::getStr) .collect(Collectors.toList()); while (this.sqls.size() > 1) { String sql = this.sqls.remove(0); int index = this.buffer.indexOf(sql.substring(0, sql.length() - 1)); this.holder.addLast(new SplitSqlString(this.offset + index, sql)); this.buffer.delete(0, index + sql.length()); this.offset += index + sql.length(); clearUselessPrefix(); this.delimiter = splitter.getDelimiter(); } } if (!this.holder.isEmpty()) { return this.holder.poll(); } if (this.sqls.isEmpty()) { return null; } return new SplitSqlString(this.offset, this.sqls.remove(0)); } catch (Exception e) { throw new RuntimeException("Failed to parse input. reason: " + e.getMessage(), e); } } private void addLineToBuffer(String line) { if (this.firstLine) { this.buffer.append(line); this.firstLine = false; } else { this.buffer.append(LINE_SEPARATOR_CHAR).append(line); } } private void clearUselessPrefix() { while (this.buffer.length() > 0 && DELIMITER_CHARACTERS.contains(this.buffer.charAt(0)) && !(this.buffer.toString().startsWith(SQL_MULTI_LINE_COMMENT_PREFIX))) { this.buffer.deleteCharAt(0); this.offset++; } while ((this.buffer.length() > 0 && (Character.isWhitespace(this.buffer.charAt(0)))) || (this.buffer.toString().startsWith(SQL_SEPARATOR_CHAR.toString()) && !this.buffer.toString().startsWith(SQL_MULTI_LINE_COMMENT_PREFIX))) { this.buffer.deleteCharAt(0); this.offset++; } } private SqlSplitter createSplitter() { return new SqlSplitter(PlSqlLexer.class, this.delimiter, addDelimiter); } } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlStatementIterator.java ================================================ /* * Copyright (c) 2023 OceanBase. * * Licensed 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. */ package ai.chat2db.spi.util; import java.util.Iterator; public interface SqlStatementIterator extends Iterator { /** * This method is used to get the current number of bytes of the SQL file stream that has been * traversed. The reason for placing it in the interface is that the exact value can only be * computed when it is processed inner the Iterator, whereas the value computed on the call side * based on the contents of the processed SQL is inaccurate. This is because there may be large * blank characters in the SQL which are removed during the SQL splitting process. * * @return bytes size that has been iterated */ long iteratedBytes(); } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java ================================================ package ai.chat2db.spi.util; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.model.ExecuteResult; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.parser.SQLParserUtils; import com.oceanbase.tools.sqlparser.oracle.PlSqlLexer; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; import net.sf.jsqlparser.statement.create.procedure.CreateProcedure; import net.sf.jsqlparser.statement.select.*; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author jipengfei * @version : SqlUtils.java */ @Slf4j public class SqlUtils { public static final String DEFAULT_TABLE_NAME = "table1"; public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult executeResult) { try { Statement statement; if (DbType.sqlserver.equals(dbType)) { statement = CCJSqlParserUtil.parse(sql, ccjSqlParser -> ccjSqlParser.withSquareBracketQuotation(true)); } else { statement = CCJSqlParserUtil.parse(sql); } if (statement instanceof Select) { Select select = (Select) statement; PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); if (plainSelect.getJoins() == null && plainSelect.getFromItem() != null) { for (SelectItem item : plainSelect.getSelectItems()) { if (item instanceof SelectExpressionItem) { SelectExpressionItem expressionItem = (SelectExpressionItem) item; if (expressionItem.getAlias() != null) { //canEdit = false; // 找到了一个别名 executeResult.setCanEdit(false); return; } if (item instanceof SelectExpressionItem) { SelectExpressionItem selectExpressionItem = (SelectExpressionItem) item; // if the expression is a function if (selectExpressionItem.getExpression() instanceof Function) { Function function = (Function) selectExpressionItem.getExpression(); // Check if the function is "COUNT" if ("COUNT".equalsIgnoreCase(function.getName())) { executeResult.setCanEdit(false); return; } } } } } executeResult.setCanEdit(true); SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if ((sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); executeResult.setTableName(getMetaDataTableName(sqlExprTableSource.getCatalog(), sqlExprTableSource.getSchema(), sqlExprTableSource.getTableName())); } } else { executeResult.setCanEdit(false); } } } catch (Exception e) { log.error("buildCanEditResult error", e); executeResult.setCanEdit(false); } } private static String getMetaDataTableName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> name).collect(Collectors.joining(".")); } public static String formatSQLString(Object para) { return para != null ? " '" + para + "' " : null; } public static String getTableName(String sql, DbType dbType) { SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if (!(sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); if (sqlExprTableSource == null) { return DEFAULT_TABLE_NAME; } return sqlExprTableSource.getTableName(); } private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSource) { if (sqlTableSource instanceof SQLExprTableSource sqlExprTableSource) { return sqlExprTableSource; } else if (sqlTableSource instanceof SQLJoinTableSource sqlJoinTableSource) { return getSQLExprTableSource(sqlJoinTableSource.getLeft()); } return null; } private static final String DELIMITER_AFTER_REGEX = "^\\s*(?i)delimiter\\s+(\\S+)"; private static final String DELIMITER_REGEX = "(?mi)^\\s*delimiter\\s*;?"; private static final String EVENT_REGEX = "(?i)\\bcreate\\s+event\\b.*?\\bend\\b"; public static List parse(String sql, DbType dbType, boolean removeComment) { List list = new ArrayList<>(); try { if (StringUtils.isBlank(sql)) { return list; } if (removeComment) { sql = SQLParserUtils.removeComment(sql, dbType); } try { if (DbType.oracle.equals(dbType)) { SqlSplitter sqlSplitter = new SqlSplitter(PlSqlLexer.class, ";", false); sqlSplitter.setRemoveCommentPrefix(true); List sqls = sqlSplitter.split(sql); return sqls.stream().map(splitSqlString -> removeComment ? SQLParserUtils.removeComment(splitSqlString.getStr(), dbType) : splitSqlString.getStr()).collect(Collectors.toList()); } } catch (Exception e) { log.error("sqlSplitter error", e); } try { if (DbType.mysql.equals(dbType) || DbType.mariadb.equals(dbType) || DbType.oceanbase.equals(dbType)) { sql = updateNow(sql, dbType); SqlSplitProcessor sqlSplitProcessor = new SqlSplitProcessor(dbType, true, true); sqlSplitProcessor.setDelimiter(";"); return split(sqlSplitProcessor, sql, dbType, removeComment); } } catch (Exception e) { log.error("sqlSplitProcessor error", e); } // sql = removeDelimiter(sql); if (StringUtils.isBlank(sql)) { return list; } Statements statements = CCJSqlParserUtil.parseStatements(sql); // Iterate through each statement for (Statement stmt : statements.getStatements()) { if (!(stmt instanceof CreateProcedure)) { list.add(stmt.toString()); } } if (CollectionUtils.isEmpty(list)) { list.add(sql); } } catch (Exception e) { try { return splitWithCreateEvent(sql, dbType); } catch (Exception e1) { if (removeComment) { return SQLParserUtils.splitAndRemoveComment(sql, dbType); }{ return SQLParserUtils.split(sql, dbType); } } } return list; } private static String removeDelimiter(String str) { try { if (str.toUpperCase().contains("DELIMITER")) { Pattern pattern = Pattern.compile(DELIMITER_AFTER_REGEX, Pattern.MULTILINE); Matcher matcher = pattern.matcher(str); while (matcher.find()) { // 获取并打印 "DELIMITER" 后的第一个字符串 String mm = matcher.group(1); if (!";".equals(mm)) { str = str.replace(mm, ""); } } } return str.replaceAll(DELIMITER_REGEX, ""); } catch (Exception e) { return str; } } private static List splitWithCreateEvent(String str, DbType dbType) { List list = new ArrayList<>(); String sql = SQLParserUtils.removeComment(str, dbType).trim(); Pattern pattern = Pattern.compile(EVENT_REGEX, Pattern.DOTALL); Matcher matcher = pattern.matcher(sql); StringBuilder stringBuilder = new StringBuilder(); int lastEnd = 0; // 用于跟踪上一个匹配的结束位置 while (matcher.find()) { if (matcher.start() > lastEnd) { List l = SQLParserUtils.split(sql.substring(lastEnd, matcher.start()), dbType); list.addAll(l); } list.add(matcher.group()); lastEnd = matcher.end(); // 更新上一个匹配的结束位置 } if (lastEnd < sql.length()) { List l = SQLParserUtils.split(sql.substring(lastEnd), dbType); list.addAll(l); } return list; } private static String updateNow(String sql, DbType dbType) { if (StringUtils.isBlank(sql) || !DbType.mysql.equals(dbType)) { return sql; } if (sql.contains("default now()")) { return sql.replace("default now()", "default CURRENT_TIMESTAMP"); } if (sql.contains("DEFAULT now()")) { return sql.replace("DEFAULT now()", "default CURRENT_TIMESTAMP"); } if (sql.contains("default now ()")) { return sql.replace("default now ()", "default CURRENT_TIMESTAMP"); } if (sql.contains("DEFAULT now ()")) { return sql.replace("DEFAULT now ()", "DEFAULT CURRENT_TIMESTAMP"); } return sql; } private static final String DEFAULT_VALUE = "CHAT2DB_UPDATE_TABLE_DATA_USER_FILLED_DEFAULT"; public static String getSqlValue(String value, String dataType) { if (value == null) { return null; } if ("".equals(value)) { return "''"; } if (DEFAULT_VALUE.equals(value)) { return "DEFAULT"; } DataTypeEnum dataTypeEnum = DataTypeEnum.getByCode(dataType); return dataTypeEnum.getSqlValue(value); } public static boolean hasPageLimit(String sql, DbType dbType) { try { Statement statement = CCJSqlParserUtil.parse(sql); if (statement instanceof Select) { Select selectStatement = (Select) statement; SelectBody selectBody = selectStatement.getSelectBody(); // Check out common pagination methods if (selectBody instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect) selectBody; // CHECK LIMIT if (plainSelect.getLimit() != null || plainSelect.getOffset() != null || plainSelect.getTop() != null || plainSelect.getFetch() != null) { return true; } if (DbType.oracle.equals(dbType)) { return sql.contains("ROWNUM") || sql.contains("rownum"); } } } } catch (Exception e) { return false; } return false; } private static List split(SqlSplitProcessor processor, String sql, DbType dbType, boolean removeComment) { StringBuffer buffer = new StringBuffer(); List sqls = processor.split(buffer, sql); String bufferStr = buffer.toString(); if (bufferStr.trim().length() != 0) { // if buffer is not empty, there will be some errors in syntax // log.info("sql processor's buffer is not empty, there may be some errors. buffer={}", bufferStr); int lastSqlOffset; if (sqls.size() == 0) { int index = sql.indexOf(bufferStr.trim(), 0); lastSqlOffset = index == -1 ? 0 : index; } else { int from = sqls.get(sqls.size() - 1).getOffset() + sqls.get(sqls.size() - 1).getStr().length(); int index = sql.indexOf(bufferStr.trim(), from); lastSqlOffset = index == -1 ? from : index; } sqls.add(new SplitSqlString(lastSqlOffset, bufferStr)); // String sqlstr = SQLParserUtils.removeComment(sql, dbType); // return Lists.newArrayList(sqlstr); } return sqls.stream().map(splitSqlString -> removeComment ? SQLParserUtils.removeComment(splitSqlString.getStr(), dbType) : splitSqlString.getStr()).collect(Collectors.toList()); } public static void main(String[] args) { } public static String quoteObjectName(String name) { return quoteObjectName(name, "\""); } public static String quoteObjectName(String name, String quoteSymbol) { if (StringUtils.isNotBlank(name)) { boolean startsWithQuote = name.startsWith(quoteSymbol); boolean endsWithQuote = name.endsWith(quoteSymbol); if (!startsWithQuote && !endsWithQuote) { // 如果前后都没有quoteSymbol return quoteSymbol + name + quoteSymbol; } else if (startsWithQuote && !endsWithQuote) { // 如果只有前面有quoteSymbol return quoteSymbol + quoteSymbol + name + quoteSymbol; } else if (!startsWithQuote) { // 如果只有后面有quoteSymbol return quoteSymbol + name + quoteSymbol + quoteSymbol; } // 如果前后都有quoteSymbol,直接返回原字符串 return name; } // 如果name为空或仅包含空白字符,返回原字符串 return name; } /** * String input = "INTERVAL DAY(2) TO SECOND(6)"; * remove (2) and (6) * * @param input * @return */ public static String removeDigits(String input) { if (StringUtils.isBlank(input)) { return input; } return input.replaceAll("\\(\\d+\\)", ""); } } ================================================ FILE: chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/TableUtils.java ================================================ package ai.chat2db.spi.util; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import org.apache.commons.collections4.CollectionUtils; public class TableUtils { public static TableColumn getTableColumn(Table table,String columnName) { if(table == null || CollectionUtils.isEmpty(table.getColumnList())){ return null ; } for (TableColumn tableColumn : table.getColumnList()) { if(tableColumn.getName().equalsIgnoreCase(columnName)){ return tableColumn ; } } return null; } } ================================================ FILE: chat2db-server/lombok.config ================================================ # Convert toString and call the previous layer lombok.toString.callSuper = CALL # Globally configure the callSuper attribute of equalsAndHashCode to true lombok.equalsAndHashCode.callSuper=call ================================================ FILE: chat2db-server/pom.xml ================================================ org.springframework.boot spring-boot-starter-parent 3.1.0 4.0.0 ai.chat2db chat2db-server-parent pom ${revision} chat2db-server-parent 2.0.0-SNAPSHOT 17 17 17 UTF-8 UTF-8 true chat2db-server-domain chat2db-server-start chat2db-server-test chat2db-server-tools chat2db-server-web chat2db-spi chat2db-plugins chat2db-server-web-start ai.chat2db chat2db-server-tools-base ${revision} ai.chat2db chat2db-server-tools-common ${revision} ai.chat2db chat2db-server-web-api ${revision} ai.chat2db chat2db-server-admin-api ${revision} ai.chat2db chat2db-server-common-api ${revision} ai.chat2db chat2db-server-domain-api ${revision} ai.chat2db chat2db-server-domain-core ${revision} ai.chat2db chat2db-server-domain-repository ${revision} ai.chat2db chat2db-server-start ${revision} ai.chat2db chat2db-spi ${revision} ai.chat2db chat2db-plugins ${revision} org.apache.commons commons-collections4 4.4 com.google.guava guava 32.0.1-jre cn.hutool hutool-all 5.8.20 com.alibaba.fastjson2 fastjson2 2.0.37 org.mapstruct mapstruct 1.5.5.Final org.mapstruct mapstruct-processor 1.5.5.Final org.projectlombok lombok-mapstruct-binding 0.2.0 com.h2database h2 2.1.214 com.baomidou mybatis-plus 3.5.3.1 com.zaxxer HikariCP 5.0.1 com.baomidou mybatis-plus-generator 3.5.3.1 org.freemarker freemarker 2.3.32 com.alibaba druid 1.2.18 cn.dev33 sa-token-spring-boot3-starter 1.34.0 cn.dev33 sa-token-jwt 1.34.0 hutool-jwt cn.hutool com.dtflys.forest forest-spring 1.5.32 com.dtflys.forest forest-core 1.5.32 org.flywaydb flyway-core 9.19.4 org.flywaydb flyway-mysql 9.19.4 com.unfbx chatgpt-java 1.0.8 org.slf4j slf4j-simple com.theokanning.openai-gpt3-java service 0.12.0 org.zalando logbook-spring-boot-starter 3.3.0 org.springframework spring-context-indexer 6.0.10 optional commons-beanutils commons-beanutils 1.9.4 org.ehcache ehcache 3.10.8 javax.cache cache-api 1.1.1 commons-io commons-io 2.7 com.alibaba easyexcel 3.3.2 com.deepoove poi-tl 1.10.5 com.itextpdf itext-asian 5.2.0 com.itextpdf itextpdf 5.5.13 org.apache.pdfbox pdfbox 2.0.24 com.auth0 java-jwt 4.4.0 com.github.vertical-blank sql-formatter 2.0.4 org.projectlombok lombok provided org.projectlombok lombok-mapstruct-binding provided org.apache.maven.plugins maven-compiler-plugin 3.11.0 -Amapstruct.disableBuilders=true org.codehaus.mojo flatten-maven-plugin 1.5.0 true oss flatten process-resources flatten flatten.clean clean clean org.apache.maven.plugins maven-surefire-plugin 3.1.2 /ai/chat2db/server/test/**/*.java /ai/chat2db/server/test/temp/**/*.java true ================================================ FILE: docker/Dockerfile ================================================ # Mirror based on jdk17 FROM openjdk:17 # Define the default location when entering the container and the working location for subsequent operations. WORKDIR /app # Copy the jar package in the current directory to the /app directory of the docker container ADD chat2db-server/chat2db-server-web-start/target/chat2db-server-web-start.jar chat2db-server-web-start.jar # Copy the lib package in the current directory to the /app/lib directory of the docker container # ADD chat2db-server/chat2db-server-web-start/target/lib lib # Let the current container expose 10824 EXPOSE 10824 # Run Jar ENTRYPOINT ["java","-Dloader.path=lib","-Dspring.profiles.active=release","-jar","chat2db-server-web-start.jar"] ================================================ FILE: docker/docker-build.sh ================================================ # First package it out chat2db-server/chat2db-server-start/target/chat2db-server-start.jar # Packing docker build -t chat2db/chat2db:test -f docker/Dockerfile . ================================================ FILE: docker/docker-compose-start.sh ================================================ docker-compose -f docker-compose.yml up -d ================================================ FILE: docker/docker-compose.yml ================================================ version: '3' services: chat2db: image: chat2db/chat2db:latest container_name: chat2db-latest volumes: - ~/.chat2db-docker:/root/.chat2db ports: - "10824:10824" ================================================ FILE: docker/docker-start.sh ================================================ docker run --name=chat2db -p 10824:10824 -v ~/.chat2db-docker:/root/.chat2db chat2db/chat2db:latest ================================================ FILE: docker/test/docker-compose.yml ================================================ version: '3' services: # Connection address:jdbc:mysql://localhost:3306 username:root password:ali_dbhub port:3306 Default database:ali_dbhub_test mysql: image: mysql:latest restart: always container_name: mysql-latest environment: MYSQL_ROOT_PASSWORD: ali_dbhub MYSQL_DATABASE: ali_dbhub_test TZ: Asia/Shanghai ports: - 3306:3306 # Connection address:jdbc:postgresql://localhost:5432/ali_dbhub_test, username:ali_dbhub, password:ali_dbhub port:5432 Default database:ali_dbhub_test postgres: image: postgres:12-alpine restart: always container_name: postgres-alpine environment: POSTGRES_USER: ali_dbhub POSTGRES_PASSWORD: ali_dbhub POSTGRES_DB: ali_dbhub_test TZ: Asia/Shanghai ports: - 5431:5431 # Connection address:jdbc:oracle:thin:@localhost:1521:XE, username:system, password:ali_dbhub # The default database XEPDB1 will start much faster oracle: image: gvenzl/oracle-xe:slim-faststart restart: always container_name: oracle-latest environment: ORACLE_DATABASE: XEPDB1 ORACLE_PASSWORD: ali_dbhub TZ: Asia/Shanghai ports: - 1521:1521 redis: image: redis:7 container_name: redis7 restart: always # Mount mapping, which allows data or configuration to be persisted volumes: #<local configuration file>: - $PWD/redis/redis.conf:/etc/redis/redis. conf:ro - $PWD/redis/data:/data - $PWD/redis/logs:/logs # command: redis-server/etc/redis/redis.conf ports: - 6379:6379 #service name # jdbc:mariadb://localhost:3303 ? user=root&password=ali_dbhub mariadb: image: mariadb:10.5.5 container_name: "mariadb1" restart: always environment: MYSQL_USER: "root" MYSQL_PASSWORD: "ali_dbhub" MYSQL_ROOT_PASSWORD: "ali_dbhub" TZ: "Asia/Shanghai" ports: - "3304:3304" clickhouse-server: # image:Specify the image, which can be the image name or image id. If the image does not exist locally, compose will try to pull the image. image: yandex/clickhouse-server # container_name:Specify the container name, which defaults to the format of project name_service name_serial number container_name: clickhouse # hostname:Specify container hostname hostname: clickhouse # networks:Configure the network the container is connected to, specifying the networks defined at the end of the file # ports:Expose port information in the format of host port:container port; when only the container port is specified, the host will randomly select the port, similar to docker run -p ports: - "8123:8123" - "9000:9000" - "9004:9004" # expose:The port is exposed but not mapped to the host, so the port cannot be accessed from the outside and can only be accessed and used within the container. expose: - 9009 # volumes:Data volume mounting path setting, similar to docker run --volumn=hostdir:containerDir, file permissions can also be specified ================================================ FILE: docker/test/redis/redis.conf ================================================ #Enable remote connection #bind 127.0.0.1 #Custom password requirepass 12345678 #Specify Redis listening port (default: 6379) port 6379 #The client closes the connection after being idle for a specified period of time (unit: seconds. 0: close this function) timeout 0 # If there is at least one write operation within 900s, execute bgsave for RDB persistence operation save 900 1 # Within 300s, if at least 10 keys are modified, perform persistence operation save 300 10 #Within 60s, if at least 10,000 keys have been modified, the persistence operation will be performed. save 60 10000 #Whether to compress data storage (default: yes. Redis uses LZ compression. If you want to save CPU time, you can turn off this option, but it will cause the database file to become huge) rdbcompression yes #Specify the local data file name (default: dump.rdb) dbfilename dump.rdb #Specify the local data file storage directory dir/data #Specify the log file location (if it is a relative path, redis will store the log in the specified dir directory) logfile "redis.log" ================================================ FILE: docker/test/start.sh ================================================ docker-compose -f docker-compose.yml up -d ================================================ FILE: docker/test/stop.sh ================================================ docker-compose -f docker-compose.yml down ================================================ FILE: document/git/git.sh ================================================ # https://www.zhangbj.com/p/1437.html # -r represents recursion # # git filter-branch --force --index-filter 'git rm -r --cached --ignore-unmatch chat2db-client/static' --prune-empty --tag-name-filter cat -- --all git push origin developing --force git push origin delete_git --force ================================================ FILE: document/sql/mysql.sql ================================================ CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID', `name` varchar(255) NOT NULL COMMENT '商品名称', `description` varchar(1000) NOT NULL COMMENT '商品描述', `price` decimal(10,2) NOT NULL COMMENT '商品单价', `category_id` int(11) NOT NULL COMMENT '所属类别ID', `brand_id` int(11) DEFAULT NULL COMMENT '品牌ID', `origin` varchar(255) DEFAULT NULL COMMENT '商品产地', `weight` decimal(10,2) DEFAULT NULL COMMENT '商品重量(kg)', `length` decimal(10,2) DEFAULT NULL COMMENT '商品长度(cm)', `width` decimal(10,2) DEFAULT NULL COMMENT '商品宽度(cm)', `height` decimal(10,2) DEFAULT NULL COMMENT '商品高度(cm)', `thumbnail` varchar(255) DEFAULT NULL COMMENT '商品缩略图URL', `image` varchar(1000) DEFAULT NULL COMMENT '商品图片URL', `is_sale` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0:下架,1:上架', `stock` int(11) NOT NULL COMMENT '商品库存', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `category_id` (`category_id`), KEY `brand_id` (`brand_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'; INSERT INTO product (name, description, price, category_id, brand_id, origin, weight, length, width, height, thumbnail, image, is_sale, stock, created_at, updated_at) SELECT CONCAT('商品', t1.n), CONCAT('这是商品', t1.n, '的描述'), ROUND(RAND() * 1000, 2), FLOOR(RAND() * 10) + 1, IF(RAND() < 0.5, NULL, FLOOR(RAND() * 10) + 1), CONCAT('产地', t1.n), ROUND(RAND() * 10, 2), ROUND(RAND() * 100, 2), ROUND(RAND() * 100, 2), ROUND(RAND() * 100, 2), CONCAT('http://example.com/thumbnail_', t1.n, '.jpg'), CONCAT('http://example.com/image_', t1.n, '.jpg'), IF(RAND() < 0.5, 0, 1), FLOOR(RAND() * 1000), NOW() - INTERVAL FLOOR(RAND() * 365) DAY, NOW() - INTERVAL FLOOR(RAND() * 365) DAY FROM (SELECT @rownum:=0) t0, (SELECT @rownum:=@rownum+1 AS n FROM information_schema.COLUMNS LIMIT 10000) t1 ; CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单ID', `user_id` int(11) NOT NULL COMMENT '用户ID', `total_price` decimal(10,2) NOT NULL COMMENT '订单总价', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; INSERT INTO `order` (user_id, total_price, created_at, updated_at) SELECT FLOOR(RAND() * 1000) + 1, ROUND(RAND() * 10000, 2), NOW() - INTERVAL FLOOR(RAND() * 365) DAY, NOW() - INTERVAL FLOOR(RAND() * 365) DAY FROM (SELECT @rownum:=0) t0, (SELECT @rownum:=@rownum+1 AS n FROM information_schema.COLUMNS LIMIT 10000) t1 ; CREATE TABLE `order_item` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单明细ID', `order_id` int(11) NOT NULL COMMENT '订单ID', `product_id` int(11) NOT NULL COMMENT '商品ID', `quantity` int(11) NOT NULL COMMENT '购买数量', `price` decimal(10,2) NOT NULL COMMENT '商品单价', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `order_id` (`order_id`), KEY `product_id` (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表'; INSERT INTO order_item (order_id, product_id, quantity, price, created_at, updated_at) SELECT FLOOR(RAND() * 10000) + 1, FLOOR(RAND() * 1000) + 1, FLOOR(RAND() * 10) + 1, ROUND(RAND() * 1000, 2), NOW() - INTERVAL FLOOR(RAND() * 365) DAY, NOW() - INTERVAL FLOOR(RAND() * 365) DAY FROM (SELECT @rownum:=0) t0, (SELECT @rownum:=@rownum+1 AS n FROM information_schema.COLUMNS LIMIT 10000) t1 ; ================================================ FILE: document/style/Alibaba_CodeStyle.xml ================================================ ================================================ FILE: script/local-client-build.sh ================================================ #!/bin/bash # JRE_DIR="${JAVA_HOME}/jre" JRE_DIR="~/Library/Java/JavaVirtualMachines/corretto-17.0.8.1/Contents/Home/" JRE_TARGET_DIR="chat2db-client/static/jre" CURRENT_ID="123" # Clean echo "Clean" rm -rf chat2db-client/static rm -rf chat2db-client/versions rm -rf chat2db-client/release # Use mkdir to create the directory, and use the -p parameter to ensure that no error is reported if the directory already exists mkdir -p "$JRE_TARGET_DIR" # Use the cp command to copy the contents of the JAVA_HOME directory to the target directory # -r Parameter means recursively copy the entire directory cp -r "${JRE_DIR}/" "$JRE_TARGET_DIR" chmod -R 777 "$JRE_TARGET_DIR" # Packaging backend code mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml mkdir -p chat2db-client/versions/99.0.${CURRENT_ID}/static echo -n 99.0.${CURRENT_ID} > chat2db-client/versions/version cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${CURRENT_ID}/static/ # Packaging front-end code cd chat2db-client yarn install yarn run build:web:desktop --app_port=10822 cp -r dist ./versions/99.0.${CURRENT_ID}/ # Packaged client yarn run build:main:prod -c.productName=Chat2DB-Test -c.extraMetadata.version=99.0.${CURRENT_ID} --mac --arm64