Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<groupId>ai.chat2db</groupId>
<artifactId>chat2db-community-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<artifactId>chat2db-community-kingbase</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -29,7 +30,7 @@ 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.escapeIdentifier(schemaName));
try {
DefaultSQLExecutor.getInstance().execute(connection, sql);
} catch (SQLException e) {
Expand All @@ -56,17 +57,17 @@ 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;
String sql = "drop table if exists " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName);
return sql;
}

@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";
sql = "CREATE TABLE " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(newTableName) + " AS TABLE " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName) + " WITH DATA";
}else {
sql = "CREATE TABLE " + newTableName + " AS TABLE " + tableName + " WITH NO DATA";
sql = "CREATE TABLE " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(newTableName) + " AS TABLE " + KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName) + " WITH NO DATA";
}
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand All @@ -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;
Expand All @@ -48,8 +46,6 @@ public class KingBaseMetaData extends DefaultMetaService implements IDbMetaData



public static final DefaultSQLIdentifierProcessor KINGBASE_SQL_IDENTIFIER_PROCESSOR = new KingBaseSQLIdentifierProcessor();

@Override
public List<Database> databases(Connection connection) {
String sql = "SELECT datname FROM sys_database";
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -139,7 +135,7 @@ 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());
}
Expand All @@ -158,7 +154,7 @@ 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(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(parentTableName)).append("\n");
if (!constraintsBuilder.isEmpty()) {
ddlBuilder.append("(\n")
.append(constraintsBuilder)
Expand All @@ -182,7 +178,7 @@ public String tableDDL(Connection connection, String databaseName, String schema
tableOwnerBuilder.append(SQL_ALTER_TABLE)
.append(format(table_name))
.append(" owner to ")
.append(owner)
.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(owner))
.append(";").append("\n");
}
}
Expand All @@ -201,7 +197,7 @@ public String tableDDL(Connection connection, String databaseName, String schema
.append(SQL_ON)
.append(formatTableName)
.append(" to ")
.append(grantee)
.append(KingBaseSQLIdentifierProcessor.INSTANCE.quoteIdentifierAlways(grantee))
.append(";").append("\n");
}
}
Expand Down Expand Up @@ -428,7 +424,7 @@ 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")
ddlBuilder.append("\n").append(SQL_CREATE_TABLE).append(subName).append("\n")
.append("partition of ").append(parentTableName).append("\n")
.append(partitionDefinition.toLowerCase()).append(";\n");
}
Expand Down Expand Up @@ -472,7 +468,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");
}
}
Expand All @@ -481,7 +477,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 ")
Expand Down Expand Up @@ -625,7 +621,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
Expand All @@ -640,6 +636,6 @@ public List<String> getSystemSchemas() {

@Override
public ISQLIdentifierProcessor getSQLIdentifierProcessor() {
return KINGBASE_SQL_IDENTIFIER_PROCESSOR;
return KingBaseSQLIdentifierProcessor.INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ai.chat2db.plugin.kingbase;

import org.apache.commons.lang3.StringUtils;

/**
* Validation helpers for non-escapable SQL expression positions in KingBase DDL
* generation (column DEFAULT expressions, database ENCODING values, index access
* method names). Escaping itself lives in
* {@link ai.chat2db.plugin.kingbase.identifier.KingBaseSQLIdentifierProcessor}.
*/
public final class KingBaseSqlGuards {

private KingBaseSqlGuards() {
}

/**
* Validate a non-escapable SQL expression position (DEFAULT expressions, ENCODING values, ...)
* and return it unchanged. Quoted string literals and quoted identifiers inside the expression
* are honored so legitimate values such as {@code 'it''s'} or {@code now()} are accepted.
*
* @throws IllegalArgumentException if the expression contains characters that could terminate
* or comment out the surrounding statement
*/
public static String requireSafeExpression(String value, String description) {
if (!isSafeSqlExpression(value)) {
throw new IllegalArgumentException("Unsafe SQL expression for " + description + ": " + value);
}
return value;
}

/**
* Validate an index access method name emitted verbatim after USING. Only plain identifier
* characters are legal (btree, hash, gist, gin, spgist, brin, ...).
*/
public static String requireIndexMethod(String method) {
if (method == null || !method.matches("[A-Za-z][A-Za-z0-9_]*")) {
throw new IllegalArgumentException("Invalid index method: " + method);
}
return method;
}

/**
* Quote-aware whitelist check for non-escapable SQL expression positions. Content inside
* single-quoted literals (with '' escapes) and double-quoted identifiers (with "" escapes) is
* allowed; outside quotes only expression characters are permitted and statement terminators
* or comment tokens are rejected.
*/
public static boolean isSafeSqlExpression(String value) {
if (StringUtils.isBlank(value)) {
return false;
}
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
int length = value.length();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (inSingleQuote) {
if (c == '\'') {
if (i + 1 < length && value.charAt(i + 1) == '\'') {
i++;
} else {
inSingleQuote = false;
}
}
continue;
}
if (inDoubleQuote) {
if (c == '"') {
if (i + 1 < length && value.charAt(i + 1) == '"') {
i++;
} else {
inDoubleQuote = false;
}
}
continue;
}
if (c == '\'') {
inSingleQuote = true;
continue;
}
if (c == '"') {
inDoubleQuote = true;
continue;
}
if (c == ';') {
return false;
}
if (c == '-' && i + 1 < length && value.charAt(i + 1) == '-') {
return false;
}
if (c == '/' && i + 1 < length && value.charAt(i + 1) == '*') {
return false;
}
if (c == '*' && i + 1 < length && value.charAt(i + 1) == '/') {
return false;
}
if (!Character.isLetterOrDigit(c) && !Character.isWhitespace(c)
&& "_.()+*/,-<>=:@%[]".indexOf(c) < 0) {
return false;
}
}
return !inSingleQuote && !inDoubleQuote;
}
}
Loading
Loading