From 8eab13317128376c64a530e303f5266b112bc07f Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Thu, 4 Sep 2025 19:28:07 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat(nl2sql):=20=E6=96=B0=E5=A2=9E=20H2?= =?UTF-8?q?=20=E6=95=B0=E6=8D=AE=E5=BA=93=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 H2 数据库的连接池、DDL 和访问器实现 - 在枚举类型中增加 H2 相关选项 - 更新配置文件以支持 H2 数据源 -增加 H2 数据库的示例数据和初始化脚本 --- spring-ai-alibaba-nl2sql/pom.xml | 7 + .../spring-ai-alibaba-nl2sql-chat/pom.xml | 5 + .../alibaba/cloud/ai/entity/Datasource.java | 5 + .../alibaba/cloud/ai/node/SqlExecuteNode.java | 4 + .../spring-ai-alibaba-nl2sql-common/pom.xml | 5 + .../ai/connector/DBConnectionPoolContext.java | 1 + .../cloud/ai/connector/h2/H2DBAccessor.java | 47 +++ .../ai/connector/h2/H2JdbcConnectionPool.java | 55 ++++ .../cloud/ai/connector/h2/H2JdbcDdl.java | 269 ++++++++++++++++++ .../cloud/ai/connector/h2/package-info.java | 19 ++ .../cloud/ai/enums/BizDataSourceTypeEnum.java | 2 + .../cloud/ai/enums/DatabaseDialectEnum.java | 4 +- .../pom.xml | 5 + .../controller/Nl2sqlForGraphController.java | 4 + .../ai/controller/SimpleChatController.java | 3 +- .../src/main/resources/application-h2.yml | 108 +++++++ .../src/main/resources/sql/data-h2.sql | 44 +++ .../src/main/resources/sql/schema-h2.sql | 201 +++++++++++++ 18 files changed, 786 insertions(+), 2 deletions(-) create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2DBAccessor.java create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcConnectionPool.java create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/package-info.java create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql diff --git a/spring-ai-alibaba-nl2sql/pom.xml b/spring-ai-alibaba-nl2sql/pom.xml index 2e64f4c43e..272df01937 100644 --- a/spring-ai-alibaba-nl2sql/pom.xml +++ b/spring-ai-alibaba-nl2sql/pom.xml @@ -38,6 +38,7 @@ 4.1.114.Final 4.1.114.Final 4.37.0 + 2.3.232 @@ -72,6 +73,12 @@ postgresql ${postgresql.version} + + com.h2database + h2 + runtime + {h2.version} + commons-collections commons-collections diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/pom.xml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/pom.xml index a5f42d30bc..f52f1ef036 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/pom.xml +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/pom.xml @@ -91,6 +91,11 @@ org.postgresql postgresql + + com.h2database + h2 + runtime + com.alibaba.cloud.ai spring-ai-alibaba-core diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/entity/Datasource.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/entity/Datasource.java index 4cea39c18b..92223e3ce8 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/entity/Datasource.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/entity/Datasource.java @@ -232,6 +232,11 @@ else if ("postgresql".equalsIgnoreCase(type)) { "jdbc:postgresql://%s:%d/%s?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai", host, port, databaseName); } + else if ("h2".equalsIgnoreCase(type)) { + this.connectionUrl = String.format( + "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE", + databaseName); + } } } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java index 3d2a90266d..8a4abb6523 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java @@ -149,6 +149,10 @@ else if ("postgresql".equalsIgnoreCase(datasource.getType())) { dbConfig.setConnectionType("jdbc"); dbConfig.setDialectType("postgresql"); } + else if ("h2".equalsIgnoreCase(datasource.getType())) { + dbConfig.setConnectionType("jdbc"); + dbConfig.setDialectType("h2"); + } else { throw new RuntimeException("不支持的数据库类型: " + datasource.getType()); } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/pom.xml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/pom.xml index cac1ab5442..27a15c40c3 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/pom.xml +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/pom.xml @@ -50,6 +50,11 @@ org.postgresql postgresql + + com.h2database + h2 + runtime + com.alibaba druid diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/DBConnectionPoolContext.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/DBConnectionPoolContext.java index b6cc56ba11..308ee5a2ab 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/DBConnectionPoolContext.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/DBConnectionPoolContext.java @@ -53,6 +53,7 @@ public DBConnectionPool getPoolByType(String type) { case "mysql", "mysqljdbcconnectionpool" -> poolMap.get("mysqlJdbcConnectionPool"); case "postgresql", "postgres", "postgresqljdbcconnectionpool" -> poolMap.get("postgreSqlJdbcConnectionPool"); + case "h2", "h2jdbcconnectionpool" -> poolMap.get("h2JdbcConnectionPool"); default -> null; }; } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2DBAccessor.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2DBAccessor.java new file mode 100644 index 0000000000..53282655bc --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2DBAccessor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ + +package com.alibaba.cloud.ai.connector.h2; + +import com.alibaba.cloud.ai.connector.DBConnectionPool; +import com.alibaba.cloud.ai.connector.accessor.defaults.AbstractAccessor; +import com.alibaba.cloud.ai.connector.support.DdlFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +/** + * @author HunterPorter + * @author HunterPorter + */ + +@Service("h2Accessor") +public class H2DBAccessor extends AbstractAccessor { + + private final static String ACCESSOR_TYPE = "H2_Accessor"; + + protected H2DBAccessor(DdlFactory ddlFactory, + @Qualifier("h2JdbcConnectionPool") DBConnectionPool dbConnectionPool) { + + super(ddlFactory, dbConnectionPool); + } + + @Override + public String getDbAccessorType() { + + return ACCESSOR_TYPE; + } + +} diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcConnectionPool.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcConnectionPool.java new file mode 100644 index 0000000000..4d46c0049c --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcConnectionPool.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ +package com.alibaba.cloud.ai.connector.h2; + +import com.alibaba.cloud.ai.connector.AbstractDBConnectionPool; +import com.alibaba.cloud.ai.enums.DatabaseDialectEnum; +import com.alibaba.cloud.ai.enums.ErrorCodeEnum; +import org.springframework.stereotype.Service; + +import static com.alibaba.cloud.ai.enums.ErrorCodeEnum.DATABASE_NOT_EXIST_42000; +import static com.alibaba.cloud.ai.enums.ErrorCodeEnum.DATASOURCE_CONNECTION_FAILURE_08S01; +import static com.alibaba.cloud.ai.enums.ErrorCodeEnum.OTHERS; +import static com.alibaba.cloud.ai.enums.ErrorCodeEnum.PASSWORD_ERROR_28000; + +@Service("h2JdbcConnectionPool") +public class H2JdbcConnectionPool extends AbstractDBConnectionPool { + + @Override + public DatabaseDialectEnum getDialect() { + return DatabaseDialectEnum.H2; + } + + @Override + public String getDriver() { + return "org.h2.Driver"; + } + + @Override + public ErrorCodeEnum errorMapping(String sqlState) { + ErrorCodeEnum ret = ErrorCodeEnum.fromCode(sqlState); + if (ret != null) { + return ret; + } + return switch (sqlState) { + case "08S01" -> DATASOURCE_CONNECTION_FAILURE_08S01; + case "28000" -> PASSWORD_ERROR_28000; + case "42000" -> DATABASE_NOT_EXIST_42000; + default -> OTHERS; + }; + } + +} diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java new file mode 100644 index 0000000000..881a5dd349 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -0,0 +1,269 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ +package com.alibaba.cloud.ai.connector.h2; + +import com.alibaba.cloud.ai.connector.AbstractJdbcDdl; +import com.alibaba.cloud.ai.connector.SqlExecutor; +import com.alibaba.cloud.ai.connector.bo.ColumnInfoBO; +import com.alibaba.cloud.ai.connector.bo.DatabaseInfoBO; +import com.alibaba.cloud.ai.connector.bo.ForeignKeyInfoBO; +import com.alibaba.cloud.ai.connector.bo.ResultSetBO; +import com.alibaba.cloud.ai.connector.bo.SchemaInfoBO; +import com.alibaba.cloud.ai.connector.bo.TableInfoBO; +import com.alibaba.cloud.ai.enums.BizDataSourceTypeEnum; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.alibaba.cloud.ai.connector.ColumnTypeParser.wrapType; + +@Service +public class H2JdbcDdl extends AbstractJdbcDdl { + + @Override + public List showDatabases(Connection connection) { + String sql = "SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.INFORMATION_SCHEMA_CATALOG_NAME;"; + List databaseInfoList = Lists.newArrayList(); + try { + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, sql); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + String database = resultArr[i][0]; + databaseInfoList.add(DatabaseInfoBO.builder().name(database).build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return databaseInfoList; + } + + @Override + public List showSchemas(Connection connection) { + String sql = "SELECT schema_name FROM information_schema.schemata;"; + List schemaInfoList = Lists.newArrayList(); + try { + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, sql); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + String database = resultArr[i][0]; + schemaInfoList.add(SchemaInfoBO.builder().name(database).build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return schemaInfoList; + } + + @Override + public List showTables(Connection connection, String schema, String tablePattern) { + String sql = "SELECT TABLE_NAME, REMARKS \n" + "FROM INFORMATION_SCHEMA.TABLES \n" + + "WHERE TABLE_SCHEMA = '%s' \n"; + if (StringUtils.isNotBlank(tablePattern)) { + sql += "AND TABLE_NAME LIKE CONCAT('%%','%s','%%') \n"; + } + sql += "limit 2000;"; + List tableInfoList = Lists.newArrayList(); + try { + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, + String.format(sql, connection.getCatalog(), tablePattern)); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + String tableName = resultArr[i][0]; + String tableDesc = resultArr[i][1]; + tableInfoList.add(TableInfoBO.builder().name(tableName).description(tableDesc).build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return tableInfoList; + } + + @Override + public List fetchTables(Connection connection, String schema, List tables) { + String sql = "SELECT TABLE_NAME, REMARKS \n" + "FROM INFORMATION_SCHEMA.TABLES \n" + + "WHERE TABLE_SCHEMA = '%s' \n" + "AND TABLE_NAME in(%s) \n" + "limit 200;"; + List tableInfoList = Lists.newArrayList(); + String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); + try { + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, + String.format(sql, connection.getCatalog(), tableListStr)); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + String tableName = resultArr[i][0]; + String tableDesc = resultArr[i][1]; + tableInfoList.add(TableInfoBO.builder().name(tableName).description(tableDesc).build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return tableInfoList; + } + + @Override + public List showColumns(Connection connection, String schema, String table) { + String sql = "SELECT column_name, remarks, data_type, " + "IF(IS_IDENTITY='YES','true','false') AS '主键唯一', \n" + + "IF(IS_NULLABLE='NO','true','false') AS '非空' \n" + "FROM information_schema.COLUMNS " + + "WHERE table_schema='%s' " + "and table_name='%s';"; + List columnInfoList = Lists.newArrayList(); + try { + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", + String.format(sql, connection.getCatalog(), table)); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + columnInfoList.add(ColumnInfoBO.builder() + .name(resultArr[i][0]) + .description(resultArr[i][1]) + .type(wrapType(resultArr[i][2])) + .primary(BooleanUtils.toBoolean(resultArr[i][3])) + .notnull(BooleanUtils.toBoolean(resultArr[i][4])) + .build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return columnInfoList; + } + + @Override + public List showForeignKeys(Connection connection, String schema, List tables) { + String sql = "SELECT \n" + " kc.TABLE_NAME AS '表名',\n" + " kc.COLUMN_NAME AS '列名',\n" + + " kc2.table_name AS '引用表名',\n" + + " kc2.column_name AS '引用列名'\n" + "FROM \n" + " information_schema.referential_constraints rc \n" + + " join information_schema.key_column_usage kc on rc.constraint_name=kc.constraint_name \n" + + " join information_schema.key_column_usage kc2 on rc.unique_constraint_name=kc2.constraint_name; "; + List foreignKeyInfoList = Lists.newArrayList(); + String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); + + try { + sql = String.format(sql, connection.getCatalog(), tableListStr, tableListStr); + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", sql); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0) { + continue; + } + foreignKeyInfoList.add(ForeignKeyInfoBO.builder() + .table(resultArr[i][0]) + .column(resultArr[i][1]) + .referencedTable(resultArr[i][2]) + .referencedColumn(resultArr[i][3]) + .build()); + } + } + catch (SQLException e) { + throw new RuntimeException(e); + } + + return foreignKeyInfoList; + } + + @Override + public List sampleColumn(Connection connection, String schema, String table, String column) { + String sql = "SELECT \n" + " `%s`\n" + "FROM \n" + " `%s`\n" + "LIMIT 99;"; + List sampleInfo = Lists.newArrayList(); + try { + sql = String.format(sql, column, table); + String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, null, sql); + if (resultArr.length <= 1) { + return Lists.newArrayList(); + } + + for (int i = 1; i < resultArr.length; i++) { + if (resultArr[i].length == 0 || column.equalsIgnoreCase(resultArr[i][0])) { + continue; + } + sampleInfo.add(resultArr[i][0]); + } + } + catch (SQLException e) { + // throw new RuntimeException(e); + } + + Set siSet = sampleInfo.stream().collect(Collectors.toSet()); + sampleInfo = siSet.stream().collect(Collectors.toList()); + return sampleInfo; + } + + @Override + public ResultSetBO scanTable(Connection connection, String schema, String table) { + String sql = "SELECT *\n" + "FROM \n" + " `%s`\n" + "LIMIT 20;"; + ResultSetBO resultSet = ResultSetBO.builder().build(); + try { + resultSet = SqlExecutor.executeSqlAndReturnObject(connection, schema, String.format(sql, table)); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + return resultSet; + } + + @Override + public BizDataSourceTypeEnum getType() { + return BizDataSourceTypeEnum.H2; + } + +} diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/package-info.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/package-info.java new file mode 100644 index 0000000000..b3fab59b3b --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ + +package com.alibaba.cloud.ai.connector.h2; + +// DataAgent H2 Data provider diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/BizDataSourceTypeEnum.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/BizDataSourceTypeEnum.java index e6bd453ecc..7dfc07876f 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/BizDataSourceTypeEnum.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/BizDataSourceTypeEnum.java @@ -23,6 +23,8 @@ public enum BizDataSourceTypeEnum { SQLITE(3, "sqlite", DatabaseDialectEnum.MYSQL.getCode(), DbAccessTypeEnum.JDBC.getCode()), + H2(4, "h2", DatabaseDialectEnum.H2.getCode(), DbAccessTypeEnum.JDBC.getCode()), + HOLOGRESS(10, "hologress", DatabaseDialectEnum.POSTGRESQL.getCode(), DbAccessTypeEnum.JDBC.getCode()), MYSQL_VPC(11, "mysql-vpc", DatabaseDialectEnum.MYSQL.getCode(), DbAccessTypeEnum.JDBC.getCode()), diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/DatabaseDialectEnum.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/DatabaseDialectEnum.java index f2656fd955..899c4ee7a7 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/DatabaseDialectEnum.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/enums/DatabaseDialectEnum.java @@ -21,7 +21,9 @@ public enum DatabaseDialectEnum { SQLite("SQLite"), - POSTGRESQL("PostgreSQL"); + POSTGRESQL("PostgreSQL"), + + H2("H2"); public String code; diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/pom.xml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/pom.xml index 168cec5564..4ba4ad634f 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/pom.xml +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/pom.xml @@ -106,6 +106,11 @@ org.postgresql postgresql + + com.h2database + h2 + runtime + com.alibaba.cloud.ai spring-ai-alibaba-core diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/Nl2sqlForGraphController.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/Nl2sqlForGraphController.java index 526c0870db..0955dd90c9 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/Nl2sqlForGraphController.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/Nl2sqlForGraphController.java @@ -147,6 +147,10 @@ else if ("postgresql".equalsIgnoreCase(datasource.getType())) { dbConfig.setConnectionType("jdbc"); dbConfig.setDialectType("postgresql"); } + else if ("h2".equalsIgnoreCase(datasource.getType())) { + dbConfig.setConnectionType("jdbc"); + dbConfig.setDialectType("h2"); + } else { throw new RuntimeException("不支持的数据库类型: " + datasource.getType()); } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/SimpleChatController.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/SimpleChatController.java index 79d237c5bb..2acda4ec2c 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/SimpleChatController.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/SimpleChatController.java @@ -19,6 +19,7 @@ import com.alibaba.cloud.ai.request.SchemaInitRequest; import com.alibaba.cloud.ai.service.simple.SimpleNl2SqlService; import com.alibaba.cloud.ai.service.simple.SimpleVectorStoreService; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -34,7 +35,7 @@ public class SimpleChatController { private final DbConfig dbConfig; - public SimpleChatController(SimpleNl2SqlService simpleNl2SqlService, + public SimpleChatController(@Qualifier("simpleNl2SqlService") SimpleNl2SqlService simpleNl2SqlService, SimpleVectorStoreService simpleVectorStoreService, DbConfig dbConfig) { this.simpleNl2SqlService = simpleNl2SqlService; this.simpleVectorStoreService = simpleVectorStoreService; diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml new file mode 100644 index 0000000000..594d32d777 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml @@ -0,0 +1,108 @@ +server: + port: 8065 +spring: + datasource: + url: ${NL2SQL_DATASOURCE_URL:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} + username: ${NL2SQL_DATASOURCE_USERNAME:root} + password: ${NL2SQL_DATASOURCE_PASSWORD:root} + driver-class-name: org.h2.Driver + type: com.alibaba.druid.pool.DruidDataSource + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.H2Dialect + defer-datasource-initialization: true + sql: + init: + mode: always + schema-locations: classpath:sql/schema-h2.sql + data-locations: classpath:sql/data-h2.sql + continue-on-error: true + separator: ; + encoding: utf-8 + ai: + openai: + base-url: https://dashscope.aliyuncs.com/compatible-mode + api-key: ${AI_DASHSCOPE_API_KEY} + model: qwen-max + embedding: + model: text-embedding-v4 + dashscope: + api-key: ${AI_DASHSCOPE_API_KEY} + mcp: + server: + name: xiyan-server # MCP服务器名称 + version: 0.0.1 # 服务器版本号 + vectorstore: + analytic: + enabled: false + collectName: chatBi + regionId: + dbInstanceId: + managerAccount: + managerAccountPassword: + namespace: chat + namespacePassword: chat + defaultTopK: 6 + defaultSimilarityThreshold: 0.75 + accessKeyId: + accessKeySecret: + alibaba: + nl2sql: + code-executor: + # 运行Python代码的环境(生产环境建议使用docker,不建议使用local) + code-pool-executor: local + h2: + console: + enabled: true + +# MyBatis Plus 配置 +mybatis-plus: + configuration: + # 开启驼峰命名转换 + map-underscore-to-camel-case: true + # SQL 日志打印(生产环境建议关闭或使用 slf4j) + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + # 设置全局缓存开关 + cache-enabled: true + # 延迟加载的核心开关 + lazy-loading-enabled: true + # 按需加载属性 + aggressive-lazy-loading: false + # 设置默认执行器类型 + default-executor-type: reuse + # 设置默认语句超时时间 + default-statement-timeout: 30 + global-config: + # 数据库配置 + db-config: + # 主键类型(AUTO 自增,ASSIGN_ID 雪花算法,ASSIGN_UUID UUID) + id-type: auto + # 逻辑删除字段名 + logic-delete-field: deleted + # 逻辑删除值(删除时设置的值) + logic-delete-value: 1 + # 逻辑未删除值(正常时的值) + logic-not-delete-value: 0 + # 字段验证策略 + insert-strategy: not_null + update-strategy: not_null + where-strategy: not_null + # 关闭 MyBatis Plus 启动横幅 + banner: false + # 是否开启 LOGO + enable-sql-runner: false + # Mapper XML 文件位置(如果有的话) + mapper-locations: classpath*:/mapper/**/*.xml + # 实体扫描,多个package用逗号或者分号分隔 + type-aliases-package: com.alibaba.cloud.ai.entity + +# 日志配置 +logging: + level: + com.alibaba.cloud.ai.mapper: debug + org.springframework.jdbc: debug diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql new file mode 100644 index 0000000000..7f942666e4 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql @@ -0,0 +1,44 @@ +-- 初始化数据文件 +-- 只在表为空时插入示例数据 + +-- 智能体示例数据 +INSERT IGNORE INTO `agent` (`id`, `name`, `description`, `avatar`, `status`, `prompt`, `category`, `admin_id`, `tags`, `create_time`, `update_time`) VALUES +(1, '中国人口GDP数据智能体', '专门处理中国人口和GDP相关数据查询分析的智能体', '/avatars/china-gdp-agent.png', 'published', '你是一个专业的数据分析助手,专门处理中国人口和GDP相关的数据查询。请根据用户的问题,生成准确的SQL查询语句。', '数据分析', 2100246635, '人口数据,GDP分析,经济统计', NOW(), NOW()), +(2, '销售数据分析智能体', '专注于销售数据分析和业务指标计算的智能体', '/avatars/sales-agent.png', 'published', '你是一个销售数据分析专家,能够帮助用户分析销售趋势、客户行为和业务指标。', '业务分析', 2100246635, '销售分析,业务指标,客户分析', NOW(), NOW()), +(3, '财务报表智能体', '专门处理财务数据和报表分析的智能体', '/avatars/finance-agent.png', 'draft', '你是一个财务分析专家,专门处理财务数据查询和报表生成。', '财务分析', 2100246635, '财务数据,报表分析,会计', NOW(), NOW()), +(4, '库存管理智能体', '专注于库存数据管理和供应链分析的智能体', '/avatars/inventory-agent.png', 'published', '你是一个库存管理专家,能够帮助用户查询库存状态、分析供应链数据。', '供应链', 2100246635, '库存管理,供应链,物流', NOW(), NOW()); + +-- 业务知识示例数据 +INSERT IGNORE INTO `business_knowledge` (`id`, `business_term`, `description`, `synonyms`, `is_recall`, `data_set_id`, `agent_id`, `created_time`, `updated_time`) VALUES +(1, 'Customer Satisfaction', 'Measures how satisfied customers are with the service or product.', 'customer happiness, client contentment', 1, 'dataset_001', 1, NOW(), NOW()), +(2, 'Net Promoter Score', 'A measure of the likelihood of customers recommending a company to others.', 'NPS, customer loyalty score', 1, 'dataset_002', 1, NOW(), NOW()), +(3, 'Customer Retention Rate', 'The percentage of customers who continue to use a service over a given period.', 'retention, customer loyalty', 1, 'dataset_003', 2, NOW(), NOW()); + +-- 语义模型示例数据 +INSERT IGNORE INTO `semantic_model` (`id`, `agent_id`, `field_name`, `synonyms`, `origin_name`, `description`, `origin_description`, `type`, `created_time`, `updated_time`, `is_recall`, `status`) VALUES +(1, 1, 'customerSatisfactionScore', 'satisfaction score, customer rating', 'csat_score', 'Customer satisfaction rating from 1-10', 'Customer satisfaction score', 'integer', NOW(), NOW(), 1, 1), +(2, 1, 'netPromoterScore', 'NPS, promoter score', 'nps_value', 'Net Promoter Score from -100 to 100', 'NPS calculation result', 'integer', NOW(), NOW(), 1, 1), +(3, 2, 'customerRetentionRate', 'retention rate, loyalty rate', 'retention_pct', 'Percentage of retained customers', 'Customer retention percentage', 'decimal', NOW(), NOW(), 1, 1); + +-- 智能体知识示例数据 +INSERT IGNORE INTO `agent_knowledge` (`id`, `agent_id`, `title`, `content`, `type`, `category`, `tags`, `status`, `source_url`, `file_type`, `embedding_status`, `creator_id`, `create_time`, `update_time`) VALUES +(1, 1, '中国人口统计数据说明', '中国人口统计数据包含了历年的人口总数、性别比例、年龄结构、城乡分布等详细信息。数据来源于国家统计局,具有权威性和准确性。查询时请注意数据的时间范围和统计口径。', 'document', '数据说明', '人口统计,数据源,统计局', 'active', 'http://stats.gov.cn/population', 'text', 'completed', 2100246635, NOW(), NOW()), +(2, 1, 'GDP数据使用指南', 'GDP(国内生产总值)数据反映了国家经济发展水平。包含名义GDP、实际GDP、GDP增长率等指标。数据按季度和年度进行统计,支持按地区、行业进行分类查询。', 'document', '使用指南', 'GDP,经济指标,增长率', 'active', 'http://stats.gov.cn/gdp', 'text', 'completed', 2100246635, NOW(), NOW()), +(3, 1, '常见查询问题', '问:如何查询2023年的人口数据?\n答:可以使用"SELECT * FROM population WHERE year = 2023"进行查询。\n\n问:如何计算GDP增长率?\n答:GDP增长率 = (当年GDP - 上年GDP) / 上年GDP * 100%', 'qa', '常见问题', '查询示例,FAQ,SQL语句', 'active', NULL, 'text', 'completed', 2100246635, NOW(), NOW()), +(4, 2, '销售数据字段说明', '销售数据表包含以下关键字段:\n- sales_amount:销售金额\n- customer_id:客户ID\n- product_id:产品ID\n- sales_date:销售日期\n- region:销售区域\n- sales_rep:销售代表', 'document', '数据字典', '销售数据,字段说明,数据结构', 'active', NULL, 'text', 'completed', 2100246635, NOW(), NOW()), +(5, 2, '客户分析指标体系', '客户分析包含多个维度:\n1. 客户价值分析:RFM模型(最近购买时间、购买频次、购买金额)\n2. 客户生命周期:新客户、活跃客户、流失客户\n3. 客户满意度:NPS评分、满意度调研\n4. 客户行为分析:购买偏好、渠道偏好', 'document', '分析方法', '客户分析,RFM,生命周期,满意度', 'active', NULL, 'text', 'processing', 2100246635, NOW(), NOW()), +(6, 3, '财务报表模板', '标准财务报表包含:\n1. 资产负债表:反映企业财务状况\n2. 利润表:反映企业经营成果\n3. 现金流量表:反映企业现金流动情况\n4. 所有者权益变动表:反映股东权益变化', 'document', '报表模板', '财务报表,资产负债表,利润表,现金流', 'active', 'https://finance.example.com/templates', 'pdf', 'completed', 2100246635, NOW(), NOW()), +(7, 4, '库存管理最佳实践', '库存管理的核心要点:\n1. 安全库存设置:确保不断货\n2. ABC分类管理:重点管理A类物料\n3. 先进先出原则:避免库存积压\n4. 定期盘点:确保数据准确性\n5. 供应商管理:建立稳定供应关系', 'document', '最佳实践', '库存管理,安全库存,ABC分类,盘点', 'active', NULL, 'text', 'completed', 2100246635, NOW(), NOW()); + +-- 数据源示例数据 +-- 示例数据源可以运行docker-compose-datasource.yml建立,或者手动修改为自己的数据源 +INSERT IGNORE INTO `datasource` (`id`, `name`, `type`, `host`, `port`, `database_name`, `username`, `password`, `connection_url`, `status`, `test_status`, `description`, `creator_id`, `create_time`, `update_time`) VALUES +(1, '生产环境MySQL数据库', 'mysql', 'mysql-data', 3306, 'product_db', 'root', 'root', 'jdbc:mysql://mysql-data:3306/product_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true', 'active', 'success', '生产环境主数据库,包含核心业务数据', 2100246635, NOW(), NOW()), +(2, '数据仓库PostgreSQL', 'postgresql', 'postgres-data', 5432, 'data_warehouse', 'postgres', 'postgres', 'jdbc:postgresql://postgres-data:5432/data_warehouse', 'active', 'success', '数据仓库,用于数据分析和报表生成', 2100246635, NOW(), NOW()); + +-- 智能体数据源关联示例数据 +INSERT IGNORE INTO `agent_datasource` (`id`, `agent_id`, `datasource_id`, `is_active`, `create_time`, `update_time`) VALUES +(1, 1, 2, 1, NOW(), NOW()), -- 中国人口GDP数据智能体使用数据仓库 +(2, 2, 1, 1, NOW(), NOW()), -- 销售数据分析智能体使用生产环境数据库 +(3, 3, 1, 1, NOW(), NOW()), -- 财务报表智能体使用生产环境数据库 +(4, 4, 1, 1, NOW(), NOW()); -- 库存管理智能体使用生产环境数据库 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql new file mode 100644 index 0000000000..6f5b448a64 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql @@ -0,0 +1,201 @@ +-- 简化的数据库初始化脚本,兼容Spring Boot SQL初始化 + +-- 智能体表 +CREATE TABLE IF NOT EXISTS agent ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL COMMENT '智能体名称', + description TEXT COMMENT '智能体描述', + avatar TEXT COMMENT '头像URL', + status VARCHAR(50) DEFAULT 'draft' COMMENT '状态:draft-待发布,published-已发布,offline-已下线', + prompt TEXT COMMENT '自定义Prompt配置', + category VARCHAR(100) COMMENT '分类', + admin_id BIGINT COMMENT '管理员ID', + tags TEXT COMMENT '标签,逗号分隔', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_name (name), + INDEX idx_status (status), + INDEX idx_category (category), + INDEX idx_admin_id (admin_id) + ) ENGINE = InnoDB COMMENT = '智能体表'; + +-- 业务知识表 +CREATE TABLE IF NOT EXISTS business_knowledge ( + id INT NOT NULL AUTO_INCREMENT, + business_term VARCHAR(255) NOT NULL COMMENT '业务名词', + description TEXT COMMENT '描述', + synonyms TEXT COMMENT '同义词', + is_recall INT DEFAULT 1 COMMENT '是否召回', + data_set_id VARCHAR(255) COMMENT '数据集id', + agent_id INT COMMENT '关联的智能体ID', + created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_business_term (business_term), + INDEX idx_data_set_id (data_set_id), + INDEX idx_agent_id (agent_id), + INDEX idx_is_recall (is_recall), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE SET NULL +) ENGINE = InnoDB COMMENT = '业务知识表'; + +-- 语义模型表 +CREATE TABLE IF NOT EXISTS semantic_model ( + id INT NOT NULL AUTO_INCREMENT, + agent_id INT COMMENT '关联的智能体ID', + field_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '智能体字段名称', + synonyms TEXT COMMENT '字段名称同义词', + origin_name VARCHAR(255) DEFAULT '' COMMENT '原始字段名', + description TEXT COMMENT '字段描述', + origin_description VARCHAR(255) COMMENT '原始字段描述', + type VARCHAR(255) DEFAULT '' COMMENT '字段类型 (integer, varchar....)', + created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + is_recall TINYINT DEFAULT 1 COMMENT '0 停用 1 启用', + status TINYINT DEFAULT 1 COMMENT '0 停用 1 启用', + PRIMARY KEY (id), + INDEX idx_agent_id_sm (agent_id), + INDEX idx_field_name (field_name), + INDEX idx_status_sm (status), + INDEX idx_is_recall_sm (is_recall), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE SET NULL +) ENGINE = InnoDB COMMENT = '语义模型表'; + + +-- 智能体知识表 +CREATE TABLE IF NOT EXISTS agent_knowledge ( + id INT NOT NULL AUTO_INCREMENT, + agent_id INT NOT NULL COMMENT '智能体ID', + title VARCHAR(255) NOT NULL COMMENT '知识标题', + content TEXT COMMENT '知识内容', + type VARCHAR(50) DEFAULT 'document' COMMENT '知识类型:document-文档,qa-问答,faq-常见问题', + category VARCHAR(100) COMMENT '知识分类', + tags TEXT COMMENT '标签,逗号分隔', + status VARCHAR(50) DEFAULT 'active' COMMENT '状态:active-启用,inactive-禁用', + source_url VARCHAR(500) COMMENT '来源URL', + file_path VARCHAR(500) COMMENT '文件路径', + file_size BIGINT COMMENT '文件大小(字节)', + file_type VARCHAR(100) COMMENT '文件类型', + embedding_status VARCHAR(50) DEFAULT 'pending' COMMENT '向量化状态:pending-待处理,processing-处理中,completed-已完成,failed-失败', + creator_id BIGINT COMMENT '创建者ID', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_agent_id_ak (agent_id), + INDEX idx_title (title), + INDEX idx_type (type), + INDEX idx_status_ak (status), + INDEX idx_category_ak (category), + INDEX idx_embedding_status (embedding_status), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE CASCADE +) ENGINE = InnoDB COMMENT = '智能体知识表'; + +-- 数据源表 +CREATE TABLE IF NOT EXISTS datasource ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL COMMENT '数据源名称', + type VARCHAR(50) NOT NULL COMMENT '数据源类型:mysql, postgresql', + host VARCHAR(255) NOT NULL COMMENT '主机地址', + port INT NOT NULL COMMENT '端口号', + database_name VARCHAR(255) NOT NULL COMMENT '数据库名称', + username VARCHAR(255) NOT NULL COMMENT '用户名', + password VARCHAR(255) NOT NULL COMMENT '密码(加密存储)', + connection_url VARCHAR(1000) COMMENT '完整连接URL', + status VARCHAR(50) DEFAULT 'active' COMMENT '状态:active-启用,inactive-禁用', + test_status VARCHAR(50) DEFAULT 'unknown' COMMENT '连接测试状态:success-成功,failed-失败,unknown-未知', + description TEXT COMMENT '描述', + creator_id BIGINT COMMENT '创建者ID', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_name_d (name), + INDEX idx_type_d (type), + INDEX idx_status_d (status), + INDEX idx_creator_id (creator_id) +) ENGINE = InnoDB COMMENT = '数据源表'; + +-- 智能体数据源关联表 +CREATE TABLE IF NOT EXISTS agent_datasource ( + id INT NOT NULL AUTO_INCREMENT, + agent_id INT NOT NULL COMMENT '智能体ID', + datasource_id INT NOT NULL COMMENT '数据源ID', + is_active TINYINT DEFAULT 1 COMMENT '是否启用:0-禁用,1-启用', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + UNIQUE KEY uk_agent_datasource (agent_id, datasource_id), + INDEX idx_agent_id_ad (agent_id), + INDEX idx_datasource_id (datasource_id), + INDEX idx_is_active (is_active), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE CASCADE, + FOREIGN KEY (datasource_id) REFERENCES datasource(id) ON DELETE CASCADE +) ENGINE = InnoDB COMMENT = '智能体数据源关联表'; + +-- 智能体预设问题表 +CREATE TABLE IF NOT EXISTS agent_preset_question ( + id INT NOT NULL AUTO_INCREMENT, + agent_id INT NOT NULL COMMENT '智能体ID', + question TEXT NOT NULL COMMENT '预设问题内容', + sort_order INT DEFAULT 0 COMMENT '排序顺序', + is_active TINYINT DEFAULT 1 COMMENT '是否启用:0-禁用,1-启用', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_agent_id_apq (agent_id), + INDEX idx_sort_order (sort_order), + INDEX idx_is_active_apq (is_active), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE CASCADE +) ENGINE = InnoDB COMMENT = '智能体预设问题表'; + +-- 会话表 +CREATE TABLE IF NOT EXISTS chat_session ( + id VARCHAR(36) NOT NULL COMMENT '会话ID(UUID)', + agent_id INT NOT NULL COMMENT '智能体ID', + title VARCHAR(255) DEFAULT '新对话' COMMENT '会话标题', + status VARCHAR(50) DEFAULT 'active' COMMENT '状态:active-活跃,archived-归档,deleted-已删除', + is_pinned TINYINT DEFAULT 0 COMMENT '是否置顶:0-否,1-是', + user_id BIGINT COMMENT '用户ID', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (id), + INDEX idx_agent_id_cs (agent_id), + INDEX idx_user_id (user_id), + INDEX idx_status_cs (status), + INDEX idx_is_pinned (is_pinned), + INDEX idx_create_time (create_time), + FOREIGN KEY (agent_id) REFERENCES agent(id) ON DELETE CASCADE +) ENGINE = InnoDB COMMENT = '聊天会话表'; + +-- 消息表 +CREATE TABLE IF NOT EXISTS chat_message ( + id BIGINT NOT NULL AUTO_INCREMENT, + session_id VARCHAR(36) NOT NULL COMMENT '会话ID', + role VARCHAR(20) NOT NULL COMMENT '角色:user-用户,assistant-助手,system-系统', + content TEXT NOT NULL COMMENT '消息内容', + message_type VARCHAR(50) DEFAULT 'text' COMMENT '消息类型:text-文本,sql-SQL查询,result-查询结果,error-错误', + metadata JSON COMMENT '元数据(JSON格式)', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (id), + INDEX idx_session_id (session_id), + INDEX idx_role (role), + INDEX idx_message_type (message_type), + INDEX idx_create_time_cm (create_time), + FOREIGN KEY (session_id) REFERENCES chat_session(id) ON DELETE CASCADE +) ENGINE = InnoDB COMMENT = '聊天消息表'; + +-- 用户Prompt配置表 +CREATE TABLE IF NOT EXISTS user_prompt_config ( + id VARCHAR(36) NOT NULL COMMENT '配置ID(UUID)', + name VARCHAR(255) NOT NULL COMMENT '配置名称', + prompt_type VARCHAR(100) NOT NULL COMMENT 'Prompt类型(如report-generator, planner等)', + system_prompt TEXT NOT NULL COMMENT '用户自定义系统Prompt内容', + enabled TINYINT DEFAULT 1 COMMENT '是否启用该配置:0-禁用,1-启用', + description TEXT COMMENT '配置描述', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + creator VARCHAR(255) COMMENT '创建者', + PRIMARY KEY (id), + INDEX idx_prompt_type (prompt_type), + INDEX idx_enabled (enabled), + INDEX idx_create_time_upc (create_time) +) ENGINE = InnoDB COMMENT = '用户Prompt配置表'; From aa549a874b8a53bc15ba841cafd733d2a9dcc744 Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Thu, 4 Sep 2025 19:37:22 +0800 Subject: [PATCH 02/11] =?UTF-8?q?style(nl2sql):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=BA=86=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=20import=20?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java index 881a5dd349..a4e5de9598 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -31,7 +31,6 @@ import java.sql.Connection; import java.sql.SQLException; -import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -188,8 +187,8 @@ public List showColumns(Connection connection, String schema, Stri @Override public List showForeignKeys(Connection connection, String schema, List tables) { String sql = "SELECT \n" + " kc.TABLE_NAME AS '表名',\n" + " kc.COLUMN_NAME AS '列名',\n" - + " kc2.table_name AS '引用表名',\n" - + " kc2.column_name AS '引用列名'\n" + "FROM \n" + " information_schema.referential_constraints rc \n" + + " kc2.table_name AS '引用表名',\n" + " kc2.column_name AS '引用列名'\n" + "FROM \n" + + " information_schema.referential_constraints rc \n" + " join information_schema.key_column_usage kc on rc.constraint_name=kc.constraint_name \n" + " join information_schema.key_column_usage kc2 on rc.unique_constraint_name=kc2.constraint_name; "; List foreignKeyInfoList = Lists.newArrayList(); From 17528719b829a278358e5f7c1b09a603d395e513 Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Thu, 4 Sep 2025 19:45:28 +0800 Subject: [PATCH 03/11] =?UTF-8?q?style(nl2sql):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=BA=86=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=20import=20?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-ai-alibaba-nl2sql/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-ai-alibaba-nl2sql/pom.xml b/spring-ai-alibaba-nl2sql/pom.xml index 272df01937..19dadc5f5d 100644 --- a/spring-ai-alibaba-nl2sql/pom.xml +++ b/spring-ai-alibaba-nl2sql/pom.xml @@ -77,7 +77,7 @@ com.h2database h2 runtime - {h2.version} + ${h2.version} commons-collections From 6eb69dbab177ba736b50a2235587de7d8ffb574b Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Fri, 5 Sep 2025 17:44:02 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat(nl2sql):=20=E6=B7=BB=E5=8A=A0=20H2?= =?UTF-8?q?=20=E6=95=B0=E6=8D=AE=E5=BA=93=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 AgentVectorService 中增加对 H2 数据库的支持 - 新增 H2 数据库配置文件 - 在示例数据中添加 H2 数据源 - 修改 H2JdbcDdl 类以适应 H2 数据库特性- 更新数据源表结构,允许空主机和端口字段 --- .../cloud/ai/connector/h2/H2JdbcDdl.java | 24 +++++++++++-------- .../cloud/ai/service/AgentVectorService.java | 3 +++ .../src/main/resources/application-h2.yml | 12 ++++++++++ .../src/main/resources/sql/data-h2.sql | 11 +++++---- .../src/main/resources/sql/schema-h2.sql | 4 ++-- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java index a4e5de9598..ae6f853325 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -101,7 +101,7 @@ public List showTables(Connection connection, String schema, String List tableInfoList = Lists.newArrayList(); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, - String.format(sql, connection.getCatalog(), tablePattern)); + String.format(sql, connection.getSchema(), tablePattern)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -130,7 +130,7 @@ public List fetchTables(Connection connection, String schema, List< String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, - String.format(sql, connection.getCatalog(), tableListStr)); + String.format(sql, connection.getSchema(), tableListStr)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -153,13 +153,15 @@ public List fetchTables(Connection connection, String schema, List< @Override public List showColumns(Connection connection, String schema, String table) { - String sql = "SELECT column_name, remarks, data_type, " + "IF(IS_IDENTITY='YES','true','false') AS '主键唯一', \n" - + "IF(IS_NULLABLE='NO','true','false') AS '非空' \n" + "FROM information_schema.COLUMNS " + String sql = "SELECT column_name, remarks, data_type, \n" + + "CASE WHEN IS_IDENTITY = 'YES' THEN TRUE ELSE FALSE END AS 主键唯一, \n" + + "CASE WHEN IS_NULLABLE = 'NO' THEN TRUE ELSE FALSE END AS 非空 \n" + + "FROM information_schema.COLUMNS " + "WHERE table_schema='%s' " + "and table_name='%s';"; List columnInfoList = Lists.newArrayList(); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", - String.format(sql, connection.getCatalog(), table)); + String.format(sql, connection.getSchema(), table)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -186,16 +188,18 @@ public List showColumns(Connection connection, String schema, Stri @Override public List showForeignKeys(Connection connection, String schema, List tables) { - String sql = "SELECT \n" + " kc.TABLE_NAME AS '表名',\n" + " kc.COLUMN_NAME AS '列名',\n" - + " kc2.table_name AS '引用表名',\n" + " kc2.column_name AS '引用列名'\n" + "FROM \n" + String sql = "SELECT \n" + " kc.TABLE_NAME AS 表名,\n" + " kc.COLUMN_NAME AS 列名,\n" + + " kc2.table_name AS 引用表名,\n" + " kc2.column_name AS 引用列名\n" + + "FROM \n" + " information_schema.referential_constraints rc \n" - + " join information_schema.key_column_usage kc on rc.constraint_name=kc.constraint_name \n" - + " join information_schema.key_column_usage kc2 on rc.unique_constraint_name=kc2.constraint_name; "; + + " join information_schema.key_column_usage kc on rc.constraint_name=kc.constraint_name \n" + + " join information_schema.key_column_usage kc2 on rc.unique_constraint_name=kc2.constraint_name \n" + + "WHERE kc.constraint_schema='%s' and kc.table_name in (%s) and kc2.table_name in (%s);"; List foreignKeyInfoList = Lists.newArrayList(); String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); try { - sql = String.format(sql, connection.getCatalog(), tableListStr, tableListStr); + sql = String.format(sql, connection.getSchema(), tableListStr, tableListStr); String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", sql); if (resultArr.length <= 1) { return Lists.newArrayList(); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java index e9a2f6aa2d..400a974c0c 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java @@ -392,6 +392,9 @@ private DbConfig createDbConfigFromDatasource(com.alibaba.cloud.ai.entity.Dataso if ("mysql".equalsIgnoreCase(datasource.getType())) { dbConfig.setConnectionType("jdbc"); dbConfig.setDialectType("mysql"); + } else if ("h2".equalsIgnoreCase(datasource.getType())) { + dbConfig.setConnectionType("jdbc"); + dbConfig.setDialectType("h2"); } // Support for other database types can be extended here // else if ("postgresql".equalsIgnoreCase(datasource.getType())) { diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml index 594d32d777..e85b2bf6bf 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml @@ -106,3 +106,15 @@ logging: level: com.alibaba.cloud.ai.mapper: debug org.springframework.jdbc: debug + +# 数据库配置 +chatbi: + dbconfig: + # 数据源配置 + url: ${JDBC_URL:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} # 如:jdbc:mysql://host:port/database + username: ${DB_USER:root} + password: ${DB_PASSWORD:root} + # 连接参数 + connectiontype: jdbc + dialecttype: h2 # 可选:mysql、postgresql + schema: ${DB_SCHEMA} # PostgreSQL需要 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql index 7f942666e4..a1bb67ba60 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql @@ -34,11 +34,12 @@ INSERT IGNORE INTO `agent_knowledge` (`id`, `agent_id`, `title`, `content`, `typ -- 示例数据源可以运行docker-compose-datasource.yml建立,或者手动修改为自己的数据源 INSERT IGNORE INTO `datasource` (`id`, `name`, `type`, `host`, `port`, `database_name`, `username`, `password`, `connection_url`, `status`, `test_status`, `description`, `creator_id`, `create_time`, `update_time`) VALUES (1, '生产环境MySQL数据库', 'mysql', 'mysql-data', 3306, 'product_db', 'root', 'root', 'jdbc:mysql://mysql-data:3306/product_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true', 'active', 'success', '生产环境主数据库,包含核心业务数据', 2100246635, NOW(), NOW()), -(2, '数据仓库PostgreSQL', 'postgresql', 'postgres-data', 5432, 'data_warehouse', 'postgres', 'postgres', 'jdbc:postgresql://postgres-data:5432/data_warehouse', 'active', 'success', '数据仓库,用于数据分析和报表生成', 2100246635, NOW(), NOW()); +(2, '数据仓库PostgreSQL', 'postgresql', 'postgres-data', 5432, 'data_warehouse', 'postgres', 'postgres', 'jdbc:postgresql://postgres-data:5432/data_warehouse', 'active', 'success', '数据仓库,用于数据分析和报表生成', 2100246635, NOW(), NOW()), +(3, 'public', 'h2', null, null, 'test', 'root', 'root', 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE', 'active', 'success', 'h2测试数据库,包含核心业务数据', 2100246635, NOW(), NOW()); -- 智能体数据源关联示例数据 INSERT IGNORE INTO `agent_datasource` (`id`, `agent_id`, `datasource_id`, `is_active`, `create_time`, `update_time`) VALUES -(1, 1, 2, 1, NOW(), NOW()), -- 中国人口GDP数据智能体使用数据仓库 -(2, 2, 1, 1, NOW(), NOW()), -- 销售数据分析智能体使用生产环境数据库 -(3, 3, 1, 1, NOW(), NOW()), -- 财务报表智能体使用生产环境数据库 -(4, 4, 1, 1, NOW(), NOW()); -- 库存管理智能体使用生产环境数据库 +(1, 1, 3, 1, NOW(), NOW()), -- 中国人口GDP数据智能体使用数据仓库 +(2, 2, 3, 1, NOW(), NOW()), -- 销售数据分析智能体使用生产环境数据库 +(3, 3, 3, 1, NOW(), NOW()), -- 财务报表智能体使用生产环境数据库 +(4, 4, 3, 1, NOW(), NOW()); -- 库存管理智能体使用生产环境数据库 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql index 6f5b448a64..413dd4e951 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql @@ -95,8 +95,8 @@ CREATE TABLE IF NOT EXISTS datasource ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT '数据源名称', type VARCHAR(50) NOT NULL COMMENT '数据源类型:mysql, postgresql', - host VARCHAR(255) NOT NULL COMMENT '主机地址', - port INT NOT NULL COMMENT '端口号', + host VARCHAR(255) COMMENT '主机地址', + port INT COMMENT '端口号', database_name VARCHAR(255) NOT NULL COMMENT '数据库名称', username VARCHAR(255) NOT NULL COMMENT '用户名', password VARCHAR(255) NOT NULL COMMENT '密码(加密存储)', From c39df03302f1f362cd8adcc4fcc4d70661bf3f23 Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Fri, 5 Sep 2025 17:48:55 +0800 Subject: [PATCH 05/11] =?UTF-8?q?style(nl2sql):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java index ae6f853325..6034e8d8b6 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -155,8 +155,7 @@ public List fetchTables(Connection connection, String schema, List< public List showColumns(Connection connection, String schema, String table) { String sql = "SELECT column_name, remarks, data_type, \n" + "CASE WHEN IS_IDENTITY = 'YES' THEN TRUE ELSE FALSE END AS 主键唯一, \n" - + "CASE WHEN IS_NULLABLE = 'NO' THEN TRUE ELSE FALSE END AS 非空 \n" - + "FROM information_schema.COLUMNS " + + "CASE WHEN IS_NULLABLE = 'NO' THEN TRUE ELSE FALSE END AS 非空 \n" + "FROM information_schema.COLUMNS " + "WHERE table_schema='%s' " + "and table_name='%s';"; List columnInfoList = Lists.newArrayList(); try { @@ -189,8 +188,7 @@ public List showColumns(Connection connection, String schema, Stri @Override public List showForeignKeys(Connection connection, String schema, List tables) { String sql = "SELECT \n" + " kc.TABLE_NAME AS 表名,\n" + " kc.COLUMN_NAME AS 列名,\n" - + " kc2.table_name AS 引用表名,\n" + " kc2.column_name AS 引用列名\n" - + "FROM \n" + + " kc2.table_name AS 引用表名,\n" + " kc2.column_name AS 引用列名\n" + "FROM \n" + " information_schema.referential_constraints rc \n" + " join information_schema.key_column_usage kc on rc.constraint_name=kc.constraint_name \n" + " join information_schema.key_column_usage kc2 on rc.unique_constraint_name=kc2.constraint_name \n" From 691093a708139e9bf8f1aa5369d0009f2c52323b Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Fri, 5 Sep 2025 17:53:23 +0800 Subject: [PATCH 06/11] =?UTF-8?q?style(nl2sql):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8D=A2=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/cloud/ai/service/AgentVectorService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java index 400a974c0c..60fbbd327b 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java @@ -392,7 +392,8 @@ private DbConfig createDbConfigFromDatasource(com.alibaba.cloud.ai.entity.Dataso if ("mysql".equalsIgnoreCase(datasource.getType())) { dbConfig.setConnectionType("jdbc"); dbConfig.setDialectType("mysql"); - } else if ("h2".equalsIgnoreCase(datasource.getType())) { + } + else if ("h2".equalsIgnoreCase(datasource.getType())) { dbConfig.setConnectionType("jdbc"); dbConfig.setDialectType("h2"); } From 438ae51f0192b3633f5db7410c607da06f68a840 Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Sat, 6 Sep 2025 07:07:32 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat(nl2sql):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AE=BF=E9=97=AE=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E5=A4=9A=E7=A7=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 mysqlAccessor 重命名为 dbAccessor,统一数据库访问接口 - 在 BaseDefaultConfiguration 中根据数据库类型动态选择合适的 Accessor - 更新相关服务类以使用新的 dbAccessor - 注释掉数据源类型检查代码,为后续多数据库支持做准备 --- .../ai/config/BaseDefaultConfiguration.java | 19 +++++++++++++++++-- .../cloud/ai/config/Nl2sqlConfiguration.java | 2 +- .../analytic/AnalyticNl2SqlService.java | 2 +- .../service/simple/SimpleNl2SqlService.java | 2 +- .../simple/SimpleVectorStoreService.java | 2 +- .../cloud/ai/service/AgentVectorService.java | 11 ++++++----- ...nalyticDbVectorStoreManagementService.java | 2 +- .../SimpleVectorStoreManagementService.java | 2 +- 8 files changed, 29 insertions(+), 13 deletions(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java index a2e0ee8e4a..4bc35bb4df 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java @@ -45,8 +45,17 @@ public class BaseDefaultConfiguration { private final DbConfig dbConfig; - private BaseDefaultConfiguration(@Qualifier("mysqlAccessor") Accessor accessor, DbConfig dbConfig) { - this.dbAccessor = accessor; + private BaseDefaultConfiguration(DbConfig dbConfig, @Qualifier("mysqlAccessor") Accessor mysqlDbAccessor, + @Qualifier("h2Accessor") Accessor h2DbAccessor, @Qualifier("postgreAccessor") Accessor postgreDbAccessor) { + if (dbConfig.getDialectType().equals("h2")) { + dbAccessor = h2DbAccessor; + } + else if (dbConfig.getDialectType().equals("postgre")) { + dbAccessor = postgreDbAccessor; + } + else { + dbAccessor = mysqlDbAccessor; + } this.dbConfig = dbConfig; } @@ -70,4 +79,10 @@ public BaseSchemaService defaultSchemaService( return new SimpleSchemaService(dbConfig, gson, vectorStoreService); } + @Bean("dbAccessor") + @ConditionalOnMissingBean(name = "dbAccessor") + public Accessor dbAccessor() { + return dbAccessor; + } + } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/Nl2sqlConfiguration.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/Nl2sqlConfiguration.java index 7caad81d6f..6bf3dce7dd 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/Nl2sqlConfiguration.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/Nl2sqlConfiguration.java @@ -144,7 +144,7 @@ public class Nl2sqlConfiguration { public Nl2sqlConfiguration(@Qualifier("nl2SqlServiceImpl") BaseNl2SqlService nl2SqlService, @Qualifier("schemaServiceImpl") BaseSchemaService schemaService, - @Qualifier("mysqlAccessor") Accessor dbAccessor, DbConfig dbConfig, + @Qualifier("dbAccessor") Accessor dbAccessor, DbConfig dbConfig, CodeExecutorProperties codeExecutorProperties, CodePoolExecutorService codePoolExecutor, SemanticModelRecallService semanticModelRecallService, BusinessKnowledgeRecallService businessKnowledgeRecallService, UserPromptConfigService promptConfigService, diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/analytic/AnalyticNl2SqlService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/analytic/AnalyticNl2SqlService.java index 2e516977b5..fad2fff65c 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/analytic/AnalyticNl2SqlService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/analytic/AnalyticNl2SqlService.java @@ -33,7 +33,7 @@ public class AnalyticNl2SqlService extends BaseNl2SqlService { @Autowired public AnalyticNl2SqlService(@Qualifier("analyticVectorStoreService") BaseVectorStoreService vectorStoreService, @Qualifier("analyticSchemaService") BaseSchemaService schemaService, LlmService aiService, - @Qualifier("mysqlAccessor") Accessor dbAccessor, DbConfig dbConfig) { + @Qualifier("dbAccessor") Accessor dbAccessor, DbConfig dbConfig) { super(vectorStoreService, schemaService, aiService, dbAccessor, dbConfig); } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleNl2SqlService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleNl2SqlService.java index b23b651d47..f0ee6f3012 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleNl2SqlService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleNl2SqlService.java @@ -32,7 +32,7 @@ public class SimpleNl2SqlService extends BaseNl2SqlService { @Autowired public SimpleNl2SqlService(@Qualifier("simpleVectorStoreService") BaseVectorStoreService vectorStoreService, @Qualifier("simpleSchemaService") BaseSchemaService schemaService, LlmService aiService, - @Qualifier("mysqlAccessor") Accessor accessor, DbConfig dbConfig) { + @Qualifier("dbAccessor") Accessor accessor, DbConfig dbConfig) { super(vectorStoreService, schemaService, aiService, accessor, dbConfig); } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleVectorStoreService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleVectorStoreService.java index e94c8eee3c..2bf43422ca 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleVectorStoreService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/simple/SimpleVectorStoreService.java @@ -69,7 +69,7 @@ public class SimpleVectorStoreService extends BaseVectorStoreService { @Autowired public SimpleVectorStoreService(EmbeddingModel embeddingModel, Gson gson, - @Qualifier("mysqlAccessor") Accessor dbAccessor, DbConfig dbConfig, + @Qualifier("dbAccessor") Accessor dbAccessor, DbConfig dbConfig, AgentVectorStoreManager agentVectorStoreManager) { log.info("Initializing SimpleVectorStoreService with EmbeddingModel: {}", embeddingModel.getClass().getSimpleName()); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java index 60fbbd327b..3ce3e34fd8 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AgentVectorService.java @@ -275,7 +275,7 @@ private Document createDocumentFromKnowledge(String agentId, AgentKnowledge know private DatasourceService datasourceService; @Autowired - @Qualifier("mysqlAccessor") + @Qualifier("dbAccessor") private com.alibaba.cloud.ai.connector.accessor.Accessor dbAccessor; /** @@ -345,10 +345,11 @@ public List getDatasourceTables(Integer datasourceId) { } // Check data source type, currently only supports MySQL - if (!"mysql".equalsIgnoreCase(datasource.getType())) { - log.warn("Unsupported datasource type: {}, only MySQL is supported currently", datasource.getType()); - return new ArrayList<>(); - } + // if (!"mysql".equalsIgnoreCase(datasource.getType())) { + // log.warn("Unsupported datasource type: {}, only MySQL is supported + // currently", datasource.getType()); + // return new ArrayList<>(); + // } // Create database configuration DbConfig dbConfig = createDbConfigFromDatasource(datasource); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AnalyticDbVectorStoreManagementService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AnalyticDbVectorStoreManagementService.java index dd28f5b086..d82c14cafd 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AnalyticDbVectorStoreManagementService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/AnalyticDbVectorStoreManagementService.java @@ -73,7 +73,7 @@ public class AnalyticDbVectorStoreManagementService implements VectorStoreManage private VectorStore vectorStore; @Autowired - @Qualifier("mysqlAccessor") + @Qualifier("dbAccessor") private Accessor dbAccessor; @Autowired diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/SimpleVectorStoreManagementService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/SimpleVectorStoreManagementService.java index 1a475bd333..49f414b592 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/SimpleVectorStoreManagementService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/service/SimpleVectorStoreManagementService.java @@ -55,7 +55,7 @@ public class SimpleVectorStoreManagementService implements VectorStoreManagement @Autowired public SimpleVectorStoreManagementService(@Value("${spring.ai.dashscope.api-key:default_api_key}") String apiKey, - Gson gson, @Qualifier("mysqlAccessor") Accessor dbAccessor, DbConfig dbConfig) { + Gson gson, @Qualifier("dbAccessor") Accessor dbAccessor, DbConfig dbConfig) { this.gson = gson; this.dbAccessor = dbAccessor; this.dbConfig = dbConfig; From 85925ee8a385da12a1448a6ae29b10df7d5d6bbc Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Mon, 8 Sep 2025 14:42:30 +0800 Subject: [PATCH 08/11] =?UTF-8?q?feat(h2):=20=E9=80=82=E9=85=8D=E5=A4=9Asc?= =?UTF-8?q?hema=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新数据初始化脚本路径和内容 - 调整 H2JdbcDdl 类以支持 schema 参数 - 修改 SqlExecuteNode 和 SqlExecutor 以支持 H2 数据库的 schema 切换 --- spring-ai-alibaba-nl2sql/pom.xml | 2 +- .../alibaba/cloud/ai/node/SqlExecuteNode.java | 1 + .../cloud/ai/connector/SqlExecutor.java | 5 + .../cloud/ai/connector/h2/H2JdbcDdl.java | 12 +-- .../src/main/resources/application-h2.yml | 16 ++-- .../main/resources/sql/{ => h2}/data-h2.sql | 4 +- .../main/resources/sql/h2/product_data.sql | 93 +++++++++++++++++++ .../main/resources/sql/h2/product_schema.sql | 71 ++++++++++++++ .../main/resources/sql/{ => h2}/schema-h2.sql | 0 9 files changed, 189 insertions(+), 15 deletions(-) rename spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/{ => h2}/data-h2.sql (95%) create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_data.sql create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_schema.sql rename spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/{ => h2}/schema-h2.sql (100%) diff --git a/spring-ai-alibaba-nl2sql/pom.xml b/spring-ai-alibaba-nl2sql/pom.xml index 19dadc5f5d..23cb863d16 100644 --- a/spring-ai-alibaba-nl2sql/pom.xml +++ b/spring-ai-alibaba-nl2sql/pom.xml @@ -76,8 +76,8 @@ com.h2database h2 - runtime ${h2.version} + runtime commons-collections diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java index 5e170ff58b..4b4d7e8265 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/SqlExecuteNode.java @@ -185,6 +185,7 @@ private Map executeSqlQuery(OverAllState state, Integer currentS // Execute business logic first - actual SQL execution DbQueryParameter dbQueryParameter = new DbQueryParameter(); dbQueryParameter.setSql(sqlQuery); + dbQueryParameter.setSchema(dbConfig.getSchema()); try { // Execute SQL query and get results immediately diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/SqlExecutor.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/SqlExecutor.java index 934bbe16c1..3c36fbb780 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/SqlExecutor.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/SqlExecutor.java @@ -58,6 +58,11 @@ public static ResultSetBO executeSqlAndReturnObject(Connection connection, Strin statement.execute("set search_path = '" + schema + "';"); } } + else if (dialect.equals(DatabaseDialectEnum.H2.code)) { + if (StringUtils.isNotEmpty(schema)) { + statement.execute("use " + schema + ";"); + } + } try (ResultSet rs = statement.executeQuery(sql)) { return ResultSetBuilder.buildFrom(rs, schema); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java index 6034e8d8b6..c42b5dc7d5 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -101,7 +101,7 @@ public List showTables(Connection connection, String schema, String List tableInfoList = Lists.newArrayList(); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, - String.format(sql, connection.getSchema(), tablePattern)); + String.format(sql, schema, tablePattern)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -112,7 +112,7 @@ public List showTables(Connection connection, String schema, String } String tableName = resultArr[i][0]; String tableDesc = resultArr[i][1]; - tableInfoList.add(TableInfoBO.builder().name(tableName).description(tableDesc).build()); + tableInfoList.add(TableInfoBO.builder().schema(schema).name(tableName).description(tableDesc).build()); } } catch (SQLException e) { @@ -130,7 +130,7 @@ public List fetchTables(Connection connection, String schema, List< String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, - String.format(sql, connection.getSchema(), tableListStr)); + String.format(sql, schema, tableListStr)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -141,7 +141,7 @@ public List fetchTables(Connection connection, String schema, List< } String tableName = resultArr[i][0]; String tableDesc = resultArr[i][1]; - tableInfoList.add(TableInfoBO.builder().name(tableName).description(tableDesc).build()); + tableInfoList.add(TableInfoBO.builder().schema(schema).name(tableName).description(tableDesc).build()); } } catch (SQLException e) { @@ -160,7 +160,7 @@ public List showColumns(Connection connection, String schema, Stri List columnInfoList = Lists.newArrayList(); try { String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", - String.format(sql, connection.getSchema(), table)); + String.format(sql, schema, table)); if (resultArr.length <= 1) { return Lists.newArrayList(); } @@ -197,7 +197,7 @@ public List showForeignKeys(Connection connection, String sche String tableListStr = String.join(", ", tables.stream().map(x -> "'" + x + "'").collect(Collectors.toList())); try { - sql = String.format(sql, connection.getSchema(), tableListStr, tableListStr); + sql = String.format(sql, schema, tableListStr, tableListStr); String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, "INFORMATION_SCHEMA", sql); if (resultArr.length <= 1) { return Lists.newArrayList(); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml index e85b2bf6bf..9404bb4c09 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/application-h2.yml @@ -2,7 +2,7 @@ server: port: 8065 spring: datasource: - url: ${NL2SQL_DATASOURCE_URL:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} + url: ${NL2SQL_DATASOURCE_URL:jdbc:h2:mem:nl2sql_database;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} username: ${NL2SQL_DATASOURCE_USERNAME:root} password: ${NL2SQL_DATASOURCE_PASSWORD:root} driver-class-name: org.h2.Driver @@ -19,8 +19,12 @@ spring: sql: init: mode: always - schema-locations: classpath:sql/schema-h2.sql - data-locations: classpath:sql/data-h2.sql + schema-locations: + - classpath:sql/h2/schema-h2.sql + - classpath:sql/h2/product_schema.sql + data-locations: + - classpath:sql/h2/data-h2.sql + - classpath:sql/h2/product_data.sql continue-on-error: true separator: ; encoding: utf-8 @@ -111,10 +115,10 @@ logging: chatbi: dbconfig: # 数据源配置 - url: ${JDBC_URL:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} # 如:jdbc:mysql://host:port/database + url: ${JDBC_URL:jdbc:h2:mem:nl2sql_database;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE} # 如:jdbc:mysql://host:port/database username: ${DB_USER:root} password: ${DB_PASSWORD:root} # 连接参数 connectiontype: jdbc - dialecttype: h2 # 可选:mysql、postgresql - schema: ${DB_SCHEMA} # PostgreSQL需要 + dialecttype: h2 # 可选:mysql、postgresql、h2 + schema: ${DB_SCHEMA:product_db} # PostgreSQL h2需要 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/data-h2.sql similarity index 95% rename from spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql rename to spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/data-h2.sql index a1bb67ba60..6f4fcedafc 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/data-h2.sql +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/data-h2.sql @@ -35,11 +35,11 @@ INSERT IGNORE INTO `agent_knowledge` (`id`, `agent_id`, `title`, `content`, `typ INSERT IGNORE INTO `datasource` (`id`, `name`, `type`, `host`, `port`, `database_name`, `username`, `password`, `connection_url`, `status`, `test_status`, `description`, `creator_id`, `create_time`, `update_time`) VALUES (1, '生产环境MySQL数据库', 'mysql', 'mysql-data', 3306, 'product_db', 'root', 'root', 'jdbc:mysql://mysql-data:3306/product_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true', 'active', 'success', '生产环境主数据库,包含核心业务数据', 2100246635, NOW(), NOW()), (2, '数据仓库PostgreSQL', 'postgresql', 'postgres-data', 5432, 'data_warehouse', 'postgres', 'postgres', 'jdbc:postgresql://postgres-data:5432/data_warehouse', 'active', 'success', '数据仓库,用于数据分析和报表生成', 2100246635, NOW(), NOW()), -(3, 'public', 'h2', null, null, 'test', 'root', 'root', 'jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE', 'active', 'success', 'h2测试数据库,包含核心业务数据', 2100246635, NOW(), NOW()); +(3, 'product_db', 'h2', null, null, 'product_db', 'root', 'root', 'jdbc:h2:mem:nl2sql_database;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE', 'active', 'success', 'h2测试数据库,包含核心业务数据', 2100246635, NOW(), NOW()); -- 智能体数据源关联示例数据 INSERT IGNORE INTO `agent_datasource` (`id`, `agent_id`, `datasource_id`, `is_active`, `create_time`, `update_time`) VALUES -(1, 1, 3, 1, NOW(), NOW()), -- 中国人口GDP数据智能体使用数据仓库 +(1, 1, 2, 1, NOW(), NOW()), -- 中国人口GDP数据智能体使用数据仓库 (2, 2, 3, 1, NOW(), NOW()), -- 销售数据分析智能体使用生产环境数据库 (3, 3, 3, 1, NOW(), NOW()), -- 财务报表智能体使用生产环境数据库 (4, 4, 3, 1, NOW(), NOW()); -- 库存管理智能体使用生产环境数据库 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_data.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_data.sql new file mode 100644 index 0000000000..4eb6e145d8 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_data.sql @@ -0,0 +1,93 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ + +-- 插入用户数据 +INSERT INTO product_db.users (username, email) VALUES + ('alice', 'alice@example.com'), + ('bob', 'bob@example.com'), + ('cathy', 'cathy@example.com'), + ('daniel', 'daniel@example.com'), + ('emily', 'emily@example.com') + ON DUPLICATE KEY UPDATE username=VALUES(username); + +-- 插入商品分类数据 +INSERT INTO product_db.categories (name) VALUES + ('电子产品'), + ('服装'), + ('图书'), + ('家居用品'), + ('食品'); + +-- 插入商品数据 +INSERT INTO product_db.products (name, price, stock) VALUES + ('智能手机', 2999.00, 100), + ('T恤衫', 89.00, 500), + ('小说', 39.00, 200), + ('咖啡机', 599.00, 50), + ('牛奶', 15.00, 300), + ('笔记本电脑', 4999.00, 30), + ('沙发', 2599.00, 10), + ('巧克力', 25.00, 100), + ('羽绒服', 399.00, 80), + ('历史书', 69.00, 150); + +-- 插入商品-分类关联数据 +INSERT INTO product_db.product_categories (product_id, category_id) VALUES + (1, 1), -- 智能手机-电子产品 + (2, 2), -- T恤衫-服装 + (3, 3), -- 小说-图书 + (4, 1), (4, 4), -- 咖啡机-电子产品、家居用品 + (5, 5), -- 牛奶-食品 + (6, 1), -- 笔记本电脑-电子产品 + (7, 4), -- 沙发-家居用品 + (8, 5), -- 巧克力-食品 + (9, 2), -- 羽绒服-服装 + (10, 3); -- 历史书-图书 + +-- 插入订单数据 +INSERT INTO product_db.orders (user_id, total_amount, status, order_date) VALUES + (1, 3088.00, 'completed', '2025-06-01 10:10:00'), + (2, 39.00, 'pending', '2025-06-02 09:23:00'), + (3, 1204.00, 'completed', '2025-06-03 13:45:00'), + (4, 65.00, 'cancelled', '2025-06-04 16:05:00'), + (5, 5113.00, 'completed', '2025-06-05 20:12:00'), + (1, 814.00, 'completed', '2025-06-05 21:03:00'), + (2, 424.00, 'pending', '2025-06-06 08:10:00'), + (3, 524.00, 'completed', '2025-06-06 14:48:00'), + (4, 399.00, 'completed', '2025-06-07 10:15:00'), + (5, 129.00, 'pending', '2025-06-07 18:00:00'); + +-- 插入订单明细数据 +INSERT INTO product_db.order_items (order_id, product_id, quantity, unit_price) VALUES + (1, 1, 1, 2999.00), + (1, 2, 1, 89.00), + (2, 3, 1, 39.00), + (3, 4, 2, 599.00), + (3, 5, 2, 3.00), + (4, 8, 2, 25.00), + (4, 5, 1, 15.00), + (5, 6, 1, 4999.00), + (5, 2, 1, 89.00), + (5, 5, 5, 5.00), + (5, 8, 1, 25.00), + (6, 9, 2, 399.00), + (6, 3, 1, 16.00), + (7, 2, 2, 89.00), + (7, 3, 3, 39.00), + (8, 10, 4, 69.00), + (9, 9, 1, 399.00), + (10, 8, 4, 25.00), + (10, 5, 1, 29.00); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_schema.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_schema.sql new file mode 100644 index 0000000000..e31d53bdd6 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/product_schema.sql @@ -0,0 +1,71 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ + +CREATE SCHEMA product_db; + +-- 用户表 +CREATE TABLE product_db.users ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID,主键自增', + username VARCHAR(50) NOT NULL COMMENT '用户名', + email VARCHAR(100) NOT NULL COMMENT '用户邮箱', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '用户注册时间' +) COMMENT='用户表'; + +-- 商品表 +CREATE TABLE product_db.products ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID,主键自增', + name VARCHAR(100) NOT NULL COMMENT '商品名称', + price DECIMAL(10,2) NOT NULL COMMENT '商品单价', + stock INT NOT NULL COMMENT '商品库存数量', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '商品上架时间' +) COMMENT='商品表'; + +-- 订单表 +CREATE TABLE product_db.orders ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID,主键自增', + user_id INT NOT NULL COMMENT '下单用户ID', + order_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间', + total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额', + status VARCHAR(20) DEFAULT 'pending' COMMENT '订单状态(pending/completed/cancelled等)', + FOREIGN KEY (user_id) REFERENCES users(id) +) COMMENT='订单表'; + +-- 订单明细表 +CREATE TABLE product_db.order_items ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单明细ID,主键自增', + order_id INT NOT NULL COMMENT '订单ID', + product_id INT NOT NULL COMMENT '商品ID', + quantity INT NOT NULL COMMENT '购买数量', + unit_price DECIMAL(10,2) NOT NULL COMMENT '下单时商品单价', + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) +) COMMENT='订单明细表'; + +-- 商品分类表 +CREATE TABLE product_db.categories ( + id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID,主键自增', + name VARCHAR(50) NOT NULL COMMENT '分类名称' +) COMMENT='商品分类表'; + +-- 商品-分类关联表(多对多) +CREATE TABLE product_db.product_categories ( + product_id INT NOT NULL COMMENT '商品ID', + category_id INT NOT NULL COMMENT '分类ID', + PRIMARY KEY (product_id, category_id), + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (category_id) REFERENCES categories(id) +) COMMENT='商品与分类关联表'; + diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/schema-h2.sql similarity index 100% rename from spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/schema-h2.sql rename to spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/resources/sql/h2/schema-h2.sql From 20f54fa96eca1efd5d3aecf59acbf693db8ba54a Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Sun, 14 Sep 2025 10:54:08 +0800 Subject: [PATCH 09/11] =?UTF-8?q?test(h2):=20=E6=B7=BB=E5=8A=A0=20H2=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 H2AccessorIntegrationTest 类,实现对 H2 数据库的各种操作测试- 新增 H2DatabaseIntegrationTest 类,验证 H2 数据库配置和初始化脚本 - 修改 H2JdbcDdl 类中的 sampleColumn 方法,增加 schema 参数 --- .../cloud/ai/connector/h2/H2JdbcDdl.java | 4 +- .../ai/service/H2AccessorIntegrationTest.java | 153 ++++++++++++++++++ .../ai/service/H2DatabaseIntegrationTest.java | 99 ++++++++++++ 3 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java create mode 100644 spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java index c42b5dc7d5..aad52dcb52 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-common/src/main/java/com/alibaba/cloud/ai/connector/h2/H2JdbcDdl.java @@ -224,10 +224,10 @@ public List showForeignKeys(Connection connection, String sche @Override public List sampleColumn(Connection connection, String schema, String table, String column) { - String sql = "SELECT \n" + " `%s`\n" + "FROM \n" + " `%s`\n" + "LIMIT 99;"; + String sql = "SELECT \n" + " `%s`\n" + "FROM \n" + " `%s`.`%s`\n" + "LIMIT 99;"; List sampleInfo = Lists.newArrayList(); try { - sql = String.format(sql, column, table); + sql = String.format(sql, column, schema, table); String[][] resultArr = SqlExecutor.executeSqlAndReturnArr(connection, null, sql); if (resultArr.length <= 1) { return Lists.newArrayList(); diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java new file mode 100644 index 0000000000..f0910143f1 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ +package com.alibaba.cloud.ai.service; + +import com.alibaba.cloud.ai.connector.accessor.Accessor; +import com.alibaba.cloud.ai.connector.bo.ColumnInfoBO; +import com.alibaba.cloud.ai.connector.bo.DbQueryParameter; +import com.alibaba.cloud.ai.connector.bo.ForeignKeyInfoBO; +import com.alibaba.cloud.ai.connector.bo.ResultSetBO; +import com.alibaba.cloud.ai.connector.bo.TableInfoBO; +import com.alibaba.cloud.ai.connector.config.DbConfig; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * H2数据库集成测试类 用于验证H2数据库配置和初始化脚本是否正确 + */ +@SpringBootTest +@ActiveProfiles("h2") +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class H2AccessorIntegrationTest { + + @Autowired + private Accessor dbAccessor; + + @Autowired + private DbConfig dbConfig; + + @Test + public void testShowTables() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + + List publicTableInfoBOS = dbAccessor.showTables(dbConfig, queryParam); + + assertThat(publicTableInfoBOS).isNotEmpty(); + + queryParam.setSchema("product_db"); + List productTableInfoBOS = dbAccessor.showTables(dbConfig, queryParam); + + assertThat(productTableInfoBOS).isNotEmpty(); + } + + @Test + public void testFetchTables() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + queryParam.setTables(List.of("datasource")); + + List publicTableInfoBOS = dbAccessor.fetchTables(dbConfig, queryParam); + + assertThat(publicTableInfoBOS).isNotEmpty(); + + queryParam.setSchema("product_db"); + queryParam.setTables(List.of("orders")); + List productTableInfoBOS = dbAccessor.fetchTables(dbConfig, queryParam); + + assertThat(productTableInfoBOS).isNotEmpty(); + } + + @Test + public void testShowColumns() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + queryParam.setTable("datasource"); + + List publicColumnInfoBOS = dbAccessor.showColumns(dbConfig, queryParam); + + assertThat(publicColumnInfoBOS).isNotEmpty(); + + queryParam.setSchema("product_db"); + queryParam.setTable("orders"); + List productColumnInfoBOS = dbAccessor.showColumns(dbConfig, queryParam); + + assertThat(productColumnInfoBOS).isNotEmpty(); + } + + @Test + public void testShowForeignKeys() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + queryParam.setTables(List.of("agent_datasource", "agent")); + + List publicForeignKeyInfoBOS = dbAccessor.showForeignKeys(dbConfig, queryParam); + + assertThat(publicForeignKeyInfoBOS).isNotEmpty(); + + queryParam.setSchema("product_db"); + queryParam.setTables(List.of("order_items", "orders")); + List productForeignKeyInfoBOS = dbAccessor.showForeignKeys(dbConfig, queryParam); + + assertThat(productForeignKeyInfoBOS).isNotEmpty(); + } + + @Test + public void testSampleColumn() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + queryParam.setTable("datasource"); + queryParam.setColumn("name"); + + List publicForeignKeyInfoBOS = dbAccessor.sampleColumn(dbConfig, queryParam); + + assertThat(publicForeignKeyInfoBOS).isNotEmpty(); + + queryParam.setSchema("product_db"); + queryParam.setTable("orders"); + queryParam.setColumn("status"); + List productForeignKeyInfoBOS = dbAccessor.sampleColumn(dbConfig, queryParam); + + assertThat(productForeignKeyInfoBOS).isNotEmpty(); + } + + @Test + public void testExecuteSqlAndReturnObject() throws Exception { + DbQueryParameter queryParam = DbQueryParameter.from(dbConfig); + queryParam.setSchema("public"); + queryParam.setSql("select count(*) from datasource"); + + ResultSetBO publicResultSetBO = dbAccessor.executeSqlAndReturnObject(dbConfig, queryParam); + + assertThat(publicResultSetBO).isNotNull(); + assertThat(publicResultSetBO.getData()).isNotEmpty(); + + queryParam.setSchema("product_db"); + queryParam.setSql("select count(*) from orders"); + ResultSetBO productResultSetBO = dbAccessor.executeSqlAndReturnObject(dbConfig, queryParam); + + assertThat(productResultSetBO).isNotNull(); + assertThat(productResultSetBO.getData()).isNotEmpty(); + } + +} diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java new file mode 100644 index 0000000000..9352a4a240 --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * 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. + */ +package com.alibaba.cloud.ai.service; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * H2数据库集成测试类 用于验证H2数据库配置和初始化脚本是否正确 + */ +@SpringBootTest +@ActiveProfiles("h2") +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class H2DatabaseIntegrationTest { + + @Autowired + private DataSource dataSource; + + @Test + public void testH2DatabaseConnection() throws Exception { + try (Connection connection = dataSource.getConnection()) { + assertThat(connection).isNotNull(); + assertThat(connection.isValid(1)).isTrue(); + } + } + + @Test + public void testAgentTableExists() throws Exception { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM agent")) { + + assertThat(resultSet.next()).isTrue(); + int count = resultSet.getInt(1); + assertThat(count).isGreaterThanOrEqualTo(0); + } + } + + @Test + public void testBusinessKnowledgeTableExists() throws Exception { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM business_knowledge")) { + + assertThat(resultSet.next()).isTrue(); + int count = resultSet.getInt(1); + assertThat(count).isGreaterThanOrEqualTo(0); + } + } + + @Test + public void testSemanticModelTableExists() throws Exception { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM semantic_model")) { + + assertThat(resultSet.next()).isTrue(); + int count = resultSet.getInt(1); + assertThat(count).isGreaterThanOrEqualTo(0); + } + } + + @Test + public void testDataInitialization() throws Exception { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM datasource")) { + + assertThat(resultSet.next()).isTrue(); + int count = resultSet.getInt(1); + // 根据data-h2.sql中的数据,应该至少有一条数据 + assertThat(count).isGreaterThanOrEqualTo(0); + } + } + +} From 2dfbee1cf1c54d5193daf9d4452d52c0adbf9c24 Mon Sep 17 00:00:00 2001 From: HunterPorter Date: Sun, 14 Sep 2025 11:14:30 +0800 Subject: [PATCH 10/11] =?UTF-8?q?test(nl2sql):=E4=B8=BA=20H2AccessorIntegr?= =?UTF-8?q?ationTest=20=E5=92=8C=20H2DatabaseIntegrationTest=20=E7=B1=BB?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为确保集成测试在设置了 AI_DASHSCOPE_API_KEY 环境变量时才运行,增加了 @EnabledIfEnvironmentVariable 注解。这样可以防止在没有设置 API 密钥的情况下执行这些测试。 --- .../com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java | 2 ++ .../com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java index f0910143f1..442f1757a6 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2AccessorIntegrationTest.java @@ -23,6 +23,7 @@ import com.alibaba.cloud.ai.connector.bo.TableInfoBO; import com.alibaba.cloud.ai.connector.config.DbConfig; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; @@ -38,6 +39,7 @@ @SpringBootTest @ActiveProfiles("h2") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@EnabledIfEnvironmentVariable(named = "AI_DASHSCOPE_API_KEY", matches = ".*") public class H2AccessorIntegrationTest { @Autowired diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java index 9352a4a240..8ccd68d9ef 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/test/java/com/alibaba/cloud/ai/service/H2DatabaseIntegrationTest.java @@ -16,6 +16,7 @@ package com.alibaba.cloud.ai.service; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; @@ -34,6 +35,7 @@ @SpringBootTest @ActiveProfiles("h2") @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@EnabledIfEnvironmentVariable(named = "AI_DASHSCOPE_API_KEY", matches = ".*") public class H2DatabaseIntegrationTest { @Autowired From 878de5897beb1127ce242ca451fb861d719b18d1 Mon Sep 17 00:00:00 2001 From: VLSMB <2047857654@qq.com> Date: Mon, 22 Sep 2025 00:42:37 +0800 Subject: [PATCH 11/11] fix bugs --- .../alibaba/cloud/ai/config/BaseDefaultConfiguration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java index 4bc35bb4df..de7706255a 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/config/BaseDefaultConfiguration.java @@ -39,7 +39,7 @@ @Configuration(proxyBeanMethods = false) public class BaseDefaultConfiguration { - private static final Logger logger = LoggerFactory.getLogger(Nl2sqlConfiguration.class); + private static final Logger logger = LoggerFactory.getLogger(BaseDefaultConfiguration.class); private final Accessor dbAccessor; @@ -47,10 +47,10 @@ public class BaseDefaultConfiguration { private BaseDefaultConfiguration(DbConfig dbConfig, @Qualifier("mysqlAccessor") Accessor mysqlDbAccessor, @Qualifier("h2Accessor") Accessor h2DbAccessor, @Qualifier("postgreAccessor") Accessor postgreDbAccessor) { - if (dbConfig.getDialectType().equals("h2")) { + if ("h2".equals(dbConfig.getDialectType())) { dbAccessor = h2DbAccessor; } - else if (dbConfig.getDialectType().equals("postgre")) { + else if ("postgre".equals(dbConfig.getDialectType())) { dbAccessor = postgreDbAccessor; } else {