diff --git a/chat2db-client/src/assets/img/databaseImg/snowflake.png b/chat2db-client/src/assets/img/databaseImg/snowflake.png new file mode 100644 index 000000000..b215e1cb2 Binary files /dev/null and b/chat2db-client/src/assets/img/databaseImg/snowflake.png differ diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 115aa9403..9f87b9be5 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -2076,4 +2076,125 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ extendInfo: [], type: DatabaseTypeCode.MONGODB }, + //SNOWFLAKE + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + envItem, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '443', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authenticationType', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + items: [], + + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:snowflake://:443', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:snowflake:\/\/(.*):(\d+)(\/\?db=(\w+))?/, + template: 'jdbc:snowflake://{host}:{port}/?db={database}', + //excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable,OperationColumn.EditTable] + }, + ssh: sshConfig, + extendInfo: [], + type: DatabaseTypeCode.SNOWFLAKE + }, ]; diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index b6f2339be..f847b4ffc 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -15,6 +15,7 @@ export enum DatabaseTypeCode { PRESTO = "PRESTO", HIVE = "HIVE", KINGBASE = "KINGBASE", + SNOWFLAKE = "SNOWFLAKE", } export enum ConsoleStatus { diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index ba7c3c75c..6a01c22ff 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -2,6 +2,7 @@ import mysqlLogo from '@/assets/img/databaseImg/mysql.png'; import redisLogo from '@/assets/img/databaseImg/redis.png'; import h2Logo from '@/assets/img/databaseImg/h2.png'; import moreDBLogo from '@/assets/img/databaseImg/other.png'; +import snowflakeLogo from '@/assets/img/databaseImg/snowflake.png'; import { IDatabase } from '@/typings'; import { DatabaseTypeCode } from '@/constants' @@ -118,6 +119,13 @@ export const databaseMap: { // port: 27017, icon: '\uec21', }, + [DatabaseTypeCode.SNOWFLAKE]: { + name: 'Snowflake', + img: snowflakeLogo, + code: DatabaseTypeCode.SNOWFLAKE, + // port: 443, + icon: '\uec21', + }, // [DatabaseTypeCode.REDIS]: { // name: 'Redis', // img: moreDBLogo, diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/pom.xml b/chat2db-server/chat2db-plugins/chat2db-snowflake/pom.xml new file mode 100644 index 000000000..b45999537 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + ai.chat2db + chat2db-plugins + ${revision} + ../pom.xml + + + + + ai.chat2db + chat2db-spi + + + + chat2db-snowflake + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeDBManage.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeDBManage.java new file mode 100644 index 000000000..603a77ab4 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeDBManage.java @@ -0,0 +1,73 @@ +package ai.chat2db.plugin.snowflake; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +public class SnowflakeDBManage extends DefaultDBManage implements DBManage { + + @Override + public Connection getConnection(ConnectInfo connectInfo) { + List extendInfo = connectInfo.getExtendInfo(); + if (StringUtils.isNotBlank(connectInfo.getDatabaseName())) { + KeyValue keyValue = new KeyValue(); + keyValue.setKey("db"); + keyValue.setValue(connectInfo.getDatabaseName()); + extendInfo.add(keyValue); + } + if (StringUtils.isNotBlank(connectInfo.getSchemaName())) { + KeyValue keyValue = new KeyValue(); + keyValue.setKey("schema"); + keyValue.setValue(connectInfo.getSchemaName()); + extendInfo.add(keyValue); + } + KeyValue keyValue = new KeyValue(); + keyValue.setKey("JDBC_QUERY_RESULT_FORMAT"); + keyValue.setValue("JSON"); + extendInfo.add(keyValue); + connectInfo.setExtendInfo(extendInfo); + return super.getConnection(connectInfo); + } + + + @Override + public void connectDatabase(Connection connection, String database) { + if (StringUtils.isEmpty(database)) { + return; + } + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { + try { + SQLExecutor.getInstance().execute(connection, "USE DATABASE \"" + database + "\";"); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } else { + try { + SQLExecutor.getInstance().execute(connection, "USE SCHEMA \"" + database + "\"." + connectInfo.getSchemaName() + ";"); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = "DROP TABLE "+ format(tableName); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); + } + + public static String format(String tableName) { + return "\"" + tableName + "\""; + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeMetaData.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeMetaData.java new file mode 100644 index 000000000..5baa38e2d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakeMetaData.java @@ -0,0 +1,189 @@ +package ai.chat2db.plugin.snowflake; + +import ai.chat2db.plugin.snowflake.builder.SnowflakeSqlBuilder; +import ai.chat2db.plugin.snowflake.type.*; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.*; +import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; +import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +public class SnowflakeMetaData extends DefaultMetaService implements MetaData { + + + private List systemSchemas = Arrays.asList("INFORMATION_SCHEMA", "PUBLIC", "SCHEMA"); + + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } + + private static String VIEW_SQL + = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS DEFINITION, CHECK_OPTION, " + + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_CATALOG = '%s' AND TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; + + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, databaseName, schemaName , viewName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + if (resultSet.next()) { + table.setDdl(resultSet.getString("DEFINITION").substring(resultSet.getString("DEFINITION").indexOf("as")+3)); + } + return table; + }); + } + + + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(SnowflakeColumnTypeEnum.getTypes()) + .charsets(SnowflakeCharsetEnum.getCharsets()) + .collations(SnowflakeCollationEnum.getCollations()) + .indexTypes(SnowflakeIndexTypeEnum.getIndexTypes()) + .defaultValues(SnowflakeDefaultValueEnum.getDefaultValues()) + .build(); + } + + @Override + public SqlBuilder getSqlBuilder() { + return new SnowflakeSqlBuilder(); + } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } + + @Override + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { + // 目前仅能查看主键 + StringBuilder queryBuf = new StringBuilder("SHOW PRIMARY KEYS in "); + queryBuf.append("\"").append(tableName).append("\""); + return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> { + LinkedHashMap map = new LinkedHashMap(); + while (resultSet.next()) { + String keyName = resultSet.getString("constraint_name"); + TableIndex tableIndex = map.get(keyName); + if (tableIndex != null) { + List columnList = tableIndex.getColumnList(); + columnList.add(getTableIndexColumn(resultSet)); + columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) + .collect(Collectors.toList()); + tableIndex.setColumnList(columnList); + } else { + TableIndex index = new TableIndex(); + index.setDatabaseName(databaseName); + index.setSchemaName(schemaName); + index.setTableName(tableName); + index.setName(keyName); + //index.setUnique(!resultSet.getBoolean("Non_unique")); + index.setType(SnowflakeIndexTypeEnum.PRIMARY_KEY.getName()); + index.setComment(resultSet.getString("comment")); + List tableIndexColumns = new ArrayList<>(); + tableIndexColumns.add(getTableIndexColumn(resultSet)); + index.setColumnList(tableIndexColumns); + if ("PRIMARY".equalsIgnoreCase(keyName)) { + index.setType(SnowflakeIndexTypeEnum.PRIMARY_KEY.getName()); + } + map.put(keyName, index); + } + } + return map.values().stream().collect(Collectors.toList()); + }); + } + + private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { + TableIndexColumn tableIndexColumn = new TableIndexColumn(); + tableIndexColumn.setColumnName(resultSet.getString("column_name")); + tableIndexColumn.setOrdinalPosition(resultSet.getShort("key_sequence")); + //tableIndexColumn.setCollation(resultSet.getString("Collation")); + //tableIndexColumn.setCardinality(resultSet.getLong("Cardinality")); + //tableIndexColumn.setSubPart(resultSet.getLong("Sub_part")); + /*String collation = resultSet.getString("Collation"); + if ("a".equalsIgnoreCase(collation)) { + tableIndexColumn.setAscOrDesc("ASC"); + } else if ("d".equalsIgnoreCase(collation)) { + tableIndexColumn.setAscOrDesc("DESC"); + }*/ + return tableIndexColumn; + } + + @Override + public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String tableName) { + // 需要后续自己实现。目前没有办法直接获取建表语句。 + return ""; + /*String sql = "SHOW CREATE TABLE " + format(schemaName) + "." + + format(tableName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + if (resultSet.next()) { + return resultSet.getString("Create Table"); + } + return null; + });*/ + } + + private static String OBJECT_SQL + = "SHOW USER FUNCTIONS IN SCHEMA \"%s\""; + + @Override + public List functions(Connection connection, String databaseName, String schemaName) { + List functions = new ArrayList<>(); + String sql = String.format(OBJECT_SQL, schemaName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(resultSet.getString("name")); + functions.add(function); + } + return functions; + }); + } + + private static String ROUTINES_SQL + = + "SELECT FUNCTION_NAME, FUNCTION_DEFINITION, COMMENT " + + "FROM INFORMATION_SCHEMA.FUNCTIONS " + + "WHERE FUNCTION_SCHEMA = '%s' AND FUNCTION_NAME = '%s';"; + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL, schemaName, functionName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + if (resultSet.next()) { + function.setSpecificName(resultSet.getString("FUNCTION_NAME")); + function.setRemarks(resultSet.getString("COMMENT")); + function.setFunctionBody(resultSet.getString("FUNCTION_DEFINITION")); + } + return function; + }); + + } + + public static String format(String tableName) { + return "\"" + tableName + "\""; + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakePlugin.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakePlugin.java new file mode 100644 index 000000000..79af0449e --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/SnowflakePlugin.java @@ -0,0 +1,25 @@ +package ai.chat2db.plugin.snowflake; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.MetaData; + +import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; + +public class SnowflakePlugin implements Plugin { + @Override + public DBConfig getDBConfig() { + return FileUtils.readJsonValue(this.getClass(),"snowflake.json", DBConfig.class); + } + + @Override + public MetaData getMetaData() { + return new SnowflakeMetaData(); + } + + @Override + public DBManage getDBManage() { + return new SnowflakeDBManage(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/builder/SnowflakeSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/builder/SnowflakeSqlBuilder.java new file mode 100644 index 000000000..0bc6f301b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/builder/SnowflakeSqlBuilder.java @@ -0,0 +1,115 @@ +package ai.chat2db.plugin.snowflake.builder; +import ai.chat2db.plugin.snowflake.type.SnowflakeColumnTypeEnum; +import ai.chat2db.plugin.snowflake.type.SnowflakeIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import org.apache.commons.lang3.StringUtils; + +public class SnowflakeSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { + + @Override + public String buildCreateTableSql(Table table){ + StringBuilder script = new StringBuilder(); + script.append("CREATE TABLE "); + if (StringUtils.isNotBlank(table.getSchemaName())) { + script.append(table.getSchemaName()).append("."); + } + script.append("\"").append(table.getName()).append("\"").append(" (").append("\n"); + + // append column + for (TableColumn column : table.getColumnList()) { + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + continue; + } + SnowflakeColumnTypeEnum typeEnum = SnowflakeColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + + // append primary key and index + for (TableIndex tableIndex : table.getIndexList()) { + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { + continue; + } + SnowflakeIndexTypeEnum mysqlIndexTypeEnum = SnowflakeIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n)"); + + + if (StringUtils.isNotBlank(table.getEngine())) { + script.append(" ENGINE=").append(table.getEngine()); + } + + if (StringUtils.isNotBlank(table.getCharset())) { + script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); + } + + if (StringUtils.isNotBlank(table.getCollate())) { + script.append(" COLLATE=").append(table.getCollate()); + } + + if (table.getIncrementValue() != null) { + script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); + } + + if (StringUtils.isNotBlank(table.getComment())) { + script.append(" COMMENT='").append(table.getComment()).append("'"); + } + + if (StringUtils.isNotBlank(table.getPartition())) { + script.append(" \n").append(table.getPartition()); + } + script.append(";"); + + return script.toString(); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "); + script.append("\"").append(oldTable.getName()).append("\"").append("\n"); + boolean isChangeTableName = false; + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); + isChangeTableName = true; + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + if (isChangeTableName) { + script.append("ALTER TABLE "); + script.append("\"").append(newTable.getName()).append("\"").append("\n"); + script.append("\t").append("set COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + } else { + script.append("\t").append("set COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + } + } + if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { + script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + } + + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { + SnowflakeColumnTypeEnum typeEnum = SnowflakeColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + } + } + + // append reorder column + //script.append(buildGenerateReorderColumnSql(oldTable, newTable)); + + if (script.length() > 2) { + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append(";"); + } + + return script.toString(); + } + +} + diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/snowflake.json b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/snowflake.json new file mode 100644 index 000000000..82aea66c0 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/snowflake.json @@ -0,0 +1,18 @@ +{ + "dbType": "SNOWFLAKE", + "supportDatabase": true, + "supportSchema": false, + "driverConfigList": [ + { + "url": "jdbc:snowflake://localhost:443/", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/snowflake-jdbc-3.15.0.jar" + ], + "jdbcDriver": "snowflake-jdbc-3.15.0.jar", + "jdbcDriverClass": "net.snowflake.client.jdbc.SnowflakeDriver" + } + ], + "name": "Snowflake" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCharsetEnum.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCharsetEnum.java new file mode 100644 index 000000000..71ef84870 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCharsetEnum.java @@ -0,0 +1,26 @@ +package ai.chat2db.plugin.snowflake.type; + +import ai.chat2db.spi.model.Charset; + +import java.util.Arrays; +import java.util.List; + +public enum SnowflakeCharsetEnum { + + UTF8("utf8", "utf8_general_ci"), + ; + + private Charset charset; + SnowflakeCharsetEnum(String charsetName, String defaultCollationName) { + this.charset = new Charset(charsetName, defaultCollationName); + } + + + public Charset getCharset() { + return charset; + } + + public static List getCharsets() { + return Arrays.stream(SnowflakeCharsetEnum.values()).map(SnowflakeCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCollationEnum.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCollationEnum.java new file mode 100644 index 000000000..604378840 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeCollationEnum.java @@ -0,0 +1,31 @@ +package ai.chat2db.plugin.snowflake.type; + +import ai.chat2db.spi.model.Collation; + +import java.util.Arrays; +import java.util.List; + +public enum SnowflakeCollationEnum { + + BINARY("BINARY"), + + CASE_INSENSITIVE("CASE_INSENSITIVE"), + + CASE_SENSITIVE("CASE_SENSITIVE"), + ; + private Collation collation; + + SnowflakeCollationEnum(String collationName) { + this.collation = new Collation(collationName); + } + + public Collation getCollation() { + return collation; + } + + + public static List getCollations() { + return Arrays.asList(SnowflakeCollationEnum.values()).stream().map(SnowflakeCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeColumnTypeEnum.java new file mode 100644 index 000000000..0b12a09f3 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeColumnTypeEnum.java @@ -0,0 +1,237 @@ +package ai.chat2db.plugin.snowflake.type; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum SnowflakeColumnTypeEnum implements ColumnBuilder { + + NUMBER("NUMBER", true, true, true, true, false, false, true, true, false, false), + DECIMAL("DECIMAL", true, true, true, true, false, false, true, true, false, false), + NUMERIC("NUMERIC", true, true, true, true, false, false, true, true, false, false), + INT("INT", false, false, true, true, false, false, true, true, false, false), + INTEGER("INTEGER", false, false, true, true, false, false, true, true, false, false), + BIGINT("BIGINT", false, false, true, true, false, false, true, true, false, false), + SMALLINT("SMALLINT", false, false, true, true, false, false, true, true, false, false), + TINYINT("TINYINT", false, false, true, true, false, false, true, true, false, false), + BYTEINT("BYTEINT", false, false, true, true, false, false, true, true, false, false), + FLOAT("FLOAT", true, true, true, true, false, false, true, true, false, false), + DOUBLE("DOUBLE", true, true, true, true, false, false, true, true, false, false), + + VARCHAR("VARCHAR", true, false, true, false, true, true, true, true, false, false), + CHAR("CHAR", true, false, true, false, true, true, true, true, false, false), + STRING("STRING", true, false, true, false, true, true, true, true, false, false), + TEXT("TEXT", true, false, true, false, true, true, true, true, false, false), + BINARY("BINARY", true, false, true, false, true, true, true, true, false, false), + VARBINARY("VARBINARY", true, false, true, false, true, true, true, true, false, false), + + BOOLEAN("BOOLEAN", false, false, true, false, false, false, true, true, false, false), + + DATE("DATE", false, false, true, false, false, false, true, true, false, false), + DATETIME("DATETIME", true, false, true, false, false, false, true, true, true, false), + TIME("TIME", true, false, true, false, false, false, true, true, false, false), + TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true, false), + TIMESTAMP_LTZ("TIMESTAMPLTZ", true, false, true, false, false, false, true, true, true, false), + TIMESTAMP_NTZ("TIMESTAMPNTZ", true, false, true, false, false, false, true, true, true, false), + TIMESTAMP_TZ("TIMESTAMPTZ", true, false, true, false, false, false, true, true, true, false), + VARIANT("VARIANT", true, false, true, false, false, false, true, true, false, false), + OBJECT("OBJECT", true, false, true, false, false, false, true, true, false, false), + ARRAY("ARRAY", true, false, true, false, false, false, true, true, false, false), + GEOGRAPHY("GEOGRAPHY", true, false, true, false, false, false, true, true, false, false), + + ; + + private ColumnType columnType; + + public static SnowflakeColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + public ColumnType getColumnType() { + return columnType; + } + + SnowflakeColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue,false); + } + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (SnowflakeColumnTypeEnum value : SnowflakeColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + @Override + public String buildCreateColumnSql(TableColumn column) { + + SnowflakeColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + + script.append("\"").append(column.getName()).append("\"").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + script.append(buildCollation(column,type)).append(" "); + + script.append(buildNullable(column,type)).append(" "); + + script.append(buildDefaultValue(column,type)).append(" "); + + script.append(buildAutoIncrement(column,type)).append(" "); + + script.append(buildComment(column,type)).append(" "); + + return script.toString(); + } + + @Override + public String buildModifyColumn(TableColumn tableColumn) { + if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("DROP COLUMN ", tableColumn.getName()); + } + if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); + } + if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { + if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { + return StringUtils.join("CHANGE COLUMN ", tableColumn.getOldName(), " ", buildCreateColumnSql(tableColumn)); + } else { + return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); + } + } + return ""; + } + + private String buildDataType(TableColumn column, SnowflakeColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + if (EditStatus.MODIFY.name().equals(column.getEditStatus()) + && StringUtils.equalsIgnoreCase(column.getOldColumn().getTableName(), column.getTableName()) + && column.getOldColumn().getColumnSize().equals(column.getColumnSize()) + && column.getOldColumn().getDecimalDigits().equals(column.getDecimalDigits())) { + return ""; + } + if (Arrays.asList(BINARY, VARBINARY, VARCHAR, CHAR, STRING, TEXT).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0){ + return columnType; + } else { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + } + + if (Arrays.asList(DATE, TIME, DATETIME, TIMESTAMP, TIMESTAMP_TZ, TIMESTAMP_LTZ, TIMESTAMP_LTZ).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0) { + return columnType; + } else { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + } + + if (Arrays.asList(DECIMAL, FLOAT, DOUBLE,TINYINT, INT, NUMBER, NUMERIC, INTEGER, BIGINT, SMALLINT, BYTEINT).contains(type)) { + if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); + } + if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + } + + return columnType; + } + + private String buildCollation(TableColumn column, SnowflakeColumnTypeEnum type) { + if(!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())){ + return ""; + } + return StringUtils.join("COLLATE ", "'", column.getCollationName(), "'"); + } + + private String buildNullable(TableColumn column, SnowflakeColumnTypeEnum type) { + if(!type.getColumnType().isSupportNullable()){ + return ""; + } + if (EditStatus.MODIFY.name().equals(column.getEditStatus()) && !column.getNullable().equals(column.getOldColumn().getNullable())) { + if (column.getNullable()!=null && 1==column.getNullable()) { + return "DROP NOT NULL"; + } else { + return "NOT NULL"; + } + } else if (EditStatus.ADD.name().equals(column.getEditStatus())) { + if (column.getNullable()!=null && 1==column.getNullable()) { + return ""; + } else { + return "NOT NULL"; + } + } + return ""; + } + + private String buildDefaultValue(TableColumn column, SnowflakeColumnTypeEnum type) { + if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ + return ""; + } + + if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("SET DEFAULT ''"); + } + + if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("SET DEFAULT NULL"); + } + + if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY).contains(type)){ + return StringUtils.join("SET DEFAULT '",column.getDefaultValue(),"'"); + } + + if(Arrays.asList(DATE,TIME).contains(type)){ + return StringUtils.join("SET DEFAULT '",column.getDefaultValue(),"'"); + } + + if(Arrays.asList(DATETIME,TIMESTAMP).contains(type)){ + if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("SET DEFAULT ",column.getDefaultValue()); + } + return StringUtils.join("SET DEFAULT '",column.getDefaultValue(),"'"); + } + + return StringUtils.join("SET DEFAULT ",column.getDefaultValue()); + } + + private String buildAutoIncrement(TableColumn column, SnowflakeColumnTypeEnum type) { + if(!type.getColumnType().isSupportAutoIncrement()){ + return ""; + } + if (column.getAutoIncrement() != null && column.getAutoIncrement()) { + return "identity"; + } + return ""; + } + + private String buildComment(TableColumn column, SnowflakeColumnTypeEnum type) { + if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ + return ""; + } + return StringUtils.join("COMMENT '",column.getComment(),"'"); + } + + public static List getTypes(){ + return Arrays.stream(SnowflakeColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } + + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeDefaultValueEnum.java new file mode 100644 index 000000000..2e1279107 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeDefaultValueEnum.java @@ -0,0 +1,30 @@ +package ai.chat2db.plugin.snowflake.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum SnowflakeDefaultValueEnum { + + + NULL("NULL"), + CURRENT_DATE("CURRENT_DATE"), + CURRENT_TIMESTAMP("CURRENT_TIMESTAMP"), + ; + private DefaultValue defaultValue; + + SnowflakeDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(SnowflakeDefaultValueEnum.values()).map(SnowflakeDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeIndexTypeEnum.java new file mode 100644 index 000000000..e93bb1f42 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/java/ai/chat2db/plugin/snowflake/type/SnowflakeIndexTypeEnum.java @@ -0,0 +1,133 @@ +package ai.chat2db.plugin.snowflake.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; + +public enum SnowflakeIndexTypeEnum { + + PRIMARY_KEY("Primary", "PRIMARY KEY"), + + NORMAL("Normal", "INDEX"), + + UNIQUE("Unique", "UNIQUE INDEX"), + + FULLTEXT("Fulltext", "FULLTEXT INDEX"), + + SPATIAL("Spatial", "SPATIAL INDEX"); + + public String getName() { + return name; + } + + private String name; + + + public String getKeyword() { + return keyword; + } + + private String keyword; + + public IndexType getIndexType() { + return indexType; + } + + public void setIndexType(IndexType indexType) { + this.indexType = indexType; + } + + private IndexType indexType; + + SnowflakeIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + this.indexType = new IndexType(name); + } + + + public static SnowflakeIndexTypeEnum getByType(String type) { + for (SnowflakeIndexTypeEnum value : SnowflakeIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + + script.append(keyword).append(" "); + + script.append(buildIndexName(tableIndex)).append(" "); + + script.append(buildIndexColumn(tableIndex)).append(" "); + + script.append(buildIndexComment(tableIndex)).append(" "); + + return script.toString(); + } + + private String buildIndexComment(TableIndex tableIndex) { + if(StringUtils.isBlank(tableIndex.getComment())){ + return ""; + }else { + return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); + } + + } + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if(StringUtils.isNotBlank(column.getColumnName())) { + script.append("\"").append(column.getColumnName()).append("\""); + if (!StringUtils.isBlank(column.getAscOrDesc()) && !PRIMARY_KEY.equals(this)) { + script.append(" ").append(column.getAscOrDesc()); + } + script.append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + if(this.equals(PRIMARY_KEY)){ + return ""; + }else { + return "\""+tableIndex.getName()+"\""; + } + } + + public String buildModifyIndex(TableIndex tableIndex) { + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join("ADD ", buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (SnowflakeIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { + return StringUtils.join("DROP PRIMARY KEY"); + } + return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(),"\""); + } + public static List getIndexTypes() { + return Arrays.asList(SnowflakeIndexTypeEnum.values()).stream().map(SnowflakeIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin new file mode 100644 index 000000000..69eb8df57 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-snowflake/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin @@ -0,0 +1 @@ +ai.chat2db.plugin.snowflake.SnowflakePlugin \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/pom.xml b/chat2db-server/chat2db-plugins/pom.xml index d058b04e7..6c1fc3ca1 100644 --- a/chat2db-server/chat2db-plugins/pom.xml +++ b/chat2db-server/chat2db-plugins/pom.xml @@ -30,6 +30,7 @@ chat2db-hive chat2db-redis chat2db-kingbase + chat2db-snowflake \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml index ce19bc340..6846396b3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml @@ -126,5 +126,10 @@ chat2db-sqlserver ${revision} + + ai.chat2db + chat2db-snowflake + ${revision} + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 3e6aff9e3..1dbd093a5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -145,7 +145,9 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar String sqlType = SqlTypeEnum.UNKNOWN.getCode(); // 解析sql String type = Chat2DBContext.getConnectInfo().getDbType(); - boolean supportDruid = !DataSourceTypeEnum.MONGODB.getCode().equals(type); + //boolean supportDruid = !DataSourceTypeEnum.MONGODB.getCode().equals(type); + boolean supportDruid = !Arrays.asList(DataSourceTypeEnum.MONGODB.getCode(), DataSourceTypeEnum.SNOEFLAKE.getCode()).contains(type); + // 解析sql分页 SQLStatement sqlStatement = null; if (supportDruid) { diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java index 7a9cbcc64..6df20198b 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/enums/DataSourceTypeEnum.java @@ -26,6 +26,11 @@ public enum DataSourceTypeEnum implements BaseEnum { */ MONGODB("mongo数据库连接"), + /** + * snowflake数据库连接 + */ + SNOEFLAKE("snowflake数据库连接"), + ; final String description;