Skip to content

Commit 9ab1d79

Browse files
robfrankclaude
andcommitted
#3588 fix: gRPC query and streaming query now propagate the language parameter
The language parameter was ignored in query() and queryStream() paths, causing all queries to be executed as SQL regardless of the specified language. - Added language field to ExecuteQueryRequest proto (field 9, backward-compatible) - Server: executeQuery() and streamQuery() modes now use the language from the request - Client: query(), queryStream(), and streamQuery() now pass language to the proto builders - Enabled Gremlin and Cypher e2e tests that were disabled pending this fix Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 43f1922 commit 9ab1d79

File tree

6 files changed

+120
-16
lines changed

6 files changed

+120
-16
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# gRPC Query Language Support
2+
3+
## Problem
4+
5+
The gRPC client (`RemoteGrpcDatabase`) ignores the `language` parameter for query operations. Only `command()` correctly propagates the language. This prevents using Cypher, Gremlin, or any non-SQL language through the gRPC query and streaming query paths.
6+
7+
The bug spans three layers:
8+
9+
1. **Proto**: `ExecuteQueryRequest` is missing a `language` field entirely
10+
2. **Client**: `query()` can't pass language (proto missing it); `queryStream()` and `streamQuery()` never call `.setLanguage()` despite the proto supporting it
11+
3. **Server**: `executeQuery()` hardcodes `db.query("sql", ...)`; all three `streamQuery` modes (`streamCursor`, `streamMaterialized`, `streamPaged`) hardcode `db.query("sql", ...)`
12+
13+
## Design Decisions
14+
15+
- Add a `language` field to `ExecuteQueryRequest` (additive, backward-compatible)
16+
- Keep using `db.query(language, ...)` on the server for the Query RPC (caller chose read-only)
17+
- Default to `"sql"` when the language field is empty/unset (backward-compatible)
18+
19+
## Changes
20+
21+
### 1. Proto (`grpc/src/main/proto/arcadedb-server.proto`)
22+
23+
Add `string language = 9;` to `ExecuteQueryRequest`:
24+
25+
```protobuf
26+
message ExecuteQueryRequest {
27+
string database = 1;
28+
string query = 2;
29+
map<string, GrpcValue> parameters = 3;
30+
DatabaseCredentials credentials = 4;
31+
TransactionContext transaction = 5;
32+
int32 limit = 6;
33+
int32 timeout_ms = 7;
34+
ProjectionSettings projectionSettings = 8;
35+
string language = 9; // "sql" if empty (default)
36+
}
37+
```
38+
39+
### 2. Server (`grpcw/.../ArcadeDbGrpcService.java`)
40+
41+
**`executeQuery()`**: Replace hardcoded `"sql"` with language from request, defaulting to `"sql"` when empty.
42+
43+
**`streamQuery()`**: Extract language from `StreamQueryRequest.getLanguage()` (proto field 7, already exists), resolve default, and pass to `streamCursor`/`streamMaterialized`/`streamPaged`. Each mode method gains a `String language` parameter.
44+
45+
### 3. Client (`grpc-client/.../RemoteGrpcDatabase.java`)
46+
47+
- `query()` path (line 556): Add `.setLanguage(language)` to `ExecuteQueryRequest` builder
48+
- `queryStream()` path (line 780): Add `.setLanguage(language)` to `StreamQueryRequest` builder
49+
- Private `streamQuery()` (line 1767): Add `.setLanguage("sql")` since it's SQL-only by design
50+
51+
### Testing
52+
53+
- Existing gRPC e2e tests verify backward compatibility (SQL still works)
54+
- Add test that runs a query with a non-SQL language through `query()` and `queryStream()` to verify language propagation
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# gRPC Query Language Support - Implementation Plan
2+
3+
## Step 1: Proto change
4+
5+
- File: `grpc/src/main/proto/arcadedb-server.proto`
6+
- Add `string language = 9;` to `ExecuteQueryRequest` (after `projectionSettings`)
7+
- Rebuild proto: `cd grpc && mvn clean install -DskipTests`
8+
9+
## Step 2: Server - `executeQuery()` language support
10+
11+
- File: `grpcw/src/main/java/com/arcadedb/server/grpc/ArcadeDbGrpcService.java`
12+
- In `executeQuery()` (~line 823): extract language from `request.getLanguage()`, default to `"sql"`
13+
- Replace `db.query("sql", ...)` with `db.query(language, ...)`
14+
15+
## Step 3: Server - `streamQuery()` language support
16+
17+
- File: `grpcw/src/main/java/com/arcadedb/server/grpc/ArcadeDbGrpcService.java`
18+
- In `streamQuery()`: extract language from `request.getLanguage()`, default to `"sql"`
19+
- Add `String language` parameter to `streamCursor()`, `streamMaterialized()`, `streamPaged()`
20+
- Replace hardcoded `"sql"` in each mode's `db.query()` call
21+
- Build server: `cd grpcw && mvn clean install -DskipTests`
22+
23+
## Step 4: Client - wire language through query paths
24+
25+
- File: `grpc-client/src/main/java/com/arcadedb/remote/grpc/RemoteGrpcDatabase.java`
26+
- `query()` at line 556: add `.setLanguage(language)` to `ExecuteQueryRequest` builder
27+
- `queryStream()` at line 780: add `.setLanguage(language)` to `StreamQueryRequest` builder
28+
- Private `streamQuery()` at line 1767: add `.setLanguage("sql")` to `StreamQueryRequest` builder
29+
- Build client: `cd grpc-client && mvn clean install -DskipTests`
30+
31+
## Step 5: Test
32+
33+
- Add e2e test verifying a non-SQL query (e.g. Cypher `MATCH (n) RETURN n LIMIT 1`) works via gRPC `query()`
34+
- Run existing gRPC e2e tests to verify no regressions

