fix(jdbc): escape completion masks for OceanBase hover preview

This commit is contained in:
efeire
2026-09-21 11:36:47 +08:00
committed by GitHub
parent 99c172b24d
commit e2d7bba760
2 changed files with 46 additions and 4 deletions
@@ -436,7 +436,7 @@ public final class StandardJdbcMetadata {
int limit
) throws Exception {
String[] tableTypes = getDriverTableTypes(meta, profile);
try (ResultSet rs = meta.getTables(catalog, blankToNull(schema), completionPattern(request), tableTypes)) {
try (ResultSet rs = meta.getTables(catalog, blankToNull(schema), completionPattern(meta, request), tableTypes)) {
while (rs.next() && result.size() < limit) {
String name = rs.getString("TABLE_NAME");
String type = normalizeTableType(rs.getString("TABLE_TYPE"));
@@ -471,7 +471,7 @@ public final class StandardJdbcMetadata {
if (table == null || table.trim().isEmpty()) {
return;
}
try (ResultSet rs = meta.getColumns(catalog, blankToNull(schema), table, completionPattern(request))) {
try (ResultSet rs = meta.getColumns(catalog, blankToNull(schema), table, completionPattern(meta, request))) {
while (rs.next() && result.size() < limit) {
String name = rs.getString("COLUMN_NAME");
if (!completionNameMatches(name, request)) {
@@ -509,12 +509,12 @@ public final class StandardJdbcMetadata {
return request.getSchema();
}
private static String completionPattern(CompletionAssistantRequest request) {
private static String completionPattern(DatabaseMetaData meta, CompletionAssistantRequest request) {
String mask = request.getMask();
if (mask.trim().isEmpty()) {
return "%";
}
String escaped = mask.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
String escaped = escapeSchemaPattern(meta, mask, true);
if (request.getMatch_mode() == CompletionAssistantMatchMode.CONTAINS) {
return "%" + escaped + "%";
}
@@ -1,6 +1,8 @@
package com.dbx.agent;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -594,6 +596,46 @@ class StandardJdbcMetadataTest {
assertEquals("DISPLAY%", capturedColumnArgs.get()[3]);
}
@ParameterizedTest
@CsvSource(value = {
"/|DBX_I7539_1001|DBX/_I7539/_1001%",
"/|A/B_%|A//B/_/%%",
"\\|A\\B_%|A\\\\B\\_\\%%",
"''|A_%|A_%%",
"|A_%|A_%%",
"/|''|%"
}, delimiter = '|')
void completionUsesDriverEscapeForTableAndColumnMasks(String escape, String mask, String prefixPattern) {
for (CompletionAssistantMatchMode mode : Arrays.asList(CompletionAssistantMatchMode.PREFIX, CompletionAssistantMatchMode.CONTAINS)) {
for (CompletionAssistantObjectKind kind : Arrays.asList(CompletionAssistantObjectKind.TABLE, CompletionAssistantObjectKind.COLUMN)) {
AtomicReference<String> capturedPattern = new AtomicReference<>();
DatabaseMetaData meta = proxy(DatabaseMetaData.class, (method, args) -> {
switch (method.getName()) {
case "getSearchStringEscape": return escape;
case "getTableTypes": return rows(row("TABLE_TYPE", "TABLE"));
case "getTables":
capturedPattern.set((String) args[2]);
return rows(row("TABLE_NAME", mask + "MATCH", "TABLE_TYPE", "TABLE"));
case "getColumns":
capturedPattern.set((String) args[3]);
return rows(row("COLUMN_NAME", mask + "MATCH", "TYPE_NAME", "VARCHAR"));
default: return defaultValue(method.getReturnType());
}
});
Connection conn = proxy(Connection.class, (method, args) ->
"getMetaData".equals(method.getName()) ? meta : defaultValue(method.getReturnType()));
CompletionAssistantRequest request = request("sales", "APP", mask, Collections.singletonList(kind), "ACCOUNTS");
setField(request, "match_mode", mode);
CompletionAssistantResponse result = StandardJdbcMetadata.INSTANCE.completionAssistantSearch(conn, profile, "sales", request);
String expected = mode == CompletionAssistantMatchMode.CONTAINS && !mask.isEmpty() ? "%" + prefixPattern : prefixPattern;
assertEquals(expected, capturedPattern.get(), kind + " " + mode);
assertEquals(mask + "MATCH", result.getCandidates().get(0).getName());
}
}
}
private static Connection connection(
ResultSet schemas,
ResultSet tables,