diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/pom.xml b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/pom.xml
index c91a2bf741..f8564399e5 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/pom.xml
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/pom.xml
@@ -19,6 +19,11 @@
ai.chat2db
chat2db-community-postgresql
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
chat2db-community-kingbase
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManager.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManager.java
index bb38195730..8684c50444 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManager.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManager.java
@@ -1,6 +1,7 @@
package ai.chat2db.plugin.kingbase;
import ai.chat2db.spi.IDbManager;
+import ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor;
import ai.chat2db.spi.DefaultDBManager;
import ai.chat2db.community.domain.api.model.async.AsyncContext;
import ai.chat2db.spi.model.datasource.ConnectInfo;
@@ -29,7 +30,8 @@ public Connection getConnection(ConnectInfo connectInfo) {
connectInfo.setSchemaName(null);
Connection connection = super.getConnection(connectInfo);
if (StringUtils.isNotBlank(schemaName)) {
- String sql = String.format(SQL_SET_SEARCH_PATH_USER_PUBLIC, schemaName);
+ String sql = String.format(SQL_SET_SEARCH_PATH_USER_PUBLIC,
+ KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(schemaName));
try {
DefaultSQLExecutor.getInstance().execute(connection, sql);
} catch (SQLException e) {
@@ -56,21 +58,31 @@ public String replaceDatabaseInJdbcUrl(String url, String newDatabase) {
@Override
public String dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = "drop table if exists " +tableName;
- return sql;
+ return "DROP TABLE IF EXISTS " + qualifiedTableName(schemaName, tableName);
}
@Override
public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException {
- String sql = "";
- if(copyData){
- sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH DATA";
- }else {
- sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH NO DATA";
- }
+ String sql = buildCopyTableSql(schemaName, tableName, newTableName, copyData);
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
}
+ static String buildCopyTableSql(String schemaName, String tableName, String newTableName,
+ boolean copyData) {
+ return "CREATE TABLE " + qualifiedTableName(schemaName, newTableName)
+ + " AS TABLE " + qualifiedTableName(schemaName, tableName)
+ + (copyData ? " WITH DATA" : " WITH NO DATA");
+ }
+
+ private static String qualifiedTableName(String schemaName, String tableName) {
+ String quotedTable = KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName);
+ if (StringUtils.isBlank(schemaName)) {
+ return quotedTable;
+ }
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(schemaName)
+ + "." + quotedTable;
+ }
+
@Override
public void exportTableData(Connection connection, String databaseName, String schemaName, String tableName, AsyncContext asyncContext) {
exportTableData(connection, databaseName, schemaName, tableName, asyncContext, 10000);
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java
index e5ff36b3e9..afd8af1055 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java
@@ -9,7 +9,6 @@
import ai.chat2db.spi.ISQLIdentifierProcessor;
import ai.chat2db.spi.ISqlBuilder;
import ai.chat2db.spi.DefaultMetaService;
-import ai.chat2db.spi.DefaultSQLIdentifierProcessor;
import ai.chat2db.community.domain.api.model.account.*;
import ai.chat2db.community.domain.api.model.async.*;
import ai.chat2db.community.domain.api.config.*;
@@ -22,7 +21,6 @@
import ai.chat2db.community.domain.api.model.view.*;
import ai.chat2db.spi.sql.Chat2DBContext;
import ai.chat2db.spi.DefaultSQLExecutor;
-import ai.chat2db.spi.util.SqlUtils;
import jakarta.validation.constraints.NotEmpty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
@@ -48,8 +46,6 @@ public class KingBaseMetaData extends DefaultMetaService implements IDbMetaData
- public static final DefaultSQLIdentifierProcessor KINGBASE_SQL_IDENTIFIER_PROCESSOR = new KingBaseSQLIdentifierProcessor();
-
@Override
public List databases(Connection connection) {
String sql = "SELECT datname FROM sys_database";
@@ -83,7 +79,7 @@ private String format(String objectName) {
if (StringUtils.isBlank(objectName)) {
return objectName;
} else {
- return SqlUtils.quoteObjectName(objectName);
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(objectName);
}
}
@@ -112,7 +108,7 @@ public String tableDDL(Connection connection, String databaseName, String schema
StringBuilder ddlBuilder = new StringBuilder(200);
- String formatTableName = format(tableName);
+ String formatTableName = getMetaDataName(schemaName, tableName);
ddlBuilder.append(SQL_CREATE_TABLE).append(formatTableName);
String options = DefaultSQLExecutor.getInstance().preExecute(connection, TABLE_OPTION_SQL, new String[]{schemaName, tableName}, resultSet -> {
if (resultSet.next()) {
@@ -139,9 +135,9 @@ public String tableDDL(Connection connection, String databaseName, String schema
constraintsBuilder.append(",\n");
}
constraintsBuilder.append("\t").append(" constraint ")
- .append(constraintName)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(constraintName))
.append(" ")
- .append(constraintDefinition.toLowerCase());
+ .append(constraintDefinition);
}
}
if (!constraintsBuilder.isEmpty()) {
@@ -158,7 +154,9 @@ public String tableDDL(Connection connection, String databaseName, String schema
String partitionDefinition = resultSet.getString("PARTITION_DEFINITION");
boolean isParentTable = resultSet.getBoolean("is_parent_table");
if (StringUtils.isNotBlank(parentTableName) && StringUtils.isNotBlank(partitionDefinition)) {
- ddlBuilder.append("\n").append(" partition of ").append(SqlUtils.quoteObjectName(parentTableName)).append("\n");
+ ddlBuilder.append("\n").append(" partition of ")
+ .append(getMetaDataName(resultSet.getString("parent_schema"), parentTableName))
+ .append("\n");
if (!constraintsBuilder.isEmpty()) {
ddlBuilder.append("(\n")
.append(constraintsBuilder)
@@ -180,9 +178,9 @@ public String tableDDL(Connection connection, String databaseName, String schema
String table_name = resultSet.getString("TABLE_NAME");
if (StringUtils.isNotBlank(owner) && StringUtils.isNotBlank(table_name)) {
tableOwnerBuilder.append(SQL_ALTER_TABLE)
- .append(format(table_name))
+ .append(getMetaDataName(schemaName, table_name))
.append(" owner to ")
- .append(owner)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(owner))
.append(";").append("\n");
}
}
@@ -197,11 +195,11 @@ public String tableDDL(Connection connection, String databaseName, String schema
String privilegeType = resultSet.getString("PRIVILEGE_TYPE");
if (StringUtils.isNotBlank(privilegeType)) {
tablePrivilegeBuilder.append(SQL_GRANT)
- .append(privilegeType.toLowerCase())
+ .append(KingBaseSqlGuards.requirePrivilege(privilegeType))
.append(SQL_ON)
.append(formatTableName)
.append(" to ")
- .append(grantee)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(grantee))
.append(";").append("\n");
}
}
@@ -413,7 +411,7 @@ else if ("USER-DEFINED".equals(dataType)) {
boolean isPartitioned = false;
if (resultSet.next()) {
ddlBuilder.append(" partition by ")
- .append(resultSet.getString("partition_key").toLowerCase())
+ .append(resultSet.getString("partition_key"))
.append(";");
isPartitioned = true;
ddlBuilder.append("\n");
@@ -428,18 +426,22 @@ else if ("USER-DEFINED".equals(dataType)) {
String parentTableName = resultSet.getString("PARENT_TABLE");
String partitionDefinition = resultSet.getString("PARTITION_DEFINITION");
if (StringUtils.isNotBlank(parentTableName) && StringUtils.isNotBlank(partitionDefinition)) {
- ddlBuilder.append("\n").append(SQL_CREATE_TABLE).append(format(subName)).append("\n")
- .append("partition of ").append(parentTableName).append("\n")
- .append(partitionDefinition.toLowerCase()).append(";\n");
+ // These three names are quote_ident() output from LIST_PARTITIONED_SUB_TABLE_SQL.
+ ddlBuilder.append("\n").append(SQL_CREATE_TABLE)
+ .append(resultSet.getString("schema_name")).append(".").append(subName).append("\n")
+ .append("partition of ").append(format(schemaName)).append(".")
+ .append(parentTableName).append("\n")
+ .append(partitionDefinition).append(";\n");
}
}
});
} else if (childTableInfo.size() >= 2) {
+ String parentSchemaName = childTableInfo.get(0);
String parentTableName = childTableInfo.get(1);
ddlBuilder.append(" ").append(" inherits ")
.append("(")
- .append(format(parentTableName))
+ .append(getMetaDataName(parentSchemaName, parentTableName))
.append(")").append("\n");
if (StringUtils.isNotBlank(options)) {
ddlBuilder.append(" ").append(options).append("\n");
@@ -472,7 +474,7 @@ else if ("USER-DEFINED".equals(dataType)) {
String comment = table.getComment();
if (StringUtils.isNotBlank(comment)) {
ddlBuilder.append("\n").append(SQL_COMMENT_TABLE).append(formatTableName).append(" is ")
- .append("'").append(comment).append("'")
+ .append("'").append(getSQLIdentifierProcessor().escapeString(comment)).append("'")
.append(";\n");
}
}
@@ -481,7 +483,7 @@ else if ("USER-DEFINED".equals(dataType)) {
String name = column.getName();
String comment = column.getComment();
if (StringUtils.isNotBlank(comment)) {
- comment = KINGBASE_SQL_IDENTIFIER_PROCESSOR.escapeString(comment);
+ comment = getSQLIdentifierProcessor().escapeString(comment);
ddlBuilder.append("\n").append(SQL_COMMENT_COLUMN)
.append(formatTableName).append(".").append(format(name))
.append(" is ")
@@ -498,7 +500,8 @@ else if ("USER-DEFINED".equals(dataType)) {
String index_name = resultSet.getString("index_name");
String index_comment = resultSet.getString("index_comment");
- ddlBuilder.append(SQL_COMMENT_INDEX).append(index_name)
+ ddlBuilder.append(SQL_COMMENT_INDEX).append(resultSet.getString("schema_name"))
+ .append(".").append(index_name)
.append(" is ").append(index_comment).append(";\n");
}
@@ -625,7 +628,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab
@Override
public String getMetaDataName(String... names) {
- return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining("."));
+ return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(KingBaseSQLIdentifierProcessor.INSTANCE::quoteIdentifierAlways).collect(Collectors.joining("."));
}
@Override
@@ -640,6 +643,6 @@ public List getSystemSchemas() {
@Override
public ISQLIdentifierProcessor getSQLIdentifierProcessor() {
- return KINGBASE_SQL_IDENTIFIER_PROCESSOR;
+ return KingBaseSQLIdentifierProcessor.INSTANCE;
}
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseSqlGuards.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseSqlGuards.java
new file mode 100644
index 0000000000..0b77f2b99f
--- /dev/null
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseSqlGuards.java
@@ -0,0 +1,35 @@
+package ai.chat2db.plugin.kingbase;
+
+import ai.chat2db.plugin.postgresql.PostgreSqlGuards;
+
+/**
+ * Validation helpers for non-escapable SQL expression positions in KingBase DDL
+ * generation. KingBase shares PostgreSQL's expression grammar for these paths,
+ * so the mature PostgreSQL scanners remain the source of truth. Escaping lives in
+ * {@link ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor}.
+ */
+public final class KingBaseSqlGuards {
+
+ private KingBaseSqlGuards() {
+ }
+
+ public static String requireDefaultExpression(String value) {
+ return PostgreSqlGuards.requireDefaultExpression(value);
+ }
+
+ public static String requireColumnTypeExpression(String value) {
+ return PostgreSqlGuards.requireColumnTypeExpression(value);
+ }
+
+ public static boolean isTemporalExpression(String value) {
+ return PostgreSqlGuards.isTemporalExpression(value);
+ }
+
+ public static boolean isFunctionOrCastExpression(String value) {
+ return PostgreSqlGuards.isFunctionOrCastExpression(value);
+ }
+
+ public static String requirePrivilege(String privilege) {
+ return PostgreSqlGuards.requirePrivilege(privilege);
+ }
+}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java
index 9f6d1f14bc..5f0de38ec5 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/KingBaseSqlBuilder.java
@@ -2,10 +2,12 @@
import ai.chat2db.spi.constant.SQLConstants;
+import ai.chat2db.community.domain.api.enums.plugin.DmlTypeEnum;
import ai.chat2db.plugin.kingbase.enums.type.KingBaseColumnTypeEnum;
import ai.chat2db.plugin.kingbase.enums.type.KingBaseIndexTypeEnum;
import ai.chat2db.spi.DefaultSqlBuilder;
import ai.chat2db.spi.model.request.PageLimitRequest;
+import ai.chat2db.spi.model.request.UpdateSqlRequest;
import ai.chat2db.community.domain.api.model.account.*;
import ai.chat2db.community.domain.api.model.async.*;
import ai.chat2db.community.domain.api.config.*;
@@ -18,17 +20,86 @@
import ai.chat2db.community.domain.api.model.view.*;
import ai.chat2db.community.domain.api.config.TableBuilderConfig;
import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static ai.chat2db.plugin.kingbase.constant.KingBaseSqlBuilderConstants.*;
+import ai.chat2db.plugin.kingbase.KingBaseSqlGuards;
+import ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor;
public class KingBaseSqlBuilder extends DefaultSqlBuilder {
+ @Override
+ public String quoteIdentifier(String identifier) {
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(identifier);
+ }
+
+ @Override
+ public String quoteQualifiedIdentifier(String... identifiers) {
+ if (identifiers.length == 3) {
+ return quoteQualifiedIdentifier(identifiers[1], identifiers[2]);
+ }
+ return Arrays.stream(identifiers)
+ .filter(StringUtils::isNotBlank)
+ .map(KingBaseSQLIdentifierProcessor.INSTANCE::quoteIdentifierAlways)
+ .collect(Collectors.joining(SQLConstants.DOT));
+ }
+
+ @Override
+ public String quoteAlias(String alias) {
+ return quoteIdentifier(alias);
+ }
+
+ @Override
+ public String buildUpdate(UpdateSqlRequest request) {
+ StringBuilder script = new StringBuilder(SQLConstants.UPDATE_KEYWORD + SQLConstants.SPACE);
+ buildTableName(request.getDatabaseName(), request.getSchemaName(), request.getTableName(), script);
+ script.append(" SET ");
+ script.append(request.getRow().entrySet().stream()
+ .map(entry -> quoteIdentifier(entry.getKey()) + SQLConstants.EQUAL_SQL + entry.getValue())
+ .collect(Collectors.joining(SQLConstants.COMMA)));
+ if (MapUtils.isNotEmpty(request.getPrimaryKeyMap())) {
+ script.append(" WHERE ");
+ script.append(request.getPrimaryKeyMap().entrySet().stream()
+ .map(entry -> quoteIdentifier(entry.getKey()) + SQLConstants.EQUAL_SQL + entry.getValue())
+ .collect(Collectors.joining(SQLConstants.SQL_AND)));
+ }
+ return script.toString();
+ }
+
+ @Override
+ public String buildTemplate(Table table, String type) {
+ if (table == null || CollectionUtils.isEmpty(table.getColumnList()) || StringUtils.isBlank(type)) {
+ return SQLConstants.EMPTY;
+ }
+ String tableName = quoteQualifiedIdentifier(table.getSchemaName(), table.getName());
+ List columnNames = table.getColumnList().stream()
+ .map(column -> quoteIdentifier(column.getName()))
+ .toList();
+ if (DmlTypeEnum.INSERT.name().equalsIgnoreCase(type)) {
+ return "INSERT INTO " + tableName + " (" + String.join(SQLConstants.COMMA, columnNames)
+ + ") VALUES (" + columnNames.stream().map(name -> SQLConstants.SPACE)
+ .collect(Collectors.joining(SQLConstants.COMMA)) + ")";
+ }
+ if (DmlTypeEnum.UPDATE.name().equalsIgnoreCase(type)) {
+ return "UPDATE " + tableName + " SET " + columnNames.stream()
+ .map(name -> name + SQLConstants.EQUAL_SQL + SQLConstants.SPACE)
+ .collect(Collectors.joining(SQLConstants.COMMA)) + " WHERE ";
+ }
+ if (DmlTypeEnum.DELETE.name().equalsIgnoreCase(type)) {
+ return "DELETE FROM " + tableName + " WHERE ";
+ }
+ if (DmlTypeEnum.SELECT.name().equalsIgnoreCase(type)) {
+ return "SELECT " + String.join(SQLConstants.COMMA, columnNames) + " FROM " + tableName;
+ }
+ return SQLConstants.EMPTY;
+ }
@@ -50,18 +121,22 @@ public class KingBaseSqlBuilder extends DefaultSqlBuilder {
@Override
public String buildCreateTable(Table table, TableBuilderConfig tableBuilderConfig) {
+ boolean needFullTableName = tableBuilderConfig != null
+ && BooleanUtils.isTrue(tableBuilderConfig.getNeedFullTableName());
StringBuilder script = new StringBuilder();
script.append(SQL_CREATE_TABLE);
- script.append(SQLConstants.DOUBLE_QUOTE).append(table.getName()).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.SPACE_OPEN_PARENTHESIS).append(SQLConstants.SPACE).append(SQLConstants.LINE_SEPARATOR);
+ script.append(needFullTableName
+ ? quoteQualifiedIdentifier(table.getSchemaName(), table.getName())
+ : quoteIdentifier(table.getName()))
+ .append(SQLConstants.SPACE_OPEN_PARENTHESIS).append(SQLConstants.SPACE)
+ .append(SQLConstants.LINE_SEPARATOR);
for (TableColumn column : table.getColumnList()) {
if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) {
continue;
}
- KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(column.getColumnType());
- if(typeEnum ==null){
- continue;
- }
- script.append(SQLConstants.TAB).append(typeEnum.buildCreateColumnSql(column)).append(SQLConstants.COMMA_LINE_SEPARATOR);
+ script.append(SQLConstants.TAB)
+ .append(KingBaseColumnTypeEnum.buildCreateColumnSqlSafely(column))
+ .append(SQLConstants.COMMA_LINE_SEPARATOR);
}
Map> tableIndexMap = table.getIndexList().stream()
.collect(Collectors.partitioningBy(v -> KingBaseIndexTypeEnum.NORMAL.getName().equals(v.getType())));
@@ -83,7 +158,7 @@ public String buildCreateTable(Table table, TableBuilderConfig tableBuilderConfi
script = new StringBuilder(script.substring(0, script.length() - 2));
script.append(SQLConstants.LINE_SEPARATOR_CLOSE_PARENTHESIS);
if(StringUtils.isNotBlank(table.getTablespace())){
- script.append(SQL_TABLESPACE_DOUBLE_QUOTE).append(table.getTablespace()).append(SQLConstants.DOUBLE_QUOTE_SEMICOLON);
+ script.append(" TABLESPACE ").append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(table.getTablespace())).append(SQLConstants.SEMICOLON);
}else {
script.append(SQL_TABLESPACE_DOUBLE_QUOTE_SYS_DEFAULT_DOUBLE_QUOTE_SEMICOLON);
}
@@ -101,8 +176,11 @@ public String buildCreateTable(Table table, TableBuilderConfig tableBuilderConfi
}
if (StringUtils.isNotBlank(table.getComment())) {
script.append(SQLConstants.LINE_SEPARATOR);
- script.append(SQL_COMMENT_TABLE).append(SQLConstants.SPACE).append(SQLConstants.DOUBLE_QUOTE).append(table.getName()).append(VALUE_DOUBLE_QUOTE_IS_SINGLE_QUOTE)
- .append(table.getComment()).append(SQLConstants.SINGLE_QUOTE_SEMICOLON_LINE_SEPARATOR);
+ script.append(SQL_COMMENT_TABLE).append(SQLConstants.SPACE)
+ .append(quoteQualifiedIdentifier(
+ needFullTableName ? table.getSchemaName() : null, table.getName()))
+ .append(SQLConstants.SQL_IS_SINGLE_QUOTE)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(table.getComment())).append(SQLConstants.SINGLE_QUOTE_SEMICOLON_LINE_SEPARATOR);
}
List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList();
for (TableColumn tableColumn : tableColumnList) {
@@ -128,32 +206,35 @@ public String buildCreateTable(Table table, TableBuilderConfig tableBuilderConfi
@Override
public String buildAlterTable(Table oldTable, Table newTable) {
StringBuilder script = new StringBuilder();
- if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) {
- script.append(SQL_ALTER_TABLE).append(SQLConstants.DOUBLE_QUOTE).append(oldTable.getName()).append(SQLConstants.DOUBLE_QUOTE);
- script.append(SQLConstants.TAB).append(SQL_RENAME).append(SQLConstants.DOUBLE_QUOTE).append(newTable.getName()).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.SEMICOLON_LINE_SEPARATOR);
+ String oldQualifiedName = quoteQualifiedIdentifier(oldTable.getSchemaName(), oldTable.getName());
+ String newQualifiedName = quoteQualifiedIdentifier(newTable.getSchemaName(), newTable.getName());
+ if (!StringUtils.equals(oldTable.getName(), newTable.getName())) {
+ script.append(SQL_ALTER_TABLE).append(oldQualifiedName);
+ script.append(SQLConstants.TAB).append(SQL_RENAME).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(newTable.getName())).append(SQLConstants.SEMICOLON_LINE_SEPARATOR);
}
- newTable.setColumnList(newTable.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList());
newTable.setIndexList(newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList());
List columnNameList = newTable.getColumnList().stream().filter(v ->
v.getOldName() != null && !StringUtils.equals(v.getOldName(), v.getName())).toList();
for (TableColumn tableColumn : columnNameList) {
- script.append(SQL_ALTER_TABLE).append(SQLConstants.DOUBLE_QUOTE).append(newTable.getName()).append(VALUE_DOUBLE_QUOTE).append(SQL_RENAME_COLUMN)
- .append(tableColumn.getOldName()).append(VALUE_DOUBLE_QUOTE_TO_DOUBLE_QUOTE).append(tableColumn.getName()).append(SQLConstants.DOUBLE_QUOTE_SEMICOLON_LINE_SEPARATOR);
+ script.append(SQL_ALTER_TABLE).append(newQualifiedName).append(SQLConstants.SPACE).append("RENAME COLUMN ")
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableColumn.getOldName())).append(" TO ").append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableColumn.getName())).append(SQLConstants.SEMICOLON_LINE_SEPARATOR);
}
Map> tableIndexMap = newTable.getIndexList().stream()
.collect(Collectors.partitioningBy(v -> KingBaseIndexTypeEnum.NORMAL.getName().equals(v.getType())));
StringBuilder scriptModify = new StringBuilder();
Boolean modify = false;
- scriptModify.append(SQL_ALTER_TABLE).append(SQLConstants.DOUBLE_QUOTE).append(newTable.getName()).append(VALUE_DOUBLE_QUOTE_2);
+ scriptModify.append(SQL_ALTER_TABLE).append(newQualifiedName).append(" \n");
for (TableColumn tableColumn : newTable.getColumnList()) {
- KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType());
- if(typeEnum == null){
+ if (StringUtils.isBlank(tableColumn.getEditStatus())) {
continue;
}
- scriptModify.append(SQLConstants.TAB).append(typeEnum.buildModifyColumn(tableColumn)).append(SQLConstants.COMMA_LINE_SEPARATOR);
- modify = true;
+ String modifyColumn = KingBaseColumnTypeEnum.buildModifyColumnSafely(tableColumn);
+ if (StringUtils.isNotBlank(modifyColumn)) {
+ scriptModify.append(SQLConstants.TAB).append(modifyColumn).append(SQLConstants.COMMA_LINE_SEPARATOR);
+ modify = true;
+ }
}
for (TableIndex tableIndex : tableIndexMap.get(Boolean.FALSE)) {
@@ -183,8 +264,8 @@ public String buildAlterTable(Table oldTable, Table newTable) {
}
if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) {
script.append(SQLConstants.LINE_SEPARATOR);
- script.append(SQL_COMMENT_TABLE).append(SQLConstants.SPACE).append(SQLConstants.DOUBLE_QUOTE).append(newTable.getName()).append(VALUE_DOUBLE_QUOTE_IS_SINGLE_QUOTE)
- .append(newTable.getComment()).append(SQLConstants.SINGLE_QUOTE_SEMICOLON_LINE_SEPARATOR);
+ script.append(SQL_COMMENT_TABLE).append(SQLConstants.SPACE).append(newQualifiedName).append(SQLConstants.SQL_IS_SINGLE_QUOTE)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(newTable.getComment())).append(SQLConstants.SINGLE_QUOTE_SEMICOLON_LINE_SEPARATOR);
}
for (TableColumn tableColumn : newTable.getColumnList()) {
KingBaseColumnTypeEnum typeEnum = KingBaseColumnTypeEnum.getByType(tableColumn.getColumnType());
@@ -229,19 +310,21 @@ public String buildPageLimit(PageLimitRequest request) {
@Override
public String buildCreateDatabase(Database database) {
StringBuilder sqlBuilder = new StringBuilder();
- sqlBuilder.append(SQL_CREATE_DATABASE+database.getName());
+ sqlBuilder.append(SQL_CREATE_DATABASE).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(database.getName()));
String owner = database.getOwner();
if (StringUtils.isBlank(owner)) {
owner = SYSTEM_KEYWORD;
}
- sqlBuilder.append(SQL_WITH_OWNER_EQUAL_DOUBLE_QUOTE).append(owner).append(SQLConstants.DOUBLE_QUOTE);
+ sqlBuilder.append(" WITH OWNER = ").append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(owner));
if (StringUtils.isNotBlank(database.getCharset())) {
- sqlBuilder.append(SQL_ENCODING).append(database.getCharset()).append(SQLConstants.EMPTY);
+ sqlBuilder.append(SQL_ENCODING).append(SQLConstants.SINGLE_QUOTE)
+ .append(KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(database.getCharset()))
+ .append(SQLConstants.SINGLE_QUOTE);
}
sqlBuilder.append(SQLConstants.SEMICOLON_LINE_SEPARATOR);
if (StringUtils.isNotBlank(database.getComment())) {
- sqlBuilder.append(SQL_COMMENT_DATABASE).append(database.getName()).append(SQLConstants.SQL_IS_SINGLE_QUOTE).append(database.getComment()).append(SQLConstants.SINGLE_QUOTE_SEMICOLON);
+ sqlBuilder.append(SQL_COMMENT_DATABASE).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(database.getName())).append(SQLConstants.SQL_IS_SINGLE_QUOTE).append(KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(database.getComment())).append(SQLConstants.SINGLE_QUOTE_SEMICOLON);
}
return sqlBuilder.toString();
}
@@ -250,13 +333,29 @@ public String buildCreateDatabase(Database database) {
@Override
public String buildCreateSchema(Schema schema){
StringBuilder sqlBuilder = new StringBuilder();
- sqlBuilder.append(SQL_CREATE_SCHEMA+schema.getName()+SQLConstants.EMPTY);
+ sqlBuilder.append(SQL_CREATE_SCHEMA).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(schema.getName()));
String owner = schema.getOwner();
if(StringUtils.isBlank(schema.getOwner())){
owner = SYSTEM_KEYWORD;
}
- sqlBuilder.append(SQL_AUTHORIZATION_DOUBLE_QUOTE).append(owner).append(SQLConstants.DOUBLE_QUOTE);
+ sqlBuilder.append(" AUTHORIZATION ").append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(owner));
return sqlBuilder.toString();
}
+ @Override
+ protected void buildTableName(String databaseName, String schemaName, String tableName,
+ StringBuilder script) {
+ script.append(quoteQualifiedIdentifier(databaseName, schemaName, tableName));
+ }
+
+ @Override
+ protected void buildColumns(List columnList, StringBuilder script) {
+ if (CollectionUtils.isNotEmpty(columnList)) {
+ script.append(SQLConstants.SPACE_OPEN_PARENTHESIS)
+ .append(columnList.stream().map(this::quoteIdentifier)
+ .collect(Collectors.joining(SQLConstants.COMMA)))
+ .append(SQLConstants.CLOSE_PARENTHESIS_SPACE);
+ }
+ }
+
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseColumnTypeEnumConstants.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseColumnTypeEnumConstants.java
index ee0ff102a8..fb0cb6daf4 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseColumnTypeEnumConstants.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseColumnTypeEnumConstants.java
@@ -15,9 +15,9 @@
public final class KingBaseColumnTypeEnumConstants {
- public static final String SQL_ALTER_COLUMN = "ALTER COLUMN \"";
+ public static final String SQL_ALTER_COLUMN = "ALTER COLUMN ";
public static final String SQL_COMMENT_COLUMN = "COMMENT ON COLUMN";
- public static final String SQL_DROP_COLUMN = "DROP COLUMN `";
+ public static final String SQL_DROP_COLUMN = "DROP COLUMN ";
private KingBaseColumnTypeEnumConstants() {
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseDBManagerConstants.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseDBManagerConstants.java
index 046e0188dd..f664f833cc 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseDBManagerConstants.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseDBManagerConstants.java
@@ -14,7 +14,7 @@
public final class KingBaseDBManagerConstants {
- public static final String SQL_SET_SEARCH_PATH_USER_PUBLIC = "SET search_path TO \"%s\",\"$user\",\"public\"";
+ public static final String SQL_SET_SEARCH_PATH_USER_PUBLIC = "SET search_path TO %s,\"$user\",\"public\"";
private KingBaseDBManagerConstants() {
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseIndexTypeEnumConstants.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseIndexTypeEnumConstants.java
index 9edf204b0f..1480df8d88 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseIndexTypeEnumConstants.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/constant/KingBaseIndexTypeEnumConstants.java
@@ -17,8 +17,8 @@ public final class KingBaseIndexTypeEnumConstants {
public static final String SQL_COMMENT_CONSTRAINT = "COMMENT ON CONSTRAINT";
public static final String SQL_COMMENT_INDEX = "COMMENT ON INDEX";
public static final String SQL_CREATE = "CREATE";
- public static final String SQL_DROP_CONSTRAINT = "DROP CONSTRAINT \"";
- public static final String SQL_DROP_INDEX = "DROP INDEX \"";
+ public static final String SQL_DROP_CONSTRAINT = "DROP CONSTRAINT ";
+ public static final String SQL_DROP_INDEX = "DROP INDEX ";
public static final String SQL_ON = "ON ";
private KingBaseIndexTypeEnumConstants() {
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseColumnTypeEnum.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseColumnTypeEnum.java
index 8c6b747aeb..da6978ce91 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseColumnTypeEnum.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseColumnTypeEnum.java
@@ -4,12 +4,14 @@
import ai.chat2db.community.domain.api.enums.plugin.EditStatusEnum;
import ai.chat2db.community.domain.api.model.metadata.ColumnType;
import ai.chat2db.community.domain.api.model.metadata.TableColumn;
-import ai.chat2db.spi.util.SqlUtils;
+import ai.chat2db.plugin.kingbase.KingBaseSqlGuards;
+import ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import static ai.chat2db.plugin.kingbase.constant.KingBaseColumnTypeEnumConstants.*;
@@ -77,11 +79,11 @@ public enum KingBaseColumnTypeEnum implements IColumnBuilder {
- private static Map COLUMN_TYPE_MAP = Maps.newHashMap();
+ private static final Map COLUMN_TYPE_MAP = Maps.newHashMap();
static {
for (KingBaseColumnTypeEnum value : KingBaseColumnTypeEnum.values()) {
- COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value);
+ COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName().toUpperCase(Locale.ROOT), value);
}
}
@@ -93,7 +95,20 @@ public enum KingBaseColumnTypeEnum implements IColumnBuilder {
}
public static KingBaseColumnTypeEnum getByType(String dataType) {
- return COLUMN_TYPE_MAP.get(SqlUtils.removeDigits(dataType.toUpperCase()));
+ if (StringUtils.isBlank(dataType)) {
+ return null;
+ }
+ String typeExpression = KingBaseSqlGuards.requireColumnTypeExpression(dataType);
+ String baseType = typeExpression;
+ int argumentsStart = baseType.indexOf('(');
+ if (argumentsStart >= 0) {
+ baseType = baseType.substring(0, argumentsStart);
+ }
+ while (baseType.stripTrailing().endsWith("[]")) {
+ baseType = baseType.stripTrailing();
+ baseType = baseType.substring(0, baseType.length() - 2);
+ }
+ return COLUMN_TYPE_MAP.get(baseType.trim().toUpperCase(Locale.ROOT));
}
public static List getTypes() {
@@ -106,15 +121,40 @@ public ColumnType getColumnType() {
return columnType;
}
+ public static String buildCreateColumnSqlSafely(TableColumn column) {
+ KingBaseColumnTypeEnum type = getByType(column.getColumnType());
+ return type == null ? buildSafeFallbackColumn(column) : type.buildCreateColumnSql(column);
+ }
+
+ public static String buildModifyColumnSafely(TableColumn column) {
+ KingBaseColumnTypeEnum type = getByType(column.getColumnType());
+ if (type != null) {
+ return type.buildModifyColumn(column);
+ }
+ if (EditStatusEnum.DELETE.name().equals(column.getEditStatus())) {
+ return SQL_DROP_COLUMN + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName());
+ }
+ if (EditStatusEnum.ADD.name().equals(column.getEditStatus())) {
+ return "ADD COLUMN " + buildSafeFallbackColumn(column);
+ }
+ if (EditStatusEnum.MODIFY.name().equals(column.getEditStatus())) {
+ String columnName = KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName());
+ String dataType = KingBaseSqlGuards.requireColumnTypeExpression(column.getColumnType());
+ return SQL_ALTER_COLUMN + columnName + " TYPE " + dataType
+ + " USING " + columnName + "::" + dataType;
+ }
+ return "";
+ }
+
@Override
public String buildCreateColumnSql(TableColumn column) {
- KingBaseColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase());
+ KingBaseColumnTypeEnum type = getByType(column.getColumnType());
if (type == null) {
- return buildDefaultColumn(column, false);
+ return buildSafeFallbackColumn(column);
}
StringBuilder script = new StringBuilder();
- script.append("\"").append(column.getName()).append("\"").append(" ");
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName())).append(" ");
script.append(buildDataType(column, type)).append(" ");
@@ -132,30 +172,30 @@ private String buildCollation(TableColumn column, KingBaseColumnTypeEnum type) {
if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) {
return "";
}
- return StringUtils.join("\"", column.getCollationName(), "\"");
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getCollationName());
}
@Override
public String buildModifyColumn(TableColumn column) {
if (EditStatusEnum.DELETE.name().equals(column.getEditStatus())) {
- return StringUtils.join(SQL_DROP_COLUMN, column.getName() + "`");
+ return StringUtils.join(SQL_DROP_COLUMN, KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName()));
}
if (EditStatusEnum.ADD.name().equals(column.getEditStatus())) {
return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column));
}
if (EditStatusEnum.MODIFY.name().equals(column.getEditStatus())) {
StringBuilder script = new StringBuilder();
- script.append(SQL_ALTER_COLUMN).append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n");
+ script.append(SQL_ALTER_COLUMN).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName())).append(" TYPE ").append(buildDataType(column, this)).append(",\n");
if (column.getNullable() != null && 1 == column.getNullable()) {
- script.append("\t").append(SQL_ALTER_COLUMN).append(column.getName()).append("\" DROP NOT NULL ,\n");
+ script.append("\t").append(SQL_ALTER_COLUMN).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName())).append(" DROP NOT NULL ,\n");
} else {
- script.append("\t").append(SQL_ALTER_COLUMN).append(column.getName()).append("\" SET NOT NULL ,\n");
+ script.append("\t").append(SQL_ALTER_COLUMN).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName())).append(" SET NOT NULL ,\n");
}
String defaultValue = buildDefaultValue(column, this);
if (StringUtils.isNotBlank(defaultValue)) {
- script.append(SQL_ALTER_COLUMN).append(column.getName()).append("\" SET ").append(defaultValue).append(",\n");
+ script.append(SQL_ALTER_COLUMN).append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName())).append(" SET ").append(defaultValue).append(",\n");
}
script = new StringBuilder(script.substring(0, script.length() - 2));
return script.toString();
@@ -168,8 +208,16 @@ public String buildComment(TableColumn column, KingBaseColumnTypeEnum type) {
|| EditStatusEnum.DELETE.name().equals(column.getEditStatus())) {
return "";
}
- return StringUtils.join(SQL_COMMENT_COLUMN, " \"", column.getTableName(),
- "\".\"", column.getName(), "\" IS '", column.getComment(), "';");
+ return StringUtils.join(SQL_COMMENT_COLUMN, " ", qualifiedName(column.getSchemaName(), column.getTableName()),
+ ".", KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName()), " IS '", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(column.getComment()), "';");
+ }
+
+ private static String qualifiedName(String schemaName, String objectName) {
+ String quotedName = KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(objectName);
+ if (StringUtils.isBlank(schemaName)) {
+ return quotedName;
+ }
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(schemaName) + "." + quotedName;
}
private String buildDefaultValue(TableColumn column, KingBaseColumnTypeEnum type) {
@@ -186,17 +234,41 @@ private String buildDefaultValue(TableColumn column, KingBaseColumnTypeEnum type
}
if (Arrays.asList(CHAR, VARCHAR).contains(type)) {
- return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'");
+ if (KingBaseSqlGuards.isFunctionOrCastExpression(column.getDefaultValue())) {
+ return StringUtils.join("DEFAULT ",
+ KingBaseSqlGuards.requireDefaultExpression(column.getDefaultValue()));
+ }
+ return StringUtils.join("DEFAULT '", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(column.getDefaultValue()), "'");
}
if (Arrays.asList(TIMESTAMP, TIME, TIMETZ, TIMESTAMPTZ, DATE).contains(type)) {
- if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) {
- return StringUtils.join("DEFAULT ", column.getDefaultValue());
+ if (KingBaseSqlGuards.isTemporalExpression(column.getDefaultValue())) {
+ return StringUtils.join("DEFAULT ",
+ KingBaseSqlGuards.requireDefaultExpression(column.getDefaultValue()));
}
- return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'");
+ return StringUtils.join("DEFAULT '", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(column.getDefaultValue()), "'");
}
- return StringUtils.join("DEFAULT ", column.getDefaultValue());
+ return StringUtils.join("DEFAULT ", KingBaseSqlGuards.requireDefaultExpression(column.getDefaultValue()));
+ }
+
+ private static String buildSafeFallbackColumn(TableColumn column) {
+ StringBuilder script = new StringBuilder();
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getName()))
+ .append(" ")
+ .append(KingBaseSqlGuards.requireColumnTypeExpression(column.getColumnType()));
+ if (column.getNullable() != null) {
+ script.append(column.getNullable() == 1 ? " NULL" : " NOT NULL");
+ }
+ if (StringUtils.isNotEmpty(column.getDefaultValue())) {
+ if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) {
+ script.append(" DEFAULT ''");
+ } else {
+ script.append(" DEFAULT ")
+ .append(KingBaseSqlGuards.requireDefaultExpression(column.getDefaultValue()));
+ }
+ }
+ return script.toString();
}
private String buildNullable(TableColumn column, KingBaseColumnTypeEnum type) {
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseIndexTypeEnum.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseIndexTypeEnum.java
index df1080bc8a..41b7bf17ee 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseIndexTypeEnum.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/enums/type/KingBaseIndexTypeEnum.java
@@ -4,6 +4,7 @@
import ai.chat2db.community.domain.api.model.metadata.IndexType;
import ai.chat2db.community.domain.api.model.metadata.TableIndex;
import ai.chat2db.community.domain.api.model.metadata.TableIndexColumn;
+import ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -74,7 +75,7 @@ public String buildIndexScript(TableIndex tableIndex) {
script.append(buildIndexUnique(tableIndex)).append(" ");
script.append(buildIndexConcurrently(tableIndex)).append(" ");
script.append(buildIndexName(tableIndex)).append(" ");
- script.append(SQL_ON).append("\"").append(tableIndex.getTableName()).append("\"").append(" ");
+ script.append(SQL_ON).append(qualifiedName(tableIndex.getSchemaName(), tableIndex.getTableName())).append(" ");
script.append(buildIndexMethod(tableIndex)).append(" ");
script.append(buildIndexColumn(tableIndex));
} else {
@@ -92,16 +93,16 @@ private String buildForeignColum(TableIndex tableIndex) {
StringBuilder script = new StringBuilder();
script.append(" REFERENCES ");
if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) {
- script.append(tableIndex.getForeignSchemaName()).append(".");
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getForeignSchemaName())).append(".");
}
if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) {
- script.append(tableIndex.getForeignTableName()).append(" ");
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getForeignTableName())).append(" ");
}
if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) {
script.append("(");
for (String column : tableIndex.getForeignColumnNamelist()) {
if (StringUtils.isNotBlank(column)) {
- script.append("\"").append(column).append("\"").append(",");
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column)).append(",");
}
}
script.deleteCharAt(script.length() - 1);
@@ -114,7 +115,7 @@ private String buildForeignColum(TableIndex tableIndex) {
private String buildIndexMethod(TableIndex tableIndex) {
if (StringUtils.isNotBlank(tableIndex.getMethod())) {
- return "USING " + tableIndex.getMethod();
+ return "USING " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getMethod());
} else {
return "";
}
@@ -141,10 +142,12 @@ public String buildIndexComment(TableIndex tableIndex) {
return "";
} else if (NORMAL.equals(this)) {
return StringUtils.join(SQL_COMMENT_INDEX, " ",
- "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';");
+ qualifiedName(tableIndex.getSchemaName(), tableIndex.getName()), " IS '", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(tableIndex.getComment()), "';");
} else {
- return StringUtils.join(SQL_COMMENT_CONSTRAINT, " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(),
- "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';");
+ return StringUtils.join(SQL_COMMENT_CONSTRAINT, " ",
+ KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getName()), " ON ",
+ qualifiedName(tableIndex.getSchemaName(), tableIndex.getTableName()), " IS '",
+ KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(tableIndex.getComment()), "';");
}
}
@@ -153,7 +156,7 @@ private String buildIndexColumn(TableIndex tableIndex) {
script.append("(");
for (TableIndexColumn column : tableIndex.getColumnList()) {
if (StringUtils.isNotBlank(column.getColumnName())) {
- script.append("\"").append(column.getColumnName()).append("\"").append(",");
+ script.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(column.getColumnName())).append(",");
}
}
script.deleteCharAt(script.length() - 1);
@@ -162,7 +165,7 @@ private String buildIndexColumn(TableIndex tableIndex) {
}
private String buildIndexName(TableIndex tableIndex) {
- return "\"" + tableIndex.getName() + "\"";
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getName());
}
public String buildModifyIndex(TableIndex tableIndex) {
@@ -181,8 +184,17 @@ public String buildModifyIndex(TableIndex tableIndex) {
private String buildDropIndex(TableIndex tableIndex) {
if (NORMAL.equals(this)) {
- return StringUtils.join(SQL_DROP_INDEX, tableIndex.getOldName(), "\"");
+ return StringUtils.join(SQL_DROP_INDEX,
+ qualifiedName(tableIndex.getSchemaName(), tableIndex.getOldName()));
}
- return StringUtils.join(SQL_DROP_CONSTRAINT, tableIndex.getOldName(), "\"");
+ return StringUtils.join(SQL_DROP_CONSTRAINT, KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableIndex.getOldName()));
+ }
+
+ private static String qualifiedName(String schemaName, String objectName) {
+ String quotedName = KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(objectName);
+ if (StringUtils.isBlank(schemaName)) {
+ return quotedName;
+ }
+ return KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(schemaName) + "." + quotedName;
}
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/identifier/KingBaseSQLIdentifierProcessor.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/identifier/KingBaseSQLIdentifierProcessor.java
index 05e9487581..664b1f4e01 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/identifier/KingBaseSQLIdentifierProcessor.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/main/java/ai/chat2db/plugin/kingbase/identifier/KingBaseSQLIdentifierProcessor.java
@@ -1,20 +1,12 @@
package ai.chat2db.plugin.kingbase.identifier;
-import ai.chat2db.spi.DefaultSQLIdentifierProcessor;
-import org.apache.commons.lang3.StringUtils;
+import ai.chat2db.plugin.postgresql.identifier.PostgreSQLIdentifierProcessor;
-public class KingBaseSQLIdentifierProcessor extends DefaultSQLIdentifierProcessor {
-
-
- @Override
- public String quoteIdentifier(String identifier) {
- if (isValidIdentifier(identifier)) {
- if (containsUpperCase(identifier) || isReservedKeyword(identifier.toUpperCase(), null, null)) {
- return StringUtils.wrap(identifier, '"');
- }
- return identifier;
- }
- return StringUtils.wrap(identifier, '"');
- }
+/**
+ * KingBase uses PostgreSQL-compatible identifier folding, delimiters, reserved
+ * words, and string literal escaping.
+ */
+public class KingBaseSQLIdentifierProcessor extends PostgreSQLIdentifierProcessor {
+ public static final KingBaseSQLIdentifierProcessor INSTANCE = new KingBaseSQLIdentifierProcessor();
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/test/java/ai/chat2db/plugin/kingbase/KingBaseSQLIdentifierProcessorTest.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/test/java/ai/chat2db/plugin/kingbase/KingBaseSQLIdentifierProcessorTest.java
new file mode 100644
index 0000000000..e4f54ad94d
--- /dev/null
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-kingbase/src/test/java/ai/chat2db/plugin/kingbase/KingBaseSQLIdentifierProcessorTest.java
@@ -0,0 +1,340 @@
+package ai.chat2db.plugin.kingbase;
+
+import ai.chat2db.community.domain.api.enums.plugin.EditStatusEnum;
+import ai.chat2db.community.domain.api.config.TableBuilderConfig;
+import ai.chat2db.community.domain.api.model.metadata.Database;
+import ai.chat2db.community.domain.api.model.metadata.Schema;
+import ai.chat2db.community.domain.api.model.metadata.Table;
+import ai.chat2db.community.domain.api.model.metadata.TableColumn;
+import ai.chat2db.community.domain.api.model.metadata.TableIndex;
+import ai.chat2db.community.domain.api.model.metadata.TableIndexColumn;
+import ai.chat2db.plugin.kingbase.builder.KingBaseSqlBuilder;
+import ai.chat2db.plugin.kingbase.enums.type.KingBaseColumnTypeEnum;
+import ai.chat2db.plugin.kingbase.enums.type.KingBaseIndexTypeEnum;
+import ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor;
+import ai.chat2db.spi.model.request.DropTableRequest;
+import ai.chat2db.spi.model.request.UpdateSqlRequest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class KingBaseSQLIdentifierProcessorTest {
+
+ @Test
+ void escapeStringDoublesSingleQuotes() {
+ assertNull(KingBaseSQLIdentifierProcessor.INSTANCE.escapeString(null));
+ assertEquals("plain", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString("plain"));
+ assertEquals("O''Brien", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString("O'Brien"));
+ assertEquals("a''; DROP TABLE t; --", KingBaseSQLIdentifierProcessor.INSTANCE.escapeString("a'; DROP TABLE t; --"));
+ }
+
+ @Test
+ void escapeIdentifierTreatsBoundaryQuotesAsRawContent() {
+ assertNull(KingBaseSQLIdentifierProcessor.escapeIdentifier(null));
+ assertEquals("plain", KingBaseSQLIdentifierProcessor.escapeIdentifier("plain"));
+ assertEquals("we\"\"name", KingBaseSQLIdentifierProcessor.escapeIdentifier("we\"name"));
+ assertEquals("\"\"quoted\"\"", KingBaseSQLIdentifierProcessor.escapeIdentifier("\"quoted\""));
+ }
+
+ @Test
+ void quoteIdentifierConditionallyQuotes() {
+ KingBaseSQLIdentifierProcessor processor = KingBaseSQLIdentifierProcessor.INSTANCE;
+ assertNull(processor.quoteIdentifier(null));
+ assertEquals("", processor.quoteIdentifier(""));
+ assertEquals("plain", processor.quoteIdentifier("plain"));
+ assertEquals("\"UPPER\"", processor.quoteIdentifier("UPPER"));
+ assertEquals("\"select\"", processor.quoteIdentifier("select"));
+ assertEquals("plain", processor.quoteIdentifier("plain", null, null));
+ assertEquals("\"we\"\"name\"", processor.quoteIdentifier("we\"name"));
+ assertEquals("\"evil\"\"; DROP TABLE t; --\"",
+ processor.quoteIdentifier("evil\"; DROP TABLE t; --"));
+ }
+
+ @Test
+ void quoteIdentifierIgnoreCaseRemainsConditionalAndPreservesCase() {
+ KingBaseSQLIdentifierProcessor processor = KingBaseSQLIdentifierProcessor.INSTANCE;
+ assertNull(processor.quoteIdentifierIgnoreCase(null));
+ assertEquals("plain", processor.quoteIdentifierIgnoreCase("plain"));
+ assertEquals("\"MixedCase\"", processor.quoteIdentifierIgnoreCase("MixedCase"));
+ assertEquals("\"we\"\"name\"", processor.quoteIdentifierIgnoreCase("we\"name"));
+ }
+
+ @Test
+ void quoteIdentifierAlwaysWrapsAndDoublesEmbeddedQuotes() {
+ KingBaseSQLIdentifierProcessor processor = KingBaseSQLIdentifierProcessor.INSTANCE;
+ assertNull(processor.quoteIdentifierAlways(null));
+ assertEquals("\"\"", processor.quoteIdentifierAlways(""));
+ assertEquals("\"plain\"", processor.quoteIdentifierAlways("plain"));
+ assertEquals("\"UPPER\"", processor.quoteIdentifierAlways("UPPER"));
+ assertEquals("\"we\"\"name\"", processor.quoteIdentifierAlways("we\"name"));
+ assertEquals("\"\"\"quoted\"\"\"", processor.quoteIdentifierAlways("\"quoted\""));
+ assertEquals("\"evil\"\"; DROP TABLE t; --\"",
+ processor.quoteIdentifierAlways("evil\"; DROP TABLE t; --"));
+ }
+
+ @Test
+ void alwaysQuoteAndRemoveQuoteRoundTripExactRawIdentifiers() {
+ KingBaseSQLIdentifierProcessor processor = KingBaseSQLIdentifierProcessor.INSTANCE;
+ for (String raw : List.of("plain", "a\"b", "\"leading", "trailing\"", "\"both\"", "")) {
+ assertEquals(raw, processor.removeIdentifierQuote(processor.quoteIdentifierAlways(raw)), raw);
+ }
+ }
+
+ @Test
+ void createTableNeutralizesMaliciousNamesAndComments() {
+ Table table = new Table();
+ table.setName("evil\"; DROP TABLE t; --");
+ table.setComment("x'; DROP TABLE t; --");
+
+ TableColumn column = new TableColumn();
+ column.setName("c1");
+ column.setColumnType("VARCHAR");
+ table.setColumnList(List.of(column));
+
+ TableIndex index = new TableIndex();
+ index.setName("idx\"evil");
+ index.setType("Normal");
+ index.setTableName("evil\"; DROP TABLE t; --");
+ TableIndexColumn indexColumn = new TableIndexColumn();
+ indexColumn.setColumnName("c1");
+ index.setColumnList(List.of(indexColumn));
+ table.setIndexList(List.of(index));
+
+ String script = new KingBaseSqlBuilder().buildCreateTable(table, null);
+ assertTrue(script.contains("CREATE TABLE \"evil\"\"; DROP TABLE t; --\""), script);
+ assertTrue(script.contains("IS 'x''; DROP TABLE t; --'"), script);
+ assertTrue(script.contains("\"idx\"\"evil\""), script);
+ assertTrue(script.contains("ON \"evil\"\"; DROP TABLE t; --\""), script);
+ }
+
+ @Test
+ void alterTableRenameNeutralizesMaliciousNames() {
+ Table oldTable = new Table();
+ oldTable.setName("old_t");
+ oldTable.setColumnList(List.of());
+ oldTable.setIndexList(List.of());
+ Table newTable = new Table();
+ newTable.setName("new\"; DROP TABLE t; --");
+ newTable.setColumnList(List.of());
+ newTable.setIndexList(List.of());
+
+ String script = new KingBaseSqlBuilder().buildAlterTable(oldTable, newTable);
+ assertTrue(script.contains("ALTER TABLE \"old_t\""), script);
+ assertTrue(script.contains("RENAME TO \"new\"\"; DROP TABLE t; --\""), script);
+ }
+
+ @Test
+ void createDatabaseEscapesAndValidates() {
+ Database database = new Database();
+ database.setName("db\"; DROP TABLE t; --");
+ database.setCharset("UTF8");
+ database.setComment("c'; DROP TABLE t; --");
+
+ String script = new KingBaseSqlBuilder().buildCreateDatabase(database);
+ assertTrue(script.contains("CREATE DATABASE \"db\"\"; DROP TABLE t; --\""), script);
+ assertTrue(script.contains("IS 'c''; DROP TABLE t; --'"), script);
+
+ Database hostileCharset = new Database();
+ hostileCharset.setName("db2");
+ hostileCharset.setCharset("UTF8'; DROP TABLE t; --");
+ String hostileCharsetSql = new KingBaseSqlBuilder().buildCreateDatabase(hostileCharset);
+ assertTrue(hostileCharsetSql.contains("ENCODING 'UTF8''; DROP TABLE t; --'"), hostileCharsetSql);
+
+ Database quotedCharset = new Database();
+ quotedCharset.setName("db3");
+ quotedCharset.setCharset("UTF8");
+ String ok = new KingBaseSqlBuilder().buildCreateDatabase(quotedCharset);
+ assertTrue(ok.contains("ENCODING 'UTF8'"), ok);
+ }
+
+ @Test
+ void createSchemaNeutralizesMaliciousNames() {
+ Schema schema = new Schema();
+ schema.setName("sch\"; x; --");
+ String script = new KingBaseSqlBuilder().buildCreateSchema(schema);
+ assertTrue(script.contains("CREATE SCHEMA \"sch\"\"; x; --\""), script);
+ assertTrue(script.contains("AUTHORIZATION \"SYSTEM\""), script);
+ }
+
+ @Test
+ void columnTypeEnumEscapesNamesCommentsAndDefaults() {
+ TableColumn column = new TableColumn();
+ column.setName("c\"; x--");
+ column.setColumnType("VARCHAR");
+ column.setDefaultValue("O'Brien");
+ String createColumn = KingBaseColumnTypeEnum.VARCHAR.buildCreateColumnSql(column);
+ assertTrue(createColumn.startsWith("\"c\"\"; x--\" VARCHAR"), createColumn);
+ assertTrue(createColumn.contains("DEFAULT 'O''Brien'"), createColumn);
+
+ TableColumn commentColumn = new TableColumn();
+ commentColumn.setName("c1");
+ commentColumn.setTableName("t1");
+ commentColumn.setComment("y'; DROP TABLE t; --");
+ String comment = KingBaseColumnTypeEnum.VARCHAR.buildComment(commentColumn, KingBaseColumnTypeEnum.VARCHAR);
+ assertEquals("COMMENT ON COLUMN \"t1\".\"c1\" IS 'y''; DROP TABLE t; --';", comment);
+
+ TableColumn badDefault = new TableColumn();
+ badDefault.setName("n");
+ badDefault.setColumnType("INTEGER");
+ badDefault.setDefaultValue("1; DROP TABLE t--");
+ assertThrows(IllegalArgumentException.class,
+ () -> KingBaseColumnTypeEnum.INTEGER.buildCreateColumnSql(badDefault));
+
+ TableColumn okDefault = new TableColumn();
+ okDefault.setName("n");
+ okDefault.setColumnType("INTEGER");
+ okDefault.setDefaultValue("-1");
+ String ok = KingBaseColumnTypeEnum.INTEGER.buildCreateColumnSql(okDefault);
+ assertTrue(ok.contains("DEFAULT -1"), ok);
+
+ TableColumn quotedDefault = new TableColumn();
+ quotedDefault.setName("n");
+ quotedDefault.setColumnType("TEXT");
+ quotedDefault.setDefaultValue("'quoted string'");
+ String okQuoted = KingBaseColumnTypeEnum.TEXT.buildCreateColumnSql(quotedDefault);
+ assertTrue(okQuoted.contains("DEFAULT 'quoted string'"), okQuoted);
+ }
+
+ @Test
+ void indexTypeEnumEscapesNamesAndComments() {
+ TableIndex index = new TableIndex();
+ index.setName("i\"; x--");
+ index.setComment("y'; z--");
+ String comment = KingBaseIndexTypeEnum.NORMAL.buildIndexComment(index);
+ assertEquals("COMMENT ON INDEX \"i\"\"; x--\" IS 'y''; z--';", comment);
+
+ TableIndex fk = new TableIndex();
+ fk.setName("fk1");
+ fk.setForeignSchemaName("s\"; x--");
+ fk.setForeignTableName("ft");
+ fk.setForeignColumnNamelist(List.of("c1"));
+ TableIndexColumn fkColumn = new TableIndexColumn();
+ fkColumn.setColumnName("c1");
+ fk.setColumnList(List.of(fkColumn));
+ String fkScript = KingBaseIndexTypeEnum.FOREIGN.buildIndexScript(fk);
+ assertTrue(fkScript.contains("REFERENCES \"s\"\"; x--\".\"ft\" (\"c1\")"), fkScript);
+
+ TableIndex drop = new TableIndex();
+ drop.setOldName("o\"; x--");
+ drop.setEditStatus(EditStatusEnum.DELETE.name());
+ String dropScript = KingBaseIndexTypeEnum.NORMAL.buildModifyIndex(drop);
+ assertEquals("DROP INDEX \"o\"\"; x--\"", dropScript);
+
+ TableIndex method = new TableIndex();
+ method.setName("idx");
+ method.setTableName("orders");
+ method.setMethod("btree); DROP TABLE t;--");
+ method.setColumnList(List.of(fkColumn));
+ String methodScript = KingBaseIndexTypeEnum.NORMAL.buildIndexScript(method);
+ assertTrue(methodScript.contains("USING \"btree); DROP TABLE t;--\""), methodScript);
+ }
+
+ @Test
+ void dbManagerQuotesObjectNames() {
+ String sql = new KingBaseDBManager().dropTable(null, null, "s\"x", "t\"; x--");
+ assertEquals("DROP TABLE IF EXISTS \"s\"\"x\".\"t\"\"; x--\"", sql);
+ assertEquals("CREATE TABLE \"sales\".\"orders_copy\" AS TABLE \"sales\".\"orders\" WITH DATA",
+ KingBaseDBManager.buildCopyTableSql("sales", "orders", "orders_copy", true));
+ }
+
+ @Test
+ void metaDataNameDoublesEmbeddedQuotes() {
+ String name = new KingBaseMetaData().getMetaDataName("s", "we\"ird");
+ assertEquals("\"s\".\"we\"\"ird\"", name);
+ }
+
+ @Test
+ void spiProcessorIsConditionalForCompletionConsumers() {
+ KingBaseSQLIdentifierProcessor processor = KingBaseSQLIdentifierProcessor.INSTANCE;
+ assertEquals("plain", processor.quoteIdentifier("plain"));
+ assertEquals("\"UPPER\"", processor.quoteIdentifier("UPPER"));
+ assertEquals("\"select\"", processor.quoteIdentifier("select"));
+ assertEquals("\"we\"\"name\"", processor.quoteIdentifier("we\"name"));
+ assertEquals("plain", processor.removeIdentifierQuote("\"plain\""));
+ assertFalse(processor.isQuoteIdentifier("plain"));
+ assertTrue(processor.isQuoteIdentifier("\"plain\""));
+ }
+
+ @Test
+ void defaultAndTypeGuardsPreserveLegalSyntaxAndRejectDdlReshape() {
+ for (String value : List.of("now()", "nextval('audit.event_id_seq'::regclass)",
+ "timezone('UTC'::text, now())", "ARRAY[]::integer[]",
+ "$tag$comma, -- and ; stay literal$tag$")) {
+ assertEquals(value, KingBaseSqlGuards.requireDefaultExpression(value));
+ }
+ assertEquals("numeric(10,2)", KingBaseSqlGuards.requireColumnTypeExpression("numeric(10,2)"));
+ assertEquals("\"Tenant\".\"InvoiceType\"[]",
+ KingBaseSqlGuards.requireColumnTypeExpression("\"Tenant\".\"InvoiceType\"[]"));
+ for (String value : List.of("1, injected integer", "0 NOT NULL", "0 CHECK (false)",
+ "now()); DROP TABLE x", "'a'; DROP TABLE x--")) {
+ assertThrows(IllegalArgumentException.class,
+ () -> KingBaseSqlGuards.requireDefaultExpression(value), value);
+ }
+ for (String value : List.of("text, injected integer", "text DEFAULT 0", "text); DROP TABLE t;--")) {
+ assertThrows(IllegalArgumentException.class,
+ () -> KingBaseSqlGuards.requireColumnTypeExpression(value), value);
+ }
+ }
+
+ @Test
+ void fallbackColumnTypesAreValidatedInsteadOfDropped() {
+ TableColumn column = new TableColumn();
+ column.setName("state\"value");
+ column.setColumnType("public.invoice_state");
+ column.setNullable(0);
+ column.setDefaultValue("'OPEN'::public.invoice_state");
+ assertEquals("\"state\"\"value\" public.invoice_state NOT NULL DEFAULT 'OPEN'::public.invoice_state",
+ KingBaseColumnTypeEnum.buildCreateColumnSqlSafely(column));
+
+ column.setColumnType("text DEFAULT 0");
+ assertThrows(IllegalArgumentException.class,
+ () -> KingBaseColumnTypeEnum.buildCreateColumnSqlSafely(column));
+ }
+
+ @Test
+ void inheritedBuilderPathsQuoteKingBaseIdentifiersAndIgnoreDatabaseQualifier() {
+ KingBaseSqlBuilder builder = new KingBaseSqlBuilder();
+ assertEquals("SELECT COUNT(1) FROM \"sales\"\"x\".\"orders\"\"x\"",
+ builder.buildSelectCount("ignored_database", "sales\"x", "orders\"x"));
+ assertEquals("DROP TABLE \"sales\"\"x\".\"orders\"\"x\"",
+ builder.buildDropTable(new DropTableRequest("ignored_database", "sales\"x", "orders\"x")));
+
+ UpdateSqlRequest update = UpdateSqlRequest.builder()
+ .databaseName("ignored_database")
+ .schemaName("sales\"schema")
+ .tableName("orders\"table")
+ .row(Map.of("total\"value", "42"))
+ .primaryKeyMap(Map.of("order\"id", "7"))
+ .build();
+ assertEquals("UPDATE \"sales\"\"schema\".\"orders\"\"table\" SET \"total\"\"value\" = 42"
+ + " WHERE \"order\"\"id\" = 7",
+ builder.buildUpdate(update));
+ }
+
+ @Test
+ void createTableAndCaseOnlyRenameUseQualifiedNames() {
+ Table table = new Table();
+ table.setSchemaName("sales\"schema");
+ table.setName("orders\"table");
+ table.setColumnList(List.of(TableColumn.builder().name("id\"value").columnType("INTEGER").build()));
+ table.setIndexList(List.of());
+ TableBuilderConfig config = TableBuilderConfig.defaultConfig();
+ config.setNeedFullTableName(true);
+ String createSql = new KingBaseSqlBuilder().buildCreateTable(table, config);
+ assertTrue(createSql.startsWith("CREATE TABLE \"sales\"\"schema\".\"orders\"\"table\""), createSql);
+
+ Table renamed = Table.builder().schemaName("sales").name("Orders")
+ .columnList(List.of()).indexList(List.of()).build();
+ Table original = Table.builder().schemaName("sales").name("orders")
+ .columnList(List.of()).indexList(List.of()).build();
+ assertEquals("ALTER TABLE \"sales\".\"orders\"\tRENAME TO \"Orders\";\n",
+ new KingBaseSqlBuilder().buildAlterTable(original, renamed));
+ }
+}