e2e/src/test/java/com/arcadedb/e2e/RemoteGrpcDatabaseTest.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.arcadedb.utility.CollectionUtils;
2525
import org.junit.jupiter.api.AfterEach;
2626
import org.junit.jupiter.api.BeforeEach;
27-
import org.junit.jupiter.api.Disabled;
2827
import org.junit.jupiter.api.Test;
2928

3029
import java.util.List;
@@ -57,16 +56,23 @@ void simpleSQLQuery() {
5756
}
5857

5958
@Test
60-
@Disabled("Gremlin not supported yet")
6159
void simpleGremlinQuery() {
6260
final ResultSet result = database.query("gremlin", "g.V().limit(10)");
6361
assertThat(CollectionUtils.countEntries(result)).isEqualTo(10);
6462
}
6563

6664
@Test
67-
@Disabled("Cypher not supported yet")
6865
void simpleCypherQuery() {
6966
final ResultSet result = database.query("cypher", "MATCH(p:Beer) RETURN * LIMIT 10");
7067
assertThat(CollectionUtils.countEntries(result)).isEqualTo(10);
7168
}
69+
70+
@Test
71+
void simpleOpenCypherQuery() {
72+
database.transaction(() -> {
73+
final ResultSet result = database.query("opencypher", "MATCH(p:Beer) RETURN * LIMIT 10");
74+
assertThat(CollectionUtils.countEntries(result)).isEqualTo(10);
75+
}, false, 10);
76+
}
77+
7278
}

