Skip to content
Merged
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-oracle</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package ai.chat2db.plugin.oceanbase.oracle;

import ai.chat2db.plugin.oceanbase.oracle.identifier.OceanbaseOracleIdentifierProcessor;
import ai.chat2db.plugin.oracle.OracleMetaData;
import ai.chat2db.community.tools.util.EasyStringUtils;
import ai.chat2db.spi.IDbMetaData;
import ai.chat2db.spi.DefaultSQLExecutor;
import ai.chat2db.spi.util.SqlUtils;
import ai.chat2db.spi.ISQLIdentifierProcessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -18,19 +19,19 @@
@Slf4j
public class OceanbaseOracleMetaData extends OracleMetaData implements IDbMetaData {





@Override
public ISQLIdentifierProcessor getSQLIdentifierProcessor() {
return OceanbaseOracleIdentifierProcessor.INSTANCE;
}


@Override
public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) {
String sql = String.format(TABLE_DDL_SQL, tableName, schemaName);
String tableCommentSql = String.format(TABLE_COMMENT_SQL, schemaName, tableName);
String tableColumnCommentSql = String.format(TABLE_COLUMN_COMMENT_SQL, schemaName, tableName);
String PUIndexSql = String.format(PU_INDEX_NAME_SQL, schemaName, tableName);
String tableIndexNameSql = String.format(TABLE_INDEX_NAME_SQL, schemaName, tableName);
String sql = buildTableDdlSql(tableName, schemaName);
String tableCommentSql = buildTableCommentSql(schemaName, tableName);
String tableColumnCommentSql = buildTableColumnCommentSql(schemaName, tableName);
String PUIndexSql = buildPuIndexNameSql(schemaName, tableName);
String tableIndexNameSql = buildTableIndexNameSql(schemaName, tableName);
StringBuilder ddlBuilder = new StringBuilder();
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> {
try {
Expand All @@ -45,7 +46,9 @@ public String tableDDL(Connection connection, String databaseName, String schema
if (resultSet.next()) {
String tableComment = resultSet.getString("comments");
if (StringUtils.isNotBlank(tableComment)) {
ddlBuilder.append("\nCOMMENT ON TABLE ").append(SqlUtils.quoteObjectName(tableName)).append(" IS ")
ddlBuilder.append("\nCOMMENT ON TABLE ")
.append(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName))
.append(" IS ")
.append(EasyStringUtils.escapeAndQuoteString(tableComment)).append(";");
}
}
Expand All @@ -56,8 +59,8 @@ public String tableDDL(Connection connection, String databaseName, String schema
String columnComment = resultSet.getString("comments");
if (StringUtils.isNotBlank(columnComment)) {
ddlBuilder.append("\nCOMMENT ON COLUMN ")
.append(SqlUtils.quoteObjectName(tableName)).append(".")
.append(SqlUtils.quoteObjectName(columnName)).append(" IS ")
.append(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways(tableName)).append(".")
.append(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways(columnName)).append(" IS ")
.append(EasyStringUtils.escapeAndQuoteString(columnComment)).append(";");
}
}
Expand Down Expand Up @@ -85,7 +88,7 @@ public String tableDDL(Connection connection, String databaseName, String schema
return indexNames;
});
for (String index : indexes) {
String tableIndexSql = String.format(TABLE_INDEX_DDL_SQL, index, schemaName);
String tableIndexSql = buildTableIndexDdlSql(index, schemaName);
DefaultSQLExecutor.getInstance().execute(connection, tableIndexSql, resultSet -> {
while (resultSet.next()) {
String ddl = resultSet.getString("ddl");
Expand All @@ -98,5 +101,35 @@ public String tableDDL(Connection connection, String databaseName, String schema
return ddlBuilder.toString();
}

static String buildTableDdlSql(String tableName, String schemaName) {
return String.format(TABLE_DDL_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(tableName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName));
}

static String buildTableCommentSql(String schemaName, String tableName) {
return String.format(TABLE_COMMENT_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(tableName));
}

static String buildTableColumnCommentSql(String schemaName, String tableName) {
return String.format(TABLE_COLUMN_COMMENT_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(tableName));
}

static String buildPuIndexNameSql(String schemaName, String tableName) {
return String.format(PU_INDEX_NAME_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(tableName));
}

static String buildTableIndexNameSql(String schemaName, String tableName) {
return String.format(TABLE_INDEX_NAME_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(tableName));
}

static String buildTableIndexDdlSql(String indexName, String schemaName) {
return String.format(TABLE_INDEX_DDL_SQL, OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(indexName),
OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(schemaName));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ai.chat2db.plugin.oceanbase.oracle.identifier;

import ai.chat2db.plugin.oracle.identifier.OracleIdentifierProcessor;

/**
* OceanBase Oracle mode uses Oracle's identifier, case-folding, and literal
* escaping rules.
*/
public class OceanbaseOracleIdentifierProcessor extends OracleIdentifierProcessor {

public static final OceanbaseOracleIdentifierProcessor INSTANCE = new OceanbaseOracleIdentifierProcessor();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package ai.chat2db.plugin.oceanbase.oracle;

import ai.chat2db.plugin.oceanbase.oracle.identifier.OceanbaseOracleIdentifierProcessor;
import org.junit.jupiter.api.Test;

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.assertSame;

class OceanbaseOracleIdentifierProcessorTest {

@Test
void escapeSqlLiteralDoublesSingleQuotes() {
assertEquals("O''Brien", OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString("O'Brien"));
assertEquals("plain", OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString("plain"));
assertNull(OceanbaseOracleIdentifierProcessor.INSTANCE.escapeString(null));
}

@Test
void quoteIdentifierPassesThroughNullAndBlank() {
assertNull(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier(null));
assertEquals("", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier(""));
assertEquals(" ", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier(" "));
}

@Test
void quoteIdentifierLeavesPlainIdentifiersUnquoted() {
assertEquals("MY_TABLE", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("MY_TABLE"));
assertEquals("\"my_table\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("my_table"));
assertEquals("COL_1", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("COL_1"));
}

@Test
void quoteIdentifierQuotesReservedKeywordsAndSpecialChars() {
assertEquals("\"TABLE\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("TABLE"));
assertEquals("\"select\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("select"));
assertEquals("\"has space\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("has space"));
assertEquals("\"we\"\"ird\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("we\"ird"));
assertEquals("\"we\"\"ird\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("\"we\"\"ird\""));
}

@Test
void versionedQuoteIdentifierDelegatesToConditional() {
assertEquals("MY_TABLE", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("MY_TABLE", 4, 2));
assertEquals("\"TABLE\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier("TABLE", null, null));
assertNull(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifier(null, null, null));
}

@Test
void quoteIdentifierAlwaysQuotesUnconditionally() {
assertNull(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways(null));
assertEquals("\"MY_TABLE\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways("MY_TABLE"));
assertEquals("\"we\"\"ird\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways("we\"ird"));

String[] rawIdentifiers = {"", "A\"B", "\"ALREADY\"", "\"A", "A\"", "\"\"", "A\"\"B"};
for (String raw : rawIdentifiers) {
assertEquals(raw,
OceanbaseOracleIdentifierProcessor.INSTANCE.removeIdentifierQuote(
OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierAlways(raw)));
}
}

@Test
void quoteIdentifierIgnoreCasePreservesCaseConditionally() {
assertNull(OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierIgnoreCase(null));
assertEquals("MY_TABLE", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierIgnoreCase("MY_TABLE"));
assertEquals("my_table", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierIgnoreCase("my_table"));
assertEquals("\"select\"", OceanbaseOracleIdentifierProcessor.INSTANCE.quoteIdentifierIgnoreCase("select"));
}

@Test
void metadataExposesTheOceanbaseOracleProcessor() {
assertSame(OceanbaseOracleIdentifierProcessor.INSTANCE,
new OceanbaseOracleMetaData().getSQLIdentifierProcessor());
}

@Test
void buildTableDdlSqlNeutralizesMaliciousNames() {
String sql = OceanbaseOracleMetaData.buildTableDdlSql("T' OR '1'='1", "S' OR '1'='1");

assertEquals("select dbms_metadata.get_ddl('TABLE','T'' OR ''1''=''1','S'' OR ''1''=''1') as sql from dual",
sql);
assertFalse(sql.contains("'T' OR '1'='1'"));
}

@Test
void buildTableCommentSqlNeutralizesMaliciousNames() {
String sql = OceanbaseOracleMetaData.buildTableCommentSql("SCOTT' OR '1'='1", "O'Brien");

assertEquals("select owner, table_name, comments from ALL_TAB_COMMENTS where OWNER = 'SCOTT'' OR ''1''=''1'"
+ " and TABLE_NAME = 'O''Brien'", sql);
}

@Test
void buildTableIndexDdlSqlNeutralizesMaliciousIndexName() {
String sql = OceanbaseOracleMetaData.buildTableIndexDdlSql("IDX', 'S', 'X", "SCOTT");

assertEquals(String.format(
ai.chat2db.plugin.oceanbase.constant.OceanbaseOracleMetaDataConstants.TABLE_INDEX_DDL_SQL,
"IDX'', ''S'', ''X", "SCOTT"),
sql);
assertFalse(sql.contains("'IDX', 'S', 'X'"));
}

@Test
void allMetadataQueriesEscapeLiteralArguments() {
assertEquals("SELECT owner, table_name, column_name, comments\n"
+ "FROM all_col_comments\n"
+ "WHERE owner = 'S''1' and table_name = 'T''1' and comments is not null",
OceanbaseOracleMetaData.buildTableColumnCommentSql("S'1", "T'1"));
assertEquals("SELECT DISTINCT AC.INDEX_NAME\n"
+ "FROM ALL_CONSTRAINTS AC\n"
+ "WHERE AC.OWNER = 'S''1' AND AC.TABLE_NAME = 'T''1'\n"
+ " AND AC.CONSTRAINT_TYPE IN ('P','U')",
OceanbaseOracleMetaData.buildPuIndexNameSql("S'1", "T'1"));
assertEquals("select\n"
+ " INDEX_NAME\n"
+ "from\n"
+ " SYS.ALL_INDEXES\n"
+ "where\n"
+ " OWNER = 'S''1'\n"
+ " and TABLE_NAME = 'T''1'\n",
OceanbaseOracleMetaData.buildTableIndexNameSql("S'1", "T'1"));
}
}
Loading