diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 756d257e..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,23 +0,0 @@ - name: Java CI - - on: [ push, pull_request ] - - jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Check out Git repository - uses: actions/checkout@main - - name: Install Java and Maven - uses: actions/setup-java@main - with: - java-version: '17' - distribution: 'adopt' - cache: 'maven' - # 安装各种数据库 用于测试 - - name: Docker Compose Action - uses: isbang/compose-action@v1.4.1 - with: - compose-file: "./docker/test/docker-compose.yml" - - name: Test with Maven - run: mvn test -B -Dmaven.test.skip=false -pl ali-dbhub-server-test -am -f ali-dbhub-server/pom.xml \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index dbc7a632..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,32 +0,0 @@ -#name: Test -# -#on: [ push, pull_request ] -# -#jobs: -# test: -# strategy: -# fail-fast: false -# matrix: -# include: -# - os: macos-latest -# arch: amd64 -# - os: ubuntu-latest -# runs-on: ${{ matrix.os }} -# steps: -# - name: Check out Git repository -# uses: actions/checkout@main -# -# # 安装jre Windows -# - name: Install Jre for Windows -# uses: actions/setup-java@main -# with: -# java-version: "17" -# distribution: "temurin" -# java-package: "jre" -# -# # java.security 开放tls1 Linux -# - name: Enable tls1 -# run: | -# ls ${{ env.JAVA_HOME }} -# ls ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ -# cat ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ADDITIONAL_LICENSE_INFO \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 447da056..fa42471b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,30 @@ - ⭐【New Features】The tree node operation menu supports right-clicking - ⭐【New Features】The tree node operation menu can copy the names of tables, fields, keys, indexes, and functions - ⭐【New Features】Edit table structure supports setting primary keys in columns -- ⚡️【Optimize】 +- ⭐【New Features】Edit data to support cell-level undo changes +- ⭐【New Features】 Edit data support right click operation + 1. Supports single-row replication of Insert, Update, table header fields, and row data + 2. Clone the selected row + 3. Replication of cell data is supported + 4. You can set the cell to Null or Default + 5. Row deletion is supported +- ⭐【New Features】History is added to the foldable panel on the right +- ⚡️【Optimize】Edit the table structure to add loading - 🐞【Fixed】Fixed table structure editing floating-point decimal Settings display exception **更新日志** - ⭐【新功能】树节点操作菜单支持右键唤出 - ⭐【新功能】树节点操作菜单支持复制表、字段、key、index、函数等名称 - ⭐【新功能】编辑表结构支持在列中设置主键 -- ⚡️【优化】 +- ⭐【新功能】编辑数据支持单元格级别撤销修改 +- ⭐【新功能】编辑数据支持右键操作 + 1. 支持单行复制 Insert、Update、表头字段、行数据 + 2. 支持克隆选中行 + 3. 支持复制单元格数据 + 4. 支持设置单元格为Null和Default + 5. 支持删除行 +- ⭐【新功能】右侧可折叠面板中增加历史记录 +- ⚡️【优化】编辑表结构添加loading - 🐞【修复】修复表结构编辑浮点数小数位设置显示异常 diff --git a/README.md b/README.md index 705e1696..e5adcc36 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Chat2DB supports connecting to the following databases: - SQLServer - SQLite - MariaDB -- ClickHouseare +- ClickHouse - DM - Presto - DB2 diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index c2802a2f..701a9b8b 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -10,13 +10,16 @@ ## 启动项目 + 强制使用 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 书写规范 +## TS书写规范 -1. 所有的 interface 与 type 必须已 I 开头 `interface IState { name: string }` // good `interface State { name: string }` // bad + 1. 所有的interface 与 type 必须已I开头 + `interface IState { name: string }` // good + `interface State { name: string }` // bad ## 如何在 js 与 css 中使用颜色 diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 550ca5f3..8da03ecf 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -1,5 +1,7 @@ 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'; @@ -9,8 +11,7 @@ import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import ExecuteSQL from '@/components/ExecuteSQL'; import { IEditTableInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; -import i18n from '@/i18n'; -import lodash from 'lodash'; +import LoadingContent from '@/components/Loading/LoadingContent'; interface IProps { dataSourceId: number; databaseName: string; @@ -93,6 +94,7 @@ export default memo((props: IProps) => { collations: [], indexTypes: [], }); + const [isLoading, setIsLoading] = useState(false); function changeTab(item: ITabItem) { setCurrentTab(item); @@ -103,7 +105,7 @@ export default memo((props: IProps) => { getTableDetails(); } getDatabaseFieldTypeList(); - }, []); + }, []) // 获取数据库字段类型列表 const getDatabaseFieldTypeList = () => { @@ -153,7 +155,7 @@ export default memo((props: IProps) => { indexTypes, }); }); - }; + } const getTableDetails = (myParams?: { tableNameProps?: string }) => { const { tableNameProps } = myParams || {}; @@ -166,13 +168,17 @@ export default memo((props: IProps) => { 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) { @@ -218,7 +224,7 @@ export default memo((props: IProps) => { }, }); } - }; + } return ( { databaseType, }} > -
+
{tabList.map((item) => { @@ -262,7 +268,8 @@ export default memo((props: IProps) => { ); })}
-
+
+ { 手动 - {syncTableModel === 0 ? ( + {/* {syncTableModel === 0 ? ( i18n('chat.input.syncTable.tips') ) : ( - <> - {i18n('chat.input.remain.tooltip')} - { + onSelectTables && onSelectTables(v); + }} + /> +
); }; @@ -108,7 +108,7 @@ const ChatInput = (props: IProps) => { )} - {i18n('chat.input.syncTable.tempTips')}} defaultOpen={!hasBubble} color={window._AppThemePack.colorBgBase} @@ -117,12 +117,13 @@ const ChatInput = (props: IProps) => { localStorage.setItem('syncTableBubble', 'true'); }} > -
- - - -
-
+ + */} +
+ + + +
{/* {props.aiType === AIType.CHAT2DBAI && ( diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index f0148d1f..21d9d6c9 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -160,7 +160,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const colors = { 'editor.lineHighlightBackground': colorPrimary + '14', // 当前行背景色 - 'editor.selectionBackground': colorPrimary + '20', // 选中文本的背景色 + 'editor.selectionBackground': colorPrimary + '50', // 选中文本的背景色 // 'editorLineNumber.foreground': colorPrimary, // 行号颜色 'editorLineNumber.activeForeground': colorPrimary, // 当前行号颜色 // 'editorCursor.foreground': colorPrimary, // 光标颜色 diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index e32d3d52..71570760 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -19,8 +19,9 @@ import { AIType } from '@/typings/ai'; import i18n from '@/i18n'; import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; -import styles from './index.less'; +import sqlService from '@/service/sql'; import indexedDB from '@/indexedDB'; +import styles from './index.less'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -60,14 +61,13 @@ interface IProps { value?: string; executeParams: { databaseName?: string; - dataSourceId?: number; + dataSourceId: number; type?: DatabaseTypeCode; consoleId?: number; schemaName?: string; consoleName?: string; status?: ConsoleStatus; }; - tableList?: ITreeNode[]; editorOptions?: IEditorOptions; aiModel: IAIState; dispatch: any; @@ -110,6 +110,8 @@ function Console(props: IProps, ref: ForwardedRef) { const timerRef = useRef(); const aiFetchIntervalRef = useRef(); const closeEventSource = useRef(); + const [tableNameList, setTableNameList] = useState([]); + // 上一次同步的console数据 const lastSyncConsole = useRef(defaultValue); const [saveStatus, setSaveStatus] = useState(executeParams.status || ConsoleStatus.DRAFT); @@ -196,6 +198,37 @@ function Console(props: IProps, ref: ForwardedRef) { } }, []); + // TODO: 暂时写在这里,后续去掉 + useEffect(() => { + if (!props.executeParams) { + return; + } + + if(!props.tables || props.tables.length === 0) { + setTableNameList([]); + setSelectedTables([]); + return + } + + + const { dataSourceId, databaseName, schemaName } = props.executeParams; + 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)); + } + }); + // debugger + }, [props.tables]); + function timingAutoSave(_status?: ConsoleStatus) { if (timerRef.current) { clearInterval(timerRef.current); @@ -221,16 +254,6 @@ function Console(props: IProps, ref: ForwardedRef) { }, 5000); } - const tableListName = useMemo(() => { - const tableList = (props.tables || []).map((t) => t.name); - - // 默认选中前八个 - // setSelectedTables(tableList.slice(0, 8)); - setSelectedTables(tableList.slice(0, 1)); - - return tableList; - }, [props.tables]); - const handleApiKeyEmptyOrGetQrCode = async (shouldPoll?: boolean) => { setIsLoading(true); try { @@ -375,8 +398,8 @@ function Console(props: IProps, ref: ForwardedRef) { } } catch (error) { setIsLoading(false); - setIsAiDrawerLoading(false); setIsStream(false); + setIsAiDrawerLoading(false); closeEventSource.current(); } }; @@ -384,6 +407,7 @@ function Console(props: IProps, ref: ForwardedRef) { const handleError = (error: any) => { console.error('Error:', error); setIsLoading(false); + setIsStream(false); closeEventSource.current(); }; @@ -511,7 +535,7 @@ function Console(props: IProps, ref: ForwardedRef) { aiType={aiModel.aiConfig?.aiSqlSource} remainingUse={aiModel.remainingUse} remainingBtnLoading={props.remainingBtnLoading} - tables={tableListName} + tables={tableNameList} onPressEnter={(value: string) => { // editorRef?.current?.toFocus(); handleAiChat(value, IPromptType.NL_2_SQL); diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 6ad189e4..022fe1e6 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -9,9 +9,9 @@ if (__ENV__ === 'local') { /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_201093ssteq.woff2?t=1698848610675') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_201093ssteq.woff?t=1698848610675') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_201093ssteq.ttf?t=1698848610675') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_0wg41yfqyqoh.woff2?t=1699018081072') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_0wg41yfqyqoh.woff?t=1699018081072') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_0wg41yfqyqoh.ttf?t=1699018081072') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/MenuLabel/index.less b/chat2db-client/src/components/MenuLabel/index.less new file mode 100644 index 00000000..cbe94af9 --- /dev/null +++ b/chat2db-client/src/components/MenuLabel/index.less @@ -0,0 +1,20 @@ +@import '../../styles/var.less'; + +.menuLabel { + display: flex; + align-items: center; + .menuLabelIconBox{ + width: 22px; + display: flex; + align-items: center; + } + .menuLabelIcon { + } + .menuLabelTitle { + } + :global { + .ant-dropdown { + z-index: 1080; + } + } +} diff --git a/chat2db-client/src/components/MenuLabel/index.tsx b/chat2db-client/src/components/MenuLabel/index.tsx new file mode 100644 index 00000000..306cbcd8 --- /dev/null +++ b/chat2db-client/src/components/MenuLabel/index.tsx @@ -0,0 +1,20 @@ +import React, { memo } from 'react'; +import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import styles from './index.less'; + +interface IProps { + className?: string; + icon?: string; + label: string; +} + +export default memo((props) => { + const { className, icon, label } = props; + return
+
+ {icon && } +
+
{label}
+
; +}); diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index 13ac703e..b69d0ece 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -117,6 +117,7 @@ function MyNotification() { onCancel={() => { setOpen(false); }} + zIndex={99999} >
{props?.errorDetail}
diff --git a/chat2db-client/src/components/Output/index.tsx b/chat2db-client/src/components/Output/index.tsx index d915220d..973fb276 100644 --- a/chat2db-client/src/components/Output/index.tsx +++ b/chat2db-client/src/components/Output/index.tsx @@ -72,9 +72,9 @@ export default memo((props) => {
- [2023-10-15 14:50:29] - {!!item.operationRows && {item.operationRows} rows} - {!!item.useTime && affected in {item.useTime} ms} + [{item.gmtCreate}] + {/* {!!item.operationRows && {item.operationRows} rows} */} + {!!item.useTime && {i18n('common.text.executionTime',item.useTime)}}
{nameList.filter((name) => name).join(' > ')}
diff --git a/chat2db-client/src/components/SearchResult/RightClickMenu/index.less b/chat2db-client/src/components/SearchResult/RightClickMenu/index.less new file mode 100644 index 00000000..2e57988d --- /dev/null +++ b/chat2db-client/src/components/SearchResult/RightClickMenu/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/SearchResult/RightClickMenu/index.tsx b/chat2db-client/src/components/SearchResult/RightClickMenu/index.tsx new file mode 100644 index 00000000..00095979 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/RightClickMenu/index.tsx @@ -0,0 +1,100 @@ +import React, { memo, useMemo } from 'react'; +import { Dropdown } from 'antd'; +import i18n from '@/i18n'; +import MenuLabel from '@/components/MenuLabel'; + +interface IProps { + className?: string; + children?: React.ReactNode; + menuList: IMenu[] +} + +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', +} + +export default memo((props) => { + const { children, menuList } = props; + 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, + }, + } + + const items = useMemo(()=>{ + return menuList.map((menu) => { + return { + ...allSupportedMenus[menu.key], + onClick: menu.callback, + children: menu.children?.map((child,index) => { + if (child.hide) return null; + return { + ...allSupportedMenus[menu.key]['children'][index], + onClick: child.callback, + } + }) + } + }) + }, [menuList]) + + return + {children} + ; +}); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 0a27d81d..9f721ddc 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -23,9 +23,6 @@ flex-direction: column; :global { .art-table { - table colgroup col { - min-width: 120px; - } table colgroup col:nth-of-type(1) { min-width: 60px; } @@ -80,6 +77,11 @@ .art-table-header-cell { padding: 0px 4px; } + .art-table-header-row .art-table-header-cell:nth-of-type(1){ + .resize-handle{ + display: none; + } + } } } @@ -191,7 +193,7 @@ } .tableItem { - height: 30px; + height: 31px; cursor: pointer; display: flex; align-items: center; @@ -202,8 +204,8 @@ background-color: var(--color-bg-base); .tableItemContent { - max-height: 30px; - line-height: 30px; + max-height: 31px; + line-height: 31px; flex: 1; width: 100%; overflow: hidden; @@ -235,10 +237,20 @@ background-color: var(--color-success-bg); } } -.tableItemEditing { - background-color: var(--color-primary-bg-hover); +.tableItemHighlight { + background-color: var(--color-bg-subtle); .tableHoverBox { - background-color: var(--color-primary-bg-hover); + background-color: var(--color-bg-subtle); + } +} +.tableItemFocus { + background-color: var(--color-primary-hover); + color: var(--color-bg-base); + .tableHoverBox { + background-color: var(--color-primary-hover); + } + .cellValueNull { + color: var(--color-bg-base); } } .tableItemSuccess { @@ -261,6 +273,7 @@ .tableHoverBox { position: sticky; + height: 31px; top: 0px; bottom: 0px; right: 0px; @@ -274,10 +287,6 @@ font-size: 15px; margin: 0px 2px; } - - i:hover { - color: var(--color-primary); - } } .pagination { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 75fdbb3a..dde387f9 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -11,12 +11,13 @@ import { IManageResultData, IResultConfig } from '@/typings/database'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { compareStrings } from '@/utils/sort'; import { DownOutlined } from '@ant-design/icons'; -import { copy } from '@/utils'; +import { copy, tableCopy } from '@/utils'; import Iconfont from '../../Iconfont'; import StateIndicator from '../../StateIndicator'; import MonacoEditor from '../../Console/MonacoEditor'; import MyPagination from '../Pagination'; import StatusBar from '../StatusBar'; +import RightClickMenu, { AllSupportedMenusType } from '../RightClickMenu'; import styles from './index.less'; import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; @@ -43,14 +44,18 @@ interface IUpdateData { rowNo: string; } +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: var(--color-hover-bg); - --header-hover-bgcolor: var(--color-hover-bg); - --highlight-bgcolor: var(--color-hover-bg); - --header-highlight-bgcolor: var(--color-hover-bg); + --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; @@ -85,8 +90,8 @@ export default function TableBox(props: ITableProps) { // DataList不带列标识的表数据 // 保存原始的表数据,用于对比新老数据看是否有变化 const [oldDataList, setOldDataList] = useState([]); - // 正在编辑的单元格的坐标 - const [editingCell, setEditingCell] = useState<[string, string] | null>(null); + // 当前聚焦的单元格的坐标,以及是否正在编辑,为false时,代表正在聚焦,但是没有编辑 + const [editingCell, setEditingCell] = useState<[number, string, boolean] | null>(null); // input受控的正在编辑的数据 const [editingData, setEditingData] = useState(''); // 当前选中的行号 @@ -109,6 +114,10 @@ export default function TableBox(props: ITableProps) { 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 = { @@ -256,28 +265,41 @@ export default function TableBox(props: ITableProps) { setViewTableCellData(null); } - const handleDoubleClickTableItem = (colIndex, rowNo, value) => { - if (!queryResultData.canEdit) { + const handleClickTableItem = (colIndex, rowNo, value, isEditing) => { + // 1. 如果当前单元格正在编辑,则不需要再次编辑 + // 2. 如果当前单元格正在编辑,则不需要聚焦 + if (editingCell?.[0] === colIndex && editingCell?.[1] === rowNo && editingCell?.[2]) { return; } + // 聚焦当前单元格,取消对于行的聚焦 + setCurOperationRowNo(null); + // 当前聚焦或者编辑的单元格的数据 setEditingData(value); - setEditingCell([colIndex, rowNo]); - setTimeout(() => { - editDataInputRef?.current?.focus(); - }, 0); + // 当前聚焦或者编辑的单元格的坐标 + setEditingCell([colIndex, rowNo, isEditing]); + // 如果是编辑状态,则需要聚焦到input + if (isEditing) { + setTimeout(() => { + editDataInputRef?.current?.focus(); + }, 0); + } }; // 编辑数据失焦 - const editDataOnBlur = () => { - setEditingCell(null); - setEditingData(''); + const editDataOnBlur = (type: 'blur' | 'set', _editingData?: string | null) => { + if (type === 'blur') { + setEditingCell(null); + setEditingData(''); + } + // 写入的数据 + const value: any = type === 'blur' ? editingData : _editingData; const [colIndex, rowNo] = editingCell!; let oldRowDataList: string[] = []; let newRowDataList: string[] = []; const newTableData = lodash.cloneDeep(tableData); newTableData.forEach((item) => { if (item[`${preCode}0No.`] === rowNo) { - item[`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; + item[`${preCode}${colIndex}${columns[colIndex].name}`] = value; newRowDataList = Object.keys(item).map((i) => item[i]); } }); @@ -330,6 +352,8 @@ export default function TableBox(props: ITableProps) { const renderTableCellValue = (value) => { if (value === null) { return {''}; + } else if (value === USER_FILLED_VALUE.DEFAULT) { + return {''}; } else if (!value) { // 如果为空需要展位 return ; @@ -342,9 +366,24 @@ export default function TableBox(props: ITableProps) { const tableCellStyle = (value, colIndex, rowNo) => { // 单元格的基础样式 const styleList = [styles.tableItem]; - // 当前单元格所在的行被选中了 + // 如果当前行中的单元格正在聚焦或编辑 + if (editingCell?.[1] === rowNo) { + // 设置正在编辑或聚焦的单元格所在行的样式为高亮 + styleList.push(styles.tableItemHighlight); + // 精确找到列,设置正在编辑或聚焦的单元格的样式为Focus + if (editingCell?.[0] === colIndex && !editingCell?.[2]) { + styleList.push(styles.tableItemFocus); + } + return classnames(...styleList); + } + // 当前单元格所在的行被选中了(行聚焦) if (rowNo === curOperationRowNo) { - styleList.push(styles.tableItemEditing); + // No列的高亮只需要用tableItemHighlight不需要用tableItemFocus + if (colIndex === 0) { + styleList.push(styles.tableItemHighlight); + } else { + styleList.push(styles.tableItemFocus); + } return classnames(...styleList); } // 新添加的行 @@ -387,100 +426,6 @@ export default function TableBox(props: ITableProps) { return newTableData; }; - // 表格的列配置 - 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; - if (isNumericalOrder) { - return { - code: `${preCode}${colIndex}No.`, - name: 'No.', - key: name, - lock: true, - width: 60, - features: { sortable: compareStrings }, - render: (value: any, rowData) => { - const rowNo = rowData[`${preCode}0No.`]; - return ( -
-
{value}
-
- ); - }, - }; - } - return { - code: `${preCode}${colIndex}${name}`, - name: name, - key: name, - render: (value: any, rowData) => { - const rowNo = rowData[`${preCode}0No.`]; - return ( -
- {editingCell?.join(',') === `${colIndex},${rowNo}` ? ( - { - setEditingData(e.target.value); - }} - onBlur={editDataOnBlur} - /> - ) : ( - <> -
{renderTableCellValue(value)}
-
- - -
- - )} -
- ); - }, - // 如果是数字类型,因为后端返回的都是字符串,所以需要调用字符串对比函数来判断 - features: { sortable: isNumber ? compareStrings : true }, - }; - }); - }, [queryResultData.headerList, editingCell, editingData, curOperationRowNo, oldDataList]); - - // 表格渲染的配置 - const pipeline = useTablePipeline() - .input({ dataSource: tableData, columns }) - .use( - features.sort({ - mode: 'single', - defaultSorts, - highlightColumnWhenActive: true, - // sorts, - // onChangeSorts, - }), - ) - .use( - features.columnResize({ - fallbackSize: 120, - handleActiveBackground: `var(--color-primary-bg-hover)`, - }), - ); - // .use( - // features.columnResize({ - // fallbackSize: 120, - // minSize: 60, - // maxSize: 1080, - // }), - // ); - const onPageNoChange = (pageNo: number) => { const config = { ...paginationConfig, pageNo }; setPaginationConfig(config); @@ -504,35 +449,60 @@ export default function TableBox(props: ITableProps) { // 处理撤销 const handleRevoke = () => { - setUpdateData(updateData.filter((item) => item.rowNo !== curOperationRowNo)); - setTableData( - tableData.map((item) => - item[`${preCode}0No.`] === curOperationRowNo - ? oldTableData.find((i) => i[`${preCode}0No.`] === curOperationRowNo)! - : item, - ), - ); + // 聚焦行撤销 + if (curOperationRowNo) { + setUpdateData(updateData.filter((item) => item.rowNo !== curOperationRowNo)); + const oldData = oldTableData.find((i) => i[`${preCode}0No.`] === curOperationRowNo)!; + const _tableData = tableData.map((item) => (item[`${preCode}0No.`] === curOperationRowNo ? oldData : item)); + setTableData(_tableData); + setCurOperationRowNo(null); + return; + } + // 聚焦单元格撤销 + if (editingCell && editingCell[2] === false) { + const oldRowDataList = oldDataList.find((item) => item[0] === editingCell[1]); + const oldData = oldRowDataList?.[editingCell[0]]; + const _tableData = tableData.map((item) => { + if (item[`${preCode}0No.`] === editingCell[1]) { + item[`${preCode}${editingCell[0]}${columns[editingCell[0]].name}`] = oldData || ''; + } + return item; + }); + + // 如果撤销后这一行的数据和原始数据一样,则删除这条更新记录 + const newRowTableData = _tableData.find((item) => item[`${preCode}0No.`] === editingCell[1])!; + const newRowDataList = Object.keys(newRowTableData).map((item) => newRowTableData[item]); + if (lodash.isEqual(newRowDataList, oldRowDataList)) { + setUpdateData(updateData.filter((item) => item.rowNo !== editingCell[1])); + } + + setTableData(_tableData); + } }; // 处理创建数据 - const handleCreateData = () => { - // 如果加的这行数据是删除过的,则恢复 - const index = updateData.findIndex((item) => item.rowNo === curOperationRowNo && item.type === CRUD.DELETE); - if (index !== -1) { - updateData.splice(index, 1); - setUpdateData([...updateData]); - return; - } + const handleCreateData = (_newData?: any) => { // 正常的新增 const newTableData = lodash.cloneDeep(tableData); - const newData = {}; - columns.forEach((t, i) => { - if (t.name === 'No.') { - newData[`${preCode}${i}${t.name}`] = (newTableData.length + 1).toString(); - } else { - newData[`${preCode}${i}${t.name}`] = null; - } - }); + let newData = {}; + if (_newData) { + newData = _newData; + } else { + columns.forEach((t, i) => { + if (t.name === 'No.') { + newData[`${preCode}${i}${t.name}`] = (newTableData.length + 1).toString(); + } else { + // 判断是否有默认值 + const hasDefaultValue = + queryResultData.headerList.find((item) => item.name === t.name)?.defaultValue !== null; + if (hasDefaultValue) { + newData[`${preCode}${i}${t.name}`] = USER_FILLED_VALUE.DEFAULT; + return; + } + newData[`${preCode}${i}${t.name}`] = null; + } + }); + } newTableData.push(newData); setTableData(newTableData); setUpdateData([ @@ -543,6 +513,7 @@ export default function TableBox(props: ITableProps) { rowNo: newTableData.length.toString(), }, ]); + setCurOperationRowNo(newTableData.length.toString()); // 新增一条数据,tableBox需要滚动到最下方 setTimeout(() => { @@ -550,6 +521,19 @@ export default function TableBox(props: ITableProps) { }, 0); }; + // // 获取tableBoxRef的宽度 + // const getTableBoxRefWidth = () => { + // setTableBoxWidth(tableBoxRef.current?.clientWidth || 0); + // }; + + // useEffect(()=>{ + // window.addEventListener('resize', getTableBoxRefWidth); + // getTableBoxRefWidth(); + // return ()=>{ + // window.removeEventListener('resize', getTableBoxRefWidth); + // } + // },[]) + // 处理删除数据 const handleDeleteData = () => { if (curOperationRowNo === null) { @@ -562,6 +546,7 @@ export default function TableBox(props: ITableProps) { updateData.splice(index, 1); setUpdateData([...updateData]); setTableData(tableData.filter((item) => item[`${preCode}0No.`] !== curOperationRowNo)); + setCurOperationRowNo(null); return; } @@ -613,7 +598,7 @@ export default function TableBox(props: ITableProps) { }; // 获取更新数据的sql - const getExecuteUpdateSql = () => { + const getExecuteUpdateSql = (_updateData?: any) => { return new Promise((resolve) => { const params = { databaseName: props.executeSqlParams?.databaseName, @@ -622,7 +607,7 @@ export default function TableBox(props: ITableProps) { type: props.executeSqlParams?.databaseType, tableName: queryResultData.tableName, headerList: queryResultData.headerList, - operations: updateData, + operations: _updateData || updateData, }; sqlService.getExecuteUpdateSql(params).then((res) => { resolve(res || ''); @@ -673,38 +658,6 @@ export default function TableBox(props: ITableProps) { setUpdateData([]); }); }; - // 不同状态下的表格行样式 - // const tableRowStyle = (rowNo: string) => { - // // 如果是当前操作的行 - // if (rowNo === curOperationRowNo) { - // return { - // '--hover-bgcolor': 'transparent', - // '--bgcolor': 'transparent', - // background: 'var(--color-primary-bg-hover)', - // }; - // } - // // 如果是删除过的行 - // const index = updateData.findIndex((item) => item.rowNo === rowNo && item.type === CRUD.DELETE); - // if (index !== -1) { - // return { - // '--hover-bgcolor': 'transparent', - // '--bgcolor': 'transparent', - // background: 'var(--color-error-bg)', - // }; - // } - // // 如果是新增的行 - // const index2 = updateData.findIndex((item) => { - // return item.rowNo === rowNo && item.type === CRUD.CREATE; - // }); - // if (index2 !== -1) { - // return { - // '--hover-bgcolor': 'transparent', - // '--bgcolor': 'transparent', - // background: 'var(--color-success-bg)', - // }; - // } - // return {}; - // }; // sql执行成功后的回调 const executeSuccessCallBack = () => { @@ -716,17 +669,285 @@ export default function TableBox(props: ITableProps) { // 撤销按钮是否可用 const revokeDisableBarState = useMemo(() => { - if (!curOperationRowNo) { - return true; + // 如果有聚焦的行,但是没有操作过的数据,则不可用 + if (curOperationRowNo) { + return ( + updateData.findIndex( + (item) => + (item.rowNo === curOperationRowNo && item.type === CRUD.UPDATE) || + (item.rowNo === curOperationRowNo && item.type === CRUD.DELETE), + ) === -1 + ); } - return ( - updateData.findIndex( - (item) => - (item.rowNo === curOperationRowNo && item.type === CRUD.UPDATE) || - (item.rowNo === curOperationRowNo && item.type === CRUD.DELETE), - ) === -1 + // 如果有聚焦的单元格 + 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 copyRow = { + key: AllSupportedMenusType.CopyRow, + children: [ + { + callback: () => { + const newRowData = tableData.find((item) => item[`${preCode}0No.`] === curOperationRowNo)!; + const newRowDataList = Object.keys(newRowData).map((item) => newRowData[item]); + const _updateData = { + type: CRUD.CREATE, + dataList: newRowDataList, + rowNo: (tableData.length + 1).toString(), + }; + getExecuteUpdateSql([_updateData]).then((res) => { + copy(res); + }); + }, + hide: !queryResultData.canEdit, + }, + { + callback: () => { + const newRowData = tableData.find((item) => item[`${preCode}0No.`] === curOperationRowNo)!; + const newRowDataList = Object.keys(newRowData).map((item) => newRowData[item]); + const _updateData = { + type: CRUD.UPDATE_COPY, + dataList: newRowDataList, + rowNo: (tableData.length + 1).toString(), + }; + getExecuteUpdateSql([_updateData]).then((res) => { + copy(res); + }); + }, + hide: !queryResultData.canEdit, + }, + // 复制当前行的数据 + { + callback: () => { + const newRowData = tableData.find((item) => item[`${preCode}0No.`] === curOperationRowNo)!; + const newRowDataList = Object.keys(newRowData).map((item) => newRowData[item]); + // 去掉No列 + newRowDataList.splice(0, 1); + tableCopy([newRowDataList]); + }, + }, + // 复制表头 + { + callback: () => { + const headerList = queryResultData.headerList.map((item) => item.name); + // 去掉No列 + headerList.splice(0, 1); + tableCopy([headerList]); + }, + }, + // 复制表头和当前行的数据 + { + callback: () => { + const headerList = queryResultData.headerList.map((item) => item.name); + const newRowData = tableData.find((item) => item[`${preCode}0No.`] === curOperationRowNo)!; + const newRowDataList = Object.keys(newRowData).map((item) => newRowData[item]); + // 去掉No列 + headerList.splice(0, 1); + const array2D = [headerList, newRowDataList]; + tableCopy(array2D); + }, + }, + ], + }; + + const cloneRow = { + key: AllSupportedMenusType.CloneRow, + callback: () => { + const newTableData = lodash.cloneDeep(tableData); + const newRowData = newTableData.find((item) => item[`${preCode}0No.`] === curOperationRowNo)!; + newRowData[`${preCode}0No.`] = (newTableData.length + 1).toString(); + handleCreateData(newRowData); + }, + }; + + const deleteRow = { + key: AllSupportedMenusType.DeleteRow, + callback: handleDeleteData, + }; + + const copyCell = { + key: AllSupportedMenusType.CopyCell, + callback: () => { + copy(editingData); + }, + }; + + const setDefault = { + key: AllSupportedMenusType.SetDefault, + callback: () => { + editDataOnBlur('set', USER_FILLED_VALUE.DEFAULT); + }, + }; + + const setNull = { + key: AllSupportedMenusType.SetNull, + callback: () => { + editDataOnBlur('set', null); + }, + }; + + // 表格的列配置 + 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; + if (isNumericalOrder) { + return { + code: `${preCode}${colIndex}No.`, + name: 'No.', + title:
, + key: name, + lock: true, + // features: { sortable: compareStrings }, + render: (value: any, rowData) => { + console.log(value, rowData); + const rowNo = rowData[`${preCode}0No.`]; + let rowRightClickMenu = [copyRow, cloneRow, deleteRow]; + // 如果当前数据不可编辑,则不显示cloneRow和deleteRow + if (!queryResultData.canEdit) { + rowRightClickMenu = rowRightClickMenu.filter( + (i) => i.key !== AllSupportedMenusType.CloneRow && i.key !== AllSupportedMenusType.DeleteRow, + ); + } + return ( + +
{ + setEditingCell(null); + setCurOperationRowNo(rowNo); + }} + // 右键 + onContextMenu={() => { + setEditingCell(null); + setCurOperationRowNo(rowNo); + }} + className={tableCellStyle(value, colIndex, rowNo)} + > +
{value}
+
+
+ ); + }, + }; + } + + return { + code: `${preCode}${colIndex}${name}`, + name: name, + key: name, + // title:
{name}
, + render: (value: any, rowData) => { + const rowNo = rowData[`${preCode}0No.`]; + let cellRightClickMenu = [copyCell, copyRow, cloneRow, setNull, setDefault, deleteRow]; + // 判断是否有默认值,如果没有默认值,则不显示设置默认值的菜单 + const hasDefaultValue = queryResultData.headerList.find((i) => i.name === name)?.defaultValue !== null; + if (!hasDefaultValue) { + cellRightClickMenu = cellRightClickMenu.filter((i) => i.key !== AllSupportedMenusType.SetDefault); + } + // 如果当前数据不可编辑,则不显示cloneRow和deleteRow + if (!queryResultData.canEdit) { + cellRightClickMenu = cellRightClickMenu.filter( + (i) => + i.key !== AllSupportedMenusType.CloneRow && + i.key !== AllSupportedMenusType.DeleteRow && + i.key !== AllSupportedMenusType.SetNull, + ); + } + return ( + +
+ {editingCell?.[0] === colIndex && editingCell?.[1] === rowNo && editingCell?.[2] ? ( + { + setEditingData(e.target.value); + }} + onBlur={() => { + editDataOnBlur('blur'); + }} + /> + ) : ( + <> +
{renderTableCellValue(value)}
+
+ + +
+ + )} +
+
+ ); + }, + // 如果是数字类型,因为后端返回的都是字符串,所以需要调用字符串对比函数来判断 + features: { sortable: isNumber ? compareStrings : true }, + }; + }); + }, [queryResultData.headerList, editingCell, editingData, curOperationRowNo, oldDataList]); + + // 表格渲染的配置 + 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) => { + console.log(sizes); + sizes[0] = 0; + setColumnResize(sizes); + }, + }), ); - }, [curOperationRowNo, updateData]); + // .use( + // features.columnResize({ + // fallbackSize: 120, + // handleActiveBackground: `var(--color-primary-bg-hover)`, + // }), + // ); + // .use( + // features.columnResize({ + // fallbackSize: 120, + // minSize: 60, + // maxSize: 1080, + // }), + // ); const renderContent = () => { const bottomStatus = ( @@ -770,14 +991,18 @@ export default function TableBox(props: ITableProps) {
{queryResultData.canEdit && (
+ {/* 新增行 */}
{ + handleCreateData(); + }} className={classnames(styles.createDataBar, styles.editTableDataBarItem)} >
+ {/* 删除行 */}
+ {/* 撤销 */}
+ {/* 查看更改sql */}
+ {/* 提交 */}
- +
)}
- + {i18n('common.text.export')} @@ -845,15 +1073,6 @@ export default function TableBox(props: ITableProps) { components={{ EmptyContent: () =>

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

}} isStickyHead stickyTop={31} - getRowProps={(record) => { - const rowNo = record[`${preCode}0No.`]; - return { - // style: tableRowStyle(rowNo), - onClick() { - setCurOperationRowNo(rowNo); - }, - }; - }} {...pipeline.getProps()} /> diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 9ab8b11c..c76efdf7 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -7,6 +7,7 @@ import React, { forwardRef, ForwardedRef, useImperativeHandle, + Fragment, } from 'react'; import classnames from 'classnames'; import Tabs from '@/components/Tabs'; @@ -61,7 +62,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = * 执行SQL * @param sql */ - const handleExecuteSQL = async (_sql: string) => { + const handleExecuteSQL = (_sql: string) => { setTableLoading(true); const executeSQLParams: IExecuteSqlParams = { @@ -72,17 +73,19 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = controllerRef.current = new AbortController(); // 获取当前SQL的查询结果 - let sqlResult = await sqlServer.executeSql(executeSQLParams, { + sqlServer.executeSql(executeSQLParams, { signal: controllerRef.current.signal, + }).then((res) => { + const sqlResult = res.map((_res) => ({ + ..._res, + uuid: uuidV4(), + })); + + setResultDataList(sqlResult); + }) + .finally(() => { + setTableLoading(false); }); - - sqlResult = sqlResult.map((res) => ({ - ...res, - uuid: uuidV4(), - })); - - setResultDataList(sqlResult); - setTableLoading(false); }; const onChange = useCallback(() => { @@ -118,7 +121,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = ); } return ( - <> + {queryResultData.success ? ( renderSuccessResult() ) : ( @@ -129,7 +132,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) = text={queryResultData.message} /> )} - + ); }; diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 2deab7da..6678bb8b 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState, useRef } from 'react'; +import React, { memo, useEffect, useState, useRef, Fragment } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import styles from './index.less'; diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 1b981598..664ebcff 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -44,4 +44,5 @@ export enum CRUD { READ = 'READ', UPDATE = 'UPDATE', DELETE = 'DELETE', + UPDATE_COPY = 'UPDATE_COPY', } diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index e8e04e02..f4c1611c 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -102,4 +102,15 @@ export default { 'common.label.name': 'Name', 'common.title.create': 'Create', 'common.title.executiveLogging': 'Executive logging', -}; + 'common.text.executionTime': 'Affected in {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', +}; diff --git a/chat2db-client/src/i18n/en-us/sqlEditor.ts b/chat2db-client/src/i18n/en-us/sqlEditor.ts index cea9e951..c65bfb3b 100644 --- a/chat2db-client/src/i18n/en-us/sqlEditor.ts +++ b/chat2db-client/src/i18n/en-us/sqlEditor.ts @@ -2,6 +2,7 @@ export default { 'sqlEditor.text.keyword': 'Keyword', 'sqlEditor.text.function': 'Function', 'sqlEditor.text.tableName': 'TableName', + 'sqlEditor.text.viewName': 'ViewName', 'sqlEditor.text.fieldName': 'FieldName', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 6752ca26..553a49fa 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -101,4 +101,16 @@ export default { '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': '设置为默认值', + }; diff --git a/chat2db-client/src/i18n/zh-cn/sqlEditor.ts b/chat2db-client/src/i18n/zh-cn/sqlEditor.ts index 27207f6a..672fc3d0 100644 --- a/chat2db-client/src/i18n/zh-cn/sqlEditor.ts +++ b/chat2db-client/src/i18n/zh-cn/sqlEditor.ts @@ -2,5 +2,6 @@ export default { 'sqlEditor.text.keyword': '关键词', 'sqlEditor.text.function': '函数', 'sqlEditor.text.tableName': '表名', + 'sqlEditor.text.viewName': '视图名', 'sqlEditor.text.fieldName': '字段名', }; diff --git a/chat2db-client/src/layouts/index.less b/chat2db-client/src/layouts/index.less index b73542a8..588594ec 100644 --- a/chat2db-client/src/layouts/index.less +++ b/chat2db-client/src/layouts/index.less @@ -4,29 +4,39 @@ :global { #root { height: 100%; - .codicon-symbol-text:before{ + + .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='); + } } } @@ -75,12 +85,14 @@ display: flex; justify-content: center; align-items: center; + &:hover { i { color: var(--color-primary); } } + i { font-size: 16px; } -} +} \ No newline at end of file diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index cebfbd40..cfe8a954 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -18,7 +18,7 @@ function createWindow() { minHeight: 720, show: false, webPreferences: { - webSercurity: false, + webSecurity: false, nodeIntegration: true, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 7efa62f9..f654e88c 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -193,16 +193,21 @@ function TreeNodeRightClick(props: IProps) { } function refresh() { - // data.children = []; setIsLoading(true); - getChildren?.({ - ...data, + const params = { ...data.extraParams, + extraParams:{ + ...data.extraParams + }, refresh: true, - }).then((res) => { - data.children = res; + } + getChildren?.(params).then((res) => { + data.children = res as any; + console.log(data) + }) + .finally(() => { setIsLoading(false); - }); + }) } function openEditTableData() { @@ -290,24 +295,22 @@ function TreeNodeRightClick(props: IProps) { <> {modelDom} {notificationDom} - {!!dropdowns.length && ( - - {children || ( -
- -
- )} -
- )} + + {children || ( +
+ +
+ )} +
{ const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; treeNodeConfig .getChildren?.({ - ...data, - ...(data.extraParams || {}), + ...data.extraParams, + extraParams:{ + ...data.extraParams + }, + // ...data, + // ...(data.extraParams || {}), }) .then((res) => { if (res.length) { @@ -123,7 +127,7 @@ const TreeNode = (props: TreeNodeIProps) => { // } } }) - .catch((error) => { + .catch(() => { setIsLoading(false); }); } @@ -191,7 +195,6 @@ const TreeNode = (props: TreeNodeIProps) => { handleClick(data); } } - return show ? ( <> { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { connectionService .getDBList(params) @@ -124,7 +126,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { name: t.name, treeNodeType: TreeNodeType.DATABASE, extraParams: { - ...params.extraParams, + ..._extraParams, databaseName: t.name, }, }; @@ -141,7 +143,9 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, [TreeNodeType.DATABASE]: { icon: '\ue62c', - getChildren: (params: ISchemaParams) => { + getChildren: (params) => { + // const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[], b?: any) => void, j) => { connectionService .getSchemaList(params) @@ -155,17 +159,6 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }; }); r(data); - // if (data.length) { - // } else { - // let data = [ - // { - // key: params.databaseName + 'tables', - // name: 'tables', - // treeNodeType: TreeNodeType.TABLES, - // } - // ] - // r(data, 'custom'); - // } }) .catch((error) => { j(); @@ -179,7 +172,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue696', getChildren: (parentData: ITreeNode) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - let data = [ + const data = [ { key: parentData.name + 'tables', name: 'tables', @@ -195,6 +188,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TABLES]: { icon: '\ueac5', getChildren: (params, options) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r, j) => { mysqlServer .getTableList(params, options) @@ -207,7 +202,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { pinned: t.pinned, comment: t.comment, extraParams: { - ...params.extraParams, + ..._extraParams, tableName: t.name, }, }; @@ -267,6 +262,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.VIEWS]: { icon: '\ue70c', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getViewList(params) @@ -279,7 +276,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { pinned: t.pinned, comment: t.comment, extraParams: { - ...params.extraParams, + ..._extraParams, tableName: t.name, }, }; @@ -296,6 +293,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.FUNCTIONS]: { icon: '\ue76a', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getFunctionList(params) @@ -309,7 +308,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { comment: t.comment, isLeaf: true, extraParams: { - ...params.extraParams, + ..._extraParams, functionName: t.functionName, }, }; @@ -333,6 +332,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.PROCEDURES]: { icon: '\ue73c', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getProcedureList(params) @@ -346,7 +347,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { comment: t.comment, isLeaf: true, extraParams: { - ...params.extraParams, + ..._extraParams, procedureName: t.procedureName, }, }; @@ -370,6 +371,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TRIGGERS]: { icon: '\ue64a', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getTriggerList(params) @@ -383,7 +386,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { comment: t.comment, isLeaf: true, extraParams: { - ...params.extraParams, + ..._extraParams, triggerName: t.triggerName, }, }; @@ -427,6 +430,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.VIEWCOLUMNS]: { icon: '\ue647', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getViewColumnList(params) @@ -439,9 +444,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { pinned: t.pinned, comment: t.comment, isLeaf: true, - extraParams: { - ...params.extraParams, - }, + extraParams: _extraParams, }; }); r(list); @@ -462,6 +465,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.COLUMNS]: { icon: '\ueac5', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getColumnList(params) @@ -474,7 +479,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { isLeaf: true, columnType: item.columnType, comment: item.comment, - extraParams: params.extraParams, + extraParams: _extraParams, }; }); r(tableList); @@ -495,6 +500,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.KEYS]: { icon: '\ueac5', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getKeyList(params) @@ -505,7 +512,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { treeNodeType: TreeNodeType.KEY, key: item.name, isLeaf: true, - extraParams: params.extraParams, + extraParams: _extraParams, }; }); r(tableList); @@ -515,6 +522,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); }); }, + operationColumn: [OperationColumn.Refresh], }, [TreeNodeType.KEY]: { icon: '\ue775', @@ -526,6 +534,8 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.INDEXES]: { icon: '\ueac5', getChildren: (params) => { + const _extraParams = params.extraParams; + delete params.extraParams; return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer .getIndexList(params) @@ -536,7 +546,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { treeNodeType: TreeNodeType.INDEX, key: item.name, isLeaf: true, - extraParams: params.extraParams, + extraParams: _extraParams, }; }); r(tableList); @@ -546,6 +556,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); }); }, + operationColumn: [OperationColumn.Refresh], }, [TreeNodeType.INDEX]: { icon: '\ue65b', diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx index fc2b1738..437664f4 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx @@ -47,8 +47,6 @@ export default memo((props) => { setActiveExtend(item); }; - console.log(props.curWorkspaceParams) - return (
((props) => { const { connectionModel, workspaceModel, mainPageModel, dispatch } = props; @@ -51,6 +50,7 @@ const WorkspaceHeader = memo((props) => { const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState(undefined); const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState(undefined); const createDatabaseRef = React.useRef(null); + const localStorageWorkspaceDatabase = getCurrentWorkspaceDatabase(); useEffect(() => { if (openDBCascaderDropdown === false) { @@ -307,10 +307,13 @@ const WorkspaceHeader = memo((props) => { code={databaseMap[curWorkspaceParams.databaseType]?.icon} />
{curWorkspaceParams.dataSourceName}
+
+ +
- {!!curDBOptions?.length && } + {/* {!!curDBOptions?.length && } */} {!!curDBOptions?.length && ( ((props) => { value={[curWorkspaceParams?.databaseName || '']} >
+
{curWorkspaceParams.databaseName}
+
+ +
)} - {!!curSchemaOptions.length && } + {/* {!!curSchemaOptions.length && } */} {!!curSchemaOptions.length && ( ((props) => { }} >
+
{curWorkspaceParams.schemaName}
+
+ +
)} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx index bc07ebd9..4ae8affe 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -29,6 +29,7 @@ import indexedDB from '@/indexedDB'; import { osNow } from '@/utils'; import { compatibleDataBaseName } from '@/utils/database'; import lodash from 'lodash'; +import { registerIntelliSenseView } from '@/utils/IntelliSense/view'; interface IProps { className?: string; @@ -48,6 +49,7 @@ const WorkspaceRight = memo((props: IProps) => { workspaceModel; const tableList = useRef>([]); + const viewList = useRef>([]); useEffect(() => { setActiveConsoleId(null); @@ -380,6 +382,26 @@ const WorkspaceRight = memo((props: IProps) => { }); }, [workspaceModel.curTableList]); //当curTableList变化时(比如手动刷新,切换databaseName、schemaName),重新注册表名提示 + useEffect(() => { + const { dataSourceId, dataSourceName, databaseName, schemaName, databaseType } = curWorkspaceParams; + // debugger + if (!dataSourceId || !(databaseName || schemaName)) { + return; + } + sqlService + .getViewList({ + dataSourceId, + dataSourceName, + databaseName, + schemaName, + databaseType, + }) + .then((res) => { + viewList.current = (res.data || []).map((item: any) => item.name); + registerIntelliSenseView(viewList.current, databaseName); + }); + }, [curWorkspaceParams.dataSourceId, curWorkspaceParams.databaseName, curWorkspaceParams.schemaName]); + function createConsole(params: { doubleClickTreeNodeData: any; workSpaceTabType: WorkspaceTabType; diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index 2bc1c6d2..cbb76576 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -78,6 +78,10 @@ export interface IHistoryRecord { * 使用时长 */ useTime?: number | null; + /** + * 创建时间 + */ + gmtCreate: string; } const saveConsole = createRequest('/api/operation/saved/create', { method: 'post' }); diff --git a/chat2db-client/src/styles/common.less b/chat2db-client/src/styles/common.less new file mode 100644 index 00000000..e69de29b diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 4dde8065..b1760429 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -10,6 +10,13 @@ export interface IDatabase { 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 { diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 33737cf5..19acb1cb 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -2,6 +2,7 @@ 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'; /** 当前库下的表 */ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { @@ -12,30 +13,7 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { }, }); -/** 根据不同的数据库,插入不同的表名 */ -const handleInsertText = (text: string, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { - const regFirstWord = /^[a-zA-Z].*/; - // 检测到第一个字符是字母,不需要加引号 - if (regFirstWord.test(text)) { - return `${text}`; - } - - if ( - [DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.ORACLE, DatabaseTypeCode.DB2, DatabaseTypeCode.SQLITE].includes( - databaseCode, - ) - ) { - return `\"${text}\"`; - } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseCode)) { - return `[${text}]`; - } else if ([DatabaseTypeCode.MYSQL].includes(databaseCode)) { - return `\`${text}\``; - } else { - return `${text}`; - } -}; - -function checkTableContext(text) { +const checkTableContext = (text) => { const normalizedText = text.trim().toUpperCase(); const tableKeywords = ['FROM', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'UPDATE']; @@ -46,11 +24,20 @@ function checkTableContext(text) { } return false; -} +}; + +const handleInsertText = (keyword: string, tableName: string, databaseCode: DatabaseTypeCode) => { + console.log('test', /^[\"\`\[]/.test(keyword)); + if (/^[\"\`\[]/.test(keyword)) { + return tableName; + } + console.log('databaseCode', databaseCode); + return compatibleDataBaseName(tableName, databaseCode); +}; const registerIntelliSenseTable = ( tableList: Array<{ name: string; comment: string }>, - databaseCode?: DatabaseTypeCode, + databaseCode: DatabaseTypeCode, dataSourceId?: number, databaseName?: string | null, schemaName?: string | null, @@ -72,7 +59,11 @@ const registerIntelliSenseTable = ( }); const isTableContext = checkTableContext(lineContentUntilPosition); + // 获取触发提示的字符 + const match = lineContentUntilPosition.match(/\S+$/); + const word = match ? match[0] : ''; + console.log('触发提示的字符:', word); return { suggestions: (tableList || []).map((tableName) => { return { @@ -82,7 +73,7 @@ const registerIntelliSenseTable = ( description: i18n('sqlEditor.text.tableName'), }, kind: monaco.languages.CompletionItemKind.Folder, - insertText: handleInsertText(tableName.name, databaseCode), + insertText: handleInsertText(word, tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), // documentation: tableName.comment, sortText: isTableContext ? '01' : '08', diff --git a/chat2db-client/src/utils/IntelliSense/view.ts b/chat2db-client/src/utils/IntelliSense/view.ts new file mode 100644 index 00000000..cf4da923 --- /dev/null +++ b/chat2db-client/src/utils/IntelliSense/view.ts @@ -0,0 +1,64 @@ +import { DatabaseTypeCode } from '@/constants'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import i18n from '@/i18n'; + +/** 当前库下的表 */ +let intelliSenseView = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + 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, +) => { + intelliSenseView.dispose(); + 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 }; diff --git a/chat2db-client/src/utils/database.ts b/chat2db-client/src/utils/database.ts index 454efdb5..b28d3bf2 100644 --- a/chat2db-client/src/utils/database.ts +++ b/chat2db-client/src/utils/database.ts @@ -45,6 +45,7 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' * @returns */ export function compatibleDataBaseName(databaseName: string, databaseType: DatabaseTypeCode) { + //"" oracele sqlite postgrsql h2 dm // ` MYSQL clickhouse MariaDB // [ sqlserver @@ -55,6 +56,8 @@ export function compatibleDataBaseName(databaseName: string, databaseType: Datab DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.H2, DatabaseTypeCode.DB2, + DatabaseTypeCode.KINGBASE, + DatabaseTypeCode.DM, ].includes(databaseType) ) { return `"${databaseName}"`; diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 9ae085c2..9c4f26c4 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -272,3 +272,14 @@ export function compareVersion(version1: string, version2: string) { } return 0; } + +// 二维数组复制 +export function tableCopy(array2D:string[][]) { + try{ + const text = array2D.map(row => row.join('\t')).join('\n') + navigator.clipboard.writeText(text); + } + catch{ + console.log('copy error') + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java index 20dc5cfb..c38a84c9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java @@ -7,6 +7,7 @@ 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 ai.chat2db.spi.util.TableUtils; import org.apache.commons.lang3.StringUtils; public class DB2SqlBuilder extends DefaultSqlBuilder { @@ -84,7 +85,7 @@ public class DB2SqlBuilder extends DefaultSqlBuilder { for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { DB2ColumnTypeEnum typeEnum = DB2ColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2ColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2ColumnTypeEnum.java index 81465314..f81ac1f1 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2ColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2ColumnTypeEnum.java @@ -229,7 +229,7 @@ public enum DB2ColumnTypeEnum implements ColumnBuilder { @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DMSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DMSqlBuilder.java index 5f840cf5..4b64bf3f 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DMSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DMSqlBuilder.java @@ -7,6 +7,7 @@ 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 ai.chat2db.spi.util.TableUtils; import org.apache.commons.lang3.StringUtils; public class DMSqlBuilder extends DefaultSqlBuilder { @@ -81,7 +82,7 @@ public class DMSqlBuilder extends DefaultSqlBuilder { for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { DMColumnTypeEnum typeEnum = DMColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMColumnTypeEnum.java index dd15ed23..8cc91906 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMColumnTypeEnum.java @@ -250,7 +250,7 @@ public enum DMColumnTypeEnum implements ColumnBuilder { @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java index 4ec37558..4f967a60 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java @@ -4,10 +4,59 @@ import java.sql.Connection; 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.StringUtils; public class KingBaseDBManage extends DefaultDBManage implements DBManage { + @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) { + e.printStackTrace(); + } + } + + @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) { + // 先在"?"字符处分割字符串,处理查询参数 + String[] urlAndParams = url.split("\\?"); + String urlWithoutParams = urlAndParams[0]; + + // 在URL中的"/"字符处分割字符串 + String[] parts = urlWithoutParams.split("/"); + + // 取最后一部分,即数据库名,并替换为新的数据库名 + parts[parts.length - 1] = newDatabase; + + // 将修改后的部分重新组合成URL + String newUrlWithoutParams = String.join("/", parts); + + // 如果存在查询参数,重新添加 + 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; diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java index 1f3404e5..288e9add 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java @@ -1,7 +1,192 @@ 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.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.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"); + + @Override + public List databases(Connection connection) { + List list = SQLExecutor.getInstance().executeSql(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"; + + @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 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(); + 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 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()) + .build(); + } + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java new file mode 100644 index 00000000..82f7983d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java @@ -0,0 +1,210 @@ +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 ai.chat2db.spi.util.TableUtils; +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 implements SqlBuilder { + @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()); + 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()); + 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()); + 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()); + 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()); + 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()); + scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).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()); + 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()); + 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()); + 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()); + 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(); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseColumnTypeEnum.java new file mode 100644 index 00000000..093d0b34 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseColumnTypeEnum.java @@ -0,0 +1,244 @@ +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 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(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, TableColumn oldColumn) { + + 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; + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseIndexTypeEnum.java new file mode 100644 index 00000000..a92b0511 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseIndexTypeEnum.java @@ -0,0 +1,180 @@ +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(), "\""); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java index c2cc32c0..f984bf67 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java @@ -8,6 +8,7 @@ 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.TableUtils; import org.apache.commons.lang3.StringUtils; @@ -86,7 +87,7 @@ public class MysqlSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(",\n"); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index bd89f786..c414c486 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -181,7 +181,7 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { } @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index 84d9be3c..b34d880c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -9,6 +9,7 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.util.TableUtils; import org.apache.commons.lang3.StringUtils; public class OracleSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @@ -81,7 +82,7 @@ public class OracleSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(";\n"); if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java index 876f8d8a..bde09d87 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java @@ -215,7 +215,7 @@ public enum OracleColumnTypeEnum implements ColumnBuilder { @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); @@ -232,7 +232,7 @@ public enum OracleColumnTypeEnum implements ColumnBuilder { 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" ); + script.append(" ").append("MODIFY (").append(buildModifyColumnSql(tableColumn,oldColumn)).append(") \n" ); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { script.append(";"); @@ -245,6 +245,27 @@ public enum OracleColumnTypeEnum implements ColumnBuilder { } 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() diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java index 78e98656..ef1413fa 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java @@ -75,9 +75,7 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { 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 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" @@ -301,4 +299,5 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { .indexTypes(PostgreSQLIndexTypeEnum.getIndexTypes()) .build(); } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java index c9290863..1491897c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java @@ -5,6 +5,7 @@ import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; +import ai.chat2db.spi.util.TableUtils; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -107,7 +108,7 @@ public class PostgreSQLSqlBuilder extends DefaultSqlBuilder implements SqlBuilde // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); - scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(",\n"); modify = true; } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java index 23977676..c5f0eccd 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java @@ -119,7 +119,7 @@ public enum PostgreSQLColumnTypeEnum implements ColumnBuilder { } @Override - public String buildModifyColumn(TableColumn column) { + public String buildModifyColumn(TableColumn column, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(column.getEditStatus())) { return StringUtils.join("DROP COLUMN `", column.getName() + "`"); diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java index 58f15763..0a6f3881 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -7,6 +7,7 @@ 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.TableUtils; import org.apache.commons.lang3.StringUtils; @@ -57,7 +58,7 @@ public class SqliteBuilder extends DefaultSqlBuilder implements SqlBuilder { 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()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append(";\n"); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java index 5aded889..3477e8e8 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java @@ -92,7 +92,7 @@ public enum SqliteColumnTypeEnum implements ColumnBuilder { } @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { // if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { // return StringUtils.join("DROP COLUMN \"", tableColumn.getName() + "\""); diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java index 54978d45..15ea87d0 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -6,6 +6,7 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.util.TableUtils; import org.apache.commons.lang3.StringUtils; public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @@ -92,7 +93,7 @@ public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append(typeEnum.buildModifyColumn(tableColumn)).append("\n"); + script.append(typeEnum.buildModifyColumn(tableColumn, TableUtils.getTableColumn(oldTable,tableColumn.getOldName()))).append("\n"); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java index 55d5ba16..6d04bd79 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java @@ -267,7 +267,7 @@ public enum SqlServerColumnTypeEnum implements ColumnBuilder { @Override - public String buildModifyColumn(TableColumn tableColumn) { + public String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java index ccffac01..cae98b5c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DlExecuteParam.java @@ -36,6 +36,12 @@ public class DlExecuteParam { @NotNull private String databaseName; + + /** + * schema名称 + */ + private String schemaName; + /** * 分页编码 * 只有select语句才有 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 0892e1cd..fd376260 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -2,15 +2,16 @@ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.api.service.OperationLogService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.core.util.MetaNameUtils; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.ConnectInfo; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; @@ -28,8 +29,6 @@ import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.enums.SqlTypeEnum; -import ai.chat2db.spi.model.ExecuteResult; -import ai.chat2db.spi.model.Header; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; @@ -37,6 +36,8 @@ import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -53,6 +54,9 @@ public class DlTemplateServiceImpl implements DlTemplateService { @Autowired private OperationLogService operationLogService; + @Autowired + private TableService tableService; + @Override public ListResult execute(DlExecuteParam param) { if (StringUtils.isBlank(param.getSql())) { @@ -147,9 +151,9 @@ public class DlTemplateServiceImpl implements DlTemplateService { log.warn("解析sql失败:{}", originalSql, e); } ExecuteResult executeResult = null; - if (SqlTypeEnum.SELECT.getCode().equals(sqlType) && !SqlUtils.hasPageLimit(originalSql,dbType)) { + if (SqlTypeEnum.SELECT.getCode().equals(sqlType) && !SqlUtils.hasPageLimit(originalSql, dbType)) { String pageLimit = Chat2DBContext.getSqlBuilder().pageLimit(originalSql, offset, pageNo, pageSize); - if(StringUtils.isNotBlank(pageLimit)) { + if (StringUtils.isNotBlank(pageLimit)) { executeResult = execute(pageLimit, 0, count); } } @@ -176,16 +180,16 @@ public class DlTemplateServiceImpl implements DlTemplateService { executeResult.setHasNextPage(Boolean.FALSE); } - // Splice row numbers - List
newHeaderList = new ArrayList<>(); - newHeaderList.add(Header.builder() + List
headers = executeResult.getHeaderList(); + if (executeResult.getSuccess() && executeResult.isCanEdit() && CollectionUtils.isNotEmpty(headers)){ + headers = setColumnInfo(headers, executeResult.getTableName(), param.getSchemaName(), param.getDatabaseName()); + } + Header rowNumberHeader = Header.builder() .name(I18nUtils.getMessage("sqlResult.rowNumber")) .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER - .getCode()).build()); - if (executeResult.getHeaderList() != null) { - newHeaderList.addAll(executeResult.getHeaderList()); - } - executeResult.setHeaderList(newHeaderList); + .getCode()).build(); + + executeResult.setHeaderList(EasyCollectionUtils.union(Arrays.asList(rowNumberHeader), headers)); if (executeResult.getDataList() != null) { int rowNumberIncrement = 1 + Math.max(pageNo - 1, 0) * pageSize; for (int i = 0; i < executeResult.getDataList().size(); i++) { @@ -246,19 +250,20 @@ public class DlTemplateServiceImpl implements DlTemplateService { public DataResult updateSelectResult(UpdateSelectResultParam param) { StringBuilder stringBuilder = new StringBuilder(); MetaData metaSchema = Chat2DBContext.getMetaData(); + List keyColumns = getPrimaryColumns(param); for (int i = 0; i < param.getOperations().size(); i++) { SelectResultOperation operation = param.getOperations().get(i); - List row = operation.getDataList(); List odlRow = operation.getOldDataList(); String sql = ""; if ("UPDATE".equalsIgnoreCase(operation.getType())) { - sql = getUpdateSql(param, row, odlRow, metaSchema); + sql = getUpdateSql(param, row, odlRow, metaSchema, keyColumns, false); } else if ("CREATE".equalsIgnoreCase(operation.getType())) { sql = getInsertSql(param, row, metaSchema); - } else if ("DELETE".equalsIgnoreCase(operation.getType())) { - sql = getDeleteSql(param, odlRow, metaSchema); + sql = getDeleteSql(param, odlRow, metaSchema, keyColumns); + } else if ("UPDATE_COPY".equalsIgnoreCase(operation.getType())) { + sql = getUpdateSql(param, row, row, metaSchema, keyColumns, true); } stringBuilder.append(sql + ";\n"); @@ -266,38 +271,71 @@ public class DlTemplateServiceImpl implements DlTemplateService { return DataResult.of(stringBuilder.toString()); } - private String getDeleteSql(UpdateSelectResultParam param, List row, MetaData metaSchema) { + private List getPrimaryColumns(UpdateSelectResultParam param) { + List
headerList = param.getHeaderList(); + 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(UpdateSelectResultParam param, List row, MetaData metaSchema, List keyColumns) { StringBuilder script = new StringBuilder(); script.append("DELETE FROM ").append(param.getTableName()).append(""); - script.append(buildWhere(param.getHeaderList(), row, metaSchema)); + script.append(buildWhere(param.getHeaderList(), row, metaSchema, keyColumns)); return script.toString(); } - private String buildWhere(List
headerList, List row, MetaData metaSchema) { + private String buildWhere(List
headerList, List row, MetaData metaSchema, List keyColumns) { StringBuilder script = new StringBuilder(); script.append(" where "); - 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 "); + 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(UpdateSelectResultParam param, List row, MetaData metaSchema) { - if (CollectionUtils.isEmpty(row)) { + if (CollectionUtils.isEmpty(row) || ObjectUtils.allNull(row.toArray())) { return ""; } StringBuilder script = new StringBuilder(); @@ -305,16 +343,21 @@ public class DlTemplateServiceImpl implements DlTemplateService { .append(" ("); for (int i = 1; i < row.size(); i++) { Header header = param.getHeaderList().get(i); - script.append(metaSchema.getMetaDataName(header.getName())) - .append(","); + //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); - Header header = param.getHeaderList().get(i); - script.append(SqlUtils.getSqlValue(newValue, header.getDataType())) - .append(","); + //if (newValue != null) { + Header header = param.getHeaderList().get(i); + script.append(SqlUtils.getSqlValue(newValue, header.getDataType())) + .append(","); + //} } script.deleteCharAt(script.length() - 1); script.append(")"); @@ -323,7 +366,8 @@ public class DlTemplateServiceImpl implements DlTemplateService { } - private String getUpdateSql(UpdateSelectResultParam param, List row, List odlRow, MetaData metaSchema) { + private String getUpdateSql(UpdateSelectResultParam param, List row, List odlRow, MetaData metaSchema, + List keyColumns, boolean copy) { StringBuilder script = new StringBuilder(); if (CollectionUtils.isEmpty(row) || CollectionUtils.isEmpty(odlRow)) { return ""; @@ -332,7 +376,7 @@ public class DlTemplateServiceImpl implements DlTemplateService { for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); String oldValue = odlRow.get(i); - if (StringUtils.equals(newValue, oldValue)) { + if (StringUtils.equals(newValue, oldValue) && !copy) { continue; } Header header = param.getHeaderList().get(i); @@ -343,10 +387,52 @@ public class DlTemplateServiceImpl implements DlTemplateService { .append(","); } script.deleteCharAt(script.length() - 1); - script.append(buildWhere(param.getHeaderList(), odlRow, metaSchema)); + script.append(buildWhere(param.getHeaderList(), odlRow, metaSchema, keyColumns)); return script.toString(); } + private List
setColumnInfo(List
headers, String tableName, String schemaName, String databaseName) { + 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()); + } + } + return headers; + } + private ExecuteResult execute(String sql, Integer offset, Integer count) { ExecuteResult executeResult; try { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/MetaNameUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/MetaNameUtils.java new file mode 100644 index 00000000..eab4d644 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/MetaNameUtils.java @@ -0,0 +1,26 @@ +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; + } +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyCollectionUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyCollectionUtils.java index 2576da48..9f1ad43a 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyCollectionUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyCollectionUtils.java @@ -1,10 +1,6 @@ package ai.chat2db.server.tools.common.util; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; @@ -189,4 +185,15 @@ public class EasyCollectionUtils { 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; + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index fb62ef3a..636c6827 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -20,6 +20,7 @@ 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; @@ -321,7 +322,7 @@ public class ChatController { messages.add(currentMessage); buildSseEmitter(sseEmitter, uid); - OpenAIEventSourceListener openAIEventSourceListener = new OpenAIEventSourceListener(sseEmitter); + Chat2dbAIEventSourceListener openAIEventSourceListener = new Chat2dbAIEventSourceListener(sseEmitter); Chat2dbAIClient.getInstance().streamCompletions(messages, openAIEventSourceListener); LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); return sseEmitter; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java index a302e56e..9cbefc1a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java @@ -3,17 +3,13 @@ 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.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.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; -import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; import okio.BufferedSource; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java index 1f63d4ab..6f6b639f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java @@ -1,13 +1,17 @@ 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.ObjectMapper; -import com.google.common.collect.Lists; import com.unfbx.chatgpt.entity.chat.ChatCompletion; import com.unfbx.chatgpt.entity.chat.Message; import com.unfbx.chatgpt.interceptor.HeaderAuthorizationInterceptor; @@ -192,7 +196,6 @@ public class Chat2DBAIStreamClient { log.error("param error:ChatEventSourceListener cannot be empty"); throw new ParamBusinessException(); } - log.info("Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); try { ChatCompletion chatCompletion = ChatCompletion.builder() .messages(chatMessages) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java new file mode 100644 index 00000000..24b0847a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/listener/Chat2dbAIEventSourceListener.java @@ -0,0 +1,129 @@ +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; + +/** + * 描述: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 返回数据:{}", data); + if (data.equals("[DONE]")) { + log.info("Chat2db AI 返回数据结束了"); + 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 关闭sse连接..."); + } + + @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连接异常data:{}", bodyString, t); + } else { + log.error("Chat2db AI sse连接异常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); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/vo/OperationLogVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/vo/OperationLogVO.java index 8fd7ad42..69da9072 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/vo/OperationLogVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/vo/OperationLogVO.java @@ -1,8 +1,11 @@ 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 2022年09月18日 11:06 moji Exp $ @@ -16,6 +19,18 @@ public class OperationLogVO { */ private Long id; + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime gmtModified; + /** * 文件别名 */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 1df806b2..18de182d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -110,7 +110,7 @@ public class RdbDmlController { //connection.commit(); return DataResult.of(resultVOS.get(0)); }else { - connection.rollback(); + //connection.rollback(); return DataResult.of(executeResult); } } catch (Exception e) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java index 3ee67f6e..03524568 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -9,6 +9,7 @@ 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; @@ -25,8 +26,9 @@ public class TriggerController { @GetMapping("/list") public WebPageResult list(@Valid TriggerPageRequest request) { ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); - return WebPageResult.of(listResult.getData(), Long.valueOf(listResult.getData().size()), 1, - listResult.getData().size()); + Long total = CollectionUtils.isNotEmpty(listResult.getData()) ? Long.valueOf(listResult.getData().size()) : 0L; + return WebPageResult.of(listResult.getData(), total, 1, + listResult.getData().size()); } @GetMapping("/detail") diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java index 0fb02315..b87b173e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java @@ -3,7 +3,7 @@ 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.server.web.api.controller.rdb.vo.HeaderVO; +import ai.chat2db.spi.model.Header; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -16,7 +16,7 @@ public class SelectResultUpdateRequest extends DataSourceBaseRequest implements /** * 展示头的列表 */ - private List headerList; + private List
headerList; /** * 修改后数据的列表 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java index 1ea6aadb..69147ade 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java @@ -3,6 +3,7 @@ package ai.chat2db.server.web.api.controller.rdb.vo; import java.util.List; +import ai.chat2db.spi.model.Header; import lombok.Data; /** @@ -46,7 +47,7 @@ public class ExecuteResultVO { /** * 展示头的列表 */ - private List headerList; + private List
headerList; /** * 数据的列表 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/HeaderVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/HeaderVO.java deleted file mode 100644 index af5596d2..00000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/HeaderVO.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai.chat2db.server.web.api.controller.rdb.vo; - -import ai.chat2db.spi.enums.DataTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 单元格头 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class HeaderVO { - /** - * 单元格类型 - * - * @see DataTypeEnum - */ - private String dataType; - - /** - * 展示的名字 - */ - private String name; -} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java index 6fec2695..6ada4cf5 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java @@ -15,7 +15,8 @@ public interface ColumnBuilder { /** * Build modify column sql * @param tableColumn + * @param oldColumn * @return */ - String buildModifyColumn(TableColumn tableColumn); + String buildModifyColumn(TableColumn tableColumn, TableColumn oldColumn); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java index 6376cc77..819a5903 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java @@ -108,7 +108,11 @@ public enum DataTypeEnum implements BaseEnum { public String getSqlValue(String value) { if (this == DataTypeEnum.BOOLEAN) { - return value; + if("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)){ + return value; + }else { + return "'" + value + "'"; + } } if (this == DataTypeEnum.NUMERIC) { return value; @@ -149,9 +153,6 @@ public enum DataTypeEnum implements BaseEnum { if (this == DataTypeEnum.UNKNOWN) { return "'" + value + "'"; } - if (this == DataTypeEnum.CHAT2DB_ROW_NUMBER) { - return "'" + value + "'"; - } return "'" + value + "'"; } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 8f39e920..29a2b94e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -10,6 +10,7 @@ import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; /** @@ -49,7 +50,11 @@ public class DefaultMetaService implements MetaData { @Override public List functions(Connection connection, String databaseName, String schemaName) { - return SQLExecutor.getInstance().functions(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : 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())).collect(Collectors.toList()); } @Override @@ -59,7 +64,12 @@ public class DefaultMetaService implements MetaData { @Override public List procedures(Connection connection, String databaseName, String schemaName) { - return SQLExecutor.getInstance().procedures(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : 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())).collect(Collectors.toList()); } @Override diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java index 299b2cef..71630626 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java @@ -38,4 +38,6 @@ public class Database implements Serializable { private String charset; private String collation; + + private String owner; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Header.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Header.java index 9fe67797..8935a240 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Header.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Header.java @@ -15,7 +15,7 @@ import lombok.experimental.SuperBuilder; @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Header { +public class Header{ /** * 单元格类型 * @@ -27,4 +27,21 @@ public class Header { * 展示的名字 */ private String name; + + + private Boolean primaryKey; + + + private String comment; + + private String defaultValue; + + private Integer autoIncrement; + + private Integer nullable; + + private Integer columnSize; + + private Integer decimalDigits; + } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index 4ee22fec..640f2180 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -86,8 +86,13 @@ public class Table { private String collate; + private Long incrementValue; + private String partition; + + + private String tablespace; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 7b0628eb..094ce9b2 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -186,7 +186,7 @@ public class SQLExecutor { ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); try (Statement stmt = connection.createStatement()) { stmt.setFetchSize(EasyToolsConstant.MAX_PAGE_SIZE); - stmt.setQueryTimeout(30); + //stmt.setQueryTimeout(30); if (offset != null && count != null) { stmt.setMaxRows(offset + count); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index cc3e578a..f39022f5 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -121,10 +121,15 @@ public class SqlUtils { return list; } + 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(DEFAULT_VALUE.equals(value)){ + return "DEFAULT"; + } DataTypeEnum dataTypeEnum = DataTypeEnum.getByCode(dataType); return dataTypeEnum.getSqlValue(value); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/TableUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/TableUtils.java new file mode 100644 index 00000000..bb1cc6c1 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/TableUtils.java @@ -0,0 +1,20 @@ +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; + } +}