grpc-client/src/main/java/com/arcadedb/remote/grpc/RemoteGrpcDatabase.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,10 @@ public ResultSet query(final String language, final String query, RemoteGrpcConf
553553
checkDatabaseIsOpen();
554554
stats.queries.incrementAndGet();
555555

556-
ExecuteQueryRequest.Builder requestBuilder = ExecuteQueryRequest.newBuilder().setDatabase(getName()).setQuery(query)
556+
ExecuteQueryRequest.Builder requestBuilder = ExecuteQueryRequest.newBuilder()
557+
.setDatabase(getName())
558+
.setQuery(query)
559+
.setLanguage(language)
557560
.setCredentials(buildCredentials());
558561

559562
if (transactionId != null) {
@@ -776,6 +779,7 @@ public ResultSet queryStream(final String language, final String query, final Re
776779
stats.queries.incrementAndGet();
777780

778781
StreamQueryRequest.Builder b = StreamQueryRequest.newBuilder().setDatabase(getName()).setQuery(query)
782+
.setLanguage(language)
779783
.setCredentials(buildCredentials())
780784
.setBatchSize(batchSize > 0 ? batchSize : 100).setRetrievalMode(mode);
781785

@@ -1763,6 +1767,7 @@ private static String langOrDefault(String language) {
17631767

17641768
private Iterator<Record> streamQuery(final String query) {
17651769
StreamQueryRequest request = StreamQueryRequest.newBuilder().setDatabase(getName()).setQuery(query)
1770+
.setLanguage("sql")
17661771
.setCredentials(buildCredentials())
17671772
.setBatchSize(100).build();
17681773

grpc/src/main/proto/arcadedb-server.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ message ExecuteQueryRequest {
286286
int32 timeout_ms = 7;
287287

288288
ProjectionSettings projectionSettings = 8;
289+
string language = 9; // "sql" if empty (default)
289290
}
290291

291292
message ExecuteQueryResponse {

grpcw/src/main/java/com/arcadedb/server/grpc/ArcadeDbGrpcService.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -818,9 +818,11 @@ public void executeQuery(ExecuteQueryRequest request, StreamObserver<ExecuteQuer
818818
// Execute the query
819819
long startTime = System.currentTimeMillis();
820820

821-
LogManager.instance().log(this, Level.FINE, "executeQuery(): query = %s", request.getQuery());
821+
final String language = (request.getLanguage() == null || request.getLanguage().isEmpty()) ? "sql" : request.getLanguage();
822822

823-
ResultSet resultSet = database.query("sql", request.getQuery(),
823+
LogManager.instance().log(this, Level.FINE, "executeQuery(): language = %s query = %s", language, request.getQuery());
824+
825+
ResultSet resultSet = database.query(language, request.getQuery(),
824826
GrpcTypeConverter.convertParameters(request.getParametersMap()));
825827

826828
LogManager.instance()
@@ -1105,12 +1107,14 @@ public void streamQuery(StreamQueryRequest request, StreamObserver<QueryResult>
11051107
beganHere = true;
11061108
}
11071109

1110+
final String language = (request.getLanguage() == null || request.getLanguage().isEmpty()) ? "sql" : request.getLanguage();
1111+
11081112
// --- Dispatch on mode (helpers do NOT manage transactions) ---
11091113
switch (request.getRetrievalMode()) {
1110-
case MATERIALIZE_ALL -> streamMaterialized(db, request, batchSize, scso, cancelled, projectionConfig);
1111-
case PAGED -> streamPaged(db, request, batchSize, scso, cancelled, projectionConfig);
1112-
case CURSOR -> streamCursor(db, request, batchSize, scso, cancelled, projectionConfig);
1113-
default -> streamCursor(db, request, batchSize, scso, cancelled, projectionConfig);
1114+
case MATERIALIZE_ALL -> streamMaterialized(db, request, batchSize, scso, cancelled, projectionConfig, language);
1115+
case PAGED -> streamPaged(db, request, batchSize, scso, cancelled, projectionConfig, language);
1116+
case CURSOR -> streamCursor(db, request, batchSize, scso, cancelled, projectionConfig, language);
1117+
default -> streamCursor(db, request, batchSize, scso, cancelled, projectionConfig, language);
11141118
}
11151119

11161120
// If the client cancelled mid-stream, choose rollback unless caller explicitly
@@ -1171,14 +1175,14 @@ public void streamQuery(StreamQueryRequest request, StreamObserver<QueryResult>
11711175
*/
11721176
private void streamCursor(Database db, StreamQueryRequest request, int batchSize,
11731177
ServerCallStreamObserver<QueryResult> scso,
1174-
AtomicBoolean cancelled, ProjectionConfig projectionConfig) {
1178+
AtomicBoolean cancelled, ProjectionConfig projectionConfig, String language) {
11751179

11761180
long running = 0L;
11771181

11781182
QueryResult.Builder batch = QueryResult.newBuilder();
11791183
int inBatch = 0;
11801184

1181-
try (ResultSet rs = db.query("sql", request.getQuery(),
1185+
try (ResultSet rs = db.query(language, request.getQuery(),
11821186
GrpcTypeConverter.convertParameters(request.getParametersMap()))) {
11831187

11841188
while (rs.hasNext()) {
@@ -1242,11 +1246,11 @@ private void streamCursor(Database db, StreamQueryRequest request, int batchSize
12421246
*/
12431247
private void streamMaterialized(Database db, StreamQueryRequest request, int batchSize,
12441248
ServerCallStreamObserver<QueryResult> scso,
1245-
AtomicBoolean cancelled, ProjectionConfig projectionConfig) {
1249+
AtomicBoolean cancelled, ProjectionConfig projectionConfig, String language) {
12461250

12471251
final List<GrpcRecord> all = new ArrayList<>();
12481252

1249-
try (ResultSet rs = db.query("sql", request.getQuery(),
1253+
try (ResultSet rs = db.query(language, request.getQuery(),
12501254
GrpcTypeConverter.convertParameters(request.getParametersMap()))) {
12511255

12521256
while (rs.hasNext()) {
@@ -1295,7 +1299,7 @@ private void streamMaterialized(Database db, StreamQueryRequest request, int bat
12951299
*/
12961300
private void streamPaged(Database db, StreamQueryRequest request, int batchSize,
12971301
ServerCallStreamObserver<QueryResult> scso,
1298-
AtomicBoolean cancelled, ProjectionConfig projectionConfig) {
1302+
AtomicBoolean cancelled, ProjectionConfig projectionConfig, String language) {
12991303

13001304
final String pagedSql = wrapWithSkipLimit(request.getQuery()); // see helper below
13011305
int offset = 0;
@@ -1313,7 +1317,7 @@ private void streamPaged(Database db, StreamQueryRequest request, int batchSize,
13131317
int count = 0;
13141318
QueryResult.Builder b = QueryResult.newBuilder();
13151319

1316-
try (ResultSet rs = db.query("sql", pagedSql, params)) {
1320+
try (ResultSet rs = db.query(language, pagedSql, params)) {
13171321
while (rs.hasNext()) {
13181322
if (cancelled.get())
13191323
return;

0 commit comments

Comments
 (0)