mirror of
https://github.com/tencentmusic/supersonic.git
synced 2026-04-20 05:26:57 +08:00
Compare commits
27 Commits
b70b7ed01a
...
v0.9.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7ce9480bb | ||
|
|
f8104687cc | ||
|
|
6eba693982 | ||
|
|
b871ae542a | ||
|
|
3ca48e1ca1 | ||
|
|
ea7238304d | ||
|
|
1746db53c1 | ||
|
|
90c2f8b374 | ||
|
|
bd64bf1f62 | ||
|
|
56cfddea60 | ||
|
|
0aa002882d | ||
|
|
5e3bafb953 | ||
|
|
11ff99cdbe | ||
|
|
f9198cb8e0 | ||
|
|
b5aa6e046e | ||
|
|
29271f7278 | ||
|
|
50ed340ae0 | ||
|
|
75f623404d | ||
|
|
94e853f57e | ||
|
|
5fa3607874 | ||
|
|
1e01f3ef60 | ||
|
|
1155ac10d8 | ||
|
|
5a22590661 | ||
|
|
fc67411618 | ||
|
|
c03be2f5d8 | ||
|
|
08a2e889e7 | ||
|
|
87fa778416 |
476
LICENSE
476
LICENSE
@@ -1,481 +1,41 @@
|
||||
SuperSonic is licensed under the MIT License, you can freely use or integrate SuperSonic within
|
||||
your organization. However, if you want to provide or integrate SuperSonic to third parties
|
||||
as a commercial software or service, you must contact the producer to obtain a commercial license.
|
||||
Please contact jerryjzhang@tencent.com by email to inquire about licensing matters.
|
||||
Apache License Version 2.0
|
||||
|
||||
As a SuperSonic contributor, you should agree that:
|
||||
Copyright (2025) The SuperSonic Project Authors. All rights reserved.
|
||||
|
||||
a. The producer can adjust the open-source agreement to be stricter or relaxed as deemed necessary.
|
||||
b. Your contributed code may be used for commercial purposes, including but not limited to its business operations.
|
||||
----------
|
||||
|
||||
Terms of the MIT License:
|
||||
--------------------------------------------------------------------
|
||||
MIT License
|
||||
SuperSonic is licensed under the Apache License 2.0, with the following additional conditions:
|
||||
|
||||
Copyright (c) 2023 Tencent Music Entertainment
|
||||
1. The commercial usage of SuperSonic:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
a. SuperSonic may be utilized commercially, including as a frontend and backend service without modifying the source
|
||||
code and logo.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
b. a commercial license must be obtained from the author if you want to develop and distribute a derivative work based
|
||||
on SuperSonic.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Please contact zhangjun2915@163.com by email to inquire about licensing matters.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
2. As a contributor, you should agree that:
|
||||
|
||||
Other dependencies and licenses:
|
||||
a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
|
||||
|
||||
b. Your contributed code may be used for commercial purposes, including but not limited to its cloud edition.
|
||||
|
||||
Open Source Software Licensed under the MIT License:
|
||||
--------------------------------------------------------------------
|
||||
1. Mybatis-PageHelper 1.2.10
|
||||
Copyright (c) 2014-2022 abel533@gmail.com
|
||||
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0.
|
||||
Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
2. lombok
|
||||
Copyright (C) 2009-2021 The Project Lombok Authors.
|
||||
----------
|
||||
|
||||
3. react
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
4. ant-design
|
||||
Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/
|
||||
|
||||
5. ant-design-pro
|
||||
Copyright (c) 2019 Alipay.inc
|
||||
|
||||
6. @ant-design/charts
|
||||
Copyright (c) 2021 Ant Design
|
||||
|
||||
7. @ant-design/icons
|
||||
Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/
|
||||
|
||||
8. @antv/layout
|
||||
Copyright (c) 2018 Alipay.inc
|
||||
|
||||
9. @antv/xflow
|
||||
Copyright (c) 2021-2023 Alipay.inc
|
||||
|
||||
10. umi
|
||||
Copyright (c) 2017-present ChenCheng (sorrycc@gmail.com)
|
||||
|
||||
11. @umijs/route-utils
|
||||
Copyright (c) 2019-present chenshuai2144 (qixian.cs@outlook.com)
|
||||
|
||||
12. ahooks
|
||||
Copyright (c) 2020 ahooks
|
||||
|
||||
13. axios
|
||||
Copyright (c) 2014-present Matt Zabriskie & Collaborators
|
||||
|
||||
14. classnames
|
||||
Copyright (c) 2018 Jed Watson
|
||||
|
||||
15. crypto-js
|
||||
Copyright (c) 2009-2013 Jeff Mott
|
||||
Copyright (c) 2013-2016 Evan Vosberg
|
||||
|
||||
16. immutability-helper
|
||||
Copyright (c) 2017 Moshe Kolodny
|
||||
|
||||
17. lodash
|
||||
Copyright JS Foundation and other contributors <https://js.foundation/>
|
||||
|
||||
18. moment
|
||||
Copyright (c) JS Foundation and other contributors
|
||||
|
||||
19. numeral
|
||||
Copyright (c) 2016 Adam Draper
|
||||
|
||||
20. omit.js
|
||||
Copyright (c) 2016 Benjy Cui
|
||||
|
||||
21. rc-menu
|
||||
Copyright (c) 2014-present yiminghe
|
||||
|
||||
22. rc-util
|
||||
Copyright (c) 2014-present yiminghe
|
||||
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
|
||||
|
||||
23. react-ace
|
||||
Copyright (c) 2014 James Hrisho
|
||||
|
||||
24. react-dev-inspector
|
||||
Copyright (c) zthxxx (https://blog.zthxxx.me)
|
||||
|
||||
25. react-lazyload
|
||||
Copyright (c) 2015 Sen Yang
|
||||
|
||||
26. react-spinners
|
||||
Copyright (c) 2017 David Hu
|
||||
|
||||
27.react-split-pane
|
||||
Copyright (c) 2015 tomkp
|
||||
|
||||
28. snappyjs
|
||||
Copyright (c) 2016 Zhipeng Jia
|
||||
|
||||
29. sql-formatter
|
||||
Copyright (c) 2016-2020 ZeroTurnaround LLC
|
||||
Copyright (c) 2020-2021 George Leslie-Waksman and other contributors
|
||||
Copyright (c) 2021-Present inferrinizzard and other contributors
|
||||
|
||||
30. @ant-design/pro-cli
|
||||
Copyright (c) 2017-2018 Alipay
|
||||
|
||||
31. cross-env
|
||||
Copyright (c) 2017 Kent C. Dodds
|
||||
|
||||
32.cross-port-killer
|
||||
Copyright (c) 2017 Rafael Milewski
|
||||
|
||||
33.detect-installer
|
||||
Copyright (c) 2019-present chenshuai2144 (qixian.cs@outlook.com)
|
||||
|
||||
34.eslint
|
||||
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
||||
|
||||
35.express
|
||||
Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
|
||||
Copyright (c) 2013-2014 Roman Shtylman <shtylman+expressjs@gmail.com>
|
||||
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
|
||||
|
||||
36.gh-pages
|
||||
Copyright (c) 2014 Tim Schaub
|
||||
|
||||
37.inflect
|
||||
Copyright (C) 2020 Pavan Kumar Sunkara
|
||||
|
||||
38.lint-staged
|
||||
Copyright (c) 2016 Andrey Okonetchnikov
|
||||
|
||||
39.prettier
|
||||
Copyright © James Long and contributors
|
||||
|
||||
40.stylelint
|
||||
Copyright (c) 2015 - present Maxime Thirouin, David Clark & Richard Hallows
|
||||
|
||||
41.umi-serve
|
||||
Copyright (c) 2017-present ChenCheng (sorrycc@gmail.com)
|
||||
|
||||
42.webpack
|
||||
Copyright JS Foundation and other contributors
|
||||
|
||||
43.react-dnd
|
||||
Copyright (c) 2015 Dan Abramov
|
||||
|
||||
44.react-grid-layout
|
||||
Copyright (c) 2016 Samuel Reed
|
||||
|
||||
45.slat
|
||||
Copyright © 2016–2023, Ian Storm Taylor
|
||||
|
||||
46.html2canvas
|
||||
Copyright (c) 2012 Niklas von Hertzen
|
||||
|
||||
47.core-js
|
||||
Copyright (c) 2014-2020 Denis Pushkarev
|
||||
|
||||
48.immer 4.0.2
|
||||
Copyright (c) 2017 Michel Weststrate
|
||||
|
||||
49.redux
|
||||
Copyright (c) 2015-present Dan Abramov
|
||||
The Redux logo is dedicated to the public domain and licensed under CC0.
|
||||
|
||||
50.redux-saga
|
||||
Copyright (c) 2015 Yassine Elouafi
|
||||
The Redux-Saga logo is dedicated to the public domain and licensed under CC0.
|
||||
|
||||
51.ts-loader
|
||||
Copyright (c) 2015 TypeStrong
|
||||
|
||||
52.minimist
|
||||
Files:https://github.com/minimistjs/minimist/tree/v1.2.3
|
||||
License Details:https://github.com/minimistjs/minimist/blob/main/LICENSE
|
||||
|
||||
53.intl
|
||||
copyright (c) 2013 Andy Earnshaw
|
||||
|
||||
|
||||
A copy of the MIT License is included in this file.
|
||||
|
||||
|
||||
Open Source Software Licensed under the Apache License Version 2.0:
|
||||
--------------------------------------------------------------------
|
||||
1. HanLP
|
||||
Files: https://github.com/hankcs/HanLP/tree/v1.8.3
|
||||
License Details: https://github.com/hankcs/HanLP/blob/v1.8.3/LICENSE
|
||||
|
||||
2. mybatis
|
||||
iBATIS
|
||||
This product includes software developed by
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
Copyright 2010 The Apache Software Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
3. guava
|
||||
Files: https://github.com/google/guava/tree/v20.0
|
||||
License Details: https://github.com/google/guava/blob/master/LICENSE
|
||||
|
||||
4. hadoop
|
||||
This product includes software developed by The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
5. Jackson
|
||||
Files: https://github.com/FasterXML/jackson-core/tree/2.11
|
||||
License Details: https://github.com/FasterXML/jackson-core/blob/2.11/LICENSE
|
||||
|
||||
6. commons-lang
|
||||
Apache Commons Lang
|
||||
Copyright 2001-2017 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
This product includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
||||
|
||||
7. testng
|
||||
Files:https://github.com/testng-team/testng/tree/6.13.1
|
||||
License Details:https://github.com/testng-team/testng/blob/6.13.1/LICENSE.txt
|
||||
|
||||
8. jackson-dataformat-yaml
|
||||
Files:https://github.com/FasterXML/jackson-dataformat-yaml/tree/jackson-dataformat-yaml-2.8.11
|
||||
License Details:https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
||||
9. druid
|
||||
Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
10. davinci
|
||||
Licensed to Apereo under one or more contributor license
|
||||
agreements. See the NOTICE file distributed with this work
|
||||
for additional information regarding copyright ownership.
|
||||
Apereo licenses this file to you under the Apache License,
|
||||
Version 2.0 (the "License"); you may not use this file
|
||||
except in compliance with the License. You may obtain a
|
||||
copy of the License at the following location:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
11. echarts
|
||||
Apache ECharts
|
||||
Copyright 2017-2023 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
12. echarts-wordcloud
|
||||
Apache ECharts
|
||||
Copyright 2017-2023 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
13. carlo
|
||||
Files:https://github.com/GoogleChromeLabs/carlo
|
||||
License Details:https://github.com/GoogleChromeLabs/carlo/blob/master/LICENSE
|
||||
|
||||
14. puppeteer-core
|
||||
Copyright 2017 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
15. swagger-ui-react
|
||||
swagger-ui
|
||||
Copyright 2020-2021 SmartBear Software Inc.
|
||||
|
||||
16. typescript
|
||||
files:https://github.com/microsoft/TypeScript
|
||||
License Details:https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt
|
||||
|
||||
17. io.jsonwebtoken
|
||||
Copyright (C) 2014 jsonwebtoken.io
|
||||
Files: https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt/0.9.1/jjwt-0.9.1.jar
|
||||
|
||||
|
||||
Terms of the Apache License Version 2.0:
|
||||
--------------------------------------------------------------------
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
Open Source Software Licensed under the Modified BSD License:
|
||||
--------------------------------------------------------------------
|
||||
1. node-sha1
|
||||
Copyright © 2009, Jeff Mott. All rights reserved.
|
||||
Copyright © 2011, Paul Vorbach. All rights reserved.
|
||||
|
||||
This project is licensed under the terms of the Modified BSD License, as follows:
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2005-2023, NumPy Developers.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name oCrypto-JS nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
2. ace-builds
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
This project is licensed under the terms of the Modified BSD License, as follows:
|
||||
-------------------------------------------------------------------
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ajax.org B.V. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Other Open Source Software:
|
||||
--------------------------------------------------------------------
|
||||
1. jsencrypt
|
||||
Files:https://github.com/travist/jsencrypt
|
||||
License Details:https://github.com/travist/jsencrypt/blob/master/LICENSE.txt
|
||||
|
||||
|
||||
|
||||
|
||||
limitations under the License.
|
||||
@@ -55,8 +55,8 @@ if "%command%"=="restart" (
|
||||
set "webDir=%baseDir%\webapp"
|
||||
set "logDir=%baseDir%\logs"
|
||||
set "classpath=%baseDir%;%webDir%;%libDir%\*;%confDir%"
|
||||
set "java-command=-Dfile.encoding=UTF-8 -Duser.language=Zh -Duser.region=CN -Duser.timezone=GMT+08 -Dspring.profiles.active=%profile% -Xms1024m
|
||||
-Xmx1024m -cp %CLASSPATH% %MAIN_CLASS%"
|
||||
set "property=-Dfile.encoding=UTF-8 -Duser.language=Zh -Duser.region=CN -Duser.timezone=GMT+08 -Dspring.profiles.active=%profile%"
|
||||
set "java-command=%property% -Xms1024m -Xmx1024m -cp %CLASSPATH% %MAIN_CLASS%"
|
||||
if not exist %logDir% mkdir %logDir%
|
||||
start /B java %java-command% >nul 2>&1
|
||||
timeout /t 10 >nul
|
||||
|
||||
@@ -38,6 +38,9 @@ public class Agent extends RecordInfo {
|
||||
private VisualConfig visualConfig;
|
||||
private List<String> admins = Lists.newArrayList();
|
||||
private List<String> viewers = Lists.newArrayList();
|
||||
private List<String> adminOrgs = Lists.newArrayList();
|
||||
private List<String> viewOrgs = Lists.newArrayList();
|
||||
private Integer isOpen = 0;
|
||||
|
||||
public List<String> getTools(AgentToolType type) {
|
||||
Map<String, Object> map = JSONObject.parseObject(toolConfig, Map.class);
|
||||
@@ -115,4 +118,8 @@ public class Agent extends RecordInfo {
|
||||
return list.apply(this).contains(user.getName());
|
||||
}
|
||||
|
||||
public boolean openToAll() {
|
||||
return isOpen != null && isOpen == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,4 +44,10 @@ public class AgentDO {
|
||||
private String admin;
|
||||
|
||||
private String viewer;
|
||||
|
||||
private String adminOrg;
|
||||
|
||||
private String viewOrg;
|
||||
|
||||
private Integer isOpen;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class ErrorMsgRewriteProcessor implements ParseResultProcessor {
|
||||
|
||||
private static final Logger keyPipelineLog = LoggerFactory.getLogger("keyPipeline");
|
||||
|
||||
public static final String APP_KEY_ERROR_MESSAGE = "REWRITE_ERROR_MESSAGE";
|
||||
public static final String APP_KEY = "REWRITE_ERROR_MESSAGE";
|
||||
private static final String REWRITE_ERROR_MESSAGE_INSTRUCTION = ""
|
||||
+ "#Role: You are a data business partner who closely interacts with business people.\n"
|
||||
+ "#Task: Your will be provided with user input, system output and some examples, "
|
||||
@@ -38,7 +38,7 @@ public class ErrorMsgRewriteProcessor implements ParseResultProcessor {
|
||||
+ "#Examples: {{examples}}\n" + "#Response: ";
|
||||
|
||||
public ErrorMsgRewriteProcessor() {
|
||||
ChatAppManager.register(APP_KEY_ERROR_MESSAGE,
|
||||
ChatAppManager.register(APP_KEY,
|
||||
ChatApp.builder().prompt(REWRITE_ERROR_MESSAGE_INSTRUCTION).name("异常提示改写")
|
||||
.appModule(AppModule.CHAT).description("通过大模型将异常信息改写为更友好和引导性的提示用语")
|
||||
.enable(true).build());
|
||||
@@ -46,7 +46,7 @@ public class ErrorMsgRewriteProcessor implements ParseResultProcessor {
|
||||
|
||||
@Override
|
||||
public boolean accept(ParseContext parseContext) {
|
||||
ChatApp chatApp = parseContext.getAgent().getChatAppConfig().get(APP_KEY_ERROR_MESSAGE);
|
||||
ChatApp chatApp = parseContext.getAgent().getChatAppConfig().get(APP_KEY);
|
||||
return StringUtils.isNotBlank(parseContext.getResponse().getErrorMsg())
|
||||
&& Objects.nonNull(chatApp) && chatApp.isEnable();
|
||||
}
|
||||
@@ -54,16 +54,20 @@ public class ErrorMsgRewriteProcessor implements ParseResultProcessor {
|
||||
@Override
|
||||
public void process(ParseContext parseContext) {
|
||||
String errMsg = parseContext.getResponse().getErrorMsg();
|
||||
ChatApp chatApp = parseContext.getAgent().getChatAppConfig().get(APP_KEY_ERROR_MESSAGE);
|
||||
ChatApp chatApp = parseContext.getAgent().getChatAppConfig().get(APP_KEY);
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put("user_question", parseContext.getRequest().getQueryText());
|
||||
variables.put("system_message", errMsg);
|
||||
|
||||
StringBuilder exampleStr = new StringBuilder();
|
||||
parseContext.getResponse().getUsedExemplars().forEach(e -> exampleStr.append(
|
||||
String.format("<Question:{%s},Schema:{%s}> ", e.getQuestion(), e.getDbSchema())));
|
||||
parseContext.getAgent().getExamples()
|
||||
.forEach(e -> exampleStr.append(String.format("<Question:{%s}> ", e)));
|
||||
if (parseContext.getResponse().getUsedExemplars() != null) {
|
||||
parseContext.getResponse().getUsedExemplars().forEach(e -> exampleStr.append(String
|
||||
.format("<Question:{%s},Schema:{%s}> ", e.getQuestion(), e.getDbSchema())));
|
||||
}
|
||||
if (parseContext.getAgent().getExamples() != null) {
|
||||
parseContext.getAgent().getExamples()
|
||||
.forEach(e -> exampleStr.append(String.format("<Question:{%s}> ", e)));
|
||||
}
|
||||
variables.put("examples", exampleStr);
|
||||
|
||||
Prompt prompt = PromptTemplate.from(chatApp.getPrompt()).apply(variables);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.tencent.supersonic.chat.server.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.tencent.supersonic.auth.api.authentication.service.UserService;
|
||||
import com.tencent.supersonic.chat.api.pojo.request.ChatMemoryFilter;
|
||||
import com.tencent.supersonic.chat.api.pojo.request.ChatParseReq;
|
||||
import com.tencent.supersonic.chat.server.agent.Agent;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -42,6 +44,9 @@ public class AgentServiceImpl extends ServiceImpl<AgentDOMapper, AgentDO> implem
|
||||
@Autowired
|
||||
private ChatModelService chatModelService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("chatExecutor")
|
||||
private ThreadPoolExecutor executor;
|
||||
@@ -53,17 +58,19 @@ public class AgentServiceImpl extends ServiceImpl<AgentDOMapper, AgentDO> implem
|
||||
}
|
||||
|
||||
private boolean filterByAuth(Agent agent, User user, AuthType authType) {
|
||||
if (user.isSuperAdmin() || user.getName().equals(agent.getCreatedBy())) {
|
||||
Set<String> orgIds = userService.getUserAllOrgId(user.getName());
|
||||
|
||||
if (user.isSuperAdmin() || agent.openToAll()
|
||||
|| user.getName().equals(agent.getCreatedBy())) {
|
||||
return true;
|
||||
}
|
||||
authType = authType == null ? AuthType.VIEWER : authType;
|
||||
switch (authType) {
|
||||
case ADMIN:
|
||||
return agent.contains(user, Agent::getAdmins);
|
||||
return checkAdminPermission(orgIds, user, agent);
|
||||
case VIEWER:
|
||||
default:
|
||||
return agent.contains(user, Agent::getAdmins)
|
||||
|| agent.contains(user, Agent::getViewers);
|
||||
return checkViewPermission(orgIds, user, agent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +168,9 @@ public class AgentServiceImpl extends ServiceImpl<AgentDOMapper, AgentDO> implem
|
||||
});
|
||||
agent.setAdmins(JsonUtil.toList(agentDO.getAdmin(), String.class));
|
||||
agent.setViewers(JsonUtil.toList(agentDO.getViewer(), String.class));
|
||||
agent.setAdminOrgs(JsonUtil.toList(agentDO.getAdminOrg(), String.class));
|
||||
agent.setViewOrgs(JsonUtil.toList(agentDO.getViewOrg(), String.class));
|
||||
agent.setIsOpen(agentDO.getIsOpen());
|
||||
return agent;
|
||||
}
|
||||
|
||||
@@ -173,9 +183,56 @@ public class AgentServiceImpl extends ServiceImpl<AgentDOMapper, AgentDO> implem
|
||||
agentDO.setVisualConfig(JsonUtil.toString(agent.getVisualConfig()));
|
||||
agentDO.setAdmin(JsonUtil.toString(agent.getAdmins()));
|
||||
agentDO.setViewer(JsonUtil.toString(agent.getViewers()));
|
||||
agentDO.setAdminOrg(JsonUtil.toString(agent.getAdminOrgs()));
|
||||
agentDO.setViewOrg(JsonUtil.toString(agent.getViewOrgs()));
|
||||
agentDO.setIsOpen(agent.getIsOpen());
|
||||
if (agentDO.getStatus() == null) {
|
||||
agentDO.setStatus(1);
|
||||
}
|
||||
return agentDO;
|
||||
}
|
||||
|
||||
private boolean checkAdminPermission(Set<String> orgIds, User user, Agent agent) {
|
||||
List<String> admins = agent.getAdmins();
|
||||
List<String> adminOrgs = agent.getAdminOrgs();
|
||||
if (user.isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
if (admins.contains(user.getName()) || agent.getCreatedBy().equals(user.getName())) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(adminOrgs)) {
|
||||
return false;
|
||||
}
|
||||
for (String orgId : orgIds) {
|
||||
if (adminOrgs.contains(orgId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkViewPermission(Set<String> orgIds, User user, Agent agent) {
|
||||
if (checkAdminPermission(orgIds, user, agent)) {
|
||||
return true;
|
||||
}
|
||||
List<String> viewers = agent.getViewers();
|
||||
List<String> viewOrgs = agent.getViewOrgs();
|
||||
if (agent.openToAll()) {
|
||||
return true;
|
||||
}
|
||||
if (viewers.contains(user.getName())) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(viewOrgs)) {
|
||||
return false;
|
||||
}
|
||||
for (String orgId : orgIds) {
|
||||
if (viewOrgs.contains(orgId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ public class SqlReplaceHelper {
|
||||
|
||||
private final static double replaceColumnThreshold = 0.4;
|
||||
|
||||
public static String escapeTableName(String table) {
|
||||
return String.format("`%s`", table);
|
||||
}
|
||||
|
||||
public static String replaceAggFields(String sql,
|
||||
Map<String, Pair<String, String>> fieldNameToAggMap) {
|
||||
Select selectStatement = SqlSelectHelper.getSelect(sql);
|
||||
|
||||
@@ -228,7 +228,7 @@ public class SqlSelectHelper {
|
||||
statement = CCJSqlParserUtil.parse(sql);
|
||||
} catch (JSQLParserException e) {
|
||||
log.error("parse error, sql:{}", sql, e);
|
||||
return null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (statement instanceof ParenthesedSelect) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.tencent.supersonic.common.util.DateUtils;
|
||||
import lombok.Data;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -12,7 +13,7 @@ import java.util.Objects;
|
||||
import static java.time.LocalDate.now;
|
||||
|
||||
@Data
|
||||
public class DateConf {
|
||||
public class DateConf implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3074129990945004340L;
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ package com.tencent.supersonic.common.pojo;
|
||||
import com.google.common.base.Objects;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
|
||||
import static com.tencent.supersonic.common.pojo.Constants.ASC_UPPER;
|
||||
|
||||
@Data
|
||||
public class Order {
|
||||
public class Order implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "Invalid order column")
|
||||
private String column;
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.tencent.supersonic.common.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class PageBaseReq {
|
||||
public class PageBaseReq implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Integer MAX_PAGESIZE = 100;
|
||||
private Integer current = 1;
|
||||
|
||||
@@ -10,7 +10,10 @@ public enum EngineType {
|
||||
OTHER(7, "OTHER"),
|
||||
DUCKDB(8, "DUCKDB"),
|
||||
HANADB(9, "HANADB"),
|
||||
STARROCKS(10, "STARROCKS"),;
|
||||
STARROCKS(10, "STARROCKS"),
|
||||
KYUUBI(11, "KYUUBI"),
|
||||
PRESTO(12, "PRESTO"),
|
||||
TRINO(13, "TRINO"),;
|
||||
|
||||
private Integer code;
|
||||
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
# Use an official OpenJDK runtime as a parent image
|
||||
FROM openjdk:21-jdk-bullseye
|
||||
FROM supersonicbi/supersonic:0.9.10-SNAPSHOT
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Delete old supersonic installation directory and the symbolic link
|
||||
RUN rm -rf /usr/src/app/supersonic-standalone-0.9.10-SNAPSHOT
|
||||
RUN rm -f /usr/src/app/supersonic-standalone-latest
|
||||
|
||||
# Argument to pass in the supersonic version at build time
|
||||
ARG SUPERSONIC_VERSION
|
||||
|
||||
# Install necessary packages, including Postgres client
|
||||
RUN apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
# Install the vim editor.
|
||||
RUN apt-get update && apt-get install -y vim && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Update the package list and install iputils-ping.
|
||||
RUN apt-get update && apt-get install -y iputils-ping
|
||||
|
||||
# 更新包列表并安装 dnsutils 包
|
||||
RUN apt-get update && apt-get install -y dnsutils
|
||||
|
||||
# Copy the supersonic standalone zip file into the container
|
||||
COPY assembly/build/supersonic-standalone-${SUPERSONIC_VERSION}.zip .
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ services:
|
||||
- 8.8.4.4
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "sh -c 'pg_isready -U supersonic_user -d postgres'"]
|
||||
interval: 30s
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
public class DbSchema {
|
||||
|
||||
private String catalog;
|
||||
|
||||
private String db;
|
||||
|
||||
private String table;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package com.tencent.supersonic.headless.api.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -21,6 +17,7 @@ public class SchemaElementMatch implements Serializable {
|
||||
private String word;
|
||||
private Long frequency;
|
||||
private boolean isInherited;
|
||||
private boolean llmMatched;
|
||||
|
||||
public boolean isFullMatched() {
|
||||
return 1.0 == similarity;
|
||||
|
||||
@@ -109,7 +109,8 @@ public class SemanticParseInfo implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SchemaNameLengthComparator implements Comparator<SchemaElement> {
|
||||
private static class SchemaNameLengthComparator
|
||||
implements Comparator<SchemaElement>, Serializable {
|
||||
@Override
|
||||
public int compare(SchemaElement o1, SchemaElement o2) {
|
||||
if (o1.getOrder() != o2.getOrder()) {
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.tencent.supersonic.headless.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SqlEvaluation {
|
||||
public class SqlEvaluation implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Boolean isValidated;
|
||||
private String validateMsg;
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.tencent.supersonic.headless.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SqlInfo {
|
||||
public class SqlInfo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// S2SQL generated by semantic parsers
|
||||
private String parsedS2SQL;
|
||||
|
||||
@@ -8,7 +8,9 @@ import java.util.Set;
|
||||
public enum DataType {
|
||||
MYSQL("mysql", "mysql", "com.mysql.cj.jdbc.Driver", "`", "`", "'", "'"),
|
||||
|
||||
HIVE2("hive2", "hive", "org.apache.hive.jdbc.HiveDriver", "`", "`", "`", "`"),
|
||||
HIVE2("hive2", "hive", "org.apache.kyuubi.jdbc.KyuubiHiveDriver", "`", "`", "`", "`"),
|
||||
|
||||
KYUUBI("kyuubi", "kyuubi", "org.apache.kyuubi.jdbc.KyuubiHiveDriver", "`", "`", "`", "`"),
|
||||
|
||||
ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""),
|
||||
|
||||
@@ -27,6 +29,8 @@ public enum DataType {
|
||||
|
||||
PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "\"", "\"", "\"", "\""),
|
||||
|
||||
TRINO("trino", "trino", "io.trino.jdbc.TrinoDriver", "\"", "\"", "\"", "\""),
|
||||
|
||||
MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"),
|
||||
|
||||
CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "",
|
||||
@@ -46,6 +50,7 @@ public enum DataType {
|
||||
TDENGINE("TAOS", "TAOS", "com.taosdata.jdbc.TSDBDriver", "'", "'", "\"", "\""),
|
||||
|
||||
POSTGRESQL("postgresql", "postgresql", "org.postgresql.Driver", "'", "'", "\"", "\""),
|
||||
|
||||
DUCKDB("duckdb", "duckdb", "org.duckdb.DuckDBDriver", "'", "'", "\"", "\"");
|
||||
|
||||
private String feature;
|
||||
|
||||
@@ -19,6 +19,8 @@ public class ModelBuildReq {
|
||||
|
||||
private String sql;
|
||||
|
||||
private String catalog;
|
||||
|
||||
private String db;
|
||||
|
||||
private List<String> tables;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.jsqlparser.SqlAddHelper;
|
||||
import com.tencent.supersonic.common.jsqlparser.SqlReplaceHelper;
|
||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
@@ -281,7 +282,7 @@ public class QueryStructReq extends SemanticQueryReq {
|
||||
|
||||
public String getTableName() {
|
||||
if (StringUtils.isNotBlank(dataSetName)) {
|
||||
return dataSetName;
|
||||
return SqlReplaceHelper.escapeTableName(dataSetName);
|
||||
}
|
||||
if (dataSetId != null) {
|
||||
return Constants.TABLE_PREFIX + dataSetId;
|
||||
|
||||
@@ -13,6 +13,7 @@ public class EmbeddingResult extends MapResult {
|
||||
|
||||
private String id;
|
||||
private Map<String, String> metadata;
|
||||
private boolean llmMatched;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.tencent.supersonic.headless.chat.mapper;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.enums.Text2SQLType;
|
||||
import com.tencent.supersonic.common.util.ContextUtils;
|
||||
import com.tencent.supersonic.common.util.JsonUtil;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaElementMatch;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaElementType;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaMapInfo;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MapModeEnum;
|
||||
import com.tencent.supersonic.headless.chat.ChatQueryContext;
|
||||
import com.tencent.supersonic.headless.chat.knowledge.EmbeddingResult;
|
||||
@@ -11,6 +14,7 @@ import com.tencent.supersonic.headless.chat.knowledge.builder.BaseWordBuilder;
|
||||
import com.tencent.supersonic.headless.chat.knowledge.helper.HanlpHelper;
|
||||
import dev.langchain4j.store.embedding.Retrieval;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -23,10 +27,16 @@ public class EmbeddingMapper extends BaseMapper {
|
||||
|
||||
@Override
|
||||
public boolean accept(ChatQueryContext chatQueryContext) {
|
||||
return MapModeEnum.LOOSE.equals(chatQueryContext.getRequest().getMapModeEnum());
|
||||
boolean b0 = MapModeEnum.LOOSE.equals(chatQueryContext.getRequest().getMapModeEnum());
|
||||
boolean b1 = chatQueryContext.getRequest().getText2SQLType() == Text2SQLType.LLM_OR_RULE;
|
||||
return b0 || b1;
|
||||
}
|
||||
|
||||
public void doMap(ChatQueryContext chatQueryContext) {
|
||||
|
||||
// TODO: 如果是在LOOSE执行过了,那么在LLM_OR_RULE阶段可以不用执行,所以这里缺乏一个状态来传递,暂时先忽略这个浪费行为吧
|
||||
SchemaMapInfo mappedInfo = chatQueryContext.getMapInfo();
|
||||
|
||||
// 1. Query from embedding by queryText
|
||||
EmbeddingMatchStrategy matchStrategy = ContextUtils.getBean(EmbeddingMatchStrategy.class);
|
||||
List<EmbeddingResult> matchResults = getMatches(chatQueryContext, matchStrategy);
|
||||
@@ -53,15 +63,26 @@ public class EmbeddingMapper extends BaseMapper {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Build SchemaElementMatch object
|
||||
SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder()
|
||||
.element(schemaElement).frequency(BaseWordBuilder.DEFAULT_FREQUENCY)
|
||||
.word(matchResult.getName()).similarity(matchResult.getSimilarity())
|
||||
.detectWord(matchResult.getDetectWord()).build();
|
||||
schemaElementMatch.setLlmMatched(matchResult.isLlmMatched());
|
||||
|
||||
// 3. Add SchemaElementMatch to mapInfo
|
||||
addToSchemaMap(chatQueryContext.getMapInfo(), dataSetId, schemaElementMatch);
|
||||
}
|
||||
if (CollectionUtils.isEmpty(matchResults)) {
|
||||
log.info("embedding mapper no match");
|
||||
} else {
|
||||
for (EmbeddingResult matchResult : matchResults) {
|
||||
log.info("embedding match name=[{}],detectWord=[{}],similarity=[{}],metadata=[{}]",
|
||||
matchResult.getName(), matchResult.getDetectWord(),
|
||||
matchResult.getSimilarity(), JsonUtil.toString(matchResult.getMetadata()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package com.tencent.supersonic.headless.chat.mapper;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.hankcs.hanlp.seg.common.Term;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.S2Term;
|
||||
import com.tencent.supersonic.headless.chat.ChatQueryContext;
|
||||
import com.tencent.supersonic.headless.chat.knowledge.EmbeddingResult;
|
||||
import com.tencent.supersonic.headless.chat.knowledge.MetaEmbeddingService;
|
||||
import com.tencent.supersonic.headless.chat.knowledge.helper.HanlpHelper;
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel;
|
||||
import dev.langchain4j.model.input.Prompt;
|
||||
import dev.langchain4j.model.input.PromptTemplate;
|
||||
import dev.langchain4j.provider.ModelProvider;
|
||||
import dev.langchain4j.store.embedding.Retrieval;
|
||||
import dev.langchain4j.store.embedding.RetrieveQuery;
|
||||
import dev.langchain4j.store.embedding.RetrieveQueryResult;
|
||||
@@ -14,18 +22,12 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.tencent.supersonic.headless.chat.mapper.MapperConfig.EMBEDDING_MAPPER_NUMBER;
|
||||
import static com.tencent.supersonic.headless.chat.mapper.MapperConfig.EMBEDDING_MAPPER_ROUND_NUMBER;
|
||||
import static com.tencent.supersonic.headless.chat.mapper.MapperConfig.EMBEDDING_MAPPER_THRESHOLD;
|
||||
import static com.tencent.supersonic.headless.chat.mapper.MapperConfig.*;
|
||||
|
||||
/**
|
||||
* EmbeddingMatchStrategy uses vector database to perform similarity search against the embeddings
|
||||
@@ -35,37 +37,167 @@ import static com.tencent.supersonic.headless.chat.mapper.MapperConfig.EMBEDDING
|
||||
@Slf4j
|
||||
public class EmbeddingMatchStrategy extends BatchMatchStrategy<EmbeddingResult> {
|
||||
|
||||
@Autowired
|
||||
protected MapperConfig mapperConfig;
|
||||
|
||||
@Autowired
|
||||
private MetaEmbeddingService metaEmbeddingService;
|
||||
|
||||
private static final String LLM_FILTER_PROMPT =
|
||||
"""
|
||||
\
|
||||
#Role: You are a professional data analyst specializing in metrics and dimensions.
|
||||
#Task: Given a user query and a list of retrieved metrics/dimensions through vector recall,
|
||||
please analyze which metrics/dimensions the user is most likely interested in.
|
||||
#Rules:
|
||||
1. Based on user query and retrieved info, accurately determine metrics/dimensions user truly cares about.
|
||||
2. Do not return all retrieved info, only select those highly relevant to user query.
|
||||
3. Maintain high quality output, exclude metrics/dimensions irrelevant to user intent.
|
||||
4. Output must be in JSON array format, only include IDs from retrieved info, e.g.: ['id1', 'id2']
|
||||
5. Return JSON content directly without markdown formatting
|
||||
#Input Example:
|
||||
#User Query: {{userText}}
|
||||
#Retrieved Metrics/Dimensions: {{retrievedInfo}}
|
||||
#Output:""";
|
||||
|
||||
@Override
|
||||
public List<EmbeddingResult> detect(ChatQueryContext chatQueryContext, List<S2Term> terms,
|
||||
Set<Long> detectDataSetIds) {
|
||||
if (chatQueryContext == null || CollectionUtils.isEmpty(detectDataSetIds)) {
|
||||
log.warn("Invalid input parameters: context={}, dataSetIds={}", chatQueryContext,
|
||||
detectDataSetIds);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 1. Base detection
|
||||
List<EmbeddingResult> baseResults = super.detect(chatQueryContext, terms, detectDataSetIds);
|
||||
|
||||
boolean useLLM =
|
||||
Boolean.parseBoolean(mapperConfig.getParameterValue(EMBEDDING_MAPPER_USE_LLM));
|
||||
|
||||
// 2. LLM enhanced detection
|
||||
if (useLLM) {
|
||||
List<EmbeddingResult> llmResults = detectWithLLM(chatQueryContext, detectDataSetIds);
|
||||
if (!CollectionUtils.isEmpty(llmResults)) {
|
||||
baseResults.addAll(llmResults);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Deduplicate results
|
||||
return baseResults.stream().distinct().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform enhanced detection using LLM
|
||||
*/
|
||||
private List<EmbeddingResult> detectWithLLM(ChatQueryContext chatQueryContext,
|
||||
Set<Long> detectDataSetIds) {
|
||||
try {
|
||||
String queryText = chatQueryContext.getRequest().getQueryText();
|
||||
if (StringUtils.isBlank(queryText)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Get segmentation results
|
||||
Set<String> detectSegments = extractValidSegments(queryText);
|
||||
if (CollectionUtils.isEmpty(detectSegments)) {
|
||||
log.info("No valid segments found for text: {}", queryText);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return detectByBatch(chatQueryContext, detectDataSetIds, detectSegments, true);
|
||||
} catch (Exception e) {
|
||||
log.error("Error in LLM detection for context: {}", chatQueryContext, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract valid word segments by filtering out unwanted word natures
|
||||
*/
|
||||
private Set<String> extractValidSegments(String text) {
|
||||
List<String> natureList = Arrays.asList(StringUtils.split(
|
||||
mapperConfig.getParameterValue(EMBEDDING_MAPPER_ALLOWED_SEGMENT_NATURE), ","));
|
||||
return HanlpHelper.getSegment().seg(text).stream()
|
||||
.filter(t -> natureList.stream().noneMatch(nature -> t.nature.startsWith(nature)))
|
||||
.map(Term::getWord).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EmbeddingResult> detectByBatch(ChatQueryContext chatQueryContext,
|
||||
Set<Long> detectDataSetIds, Set<String> detectSegments) {
|
||||
return detectByBatch(chatQueryContext, detectDataSetIds, detectSegments, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process detection in batches with LLM option
|
||||
*
|
||||
* @param chatQueryContext The context of the chat query
|
||||
* @param detectDataSetIds Target dataset IDs for detection
|
||||
* @param detectSegments Segments to be detected
|
||||
* @param useLlm Whether to use LLM for filtering results
|
||||
* @return List of embedding results
|
||||
*/
|
||||
public List<EmbeddingResult> detectByBatch(ChatQueryContext chatQueryContext,
|
||||
Set<Long> detectDataSetIds, Set<String> detectSegments, boolean useLlm) {
|
||||
Set<EmbeddingResult> results = ConcurrentHashMap.newKeySet();
|
||||
int embeddingMapperBatch = Integer
|
||||
.valueOf(mapperConfig.getParameterValue(MapperConfig.EMBEDDING_MAPPER_BATCH));
|
||||
|
||||
List<String> queryTextsList =
|
||||
detectSegments.stream().map(detectSegment -> detectSegment.trim())
|
||||
.filter(detectSegment -> StringUtils.isNotBlank(detectSegment))
|
||||
.collect(Collectors.toList());
|
||||
// Process and filter query texts
|
||||
List<String> queryTextsList = detectSegments.stream().map(String::trim)
|
||||
.filter(StringUtils::isNotBlank).collect(Collectors.toList());
|
||||
|
||||
// Partition queries into sub-lists for batch processing
|
||||
List<List<String>> queryTextsSubList =
|
||||
Lists.partition(queryTextsList, embeddingMapperBatch);
|
||||
|
||||
// Create and execute tasks for each batch
|
||||
List<Callable<Void>> tasks = new ArrayList<>();
|
||||
for (List<String> queryTextsSub : queryTextsSubList) {
|
||||
tasks.add(createTask(chatQueryContext, detectDataSetIds, queryTextsSub, results));
|
||||
tasks.add(
|
||||
createTask(chatQueryContext, detectDataSetIds, queryTextsSub, results, useLlm));
|
||||
}
|
||||
executeTasks(tasks);
|
||||
|
||||
// Apply LLM filtering if enabled
|
||||
if (useLlm) {
|
||||
Map<String, Object> variable = new HashMap<>();
|
||||
variable.put("userText", chatQueryContext.getRequest().getQueryText());
|
||||
variable.put("retrievedInfo", JSONObject.toJSONString(results));
|
||||
|
||||
Prompt prompt = PromptTemplate.from(LLM_FILTER_PROMPT).apply(variable);
|
||||
ChatLanguageModel chatLanguageModel = ModelProvider.getChatModel();
|
||||
String response = chatLanguageModel.generate(prompt.toUserMessage().singleText());
|
||||
|
||||
if (StringUtils.isBlank(response)) {
|
||||
results.clear();
|
||||
} else {
|
||||
List<String> retrievedIds = JSONObject.parseArray(response, String.class);
|
||||
results = results.stream().filter(t -> retrievedIds.contains(t.getId()))
|
||||
.collect(Collectors.toSet());
|
||||
results.forEach(r -> r.setLlmMatched(true));
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a task for batch processing
|
||||
*
|
||||
* @param chatQueryContext The context of the chat query
|
||||
* @param detectDataSetIds Target dataset IDs
|
||||
* @param queryTextsSub Sub-list of query texts to process
|
||||
* @param results Shared result set for collecting results
|
||||
* @param useLlm Whether to use LLM
|
||||
* @return Callable task
|
||||
*/
|
||||
private Callable<Void> createTask(ChatQueryContext chatQueryContext, Set<Long> detectDataSetIds,
|
||||
List<String> queryTextsSub, Set<EmbeddingResult> results) {
|
||||
List<String> queryTextsSub, Set<EmbeddingResult> results, boolean useLlm) {
|
||||
return () -> {
|
||||
List<EmbeddingResult> oneRoundResults =
|
||||
detectByQueryTextsSub(detectDataSetIds, queryTextsSub, chatQueryContext);
|
||||
List<EmbeddingResult> oneRoundResults = detectByQueryTextsSub(detectDataSetIds,
|
||||
queryTextsSub, chatQueryContext, useLlm);
|
||||
synchronized (results) {
|
||||
selectResultInOneRound(results, oneRoundResults);
|
||||
}
|
||||
@@ -73,57 +205,73 @@ public class EmbeddingMatchStrategy extends BatchMatchStrategy<EmbeddingResult>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a sub-list of query texts
|
||||
*
|
||||
* @param detectDataSetIds Target dataset IDs
|
||||
* @param queryTextsSub Sub-list of query texts
|
||||
* @param chatQueryContext Chat query context
|
||||
* @param useLlm Whether to use LLM
|
||||
* @return List of embedding results for this batch
|
||||
*/
|
||||
private List<EmbeddingResult> detectByQueryTextsSub(Set<Long> detectDataSetIds,
|
||||
List<String> queryTextsSub, ChatQueryContext chatQueryContext) {
|
||||
List<String> queryTextsSub, ChatQueryContext chatQueryContext, boolean useLlm) {
|
||||
Map<Long, List<Long>> modelIdToDataSetIds = chatQueryContext.getModelIdToDataSetIds();
|
||||
|
||||
// Get configuration parameters
|
||||
double threshold =
|
||||
Double.valueOf(mapperConfig.getParameterValue(EMBEDDING_MAPPER_THRESHOLD));
|
||||
|
||||
// step1. build query params
|
||||
RetrieveQuery retrieveQuery = RetrieveQuery.builder().queryTextsList(queryTextsSub).build();
|
||||
|
||||
// step2. retrieveQuery by detectSegment
|
||||
Double.parseDouble(mapperConfig.getParameterValue(EMBEDDING_MAPPER_THRESHOLD));
|
||||
int embeddingNumber =
|
||||
Integer.valueOf(mapperConfig.getParameterValue(EMBEDDING_MAPPER_NUMBER));
|
||||
Integer.parseInt(mapperConfig.getParameterValue(EMBEDDING_MAPPER_NUMBER));
|
||||
int embeddingRoundNumber =
|
||||
Integer.parseInt(mapperConfig.getParameterValue(EMBEDDING_MAPPER_ROUND_NUMBER));
|
||||
|
||||
// Build and execute query
|
||||
RetrieveQuery retrieveQuery = RetrieveQuery.builder().queryTextsList(queryTextsSub).build();
|
||||
List<RetrieveQueryResult> retrieveQueryResults = metaEmbeddingService.retrieveQuery(
|
||||
retrieveQuery, embeddingNumber, modelIdToDataSetIds, detectDataSetIds);
|
||||
|
||||
if (CollectionUtils.isEmpty(retrieveQueryResults)) {
|
||||
return new ArrayList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// step3. build EmbeddingResults
|
||||
List<EmbeddingResult> collect = retrieveQueryResults.stream().map(retrieveQueryResult -> {
|
||||
List<Retrieval> retrievals = retrieveQueryResult.getRetrieval();
|
||||
if (CollectionUtils.isNotEmpty(retrievals)) {
|
||||
retrievals.removeIf(retrieval -> {
|
||||
if (!retrieveQueryResult.getQuery().contains(retrieval.getQuery())) {
|
||||
return retrieval.getSimilarity() < threshold;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Process results
|
||||
List<EmbeddingResult> collect = retrieveQueryResults.stream().peek(result -> {
|
||||
if (!useLlm && CollectionUtils.isNotEmpty(result.getRetrieval())) {
|
||||
result.getRetrieval()
|
||||
.removeIf(retrieval -> !result.getQuery().contains(retrieval.getQuery())
|
||||
&& retrieval.getSimilarity() < threshold);
|
||||
}
|
||||
return retrieveQueryResult;
|
||||
}).filter(retrieveQueryResult -> CollectionUtils
|
||||
.isNotEmpty(retrieveQueryResult.getRetrieval()))
|
||||
.flatMap(retrieveQueryResult -> retrieveQueryResult.getRetrieval().stream()
|
||||
.map(retrieval -> {
|
||||
EmbeddingResult embeddingResult = new EmbeddingResult();
|
||||
BeanUtils.copyProperties(retrieval, embeddingResult);
|
||||
embeddingResult.setDetectWord(retrieveQueryResult.getQuery());
|
||||
embeddingResult.setName(retrieval.getQuery());
|
||||
Map<String, String> convertedMap = retrieval.getMetadata().entrySet()
|
||||
.stream().collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> entry.getValue().toString()));
|
||||
embeddingResult.setMetadata(convertedMap);
|
||||
return embeddingResult;
|
||||
}))
|
||||
}).filter(result -> CollectionUtils.isNotEmpty(result.getRetrieval()))
|
||||
.flatMap(result -> result.getRetrieval().stream()
|
||||
.map(retrieval -> convertToEmbeddingResult(result, retrieval)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// step4. select mapResul in one round
|
||||
int embeddingRoundNumber =
|
||||
Integer.valueOf(mapperConfig.getParameterValue(EMBEDDING_MAPPER_ROUND_NUMBER));
|
||||
int roundNumber = embeddingRoundNumber * queryTextsSub.size();
|
||||
return collect.stream().sorted(Comparator.comparingDouble(EmbeddingResult::getSimilarity))
|
||||
.limit(roundNumber).collect(Collectors.toList());
|
||||
// Sort and limit results
|
||||
return collect.stream()
|
||||
.sorted(Comparator.comparingDouble(EmbeddingResult::getSimilarity).reversed())
|
||||
.limit(embeddingRoundNumber * queryTextsSub.size()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RetrieveQueryResult and Retrieval to EmbeddingResult
|
||||
*
|
||||
* @param queryResult The query result containing retrieval information
|
||||
* @param retrieval The retrieval data to be converted
|
||||
* @return Converted EmbeddingResult
|
||||
*/
|
||||
private EmbeddingResult convertToEmbeddingResult(RetrieveQueryResult queryResult,
|
||||
Retrieval retrieval) {
|
||||
EmbeddingResult embeddingResult = new EmbeddingResult();
|
||||
BeanUtils.copyProperties(retrieval, embeddingResult);
|
||||
embeddingResult.setDetectWord(queryResult.getQuery());
|
||||
embeddingResult.setName(retrieval.getQuery());
|
||||
|
||||
// Convert metadata to string values
|
||||
Map<String, String> metadata = retrieval.getMetadata().entrySet().stream().collect(
|
||||
Collectors.toMap(Map.Entry::getKey, entry -> String.valueOf(entry.getValue())));
|
||||
embeddingResult.setMetadata(metadata);
|
||||
|
||||
return embeddingResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@ import com.tencent.supersonic.headless.chat.ChatQueryContext;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -66,7 +61,8 @@ public class MapFilter {
|
||||
List<SchemaElementMatch> value = entry.getValue();
|
||||
if (!CollectionUtils.isEmpty(value)) {
|
||||
value.removeIf(schemaElementMatch -> StringUtils
|
||||
.length(schemaElementMatch.getDetectWord()) <= 1);
|
||||
.length(schemaElementMatch.getDetectWord()) <= 1
|
||||
&& !schemaElementMatch.isLlmMatched());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,12 @@ public class MapperConfig extends ParameterConfig {
|
||||
public static final Parameter EMBEDDING_MAPPER_ROUND_NUMBER =
|
||||
new Parameter("s2.mapper.embedding.round.number", "10", "向量召回最小相似度阈值",
|
||||
"向量召回相似度阈值在动态调整中的最低值", "number", "Mapper相关配置");
|
||||
|
||||
public static final Parameter EMBEDDING_MAPPER_USE_LLM =
|
||||
new Parameter("s2.mapper.embedding.use-llm-enhance", "false", "使用LLM对召回的向量进行二次判断开关",
|
||||
"embedding的结果再通过一次LLM来筛选,这时候忽略各个向量阀值", "bool", "Mapper相关配置");
|
||||
|
||||
public static final Parameter EMBEDDING_MAPPER_ALLOWED_SEGMENT_NATURE =
|
||||
new Parameter("s2.mapper.embedding.allowed-segment-nature", "['v', 'd', 'a']",
|
||||
"使用LLM召回二次处理时对问题分词词性的控制", "分词后允许的词性才会进行向量召回", "list", "Mapper相关配置");
|
||||
}
|
||||
|
||||
@@ -15,12 +15,19 @@ import com.tencent.supersonic.headless.api.pojo.request.QueryFilter;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMultiStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DataSetResp;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@@ -97,15 +104,16 @@ public class QueryReqBuilder {
|
||||
* convert to QueryS2SQLReq
|
||||
*
|
||||
* @param querySql
|
||||
* @param dataSetId
|
||||
* @param dataSet
|
||||
* @return
|
||||
*/
|
||||
public static QuerySqlReq buildS2SQLReq(String querySql, Long dataSetId) {
|
||||
public static QuerySqlReq buildS2SQLReq(String querySql, DataSetResp dataSet) {
|
||||
QuerySqlReq querySQLReq = new QuerySqlReq();
|
||||
if (Objects.nonNull(querySql)) {
|
||||
querySQLReq.setSql(querySql);
|
||||
}
|
||||
querySQLReq.setDataSetId(dataSetId);
|
||||
querySQLReq.setDataSetId(dataSet.getId());
|
||||
querySQLReq.setDataSetName(dataSet.getName());
|
||||
return querySQLReq;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,22 @@
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
<version>8.1.2.192</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kyuubi</groupId>
|
||||
<artifactId>kyuubi-hive-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.facebook.presto</groupId>
|
||||
<artifactId>presto-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.trino</groupId>
|
||||
<artifactId>trino-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
<artifactId>jgrapht-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -5,23 +5,27 @@ import com.tencent.supersonic.headless.api.pojo.DBColumn;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.FieldType;
|
||||
import com.tencent.supersonic.headless.core.pojo.ConnectInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BaseDbAdaptor implements DbAdaptor {
|
||||
|
||||
@Override
|
||||
public List<String> getCatalogs(ConnectInfo connectInfo) throws SQLException {
|
||||
// Apart from supporting multiple catalog types of data sources, other types will return an
|
||||
// empty set by default.
|
||||
return List.of();
|
||||
List<String> catalogs = Lists.newArrayList();
|
||||
try (Connection con = getConnection(connectInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery("SHOW CATALOGS")) {
|
||||
while (rs.next()) {
|
||||
catalogs.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return catalogs;
|
||||
}
|
||||
|
||||
public List<String> getDBs(ConnectInfo connectionInfo, String catalog) throws SQLException {
|
||||
@@ -32,38 +36,50 @@ public abstract class BaseDbAdaptor implements DbAdaptor {
|
||||
|
||||
protected List<String> getDBs(ConnectInfo connectionInfo) throws SQLException {
|
||||
List<String> dbs = Lists.newArrayList();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectionInfo);
|
||||
try {
|
||||
ResultSet schemaSet = metaData.getSchemas();
|
||||
while (schemaSet.next()) {
|
||||
String db = schemaSet.getString("TABLE_SCHEM");
|
||||
dbs.add(db);
|
||||
try (ResultSet schemaSet = getDatabaseMetaData(connectionInfo).getSchemas()) {
|
||||
while (schemaSet.next()) {
|
||||
String db = schemaSet.getString("TABLE_SCHEM");
|
||||
dbs.add(db);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("get meta schemas failed, try to get catalogs");
|
||||
log.warn("get meta schemas failed", e);
|
||||
log.warn("get meta schemas failed, try to get catalogs");
|
||||
}
|
||||
try {
|
||||
ResultSet catalogSet = metaData.getCatalogs();
|
||||
while (catalogSet.next()) {
|
||||
String db = catalogSet.getString("TABLE_CAT");
|
||||
dbs.add(db);
|
||||
try (ResultSet catalogSet = getDatabaseMetaData(connectionInfo).getCatalogs()) {
|
||||
while (catalogSet.next()) {
|
||||
String db = catalogSet.getString("TABLE_CAT");
|
||||
dbs.add(db);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("get meta catalogs failed, try to get schemas");
|
||||
log.warn("get meta catalogs failed", e);
|
||||
log.warn("get meta catalogs failed, try to get schemas");
|
||||
}
|
||||
return dbs;
|
||||
}
|
||||
|
||||
public List<String> getTables(ConnectInfo connectionInfo, String schemaName)
|
||||
@Override
|
||||
public List<String> getTables(ConnectInfo connectInfo, String catalog, String schemaName)
|
||||
throws SQLException {
|
||||
// Except for special types implemented separately, the generic logic catalog does not take
|
||||
// effect.
|
||||
return getTables(connectInfo, schemaName);
|
||||
}
|
||||
|
||||
protected List<String> getTables(ConnectInfo connectionInfo, String schemaName)
|
||||
throws SQLException {
|
||||
List<String> tablesAndViews = new ArrayList<>();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectionInfo);
|
||||
|
||||
try {
|
||||
ResultSet resultSet = getResultSet(schemaName, metaData);
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString("TABLE_NAME");
|
||||
tablesAndViews.add(name);
|
||||
try (ResultSet resultSet =
|
||||
getResultSet(schemaName, getDatabaseMetaData(connectionInfo))) {
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString("TABLE_NAME");
|
||||
tablesAndViews.add(name);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Failed to get tables and views", e);
|
||||
@@ -76,27 +92,35 @@ public abstract class BaseDbAdaptor implements DbAdaptor {
|
||||
return metaData.getTables(schemaName, schemaName, null, new String[] {"TABLE", "VIEW"});
|
||||
}
|
||||
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String schemaName, String tableName)
|
||||
throws SQLException {
|
||||
List<DBColumn> dbColumns = Lists.newArrayList();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectInfo);
|
||||
ResultSet columns = metaData.getColumns(schemaName, schemaName, tableName, null);
|
||||
while (columns.next()) {
|
||||
String columnName = columns.getString("COLUMN_NAME");
|
||||
String dataType = columns.getString("TYPE_NAME");
|
||||
String remarks = columns.getString("REMARKS");
|
||||
FieldType fieldType = classifyColumnType(dataType);
|
||||
dbColumns.add(new DBColumn(columnName, dataType, remarks, fieldType));
|
||||
|
||||
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException {
|
||||
List<DBColumn> dbColumns = new ArrayList<>();
|
||||
// 确保连接会自动关闭
|
||||
try (ResultSet columns =
|
||||
getDatabaseMetaData(connectInfo).getColumns(catalog, schemaName, tableName, null)) {
|
||||
while (columns.next()) {
|
||||
String columnName = columns.getString("COLUMN_NAME");
|
||||
String dataType = columns.getString("TYPE_NAME");
|
||||
String remarks = columns.getString("REMARKS");
|
||||
FieldType fieldType = classifyColumnType(dataType);
|
||||
dbColumns.add(new DBColumn(columnName, dataType, remarks, fieldType));
|
||||
}
|
||||
}
|
||||
return dbColumns;
|
||||
}
|
||||
|
||||
protected DatabaseMetaData getDatabaseMetaData(ConnectInfo connectionInfo) throws SQLException {
|
||||
Connection connection = DriverManager.getConnection(connectionInfo.getUrl(),
|
||||
connectionInfo.getUserName(), connectionInfo.getPassword());
|
||||
Connection connection = getConnection(connectionInfo);
|
||||
return connection.getMetaData();
|
||||
}
|
||||
|
||||
public Connection getConnection(ConnectInfo connectionInfo) throws SQLException {
|
||||
final Properties properties = getProperties(connectionInfo);
|
||||
return DriverManager.getConnection(connectionInfo.getUrl(), properties);
|
||||
}
|
||||
|
||||
public FieldType classifyColumnType(String typeName) {
|
||||
switch (typeName.toUpperCase()) {
|
||||
case "INT":
|
||||
@@ -118,4 +142,24 @@ public abstract class BaseDbAdaptor implements DbAdaptor {
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getProperties(ConnectInfo connectionInfo) {
|
||||
final Properties properties = new Properties();
|
||||
String url = connectionInfo.getUrl().toLowerCase();
|
||||
|
||||
// 设置通用属性
|
||||
properties.setProperty("user", connectionInfo.getUserName());
|
||||
|
||||
// 针对 Presto 和 Trino ssl=false 的情况,不需要设置密码
|
||||
if (url.startsWith("jdbc:presto") || url.startsWith("jdbc:trino")) {
|
||||
// 检查是否需要处理 SSL
|
||||
if (!url.contains("ssl=false")) {
|
||||
properties.setProperty("password", connectionInfo.getPassword());
|
||||
}
|
||||
} else {
|
||||
// 针对其他数据库类型
|
||||
properties.setProperty("password", connectionInfo.getPassword());
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ public interface DbAdaptor {
|
||||
|
||||
List<String> getDBs(ConnectInfo connectInfo, String catalog) throws SQLException;
|
||||
|
||||
List<String> getTables(ConnectInfo connectInfo, String schemaName) throws SQLException;
|
||||
|
||||
List<DBColumn> getColumns(ConnectInfo connectInfo, String schemaName, String tableName)
|
||||
List<String> getTables(ConnectInfo connectInfo, String catalog, String schemaName)
|
||||
throws SQLException;
|
||||
|
||||
List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException;
|
||||
|
||||
FieldType classifyColumnType(String typeName);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ public class DbAdaptorFactory {
|
||||
dbAdaptorMap.put(EngineType.DUCKDB.getName(), new DuckdbAdaptor());
|
||||
dbAdaptorMap.put(EngineType.HANADB.getName(), new HanadbAdaptor());
|
||||
dbAdaptorMap.put(EngineType.STARROCKS.getName(), new StarrocksAdaptor());
|
||||
dbAdaptorMap.put(EngineType.KYUUBI.getName(), new KyuubiAdaptor());
|
||||
dbAdaptorMap.put(EngineType.PRESTO.getName(), new PrestoAdaptor());
|
||||
dbAdaptorMap.put(EngineType.TRINO.getName(), new TrinoAdaptor());
|
||||
}
|
||||
|
||||
public static DbAdaptor getEngineAdaptor(String engineType) {
|
||||
|
||||
@@ -19,8 +19,8 @@ public class DuckdbAdaptor extends DefaultDbAdaptor {
|
||||
return metaData.getTables(schemaName, null, null, new String[] {"TABLE", "VIEW"});
|
||||
}
|
||||
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String schemaName, String tableName)
|
||||
throws SQLException {
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException {
|
||||
List<DBColumn> dbColumns = Lists.newArrayList();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectInfo);
|
||||
ResultSet columns = metaData.getColumns(schemaName, null, tableName, null);
|
||||
|
||||
@@ -46,8 +46,8 @@ public class H2Adaptor extends BaseDbAdaptor {
|
||||
return metaData.getTables(schemaName, null, null, new String[] {"TABLE", "VIEW"});
|
||||
}
|
||||
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String schemaName, String tableName)
|
||||
throws SQLException {
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException {
|
||||
List<DBColumn> dbColumns = Lists.newArrayList();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectInfo);
|
||||
ResultSet columns = metaData.getColumns(schemaName, null, tableName, null);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.tencent.supersonic.headless.core.adaptor.db;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||
import com.tencent.supersonic.headless.api.pojo.DBColumn;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.FieldType;
|
||||
import com.tencent.supersonic.headless.core.pojo.ConnectInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class KyuubiAdaptor extends BaseDbAdaptor {
|
||||
|
||||
/** transform YYYYMMDD to YYYY-MM-DD YYYY-MM YYYY-MM-DD(MONDAY) */
|
||||
@Override
|
||||
public String getDateFormat(String dateType, String dateFormat, String column) {
|
||||
if (dateFormat.equalsIgnoreCase(Constants.DAY_FORMAT_INT)) {
|
||||
if (TimeDimensionEnum.MONTH.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(%s, 'yyyy-MM')", column);
|
||||
} else if (TimeDimensionEnum.WEEK.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(date_sub(%s, (dayofweek(%s) - 2)), 'yyyy-MM-dd')",
|
||||
column, column);
|
||||
} else {
|
||||
return String.format(
|
||||
"date_format(to_date(cast(%s as string), 'yyyyMMdd'), 'yyyy-MM-dd')",
|
||||
column);
|
||||
}
|
||||
} else if (dateFormat.equalsIgnoreCase(Constants.DAY_FORMAT)) {
|
||||
if (TimeDimensionEnum.MONTH.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(%s, 'yyyy-MM')", column);
|
||||
} else if (TimeDimensionEnum.WEEK.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(date_sub(%s, (dayofweek(%s) - 2)), 'yyyy-MM-dd')",
|
||||
column, column);
|
||||
} else {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDBs(ConnectInfo connectionInfo, String catalog) throws SQLException {
|
||||
List<String> dbs = Lists.newArrayList();
|
||||
final StringBuilder sql = new StringBuilder("SHOW DATABASES");
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
sql.append(" IN ").append(catalog);
|
||||
}
|
||||
try (Connection con = getConnection(connectionInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery(sql.toString())) {
|
||||
while (rs.next()) {
|
||||
dbs.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return dbs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTables(ConnectInfo connectInfo, String catalog, String schemaName)
|
||||
throws SQLException {
|
||||
List<String> tablesAndViews = new ArrayList<>();
|
||||
|
||||
try {
|
||||
try (ResultSet resultSet = getDatabaseMetaData(connectInfo).getTables(catalog,
|
||||
schemaName, null, new String[] {"TABLE", "VIEW"})) {
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString("TABLE_NAME");
|
||||
tablesAndViews.add(name);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Failed to get tables and views", e);
|
||||
}
|
||||
return tablesAndViews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewriteSql(String sql) {
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,8 @@ public class PostgresqlAdaptor extends BaseDbAdaptor {
|
||||
return tablesAndViews;
|
||||
}
|
||||
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String schemaName, String tableName)
|
||||
throws SQLException {
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException {
|
||||
List<DBColumn> dbColumns = Lists.newArrayList();
|
||||
DatabaseMetaData metaData = getDatabaseMetaData(connectInfo);
|
||||
ResultSet columns = metaData.getColumns(null, null, tableName, null);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.tencent.supersonic.headless.core.adaptor.db;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||
import com.tencent.supersonic.headless.api.pojo.DBColumn;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.FieldType;
|
||||
import com.tencent.supersonic.headless.core.pojo.ConnectInfo;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
public class PrestoAdaptor extends BaseDbAdaptor {
|
||||
|
||||
/** transform YYYYMMDD to YYYY-MM-DD YYYY-MM YYYY-MM-DD(MONDAY) */
|
||||
@Override
|
||||
public String getDateFormat(String dateType, String dateFormat, String column) {
|
||||
if (dateFormat.equalsIgnoreCase(Constants.DAY_FORMAT_INT)) {
|
||||
if (TimeDimensionEnum.MONTH.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(%s, '%%Y-%%m')", column);
|
||||
} else if (TimeDimensionEnum.WEEK.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format(
|
||||
"date_format(date_add('day', - (day_of_week(%s) - 2), %s), '%%Y-%%m-%%d')",
|
||||
column, column);
|
||||
} else {
|
||||
return String.format("date_format(date_parse(%s, '%%Y%%m%%d'), '%%Y-%%m-%%d')",
|
||||
column);
|
||||
}
|
||||
} else if (dateFormat.equalsIgnoreCase(Constants.DAY_FORMAT)) {
|
||||
if (TimeDimensionEnum.MONTH.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format("date_format(%s, '%%Y-%%m')", column);
|
||||
} else if (TimeDimensionEnum.WEEK.name().equalsIgnoreCase(dateType)) {
|
||||
return String.format(
|
||||
"date_format(date_add('day', - (day_of_week(%s) - 2), %s), '%%Y-%%m-%%d')",
|
||||
column, column);
|
||||
} else {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDBs(ConnectInfo connectionInfo, String catalog) throws SQLException {
|
||||
List<String> dbs = Lists.newArrayList();
|
||||
final StringBuilder sql = new StringBuilder("SHOW SCHEMAS");
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
sql.append(" IN ").append(catalog);
|
||||
}
|
||||
try (Connection con = getConnection(connectionInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery(sql.toString())) {
|
||||
while (rs.next()) {
|
||||
dbs.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return dbs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTables(ConnectInfo connectInfo, String catalog, String schemaName)
|
||||
throws SQLException {
|
||||
List<String> tablesAndViews = new ArrayList<>();
|
||||
final StringBuilder sql = new StringBuilder("SHOW TABLES");
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
sql.append(" IN ").append(catalog).append(".").append(schemaName);
|
||||
} else {
|
||||
sql.append(" IN ").append(schemaName);
|
||||
}
|
||||
|
||||
try (Connection con = getConnection(connectInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery(sql.toString())) {
|
||||
while (rs.next()) {
|
||||
tablesAndViews.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return tablesAndViews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewriteSql(String sql) {
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,86 @@
|
||||
package com.tencent.supersonic.headless.core.adaptor.db;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.headless.api.pojo.DBColumn;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.FieldType;
|
||||
import com.tencent.supersonic.headless.core.pojo.ConnectInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@Slf4j
|
||||
public class StarrocksAdaptor extends MysqlAdaptor {
|
||||
|
||||
@Override
|
||||
public List<String> getCatalogs(ConnectInfo connectInfo) throws SQLException {
|
||||
List<String> catalogs = Lists.newArrayList();
|
||||
try (Connection con = DriverManager.getConnection(connectInfo.getUrl(),
|
||||
connectInfo.getUserName(), connectInfo.getPassword());
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery("SHOW CATALOGS")) {
|
||||
while (rs.next()) {
|
||||
catalogs.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return catalogs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDBs(ConnectInfo connectionInfo, String catalog) throws SQLException {
|
||||
Assert.hasText(catalog, "StarRocks type catalog can not be null or empty");
|
||||
List<String> dbs = Lists.newArrayList();
|
||||
try (Connection con = DriverManager.getConnection(connectionInfo.getUrl(),
|
||||
connectionInfo.getUserName(), connectionInfo.getPassword());
|
||||
final StringBuilder sql = new StringBuilder("SHOW DATABASES");
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
sql.append(" IN ").append(catalog);
|
||||
}
|
||||
try (Connection con = getConnection(connectionInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery("SHOW DATABASES IN " + catalog)) {
|
||||
ResultSet rs = st.executeQuery(sql.toString())) {
|
||||
while (rs.next()) {
|
||||
dbs.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return dbs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTables(ConnectInfo connectInfo, String catalog, String schemaName)
|
||||
throws SQLException {
|
||||
List<String> tablesAndViews = new ArrayList<>();
|
||||
final StringBuilder sql = new StringBuilder("SHOW TABLES");
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
sql.append(" IN ").append(catalog).append(".").append(schemaName);
|
||||
} else {
|
||||
sql.append(" IN ").append(schemaName);
|
||||
}
|
||||
|
||||
try (Connection con = getConnection(connectInfo);
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery(sql.toString())) {
|
||||
while (rs.next()) {
|
||||
tablesAndViews.add(rs.getString(1));
|
||||
}
|
||||
}
|
||||
return tablesAndViews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DBColumn> getColumns(ConnectInfo connectInfo, String catalog, String schemaName,
|
||||
String tableName) throws SQLException {
|
||||
List<DBColumn> dbColumns = new ArrayList<>();
|
||||
|
||||
try (Connection con = getConnection(connectInfo); Statement st = con.createStatement()) {
|
||||
|
||||
// 切换到指定的 catalog(或 database/schema),这在某些 SQL 方言中很重要
|
||||
if (StringUtils.isNotBlank(catalog)) {
|
||||
st.execute("SET CATALOG " + catalog);
|
||||
}
|
||||
|
||||
// 获取 DatabaseMetaData; 需要注意调用此方法的位置(在 USE 之后)
|
||||
DatabaseMetaData metaData = con.getMetaData();
|
||||
|
||||
// 获取特定表的列信息
|
||||
try (ResultSet columns = metaData.getColumns(schemaName, schemaName, tableName, null)) {
|
||||
while (columns.next()) {
|
||||
String columnName = columns.getString("COLUMN_NAME");
|
||||
String dataType = columns.getString("TYPE_NAME");
|
||||
String remarks = columns.getString("REMARKS");
|
||||
FieldType fieldType = classifyColumnType(dataType);
|
||||
dbColumns.add(new DBColumn(columnName, dataType, remarks, fieldType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dbColumns;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.tencent.supersonic.headless.core.adaptor.db;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||
|
||||
public class TrinoAdaptor extends PrestoAdaptor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.headless.core.translator;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.config.ParameterConfig;
|
||||
import com.tencent.supersonic.common.pojo.Parameter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service("HeadlessTranslatorConfig")
|
||||
@Slf4j
|
||||
public class TranslatorConfig extends ParameterConfig {
|
||||
|
||||
public static final Parameter TRANSLATOR_RESULT_LIMIT =
|
||||
new Parameter("s2.query-optimizer.resultLimit", "1000", "查询最大返回数据行数",
|
||||
"为了前端展示性能考虑,请不要设置过大", "number", "语义翻译配置");
|
||||
|
||||
@Override
|
||||
public List<Parameter> getSysParameters() {
|
||||
return Lists.newArrayList(TRANSLATOR_RESULT_LIMIT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,6 @@ public class ResultLimitOptimizer implements QueryOptimizer {
|
||||
|
||||
@Override
|
||||
public void rewrite(QueryStatement queryStatement) {
|
||||
queryStatement.setSql(queryStatement.getSql() + " limit " + queryStatement.getLimit());
|
||||
queryStatement.setSql(queryStatement.getSql() + " LIMIT " + queryStatement.getLimit());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.tencent.supersonic.headless.core.translator.parser.calcite;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.tencent.supersonic.common.calcite.Configuration;
|
||||
import com.tencent.supersonic.common.pojo.enums.EngineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.Dimension;
|
||||
@@ -9,17 +10,26 @@ import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DimSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.core.pojo.*;
|
||||
import com.tencent.supersonic.headless.core.pojo.JoinRelation;
|
||||
import com.tencent.supersonic.headless.core.pojo.Ontology;
|
||||
import com.tencent.supersonic.headless.core.pojo.OntologyQuery;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.translator.parser.Constants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.calcite.sql.*;
|
||||
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
|
||||
import org.apache.calcite.sql.parser.SqlParseException;
|
||||
import org.apache.calcite.sql.parser.SqlParser;
|
||||
import org.apache.calcite.sql.parser.SqlParserPos;
|
||||
import org.apache.calcite.sql.validate.SqlValidatorScope;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.jgrapht.Graph;
|
||||
import org.jgrapht.GraphPath;
|
||||
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
|
||||
import org.jgrapht.graph.DefaultEdge;
|
||||
import org.jgrapht.graph.DefaultUndirectedGraph;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -37,6 +47,8 @@ public class SqlBuilder {
|
||||
|
||||
public String buildOntologySql(QueryStatement queryStatement) throws Exception {
|
||||
OntologyQuery ontologyQuery = queryStatement.getOntologyQuery();
|
||||
Ontology ontology = queryStatement.getOntology();
|
||||
|
||||
if (ontologyQuery.getLimit() == null) {
|
||||
ontologyQuery.setLimit(0L);
|
||||
}
|
||||
@@ -46,7 +58,14 @@ public class SqlBuilder {
|
||||
throw new Exception("data model not found");
|
||||
}
|
||||
|
||||
TableView tableView = render(ontologyQuery, new ArrayList<>(dataModels), scope, schema);
|
||||
TableView tableView;
|
||||
if (!CollectionUtils.isEmpty(ontology.getJoinRelations()) && dataModels.size() > 1) {
|
||||
Set<ModelResp> models = probeRelatedModels(dataModels, queryStatement.getOntology());
|
||||
tableView = render(ontologyQuery, models, scope, schema);
|
||||
} else {
|
||||
tableView = render(ontologyQuery, dataModels, scope, schema);
|
||||
}
|
||||
|
||||
SqlNode parserNode = tableView.build();
|
||||
DatabaseResp database = queryStatement.getOntology().getDatabase();
|
||||
EngineType engineType = EngineType.fromString(database.getType());
|
||||
@@ -54,7 +73,61 @@ public class SqlBuilder {
|
||||
return SemanticNode.getSql(parserNode, engineType);
|
||||
}
|
||||
|
||||
private SqlNode optimizeParseNode(SqlNode parserNode, EngineType engineType) {
|
||||
private Set<ModelResp> probeRelatedModels(Set<ModelResp> dataModels, Ontology ontology) {
|
||||
List<JoinRelation> joinRelations = ontology.getJoinRelations();
|
||||
Graph<String, DefaultEdge> graph = buildGraph(joinRelations);
|
||||
DijkstraShortestPath<String, DefaultEdge> dijkstraAlg = new DijkstraShortestPath<>(graph);
|
||||
Set<String> queryModels =
|
||||
dataModels.stream().map(ModelResp::getName).collect(Collectors.toSet());
|
||||
GraphPath<String, DefaultEdge> selectedGraphPath = null;
|
||||
for (String fromModel : queryModels) {
|
||||
for (String toModel : queryModels) {
|
||||
if (fromModel != toModel) {
|
||||
GraphPath<String, DefaultEdge> path = dijkstraAlg.getPath(fromModel, toModel);
|
||||
if (isGraphPathContainsAll(path, queryModels)) {
|
||||
selectedGraphPath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedGraphPath == null) {
|
||||
return dataModels;
|
||||
}
|
||||
Set<String> modelNames = Sets.newHashSet();
|
||||
for (DefaultEdge edge : selectedGraphPath.getEdgeList()) {
|
||||
modelNames.add(selectedGraphPath.getGraph().getEdgeSource(edge));
|
||||
modelNames.add(selectedGraphPath.getGraph().getEdgeTarget(edge));
|
||||
}
|
||||
return modelNames.stream().map(m -> ontology.getModelMap().get(m))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isGraphPathContainsAll(GraphPath<String, DefaultEdge> graphPath,
|
||||
Set<String> vertex) {
|
||||
Set<String> allVertex = Sets.newHashSet();
|
||||
for (DefaultEdge edge : graphPath.getEdgeList()) {
|
||||
allVertex.add(graphPath.getGraph().getEdgeSource(edge));
|
||||
allVertex.add(graphPath.getGraph().getEdgeTarget(edge));
|
||||
}
|
||||
Collection<String> intersect =
|
||||
org.apache.commons.collections.CollectionUtils.intersection(vertex, allVertex);
|
||||
|
||||
return intersect.size() == vertex.size() ? true : false;
|
||||
}
|
||||
|
||||
private Graph<String, DefaultEdge> buildGraph(List<JoinRelation> joinRelations) {
|
||||
Graph<String, DefaultEdge> directedGraph = new DefaultUndirectedGraph<>(DefaultEdge.class);
|
||||
for (JoinRelation joinRelation : joinRelations) {
|
||||
directedGraph.addVertex(joinRelation.getLeft());
|
||||
directedGraph.addVertex(joinRelation.getRight());
|
||||
directedGraph.addEdge(joinRelation.getLeft(), joinRelation.getRight());
|
||||
}
|
||||
return directedGraph;
|
||||
}
|
||||
|
||||
private SqlNode optimizeParseNode(SqlNode parserNode, EngineType engineType)
|
||||
throws SqlParseException {
|
||||
if (Objects.isNull(schema.getRuntimeOptions())
|
||||
|| Objects.isNull(schema.getRuntimeOptions().getEnableOptimize())
|
||||
|| !schema.getRuntimeOptions().getEnableOptimize()) {
|
||||
@@ -62,14 +135,10 @@ public class SqlBuilder {
|
||||
}
|
||||
|
||||
SqlNode optimizeNode = null;
|
||||
try {
|
||||
SqlNode sqlNode = SqlParser.create(SemanticNode.getSql(parserNode, engineType),
|
||||
Configuration.getParserConfig(engineType)).parseStmt();
|
||||
if (Objects.nonNull(sqlNode)) {
|
||||
optimizeNode = SemanticNode.optimize(scope, schema, sqlNode, engineType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("optimize error {}", e);
|
||||
SqlNode sqlNode = SqlParser.create(SemanticNode.getSql(parserNode, engineType),
|
||||
Configuration.getParserConfig(engineType)).parseStmt();
|
||||
if (Objects.nonNull(sqlNode)) {
|
||||
optimizeNode = SemanticNode.optimize(scope, schema, sqlNode, engineType);
|
||||
}
|
||||
|
||||
if (Objects.nonNull(optimizeNode)) {
|
||||
@@ -79,7 +148,7 @@ public class SqlBuilder {
|
||||
return parserNode;
|
||||
}
|
||||
|
||||
private TableView render(OntologyQuery ontologyQuery, List<ModelResp> dataModels,
|
||||
private TableView render(OntologyQuery ontologyQuery, Set<ModelResp> dataModels,
|
||||
SqlValidatorScope scope, S2CalciteSchema schema) throws Exception {
|
||||
SqlNode left = null;
|
||||
TableView leftTable = null;
|
||||
@@ -88,8 +157,7 @@ public class SqlBuilder {
|
||||
Map<String, String> beforeModels = new HashMap<>();
|
||||
EngineType engineType = EngineType.fromString(schema.getOntology().getDatabase().getType());
|
||||
|
||||
for (int i = 0; i < dataModels.size(); i++) {
|
||||
final ModelResp dataModel = dataModels.get(i);
|
||||
for (ModelResp dataModel : dataModels) {
|
||||
final Set<DimSchemaResp> queryDimensions =
|
||||
ontologyQuery.getDimensionsByModel(dataModel.getName());
|
||||
final Set<MetricSchemaResp> queryMetrics =
|
||||
@@ -141,7 +209,8 @@ public class SqlBuilder {
|
||||
SqlLiteral sqlLiteral = SemanticNode.getJoinSqlLiteral("");
|
||||
JoinRelation matchJoinRelation = getMatchJoinRelation(before, rightTable, schema);
|
||||
SqlNode joinRelationCondition;
|
||||
if (!CollectionUtils.isEmpty(matchJoinRelation.getJoinCondition())) {
|
||||
if (!org.apache.commons.collections.CollectionUtils
|
||||
.isEmpty(matchJoinRelation.getJoinCondition())) {
|
||||
sqlLiteral = SemanticNode.getJoinSqlLiteral(matchJoinRelation.getJoinType());
|
||||
joinRelationCondition = getCondition(matchJoinRelation, scope, engineType);
|
||||
condition = joinRelationCondition;
|
||||
@@ -170,12 +239,19 @@ public class SqlBuilder {
|
||||
} else if (joinRelation.getLeft()
|
||||
.equalsIgnoreCase(tableView.getDataModel().getName())
|
||||
&& before.containsKey(joinRelation.getRight())) {
|
||||
matchJoinRelation.setJoinCondition(joinRelation.getJoinCondition().stream()
|
||||
List<Triple<String, String, String>> candidateJoinCon = joinRelation
|
||||
.getJoinCondition().stream()
|
||||
.map(r -> Triple.of(
|
||||
before.get(joinRelation.getRight()) + "." + r.getRight(),
|
||||
r.getMiddle(), tableView.getAlias() + "." + r.getLeft()))
|
||||
.collect(Collectors.toList()));
|
||||
matchJoinRelation.setJoinType(joinRelation.getJoinType());
|
||||
.collect(Collectors.toList());
|
||||
// added by jerryjzhang on 20250214
|
||||
// use the one with the most conditions to join left and right tables
|
||||
if (matchJoinRelation.getJoinCondition() == null || candidateJoinCon
|
||||
.size() > matchJoinRelation.getJoinCondition().size()) {
|
||||
matchJoinRelation.setJoinCondition(candidateJoinCon);
|
||||
matchJoinRelation.setJoinType(joinRelation.getJoinType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,11 +40,24 @@ public class JdbcDataSourceUtils {
|
||||
log.error(e.toString(), e);
|
||||
return false;
|
||||
}
|
||||
try (Connection con = DriverManager.getConnection(database.getUrl(), database.getUsername(),
|
||||
database.passwordDecrypt())) {
|
||||
return con != null;
|
||||
} catch (SQLException e) {
|
||||
log.error(e.toString(), e);
|
||||
// presto/trino ssl=false connection need password
|
||||
if (database.getUrl().startsWith("jdbc:presto")
|
||||
|| database.getUrl().startsWith("jdbc:trino")) {
|
||||
if (database.getUrl().toLowerCase().contains("ssl=false")) {
|
||||
try (Connection con = DriverManager.getConnection(database.getUrl(),
|
||||
database.getUsername(), null)) {
|
||||
return con != null;
|
||||
} catch (SQLException e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try (Connection con = DriverManager.getConnection(database.getUrl(),
|
||||
database.getUsername(), database.passwordDecrypt())) {
|
||||
return con != null;
|
||||
} catch (SQLException e) {
|
||||
log.error(e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -73,30 +73,46 @@ public class SqlGenerateUtils {
|
||||
public String getSelect(StructQuery structQuery) {
|
||||
String aggStr = structQuery.getAggregators().stream().map(this::getSelectField)
|
||||
.collect(Collectors.joining(","));
|
||||
return CollectionUtils.isEmpty(structQuery.getGroups()) ? aggStr
|
||||
: String.join(",", structQuery.getGroups()) + "," + aggStr;
|
||||
String result = String.join(",", structQuery.getGroups());
|
||||
if (StringUtils.isNotBlank(aggStr)) {
|
||||
if (!CollectionUtils.isEmpty(structQuery.getGroups())) {
|
||||
result = String.join(",", structQuery.getGroups()) + "," + aggStr;
|
||||
} else {
|
||||
result = aggStr;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getSelect(StructQuery structQuery, Map<String, String> deriveMetrics) {
|
||||
String aggStr = structQuery.getAggregators().stream()
|
||||
.map(a -> getSelectField(a, deriveMetrics)).collect(Collectors.joining(","));
|
||||
return CollectionUtils.isEmpty(structQuery.getGroups()) ? aggStr
|
||||
: String.join(",", structQuery.getGroups()) + "," + aggStr;
|
||||
String result = String.join(",", structQuery.getGroups());
|
||||
if (StringUtils.isNotBlank(aggStr)) {
|
||||
if (!CollectionUtils.isEmpty(structQuery.getGroups())) {
|
||||
result = String.join(",", structQuery.getGroups()) + "," + aggStr;
|
||||
} else {
|
||||
result = aggStr;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getSelectField(final Aggregator agg) {
|
||||
if (AggOperatorEnum.COUNT_DISTINCT.equals(agg.getFunc())) {
|
||||
return "count(distinct " + agg.getColumn() + " ) AS " + agg.getColumn() + " ";
|
||||
return "count(distinct " + agg.getColumn() + " ) ";
|
||||
}
|
||||
if (CollectionUtils.isEmpty(agg.getArgs())) {
|
||||
return agg.getFunc() + "( " + agg.getColumn() + " ) AS " + agg.getColumn() + " ";
|
||||
return agg.getFunc() + "( " + agg.getColumn() + " ) ";
|
||||
}
|
||||
return agg.getFunc() + "( "
|
||||
+ agg.getArgs().stream()
|
||||
.map(arg -> arg.equals(agg.getColumn()) ? arg
|
||||
: (StringUtils.isNumeric(arg) ? arg : ("'" + arg + "'")))
|
||||
.collect(Collectors.joining(","))
|
||||
+ " ) AS " + agg.getColumn() + " ";
|
||||
+ " ) ";
|
||||
}
|
||||
|
||||
public String getSelectField(final Aggregator agg, Map<String, String> deriveMetrics) {
|
||||
@@ -140,7 +156,10 @@ public class SqlGenerateUtils {
|
||||
public String generateWhere(StructQuery structQuery, ItemDateResp itemDateResp) {
|
||||
String whereClauseFromFilter =
|
||||
sqlFilterUtils.getWhereClause(structQuery.getDimensionFilters());
|
||||
String whereFromDate = getDateWhereClause(structQuery.getDateInfo(), itemDateResp);
|
||||
String whereFromDate = "";
|
||||
if (structQuery.getDateInfo() != null) {
|
||||
whereFromDate = getDateWhereClause(structQuery.getDateInfo(), itemDateResp);
|
||||
}
|
||||
String mergedWhere =
|
||||
mergeDateWhereClause(structQuery, whereClauseFromFilter, whereFromDate);
|
||||
if (StringUtils.isNotBlank(mergedWhere)) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
|
||||
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
|
||||
import com.tencent.supersonic.auth.api.authorization.service.AuthService;
|
||||
import com.tencent.supersonic.common.jsqlparser.SqlAddHelper;
|
||||
import com.tencent.supersonic.common.jsqlparser.SqlReplaceHelper;
|
||||
import com.tencent.supersonic.common.pojo.Filter;
|
||||
import com.tencent.supersonic.common.pojo.QueryAuthorization;
|
||||
import com.tencent.supersonic.common.pojo.User;
|
||||
@@ -73,6 +74,15 @@ public class S2DataPermissionAspect {
|
||||
SemanticQueryReq queryReq = null;
|
||||
if (objects[0] instanceof SemanticQueryReq) {
|
||||
queryReq = (SemanticQueryReq) objects[0];
|
||||
if (queryReq instanceof QuerySqlReq) {
|
||||
QuerySqlReq sqlReq = (QuerySqlReq) queryReq;
|
||||
if (sqlReq.getDataSetName() != null) {
|
||||
String escapedTable = SqlReplaceHelper.escapeTableName(sqlReq.getDataSetName());
|
||||
sqlReq.setSql(sqlReq.getSql().replaceAll(
|
||||
String.format(" %s ", sqlReq.getDataSetName()),
|
||||
String.format(" %s ", escapedTable)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (queryReq == null) {
|
||||
throw new InvalidArgumentException("queryReq is not Invalid");
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.pojo.SqlQuery;
|
||||
import com.tencent.supersonic.headless.core.pojo.StructQuery;
|
||||
import com.tencent.supersonic.headless.core.translator.SemanticTranslator;
|
||||
import com.tencent.supersonic.headless.core.translator.TranslatorConfig;
|
||||
import com.tencent.supersonic.headless.core.utils.ComponentFactory;
|
||||
import com.tencent.supersonic.headless.server.annotation.S2DataPermission;
|
||||
import com.tencent.supersonic.headless.server.facade.service.SemanticLayerService;
|
||||
@@ -59,6 +60,7 @@ public class S2SemanticLayerService implements SemanticLayerService {
|
||||
private final KnowledgeBaseService knowledgeBaseService;
|
||||
private final MetricService metricService;
|
||||
private final DimensionService dimensionService;
|
||||
private final TranslatorConfig translatorConfig;
|
||||
private final QueryCache queryCache = ComponentFactory.getQueryCache();
|
||||
private final List<QueryExecutor> queryExecutors = ComponentFactory.getQueryExecutors();
|
||||
|
||||
@@ -67,7 +69,7 @@ public class S2SemanticLayerService implements SemanticLayerService {
|
||||
SchemaService schemaService, SemanticTranslator semanticTranslator,
|
||||
MetricDrillDownChecker metricDrillDownChecker,
|
||||
KnowledgeBaseService knowledgeBaseService, MetricService metricService,
|
||||
DimensionService dimensionService) {
|
||||
DimensionService dimensionService, TranslatorConfig translatorConfig) {
|
||||
this.statUtils = statUtils;
|
||||
this.queryUtils = queryUtils;
|
||||
this.semanticSchemaManager = semanticSchemaManager;
|
||||
@@ -78,6 +80,7 @@ public class S2SemanticLayerService implements SemanticLayerService {
|
||||
this.knowledgeBaseService = knowledgeBaseService;
|
||||
this.metricService = metricService;
|
||||
this.dimensionService = dimensionService;
|
||||
this.translatorConfig = translatorConfig;
|
||||
}
|
||||
|
||||
public DataSetSchema getDataSetSchema(Long id) {
|
||||
@@ -300,6 +303,8 @@ public class S2SemanticLayerService implements SemanticLayerService {
|
||||
|
||||
QueryStatement queryStatement = new QueryStatement();
|
||||
queryStatement.setEnableOptimize(queryUtils.enableOptimize());
|
||||
queryStatement.setLimit(Integer.parseInt(
|
||||
translatorConfig.getParameterValue(TranslatorConfig.TRANSLATOR_RESULT_LIMIT)));
|
||||
queryStatement.setDataSetId(queryReq.getDataSetId());
|
||||
queryStatement.setDataSetName(queryReq.getDataSetName());
|
||||
queryStatement.setSemanticSchema(semanticSchemaResp);
|
||||
|
||||
@@ -17,6 +17,9 @@ public class DbParameterFactory {
|
||||
parametersBuilder.put(EngineType.POSTGRESQL.getName(), new PostgresqlParametersBuilder());
|
||||
parametersBuilder.put(EngineType.HANADB.getName(), new HanadbParametersBuilder());
|
||||
parametersBuilder.put(EngineType.STARROCKS.getName(), new StarrocksParametersBuilder());
|
||||
parametersBuilder.put(EngineType.KYUUBI.getName(), new KyuubiParametersBuilder());
|
||||
parametersBuilder.put(EngineType.PRESTO.getName(), new PrestoParametersBuilder());
|
||||
parametersBuilder.put(EngineType.TRINO.getName(), new TrinoParametersBuilder());
|
||||
parametersBuilder.put(EngineType.OTHER.getName(), new OtherParametersBuilder());
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class DefaultParametersBuilder implements DbParametersBuilder {
|
||||
password.setComment("密码");
|
||||
password.setName("password");
|
||||
password.setPlaceholder("请输入密码");
|
||||
password.setRequire(false);
|
||||
databaseParameters.add(password);
|
||||
|
||||
return databaseParameters;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.supersonic.headless.server.pojo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class KyuubiParametersBuilder extends DefaultParametersBuilder {
|
||||
|
||||
@Override
|
||||
public List<DatabaseParameter> build() {
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.supersonic.headless.server.pojo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PrestoParametersBuilder extends DefaultParametersBuilder {
|
||||
|
||||
@Override
|
||||
public List<DatabaseParameter> build() {
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.supersonic.headless.server.pojo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class TrinoParametersBuilder extends DefaultParametersBuilder {
|
||||
|
||||
@Override
|
||||
public List<DatabaseParameter> build() {
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -89,15 +89,17 @@ public class DatabaseController {
|
||||
|
||||
@RequestMapping("/getTables")
|
||||
public List<String> getTables(@RequestParam("databaseId") Long databaseId,
|
||||
@RequestParam(value = "catalog", required = false) String catalog,
|
||||
@RequestParam("db") String db) throws SQLException {
|
||||
return databaseService.getTables(databaseId, db);
|
||||
return databaseService.getTables(databaseId, catalog, db);
|
||||
}
|
||||
|
||||
@RequestMapping("/getColumnsByName")
|
||||
public List<DBColumn> getColumnsByName(@RequestParam("databaseId") Long databaseId,
|
||||
@RequestParam(name = "catalog", required = false) String catalog,
|
||||
@RequestParam("db") String db, @RequestParam("table") String table)
|
||||
throws SQLException {
|
||||
return databaseService.getColumns(databaseId, db, table);
|
||||
return databaseService.getColumns(databaseId, catalog, db, table);
|
||||
}
|
||||
|
||||
@PostMapping("/listColumnsBySql")
|
||||
|
||||
@@ -40,11 +40,11 @@ public interface DatabaseService {
|
||||
|
||||
List<String> getDbNames(Long id, String catalog) throws SQLException;
|
||||
|
||||
List<String> getTables(Long id, String db) throws SQLException;
|
||||
List<String> getTables(Long id, String catalog, String db) throws SQLException;
|
||||
|
||||
Map<String, List<DBColumn>> getDbColumns(ModelBuildReq modelBuildReq) throws SQLException;
|
||||
|
||||
List<DBColumn> getColumns(Long id, String db, String table) throws SQLException;
|
||||
List<DBColumn> getColumns(Long id, String catalog, String db, String table) throws SQLException;
|
||||
|
||||
List<DBColumn> getColumns(Long id, String sql) throws SQLException;
|
||||
}
|
||||
|
||||
@@ -214,10 +214,10 @@ public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseD
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTables(Long id, String db) throws SQLException {
|
||||
public List<String> getTables(Long id, String catalog, String db) throws SQLException {
|
||||
DatabaseResp databaseResp = getDatabase(id);
|
||||
DbAdaptor dbAdaptor = DbAdaptorFactory.getEngineAdaptor(databaseResp.getType());
|
||||
return dbAdaptor.getTables(DatabaseConverter.getConnectInfo(databaseResp), db);
|
||||
return dbAdaptor.getTables(DatabaseConverter.getConnectInfo(databaseResp), catalog, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,8 +233,8 @@ public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseD
|
||||
dbColumnMap.put(modelBuildReq.getSql(), columns);
|
||||
} else {
|
||||
for (String table : modelBuildReq.getTables()) {
|
||||
List<DBColumn> columns =
|
||||
getColumns(modelBuildReq.getDatabaseId(), modelBuildReq.getDb(), table);
|
||||
List<DBColumn> columns = getColumns(modelBuildReq.getDatabaseId(),
|
||||
modelBuildReq.getCatalog(), modelBuildReq.getDb(), table);
|
||||
dbColumnMap.put(table, columns);
|
||||
}
|
||||
}
|
||||
@@ -242,15 +242,17 @@ public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseD
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DBColumn> getColumns(Long id, String db, String table) throws SQLException {
|
||||
public List<DBColumn> getColumns(Long id, String catalog, String db, String table)
|
||||
throws SQLException {
|
||||
DatabaseResp databaseResp = getDatabase(id);
|
||||
return getColumns(databaseResp, db, table);
|
||||
return getColumns(databaseResp, catalog, db, table);
|
||||
}
|
||||
|
||||
public List<DBColumn> getColumns(DatabaseResp databaseResp, String db, String table)
|
||||
throws SQLException {
|
||||
public List<DBColumn> getColumns(DatabaseResp databaseResp, String catalog, String db,
|
||||
String table) throws SQLException {
|
||||
DbAdaptor engineAdaptor = DbAdaptorFactory.getEngineAdaptor(databaseResp.getType());
|
||||
return engineAdaptor.getColumns(DatabaseConverter.getConnectInfo(databaseResp), db, table);
|
||||
return engineAdaptor.getColumns(DatabaseConverter.getConnectInfo(databaseResp), catalog, db,
|
||||
table);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -145,8 +145,7 @@ public class ModelServiceImpl implements ModelService {
|
||||
// Comment out below checks for now, they seem unnecessary and
|
||||
// lead to unexpected exception in updating model
|
||||
/*
|
||||
checkParams(modelReq);
|
||||
checkRelations(modelReq);
|
||||
* checkParams(modelReq); checkRelations(modelReq);
|
||||
*/
|
||||
ModelDO modelDO = modelRepository.getModelById(modelReq.getId());
|
||||
ModelConverter.convert(modelDO, modelReq, user);
|
||||
|
||||
@@ -110,7 +110,8 @@ public class ModelConverter {
|
||||
dimensionReq.setExpr(dim.getExpr());
|
||||
dimensionReq.setType(dim.getType().name());
|
||||
dimensionReq
|
||||
.setDescription(Objects.isNull(dim.getDescription()) ? "" : dim.getDescription());
|
||||
.setDescription(Objects.isNull(dim.getDescription()) ? dimensionReq.getDescription()
|
||||
: dim.getDescription());
|
||||
dimensionReq.setTypeParams(dim.getTypeParams());
|
||||
return dimensionReq;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,28 @@ public class QueryUtils {
|
||||
column.setDataFormatType(metricRespMap.get(nameEn).getDataFormatType());
|
||||
column.setDataFormat(metricRespMap.get(nameEn).getDataFormat());
|
||||
column.setModelId(metricRespMap.get(nameEn).getModelId());
|
||||
} else {
|
||||
// if column nameEn contains metric name, use metric dataFormatType
|
||||
metricRespMap.values().forEach(metric -> {
|
||||
if (nameEn.contains(metric.getName()) || nameEn.contains(metric.getBizName())) {
|
||||
column.setDataFormatType(metric.getDataFormatType());
|
||||
column.setDataFormat(metric.getDataFormat());
|
||||
column.setModelId(metric.getModelId());
|
||||
}
|
||||
// if column nameEn contains metric alias, use metric dataFormatType
|
||||
if (column.getDataFormatType() == null && metric.getAlias() != null) {
|
||||
for (String alias : metric.getAlias().split(",")) {
|
||||
if (nameEn.contains(alias)) {
|
||||
column.setDataFormatType(metric.getDataFormatType());
|
||||
column.setDataFormat(metric.getDataFormat());
|
||||
column.setModelId(metric.getModelId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (dimensionRespMap.containsKey(nameEn)) {
|
||||
column.setModelId(dimensionRespMap.get(nameEn).getModelId());
|
||||
}
|
||||
@@ -119,7 +140,8 @@ public class QueryUtils {
|
||||
|| type.equalsIgnoreCase("float") || type.equalsIgnoreCase("double")
|
||||
|| type.equalsIgnoreCase("real") || type.equalsIgnoreCase("numeric")
|
||||
|| type.toLowerCase().startsWith("decimal") || type.toLowerCase().startsWith("uint")
|
||||
|| type.toLowerCase().startsWith("int");
|
||||
|| type.toLowerCase().startsWith("int")
|
||||
|| type.toLowerCase().equalsIgnoreCase("decfloat");
|
||||
}
|
||||
|
||||
private String getName(String nameEn) {
|
||||
|
||||
@@ -18,23 +18,14 @@
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>4.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencent.supersonic</groupId>
|
||||
|
||||
@@ -192,14 +192,14 @@ public class S2VisitsDemo extends S2BaseDemo {
|
||||
modelReq.setAdminOrgs(Collections.emptyList());
|
||||
List<Identify> identifiers = new ArrayList<>();
|
||||
ModelDetail modelDetail = new ModelDetail();
|
||||
identifiers.add(new Identify("用户名", IdentifyType.foreign.name(), "user_name", 0));
|
||||
identifiers.add(new Identify("用户名", IdentifyType.foreign.name(), "user_name", 1));
|
||||
modelDetail.setIdentifiers(identifiers);
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
Dimension dimension1 = new Dimension("数据日期", "imp_date", DimensionType.partition_time, 1);
|
||||
dimension1.setTypeParams(new DimensionTimeTypeParams());
|
||||
dimensions.add(dimension1);
|
||||
Dimension dimension2 = new Dimension("", "page", DimensionType.categorical, 0);
|
||||
Dimension dimension2 = new Dimension("", "page", DimensionType.categorical, 1);
|
||||
dimension2.setExpr("page");
|
||||
dimensions.add(dimension2);
|
||||
modelDetail.setDimensions(dimensions);
|
||||
@@ -227,11 +227,11 @@ public class S2VisitsDemo extends S2BaseDemo {
|
||||
modelReq.setAdminOrgs(Collections.emptyList());
|
||||
List<Identify> identifiers = new ArrayList<>();
|
||||
ModelDetail modelDetail = new ModelDetail();
|
||||
identifiers.add(new Identify("用户名", IdentifyType.foreign.name(), "user_name", 0));
|
||||
identifiers.add(new Identify("用户名", IdentifyType.foreign.name(), "user_name", 1));
|
||||
modelDetail.setIdentifiers(identifiers);
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
Dimension dimension1 = new Dimension("数据日期", "imp_date", DimensionType.partition_time, 0);
|
||||
Dimension dimension1 = new Dimension("数据日期", "imp_date", DimensionType.partition_time, 1);
|
||||
dimension1.setTypeParams(new DimensionTimeTypeParams());
|
||||
dimensions.add(dimension1);
|
||||
Dimension dimension2 = new Dimension("页面", "visits_page", DimensionType.categorical, 1);
|
||||
|
||||
@@ -4,9 +4,9 @@ spring:
|
||||
url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/${DB_NAME}?stringtype=unspecified
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
|
||||
sql:
|
||||
init:
|
||||
continue-on-error: true
|
||||
mode: always
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
|
||||
@@ -6,6 +6,7 @@ spring:
|
||||
password: ${S2_DB_PASSWORD:}
|
||||
sql:
|
||||
init:
|
||||
continue-on-error: true
|
||||
mode: always
|
||||
username: ${S2_DB_USER:root}
|
||||
password: ${S2_DB_PASSWORD:}
|
||||
|
||||
@@ -6,6 +6,7 @@ spring:
|
||||
password: ${S2_DB_PASSWORD:postgres}
|
||||
sql:
|
||||
init:
|
||||
continue-on-error: true
|
||||
mode: always
|
||||
username: ${S2_DB_USER:postgres}
|
||||
password: ${S2_DB_PASSWORD:postgres}
|
||||
|
||||
@@ -30,9 +30,29 @@ logging:
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
enabled: true
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
enabled: true
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
paths-to-match: '/**'
|
||||
packages-to-scan: com.tencent.supersonic
|
||||
paths-to-match: /api/chat/**,/api/semantic/**
|
||||
paths-to-match: /api/chat/**,/api/semantic/**
|
||||
|
||||
knife4j:
|
||||
enable: true
|
||||
openapi:
|
||||
title: 'SuperSonic API Documentation'
|
||||
description: 'SuperSonic API Documentation'
|
||||
version: v1.0
|
||||
setting:
|
||||
language: zh-CN
|
||||
# basic:
|
||||
# enable: true
|
||||
# username: test
|
||||
# password: 123456#
|
||||
documents:
|
||||
default:
|
||||
title: ChatBI API Documents
|
||||
description: ChatBI API Documents
|
||||
@@ -405,4 +405,9 @@ ALTER TABLE s2_chat_context RENAME COLUMN `user` TO `query_user`;
|
||||
|
||||
--20241226
|
||||
ALTER TABLE s2_chat_memory add column `query_id` BIGINT DEFAULT NULL;
|
||||
ALTER TABLE s2_query_stat_info RENAME COLUMN `sql` TO `query_sql`;
|
||||
ALTER TABLE s2_query_stat_info RENAME COLUMN `sql` TO `query_sql`;
|
||||
|
||||
--20250224
|
||||
ALTER TABLE s2_agent add column `admin_org` varchar(3000) DEFAULT NULL COMMENT '管理员组织';
|
||||
ALTER TABLE s2_agent add column `view_org` varchar(3000) DEFAULT NULL COMMENT '可用组织';
|
||||
ALTER TABLE s2_agent add column `is_open` tinyint DEFAULT NULL COMMENT '是否公开';
|
||||
@@ -1,8 +1,3 @@
|
||||
-- clear data if already exists
|
||||
DELETE FROM s2_user;
|
||||
DELETE FROM s2_available_date_info;
|
||||
DELETE FROM s2_canvas;
|
||||
|
||||
-- sample user
|
||||
-- The default value for the password is 123456
|
||||
INSERT INTO s2_user (`name`, password, salt, display_name, email, is_admin) values ('admin','c3VwZXJzb25pY0BiaWNvbdktJJYWw6A3rEmBUPzbn/6DNeYnD+y3mAwDKEMS3KVT','jGl25bVBBBW96Qi9Te4V3w==','admin','admin@xx.com', 1);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
-- clear data if already exists
|
||||
DELETE FROM s2_user;
|
||||
DELETE FROM s2_available_date_info;
|
||||
DELETE FROM s2_canvas;
|
||||
|
||||
-- sample user
|
||||
-- The default value for the password is 123456
|
||||
insert into s2_user ("name", password, salt, display_name, email, is_admin) values ('admin','c3VwZXJzb25pY0BiaWNvbdktJJYWw6A3rEmBUPzbn/6DNeYnD+y3mAwDKEMS3KVT','jGl25bVBBBW96Qi9Te4V3w==','admin','admin@xx.com', 1);
|
||||
|
||||
@@ -377,8 +377,11 @@ CREATE TABLE IF NOT EXISTS s2_agent
|
||||
updated_at TIMESTAMP null,
|
||||
enable_search int null,
|
||||
enable_feedback int null,
|
||||
admin varchar(1000),
|
||||
viewer varchar(1000),
|
||||
`admin` varchar(3000) DEFAULT NULL , -- administrator
|
||||
`admin_org` varchar(3000) DEFAULT NULL , -- administrators organization
|
||||
`is_open` TINYINT DEFAULT NULL , -- whether the public
|
||||
`viewer` varchar(3000) DEFAULT NULL , -- available users
|
||||
`view_org` varchar(3000) DEFAULT NULL , -- available organization
|
||||
PRIMARY KEY (`id`)
|
||||
); COMMENT ON TABLE s2_agent IS 'agent information table';
|
||||
|
||||
|
||||
@@ -5,18 +5,21 @@ CREATE TABLE IF NOT EXISTS `s2_agent` (
|
||||
`examples` TEXT COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`status` tinyint DEFAULT NULL,
|
||||
`model` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`tool_config` varchar(6000) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`llm_config` varchar(2000) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`tool_config` TEXT COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`llm_config` TEXT COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`chat_model_config` text COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`visual_config` varchar(2000) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`visual_config` TEXT COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`enable_search` tinyint DEFAULT 1,
|
||||
`enable_feedback` tinyint DEFAULT 1,
|
||||
`created_by` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_by` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`admin` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`viewer` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`admin` varchar(1000) DEFAULT NULL COMMENT '管理员',
|
||||
`admin_org` varchar(1000) DEFAULT NULL COMMENT '管理员组织',
|
||||
`is_open` tinyint DEFAULT NULL COMMENT '是否公开',
|
||||
`viewer` varchar(1000) DEFAULT NULL COMMENT '可用用户',
|
||||
`view_org` varchar(1000) DEFAULT NULL COMMENT '可用组织',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
|
||||
@@ -41,6 +44,7 @@ CREATE TABLE IF NOT EXISTS `s2_available_date_info` (
|
||||
`updated_at` timestamp NULL,
|
||||
`updated_by` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`status` tinyint DEFAULT 0,
|
||||
UNIQUE(`item_id`, `type`),
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@@ -382,6 +386,7 @@ CREATE TABLE IF NOT EXISTS s2_user
|
||||
salt varchar(256) DEFAULT NULL COMMENT 'md5密码盐',
|
||||
email varchar(100) null,
|
||||
is_admin tinyint null,
|
||||
UNIQUE (`name`),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@ CREATE TABLE IF NOT EXISTS s2_agent (
|
||||
created_at timestamp DEFAULT NULL,
|
||||
updated_by varchar(100) DEFAULT NULL,
|
||||
updated_at timestamp DEFAULT NULL,
|
||||
admin varchar(1000) DEFAULT NULL,
|
||||
viewer varchar(1000) DEFAULT NULL
|
||||
admin varchar(3000) DEFAULT NULL,
|
||||
admin_org varchar(3000) DEFAULT NULL,
|
||||
is_open smallint DEFAULT NULL,
|
||||
viewer varchar(3000) DEFAULT NULL,
|
||||
view_org varchar(3000) DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS s2_auth_groups (
|
||||
@@ -37,7 +40,8 @@ CREATE TABLE IF NOT EXISTS s2_available_date_info (
|
||||
created_by varchar(100) NOT NULL,
|
||||
updated_at timestamp NULL,
|
||||
updated_by varchar(100) NOT NULL,
|
||||
status smallint DEFAULT 0
|
||||
status smallint DEFAULT 0,
|
||||
UNIQUE(item_id, type)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS s2_chat (
|
||||
@@ -491,5 +495,6 @@ CREATE TABLE IF NOT EXISTS s2_user (
|
||||
password varchar(256) NULL,
|
||||
salt varchar(256) DEFAULT NULL,
|
||||
email varchar(100) NULL,
|
||||
is_admin smallint NULL
|
||||
is_admin smallint NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
@@ -41,3 +41,5 @@ s2:
|
||||
threshold: 0.5
|
||||
min:
|
||||
threshold: 0.3
|
||||
embedding:
|
||||
use-llm-enhance: true
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
|
||||
import com.tencent.supersonic.headless.server.facade.service.SemanticLayerService;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.DomainDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.DomainRepository;
|
||||
import com.tencent.supersonic.headless.server.service.DataSetService;
|
||||
import com.tencent.supersonic.headless.server.service.DatabaseService;
|
||||
import com.tencent.supersonic.headless.server.service.SchemaService;
|
||||
import com.tencent.supersonic.util.DataUtils;
|
||||
@@ -46,6 +47,8 @@ public class BaseTest extends BaseApplication {
|
||||
private AgentService agentService;
|
||||
@Autowired
|
||||
protected DatabaseService databaseService;
|
||||
@Autowired
|
||||
protected DataSetService dataSetService;
|
||||
|
||||
protected Agent agent;
|
||||
protected SemanticSchema schema;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.tencent.supersonic.headless;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.User;
|
||||
import com.tencent.supersonic.demo.S2VisitsDemo;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DataSetResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticTranslateResp;
|
||||
import com.tencent.supersonic.headless.chat.utils.QueryReqBuilder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -18,14 +19,15 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TranslatorTest extends BaseTest {
|
||||
|
||||
private Long dataSetId;
|
||||
private DataSetResp dataSet;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
agent = getAgentByName(S2VisitsDemo.AGENT_NAME);
|
||||
schema = schemaService.getSemanticSchema(agent.getDataSetIds());
|
||||
if (Objects.nonNull(agent)) {
|
||||
dataSetId = agent.getDataSetIds().stream().findFirst().get();
|
||||
long dataSetId = agent.getDataSetIds().stream().findFirst().get();
|
||||
dataSet = dataSetService.getDataSet(dataSetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ public class TranslatorTest extends BaseTest {
|
||||
String sql =
|
||||
"SELECT SUM(访问次数) AS _总访问次数_ FROM 超音数数据集 WHERE 数据日期 >= '2024-11-15' AND 数据日期 <= '2024-12-15'";
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
assertTrue(explain.getQuerySQL().contains("count(1)"));
|
||||
@@ -45,7 +47,7 @@ public class TranslatorTest extends BaseTest {
|
||||
public void testSql_1() throws Exception {
|
||||
String sql = "SELECT 部门, SUM(访问次数) AS 总访问次数 FROM 超音数PVUV统计 GROUP BY 部门 ";
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
assertTrue(explain.getQuerySQL().contains("department"));
|
||||
@@ -59,7 +61,7 @@ public class TranslatorTest extends BaseTest {
|
||||
String sql =
|
||||
"WITH _department_visits_ AS (SELECT 部门, SUM(访问次数) AS _total_visits_ FROM 超音数数据集 WHERE 数据日期 >= '2024-11-15' AND 数据日期 <= '2024-12-15' GROUP BY 部门) SELECT 部门 FROM _department_visits_ ORDER BY _total_visits_ DESC LIMIT 2";
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
assertTrue(explain.getQuerySQL().toLowerCase().contains("department"));
|
||||
@@ -73,7 +75,7 @@ public class TranslatorTest extends BaseTest {
|
||||
String sql =
|
||||
"WITH recent_data AS (SELECT 用户名, 访问次数 FROM 超音数数据集 WHERE 部门 = 'marketing' AND 数据日期 >= '2024-12-01' AND 数据日期 <= '2024-12-15') SELECT 用户名 FROM recent_data ORDER BY 访问次数 DESC LIMIT 1";
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
assertTrue(explain.getQuerySQL().toLowerCase().contains("department"));
|
||||
@@ -89,7 +91,7 @@ public class TranslatorTest extends BaseTest {
|
||||
Paths.get(ClassLoader.getSystemResource("sql/testUnion.sql").toURI())),
|
||||
StandardCharsets.UTF_8);
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
assertTrue(explain.getQuerySQL().contains("user_name"));
|
||||
@@ -105,7 +107,7 @@ public class TranslatorTest extends BaseTest {
|
||||
Paths.get(ClassLoader.getSystemResource("sql/testWith.sql").toURI())),
|
||||
StandardCharsets.UTF_8);
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
executeSql(explain.getQuerySQL());
|
||||
@@ -119,7 +121,7 @@ public class TranslatorTest extends BaseTest {
|
||||
Paths.get(ClassLoader.getSystemResource("sql/testSubquery.sql").toURI())),
|
||||
StandardCharsets.UTF_8);
|
||||
SemanticTranslateResp explain = semanticLayerService
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSetId), User.getDefaultUser());
|
||||
.translate(QueryReqBuilder.buildS2SQLReq(sql, dataSet), User.getDefaultUser());
|
||||
assertNotNull(explain);
|
||||
assertNotNull(explain.getQuerySQL());
|
||||
executeSql(explain.getQuerySQL());
|
||||
|
||||
27
pom.xml
27
pom.xml
@@ -26,7 +26,7 @@
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<revision>0.9.10-SNAPSHOT</revision>
|
||||
<revision>0.9.10</revision>
|
||||
<java.source.version>21</java.source.version>
|
||||
<java.target.version>21</java.target.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
@@ -47,6 +47,9 @@
|
||||
<jjwt.version>0.12.3</jjwt.version>
|
||||
<alibaba.druid.version>1.2.24</alibaba.druid.version>
|
||||
<mysql.connector.java.version>5.1.46</mysql.connector.java.version>
|
||||
<kyuubi.version>1.10.1</kyuubi.version>
|
||||
<presto.version>0.291</presto.version>
|
||||
<trino.version>471</trino.version>
|
||||
<mybatis.plus.version>3.5.7</mybatis.plus.version>
|
||||
<httpclient5.version>5.4.1</httpclient5.version>
|
||||
<!-- <httpcore.version>4.4.16</httpcore.version>-->
|
||||
@@ -78,8 +81,8 @@
|
||||
<spotless.version>2.27.1</spotless.version>
|
||||
<spotless.skip>false</spotless.skip>
|
||||
<stax2.version>4.2.1</stax2.version>
|
||||
<io.springfox.version>3.0.0</io.springfox.version>
|
||||
<aws-java-sdk.version>1.12.780</aws-java-sdk.version>
|
||||
<jgrapht.version>1.5.2</jgrapht.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -209,6 +212,21 @@
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.connector.java.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kyuubi</groupId>
|
||||
<artifactId>kyuubi-hive-jdbc</artifactId>
|
||||
<version>${kyuubi.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.facebook.presto</groupId>
|
||||
<artifactId>presto-jdbc</artifactId>
|
||||
<version>${presto.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.trino</groupId>
|
||||
<artifactId>trino-jdbc</artifactId>
|
||||
<version>${trino.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
@@ -219,6 +237,11 @@
|
||||
<artifactId>aws-java-sdk</artifactId>
|
||||
<version>${aws-java-sdk.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
<artifactId>jgrapht-core</artifactId>
|
||||
<version>${jgrapht.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -209,5 +209,6 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"
|
||||
}
|
||||
|
||||
@@ -155,29 +155,8 @@ export default defineConfig({
|
||||
|
||||
//================ pro 插件配置 =================
|
||||
presets: ['umi-presets-pro'],
|
||||
/**
|
||||
* @name openAPI 插件的配置
|
||||
* @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
|
||||
* @doc https://pro.ant.design/zh-cn/docs/openapi/
|
||||
*/
|
||||
// openAPI: [
|
||||
// {
|
||||
// requestLibPath: "import { request } from '@umijs/max'",
|
||||
// // 或者使用在线的版本
|
||||
// // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
|
||||
// schemaPath: join(__dirname, 'oneapi.json'),
|
||||
// mock: false,
|
||||
// },
|
||||
// {
|
||||
// requestLibPath: "import { request } from '@umijs/max'",
|
||||
// schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
|
||||
// projectName: 'swagger',
|
||||
// },
|
||||
// ],
|
||||
// 将insights-flow相关包排除出mfsu编译,在pnpm link 模式下保持热更新
|
||||
mfsu: {
|
||||
strategy: 'normal',
|
||||
// exclude: ['supersonic-insights-flow-components', 'supersonic-insights-flow-core'],
|
||||
},
|
||||
requestRecord: {},
|
||||
exportStatic: {},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -147,8 +147,8 @@ export const layout: RunTimeLayoutConfig = (params) => {
|
||||
height: location.pathname.includes('chat') ? 'calc(100vh - 56px)' : undefined,
|
||||
}}
|
||||
>
|
||||
<AppPage dom={dom} />
|
||||
{/* {dom} */}
|
||||
{/* <AppPage dom={dom} /> */}
|
||||
{dom}
|
||||
{history.location.pathname !== '/chat' && !isMobile && (
|
||||
<Copilot token={getToken() || ''} isDeveloper />
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Form, Input, Button, Switch, Tabs, Select, message, Space, Tooltip, Row, Col } from 'antd';
|
||||
import { Form, Input, Button, Switch, Tabs, Select, message, Space, Tooltip } from 'antd';
|
||||
import MainTitleMark from '@/components/MainTitleMark';
|
||||
import { AgentType, ChatAppConfig, ChatAppConfigItem } from './type';
|
||||
import { AgentType, ChatAppConfig } from './type';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './style.less';
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
@@ -9,9 +9,9 @@ import ToolsSection from './ToolsSection';
|
||||
import globalStyles from '@/global.less';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import SelectTMEPerson from '@/components/SelectTMEPerson';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import { getLlmModelTypeList, getLlmModelAppList, getLlmList } from '../../services/system';
|
||||
import { getLlmModelAppList, getLlmList } from '../../services/system';
|
||||
import MemorySection from './MemorySection';
|
||||
import PermissionSection from './PermissionSection';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { TextArea } = Input;
|
||||
@@ -195,16 +195,6 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
<FormItem name="enableFeedback" label="开启用户确认" valuePropName="checked" htmlFor="">
|
||||
<Switch />
|
||||
</FormItem>
|
||||
{/* <FormItem
|
||||
name={['multiTurnConfig', 'enableMultiTurn']}
|
||||
label="开启多轮对话"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</FormItem>
|
||||
<FormItem name="enableMemoryReview" label="开启记忆评估" valuePropName="checked">
|
||||
<Switch />
|
||||
</FormItem> */}
|
||||
<FormItem
|
||||
name={['toolConfig', 'simpleMode']}
|
||||
label="开启精简模式"
|
||||
@@ -214,7 +204,6 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
>
|
||||
<Switch />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
name={['toolConfig', 'debugMode']}
|
||||
label="开启调试信息"
|
||||
@@ -286,8 +275,6 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
</Space>
|
||||
</div>
|
||||
<Space style={{ alignItems: 'start' }}>
|
||||
{/* <Row> */}
|
||||
{/* <Col flex="400px"> */}
|
||||
<div style={{ width: 350 }}>
|
||||
{modelTypeOptions.map((item) => {
|
||||
return (
|
||||
@@ -312,8 +299,6 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* </Col>
|
||||
<Col flex="auto"> */}
|
||||
<div style={{ width: 900 }}>
|
||||
{modelTypeOptions.map((item) => {
|
||||
return (
|
||||
@@ -358,44 +343,10 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* </Col> */}
|
||||
{/* </Row> */}
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// label: '提示词配置',
|
||||
// key: 'promptConfig',
|
||||
// children: (
|
||||
// <div className={styles.agentFormContainer}>
|
||||
// <FormItem
|
||||
// name={['promptConfig', 'promptTemplate']}
|
||||
// label={
|
||||
// <>
|
||||
// <Space>
|
||||
// 提示词模板
|
||||
// <Tooltip
|
||||
// overlayInnerStyle={{ width: 400 }}
|
||||
// title={
|
||||
// <>
|
||||
// {tips.map((tip) => (
|
||||
// <div>{tip}</div>
|
||||
// ))}
|
||||
// </>
|
||||
// }
|
||||
// >
|
||||
// <QuestionCircleOutlined />
|
||||
// </Tooltip>
|
||||
// </Space>
|
||||
// </>
|
||||
// }
|
||||
// >
|
||||
// <Input.TextArea style={{ minHeight: 600 }} />
|
||||
// </FormItem>
|
||||
// </div>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
label: '工具配置',
|
||||
key: 'tools',
|
||||
@@ -406,6 +357,11 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
key: 'memory',
|
||||
children: <MemorySection agentId={editAgent?.id} />,
|
||||
},
|
||||
{
|
||||
label: '权限管理',
|
||||
key: 'permissonSetting',
|
||||
children: <PermissionSection currentAgent={editAgent} onSaveAgent={onSaveAgent} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -421,7 +377,7 @@ const AgentForm: React.FC<Props> = ({ editAgent, onSaveAgent, onCreateToolBtnCli
|
||||
<Tabs
|
||||
tabBarExtraContent={
|
||||
<Space>
|
||||
{activeKey !== 'memory' && (
|
||||
{activeKey !== 'memory' && activeKey !== 'permissonSetting' && (
|
||||
<Button
|
||||
type="primary"
|
||||
loading={saveLoading}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Form, Switch } from 'antd';
|
||||
import SelectPartner from '@/components/SelectPartner';
|
||||
import SelectTMEPerson from '@/components/SelectTMEPerson';
|
||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||
import styles from './style.less';
|
||||
import { AgentType } from './type';
|
||||
|
||||
type Props = {
|
||||
currentAgent?: AgentType;
|
||||
onSaveAgent: (agent: AgentType, noTip?: boolean) => Promise<void>;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const PermissionSection: React.FC<Props> = ({ currentAgent, onSaveAgent }) => {
|
||||
const [isOpenState, setIsOpenState] = useState(true);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...currentAgent,
|
||||
});
|
||||
setIsOpenState(currentAgent?.isOpen === 1);
|
||||
}, []);
|
||||
|
||||
const saveAuth = async () => {
|
||||
const values = await form.validateFields();
|
||||
const { admins, adminOrgs, isOpen, viewOrgs = [], viewers = [] } = values;
|
||||
const agent = {
|
||||
...(currentAgent || {}),
|
||||
admins,
|
||||
adminOrgs,
|
||||
viewOrgs,
|
||||
viewers,
|
||||
isOpen: isOpen ? 1 : 0,
|
||||
};
|
||||
onSaveAgent(agent as any);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={(value) => {
|
||||
const { isOpen } = value;
|
||||
if (isOpen !== undefined) {
|
||||
setIsOpenState(isOpen);
|
||||
}
|
||||
saveAuth();
|
||||
}}
|
||||
className={styles.permissionSection}
|
||||
>
|
||||
<FormItem
|
||||
name="admins"
|
||||
label={
|
||||
<FormItemTitle title={'管理员'} subTitle={'管理员将拥有主题域下所有编辑及访问权限'} />
|
||||
}
|
||||
>
|
||||
<SelectTMEPerson placeholder="请邀请团队成员" />
|
||||
</FormItem>
|
||||
<FormItem name="adminOrgs" label="按组织">
|
||||
<SelectPartner
|
||||
type="selectedDepartment"
|
||||
treeSelectProps={{
|
||||
placeholder: '请选择需要授权的部门',
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
<Form.Item label={<FormItemTitle title={'设为公开'} />} name="isOpen" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{!isOpenState && (
|
||||
<>
|
||||
<FormItem name="viewOrgs" label="按组织">
|
||||
<SelectPartner
|
||||
type="selectedDepartment"
|
||||
treeSelectProps={{
|
||||
placeholder: '请选择需要授权的部门',
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="viewers" label="按个人">
|
||||
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
||||
</FormItem>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionSection;
|
||||
@@ -19,7 +19,7 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.agentFormTitle{
|
||||
.agentFormTitle {
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
@@ -381,7 +381,6 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
.agentChatModelCell {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
@@ -391,4 +390,15 @@
|
||||
|
||||
.agentChatModelCellActive {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.permissionSection {
|
||||
width: 1000px;
|
||||
height: calc(100vh - 250px);
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 40px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -95,10 +95,14 @@ export type AgentType = {
|
||||
enableSearch?: 0 | 1;
|
||||
enableFeedback?: 0 | 1;
|
||||
toolConfig?: string;
|
||||
// modelConfig?: LlmConfigType;
|
||||
chatAppConfig: ChatAppConfig;
|
||||
multiTurnConfig?: MultiTurnConfig;
|
||||
visualConfig?: VisualConfig;
|
||||
admins?: string[];
|
||||
adminOrgs?: string[];
|
||||
viewers?: string[];
|
||||
viewOrgs?: string[];
|
||||
isOpen: number;
|
||||
};
|
||||
|
||||
export type ModelType = {
|
||||
|
||||
@@ -24,6 +24,7 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
mode = 'normal',
|
||||
}) => {
|
||||
const [currentDbLinkConfigId, setCurrentDbLinkConfigId] = useState<number>();
|
||||
const [currentCatalog, setCurrentCatalog] = useState<string>("");
|
||||
const [catalogList, setCatalogList] = useState<string[]>([]);
|
||||
const [dbNameList, setDbNameList] = useState<string[]>([]);
|
||||
const [tableNameList, setTableNameList] = useState<any[]>([]);
|
||||
@@ -55,8 +56,8 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
|
||||
const onDatabaseSelect = (databaseId: number, type: string) => {
|
||||
setLoading(true);
|
||||
if (type === 'STARROCKS') {
|
||||
queryCatalogList(databaseId)
|
||||
if (type === 'STARROCKS' || type === 'KYUUBI' || type === 'PRESTO' || type === 'TRINO') {
|
||||
queryCatalogList(databaseId);
|
||||
setCatalogSelectOpen(true);
|
||||
setDbNameList([]);
|
||||
} else {
|
||||
@@ -88,9 +89,11 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
queryDbNameList(currentDbLinkConfigId, catalog);
|
||||
}
|
||||
form.setFieldsValue({
|
||||
catalog: catalog,
|
||||
dbName: undefined,
|
||||
tableName: undefined,
|
||||
})
|
||||
setCurrentCatalog(catalog);
|
||||
}
|
||||
|
||||
const queryDbNameList = async (databaseId: number, catalog: string) => {
|
||||
@@ -110,7 +113,7 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const { code, data, msg } = await getTables(currentDbLinkConfigId, databaseName);
|
||||
const { code, data, msg } = await getTables(currentDbLinkConfigId, currentCatalog, databaseName);
|
||||
setLoading(false);
|
||||
if (code === 200) {
|
||||
const list = data || [];
|
||||
@@ -136,6 +139,7 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
onSelect={(dbLinkConfigId: number, option) => {
|
||||
onDatabaseSelect(dbLinkConfigId, option.type);
|
||||
setCurrentDbLinkConfigId(dbLinkConfigId);
|
||||
setCurrentCatalog("");
|
||||
}}
|
||||
>
|
||||
{databaseConfigList.map((item) => (
|
||||
@@ -145,25 +149,27 @@ const ModelBasicForm: React.FC<Props> = ({
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="catalog"
|
||||
label="Catalog"
|
||||
rules={[{ required: true, message: '请选择Catalog' }]}
|
||||
hidden={!catalogSelectOpen}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="请选择Catalog"
|
||||
disabled={isEdit}
|
||||
onSelect={onCatalogSelect}
|
||||
|
||||
<FormItem
|
||||
name="catalog"
|
||||
label="Catalog"
|
||||
rules={[{ required: catalogSelectOpen, message: '请选择Catalog' }]}
|
||||
hidden={!catalogSelectOpen}
|
||||
>
|
||||
{catalogList.map((item) => (
|
||||
<Select.Option key={item} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="请选择Catalog"
|
||||
disabled={isEdit}
|
||||
onSelect={onCatalogSelect}
|
||||
>
|
||||
{catalogList.map((item) => (
|
||||
<Select.Option key={item} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
name="dbName"
|
||||
label="数据库名"
|
||||
|
||||
@@ -351,12 +351,24 @@ const ModelCreateForm: React.FC<CreateFormProps> = ({
|
||||
let columns = fieldColumns || [];
|
||||
if (queryType === 'table_query') {
|
||||
const tableQueryString = tableQuery || '';
|
||||
const [dbName, tableName] = tableQueryString.split('.');
|
||||
columns = await queryTableColumnList(modelItem.databaseId, dbName, tableName);
|
||||
tableQueryInitValue = {
|
||||
dbName,
|
||||
tableName,
|
||||
};
|
||||
if (tableQueryString.split('.').length === 3) {
|
||||
const [catalog, dbName, tableName] = tableQueryString.split('.');
|
||||
columns = await queryTableColumnList(modelItem.databaseId, catalog, dbName, tableName);
|
||||
tableQueryInitValue = {
|
||||
catalog,
|
||||
dbName,
|
||||
tableName,
|
||||
};
|
||||
}
|
||||
if (tableQueryString.split('.').length === 2) {
|
||||
const [dbName, tableName] = tableQueryString.split('.');
|
||||
columns = await queryTableColumnList(modelItem.databaseId, '', dbName, tableName);
|
||||
tableQueryInitValue = {
|
||||
catalog: '',
|
||||
dbName,
|
||||
tableName,
|
||||
};
|
||||
}
|
||||
}
|
||||
formatterInitData(columns, tableQueryInitValue);
|
||||
};
|
||||
@@ -426,8 +438,8 @@ const ModelCreateForm: React.FC<CreateFormProps> = ({
|
||||
setFields(result);
|
||||
};
|
||||
|
||||
const queryTableColumnList = async (databaseId: number, dbName: string, tableName: string) => {
|
||||
const { code, data, msg } = await getColumns(databaseId, dbName, tableName);
|
||||
const queryTableColumnList = async (databaseId: number, catalog: string, dbName: string, tableName: string) => {
|
||||
const { code, data, msg } = await getColumns(databaseId, catalog, dbName, tableName);
|
||||
if (code === 200) {
|
||||
const list = data || [];
|
||||
const columns = list.map((item: any, index: number) => {
|
||||
@@ -563,10 +575,10 @@ const ModelCreateForm: React.FC<CreateFormProps> = ({
|
||||
}}
|
||||
onValuesChange={(value, values) => {
|
||||
const { tableName } = value;
|
||||
const { dbName, databaseId } = values;
|
||||
const { catalog, dbName, databaseId } = values;
|
||||
setFormDatabaseId(databaseId);
|
||||
if (tableName) {
|
||||
queryTableColumnList(databaseId, dbName, tableName);
|
||||
queryTableColumnList(databaseId, catalog, dbName, tableName);
|
||||
}
|
||||
}}
|
||||
className={styles.form}
|
||||
|
||||
@@ -51,6 +51,7 @@ const getCreateFieldName = (type: EnumDataSourceType) => {
|
||||
EnumDataSourceType.CATEGORICAL,
|
||||
EnumDataSourceType.TIME,
|
||||
EnumDataSourceType.PARTITION_TIME,
|
||||
EnumDataSourceType.FOREIGN,
|
||||
].includes(type as EnumDataSourceType)
|
||||
? 'isCreateDimension'
|
||||
: 'isCreateMetric';
|
||||
@@ -101,7 +102,7 @@ const ModelFieldForm: React.FC<Props> = ({
|
||||
value={selectTypeValue}
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
let defaultParams = {};
|
||||
let defaultParams:any = {};
|
||||
if (value === EnumDataSourceType.MEASURES) {
|
||||
defaultParams = {
|
||||
agg: AGG_OPTIONS[0].value,
|
||||
@@ -127,12 +128,13 @@ const ModelFieldForm: React.FC<Props> = ({
|
||||
};
|
||||
} else {
|
||||
defaultParams = {
|
||||
type: value,
|
||||
agg: undefined,
|
||||
dateFormat: undefined,
|
||||
timeGranularity: undefined,
|
||||
};
|
||||
}
|
||||
const isCreateName = getCreateFieldName(value);
|
||||
const isCreateName = getCreateFieldName(defaultParams.type);
|
||||
const editState = !isUndefined(record[isCreateName])
|
||||
? !!record[isCreateName]
|
||||
: true;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { Input, message, Popconfirm, Tooltip, Row, Col, Button, Menu } from 'antd';
|
||||
import { Input, message, Popconfirm, Tooltip, Row, Col, Button, Menu, MenuProps } from 'antd';
|
||||
import type { DataNode } from 'antd/lib/tree';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { useModel, history } from '@umijs/max';
|
||||
import { createDomain, updateDomain, deleteDomain } from '../service';
|
||||
import DomainInfoForm from './DomainInfoForm';
|
||||
import styles from './style.less';
|
||||
@@ -71,6 +71,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
message.success('编辑分类成功');
|
||||
setProjectInfoModalVisible(false);
|
||||
onTreeDataUpdate?.();
|
||||
window.location.reload();
|
||||
} else {
|
||||
message.error(res.msg);
|
||||
}
|
||||
@@ -78,10 +79,13 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
|
||||
const domainSubmit = async (values: any) => {
|
||||
if (values.modelType === 'add') {
|
||||
const { code, data } = await createDomain(values);
|
||||
// if (code === 200 && values.type === 'top') {
|
||||
// await createDefaultModelSet(data.id);
|
||||
// }
|
||||
const { code, data, msg } = await createDomain(values);
|
||||
if (code === 200) {
|
||||
history.push(`/model/domain/${data.id}`);
|
||||
window.location.reload();
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
} else if (values.modelType === 'edit') {
|
||||
await editProject(values);
|
||||
}
|
||||
@@ -95,6 +99,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
message.success('删除成功');
|
||||
setProjectInfoModalVisible(false);
|
||||
onTreeDataUpdate?.();
|
||||
window.location.reload();
|
||||
} else {
|
||||
message.error(res.msg);
|
||||
}
|
||||
@@ -104,12 +109,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
const { id, name, path, hasEditPermission, parentId, hasModel } = node as any;
|
||||
const type = parentId === 0 ? 'top' : 'normal';
|
||||
return (
|
||||
<div
|
||||
className={styles.projectItem}
|
||||
// onClick={() => {
|
||||
// handleSelect(id);
|
||||
// }}
|
||||
>
|
||||
<div className={styles.projectItem}>
|
||||
<span className={styles.projectItemTitle}>{name}</span>
|
||||
{createDomainBtnVisible && hasEditPermission && (
|
||||
<span className={`${styles.operation} ${styles.rowHover} `}>
|
||||
@@ -175,13 +175,12 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
return {
|
||||
key: domain.id,
|
||||
label: titleRender(domain),
|
||||
// icon: <AppstoreOutlined />,
|
||||
};
|
||||
});
|
||||
|
||||
const getLevelKeys = (items1: LevelKeysProps[]) => {
|
||||
const getLevelKeys = (items1: any[]) => {
|
||||
const key: Record<string, number> = {};
|
||||
const func = (items2: LevelKeysProps[], level = 1) => {
|
||||
const func = (items2: any[], level = 1) => {
|
||||
items2.forEach((item) => {
|
||||
if (item.key) {
|
||||
key[item.key] = level;
|
||||
@@ -194,7 +193,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
||||
func(items1);
|
||||
return key;
|
||||
};
|
||||
const levelKeys = getLevelKeys(items as LevelKeysProps[]);
|
||||
const levelKeys = getLevelKeys(items as any[]);
|
||||
const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
|
||||
|
||||
const onOpenChange: MenuProps['onOpenChange'] = (openKeys) => {
|
||||
|
||||
@@ -82,6 +82,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
|
||||
});
|
||||
if (code === 200) {
|
||||
onModelChange?.();
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
@@ -198,6 +199,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
|
||||
const { code, msg } = await deleteModel(record.id);
|
||||
if (code === 200) {
|
||||
onModelChange?.();
|
||||
window.location.reload();
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
@@ -292,6 +294,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
|
||||
onModelChange?.();
|
||||
setIsEditing(false);
|
||||
setCreateDataSourceModalOpen(false);
|
||||
window.location.reload();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsEditing(false);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { history, useParams, useModel, Outlet } from '@umijs/max';
|
||||
import DomainListTree from './components/DomainList';
|
||||
import styles from './components/style.less';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useParams, useModel, Outlet } from '@umijs/max';
|
||||
import { ISemantic } from './data';
|
||||
import { getDomainList, getDataSetList, getModelDetail } from './service';
|
||||
import { getDomainList, getModelDetail } from './service';
|
||||
import PageBreadcrumb from './PageBreadcrumb';
|
||||
|
||||
type Props = {};
|
||||
@@ -17,20 +14,10 @@ const SemanticModel: React.FC<Props> = ({}) => {
|
||||
const domainModel = useModel('SemanticModel.domainData');
|
||||
const modelModel = useModel('SemanticModel.modelData');
|
||||
const databaseModel = useModel('SemanticModel.databaseData');
|
||||
const metricModel = useModel('SemanticModel.metricData');
|
||||
const { setSelectDomain, setDomainList, selectDomainId } = domainModel;
|
||||
const { selectModel, setSelectModel, setModelTableHistoryParams, MrefreshModelList } = modelModel;
|
||||
const { setSelectDomain, setDomainList } = domainModel;
|
||||
const { selectModel, setSelectModel } = modelModel;
|
||||
const { MrefreshDatabaseList } = databaseModel;
|
||||
|
||||
const { selectMetric, setSelectMetric } = metricModel;
|
||||
|
||||
// useEffect(() => {
|
||||
|
||||
// return () => {
|
||||
// setSelectMetric(undefined);
|
||||
// }
|
||||
// }, [])
|
||||
|
||||
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
|
||||
const targetNode = domainList.filter((item: any) => {
|
||||
return `${item.id}` === domainId;
|
||||
@@ -40,9 +27,7 @@ const SemanticModel: React.FC<Props> = ({}) => {
|
||||
return item.parentId === 0;
|
||||
})[0];
|
||||
if (firstRootNode) {
|
||||
const { id } = firstRootNode;
|
||||
setSelectDomain(firstRootNode);
|
||||
// pushUrlMenu(id, menuKey);
|
||||
}
|
||||
} else {
|
||||
setSelectDomain(targetNode);
|
||||
@@ -87,7 +72,6 @@ const SemanticModel: React.FC<Props> = ({}) => {
|
||||
</div>
|
||||
<div>
|
||||
<Outlet />
|
||||
{/* <OverviewContainer /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useModel } from '@umijs/max';
|
||||
|
||||
export default function Domain() {
|
||||
const [selectDomain, setSelectDomain] = useState<ISemantic.IDomainItem>(
|
||||
const [selectDomain, setSelectDomain] = useState<ISemantic.IDomainItem | undefined>(
|
||||
{} as ISemantic.IDomainItem,
|
||||
);
|
||||
const [selectDataSet, setSelectDataSet] = useState<ISemantic.IDatasetItem>();
|
||||
|
||||
@@ -398,21 +398,23 @@ export function getDbNames(dbId: number, catalog: string): Promise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
export function getTables(databaseId: number, dbName: string): Promise<any> {
|
||||
export function getTables(databaseId: number, catalog: string, dbName: string): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}database/getTables`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
databaseId,
|
||||
catalog: catalog,
|
||||
db: dbName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function getColumns(databaseId: number, dbName: string, tableName: string): Promise<any> {
|
||||
export function getColumns(databaseId: number, catalog: string, dbName: string, tableName: string): Promise<any> {
|
||||
return request(`${process.env.API_BASE_URL}database/getColumnsByName`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
databaseId,
|
||||
catalog: catalog,
|
||||
db: dbName,
|
||||
table: tableName,
|
||||
},
|
||||
|
||||
28637
webapp/pnpm-lock.yaml
generated
28637
webapp/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user