Skip to content

Commit fa5a585

Browse files
authored
test: add end-to-end DatabaseMetaData integration tests against local Hyper (#172)
Existing DatabaseMetaData coverage was unit-test-only with mocked gRPC responses, which couldn't catch mismatches between QueryMetadataUtil's type mapping and what a real pg_catalog returns. Test harness: - DatabaseAttachInterceptor: gRPC ClientInterceptor that attaches a named .hyper database to every ExecuteQuery call. Lets a JDBC connection target a specific database file without touching the caller's QueryParam. - HyperDatabaseSetup: raw-gRPC (JDBC-independent) utility that creates a temp .hyper database and populates a test table covering every supported type — bool, smallint, int, bigint, double precision, numeric, text, varchar, char, date, time, timestamp, timestamptz, interval, json, oid, int[], text[], plus explicit nullable / NOT NULL int columns. - hyper_with_database.yaml: hyperd config with DATABASE_HYPER_EXTERNAL authorization, fixed init-user, and experimental_data_type_persistence so arrays survive in non-temp tables. - HyperServerConfig.databasePath: new -d flag passthrough for hyperd. - HyperServerManager.ConfigFile.WITH_DATABASE: new config profile. Tests: DatabaseMetadataIntegrationTest boots hyperd with -d, creates a real database via the interceptor, and exercises getSchemas / getTables / getColumns against actual pg_catalog rows. The suite is deliberately a living bug list: every assertion that pins current-but-incorrect behaviour carries a BUG: comment stating the spec-correct value. Fixing the underlying defect will break the assertion and force the override to be deleted. The single strongest test is a cross-check between DatabaseMetaData .getColumns() (pg_catalog path, via QueryMetadataUtil) and ResultSetMetaData from SELECT * WHERE false (Arrow schema path, via ArrowToColumnTypeMapper). The two paths both claim to describe the same column and must agree — today they disagree for 14 of 20 cross-checked columns. The test encodes the exact set of known disagreements, so any fix to either path immediately surfaces. Bugs surfaced (all in QueryMetadataUtil unless noted): - dbTypeToSql keys on pg-internal short names (int2, int4, float8, ...) but the SQL template calls format_type() which emits long forms (smallint, integer, double precision, ...). 7 of the 15 map entries are unreachable; DATA_TYPE falls through to raw pg type OIDs instead of java.sql.Types values. - No prefix/contains handling for parameterised types, so 'character varying(255)', 'character(1)', 'array(integer)', 'array(text)' never match. - COLUMN_SIZE is hardcoded to 255 regardless of the column. - DECIMAL_DIGITS is hardcoded to 2 regardless of atttypmod. - interval and json have no JDBC mapping at all. - DECIMAL (Arrow) vs NUMERIC (pg_catalog) disagreement for numeric(). - oid is reported as INTEGER via Arrow but BIGINT via pg_catalog. - ArrowToColumnTypeMapper.visit(Interval) throws, so any SELECT that materialises an interval column crashes the driver. Not covered: 32-bit floats (hyperd rejects 'real' locally with 'This database does not support 32-bit floating points') and bytea.
1 parent 5a78b40 commit fa5a585

6 files changed

Lines changed: 1188 additions & 2 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* This file is part of https://github.com/forcedotcom/datacloud-jdbc which is released under the
3+
* Apache 2.0 license. See https://github.com/forcedotcom/datacloud-jdbc/blob/main/LICENSE.txt
4+
*/
5+
package com.salesforce.datacloud.jdbc.interceptor;
6+
7+
import io.grpc.CallOptions;
8+
import io.grpc.Channel;
9+
import io.grpc.ClientCall;
10+
import io.grpc.ClientInterceptor;
11+
import io.grpc.ForwardingClientCall;
12+
import io.grpc.MethodDescriptor;
13+
import salesforce.cdp.hyperdb.v1.AttachedDatabase;
14+
import salesforce.cdp.hyperdb.v1.QueryParam;
15+
16+
/**
17+
* A gRPC {@link ClientInterceptor} that attaches a database to every {@code ExecuteQuery} call.
18+
*
19+
* <p>This interceptor rewrites outgoing {@link QueryParam} messages to
20+
* include the configured {@link AttachedDatabase} entry, which makes a Hyper
21+
* database available in SQL under the given alias.</p>
22+
*
23+
* <p>Usage example:
24+
* <pre>{@code
25+
* var interceptor = new DatabaseAttachInterceptor("/tmp/test.hyper", "default");
26+
* var channel = ManagedChannelBuilder.forAddress("127.0.0.1", port)
27+
* .usePlaintext()
28+
* .intercept(interceptor)
29+
* .build();
30+
* }</pre>
31+
*/
32+
public class DatabaseAttachInterceptor implements ClientInterceptor {
33+
34+
private static final String EXECUTE_QUERY_METHOD = "salesforce.hyperdb.grpc.v1.HyperService/ExecuteQuery";
35+
36+
private final AttachedDatabase attachedDatabase;
37+
38+
/**
39+
* Creates an interceptor that attaches the given database under the given alias.
40+
*
41+
* @param databasePath the path to the Hyper database (e.g. a file path or {@code hyper.external:} URI)
42+
* @param alias the SQL alias under which the database is accessible
43+
*/
44+
public DatabaseAttachInterceptor(String databasePath, String alias) {
45+
this.attachedDatabase = AttachedDatabase.newBuilder()
46+
.setPath(databasePath)
47+
.setAlias(alias)
48+
.build();
49+
}
50+
51+
@Override
52+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
53+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
54+
ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
55+
56+
if (!EXECUTE_QUERY_METHOD.equals(method.getFullMethodName())) {
57+
return call;
58+
}
59+
60+
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
61+
@Override
62+
public void sendMessage(ReqT message) {
63+
if (message instanceof QueryParam) {
64+
QueryParam original = (QueryParam) message;
65+
@SuppressWarnings("unchecked")
66+
ReqT rewritten = (ReqT)
67+
original.toBuilder().addDatabases(attachedDatabase).build();
68+
super.sendMessage(rewritten);
69+
} else {
70+
super.sendMessage(message);
71+
}
72+
}
73+
};
74+
}
75+
}

0 commit comments

Comments
 (0)