Skip to content
Draft
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/),

## [Unreleased]

**Added**

- feat: add `runDQL()` methods to `DgraphClient` and `DgraphAsyncClient` for direct DQL query
execution
- feat: add `allocateUIDs()` method to `DgraphClient` and `DgraphAsyncClient` for UID allocation
- feat: add `allocateTimestamps()` method to `DgraphClient` and `DgraphAsyncClient` for timestamp
allocation
- feat: add `allocateNamespaces()` method to `DgraphClient` and `DgraphAsyncClient` for namespace
allocation
- feat: add `createNamespace()` method to `DgraphClient` and `DgraphAsyncClient` for namespace
creation
- feat: add `dropNamespace()` method to `DgraphClient` and `DgraphAsyncClient` for namespace
deletion
- feat: add `listNamespaces()` method to `DgraphClient` and `DgraphAsyncClient` for listing all
namespaces
- feat: add namespace parameter support in connection strings and ClientOptions
- feat: add `withNamespace()` method to `ClientOptions` for programmatic namespace configuration

## [24.2.0] - 2025-04-21

**Added**
Expand Down Expand Up @@ -66,7 +86,7 @@
[#193]: https://github.com/hypermodeinc/dgraph4j/pull/193
[#182]: https://github.com/hypermodeinc/dgraph4j/pull/182

## [Unreleased]

Check notice on line 89 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Trunk Check

markdownlint(MD024)

[new] Multiple headings with the same content

## [21.12.0] - 2021-12-24

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ Valid connection string args:
| apikey | \<key\> | a Dgraph Cloud API Key |
| bearertoken | \<token\> | an access token |
| sslmode | disable \| require \| verify-ca | TLS option, the default is `disable`. If `verify-ca` is set, the TLS certificate configured in the Dgraph cluster must be from a valid certificate authority. |
| namespace | \<namespace\> | a previously created integer-based namespace, username and password must be supplied |

Note that using `sslmode=require` disables certificate validation and significantly reduces the
security of TLS. This mode should only be used in non-production (e.g., testing or development)
Expand All @@ -191,6 +192,7 @@ Some example connection strings:
| dgraph://sally:[email protected]:443?sslmode=verify-ca | Connect to remote server, use ACL and require TLS and a valid certificate from a CA |
| dgraph://foo-bar.grpc.us-west-2.aws.cloud.dgraph.io:443?sslmode=verify-ca&apikey=\<your-api-connection-key\> | Connect to a Dgraph Cloud cluster |
| dgraph://foo-bar.grpc.hypermode.com?sslmode=verify-ca&bearertoken=\<some access token\> | Connect to a Dgraph cluster protected by a secure gateway |
| dgraph://sally:[email protected]:443?namespace=2 | Connect to a ACL enabled Dgraph cluster in namespace 2 |

Using the `DgraphClient.open` function with a connection string:

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ apply plugin: 'idea'
apply plugin: 'signing'

group = 'io.dgraph'
version = '24.2.0'
version = '25.0.0'

base {
archivesName = 'dgraph4j'
Expand Down
188 changes: 185 additions & 3 deletions src/main/java/io/dgraph/DgraphAsyncClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@
import static java.util.Arrays.asList;

import com.google.protobuf.InvalidProtocolBufferException;
import io.dgraph.DgraphProto.AllocateIDsRequest;
import io.dgraph.DgraphProto.AllocateIDsResponse;
import io.dgraph.DgraphProto.CreateNamespaceRequest;
import io.dgraph.DgraphProto.CreateNamespaceResponse;
import io.dgraph.DgraphProto.DropNamespaceRequest;
import io.dgraph.DgraphProto.DropNamespaceResponse;
import io.dgraph.DgraphProto.LeaseType;
import io.dgraph.DgraphProto.ListNamespacesRequest;
import io.dgraph.DgraphProto.ListNamespacesResponse;
import io.dgraph.DgraphProto.Payload;
import io.dgraph.DgraphProto.Response;
import io.dgraph.DgraphProto.RunDQLRequest;
import io.dgraph.DgraphProto.TxnContext;
import io.dgraph.DgraphProto.Version;
import io.grpc.Channel;
Expand All @@ -19,6 +30,7 @@
import io.grpc.StatusRuntimeException;
import io.grpc.stub.MetadataUtils;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
Expand All @@ -41,6 +53,7 @@ public class DgraphAsyncClient {
private final Executor executor;
private final ReadWriteLock jwtLock;
private DgraphProto.Jwt jwt;
private long currentNamespace = 0L; // Default namespace

/**
* Creates a new client for interacting with a Dgraph store.
Expand Down Expand Up @@ -101,6 +114,7 @@ public CompletableFuture<Void> loginIntoNamespace(
Lock wlock = jwtLock.writeLock();
wlock.lock();
try {
this.currentNamespace = namespace; // Track the current namespace
final DgraphGrpc.DgraphStub client = anyClient();
final DgraphProto.LoginRequest loginRequest =
DgraphProto.LoginRequest.newBuilder()
Expand Down Expand Up @@ -173,14 +187,19 @@ protected DgraphGrpc.DgraphStub getStubWithJwt(DgraphGrpc.DgraphStub stub) {
Lock readLock = jwtLock.readLock();
readLock.lock();
try {
Metadata metadata = new Metadata();

// Add JWT token if available
if (jwt != null && !jwt.getAccessJwt().isEmpty()) {
Metadata metadata = new Metadata();
metadata.put(
Metadata.Key.of("accessJwt", Metadata.ASCII_STRING_MARSHALLER), jwt.getAccessJwt());
return stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
}

return stub;
// Add namespace metadata (required for v25 methods like runDQL)
metadata.put(
Metadata.Key.of("namespace", Metadata.ASCII_STRING_MARSHALLER),
String.valueOf(currentNamespace));
return stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
} finally {
readLock.unlock();
}
Expand Down Expand Up @@ -293,6 +312,169 @@ public CompletableFuture<Version> checkVersion() {
});
}

/**
* runDQL executes a DQL query or mutation.
*
* @param dqlQuery the DQL query string to execute
* @param vars variables to substitute in the query
* @param readOnly whether this is a read-only query
* @param bestEffort whether to use best effort for read queries
* @param respFormat response format (JSON or RDF)
* @return A CompletableFuture containing the Response from the query
*/
public CompletableFuture<Response> runDQL(
String dqlQuery,
Map<String, String> vars,
boolean readOnly,
boolean bestEffort,
DgraphProto.Request.RespFormat respFormat) {
final DgraphGrpc.DgraphStub stub = anyClient();
final RunDQLRequest.Builder requestBuilder = RunDQLRequest.newBuilder()
.setDqlQuery(dqlQuery)
.setReadOnly(readOnly)
.setBestEffort(bestEffort)
.setRespFormat(respFormat);

if (vars != null) {
requestBuilder.putAllVars(vars);
}

final RunDQLRequest request = requestBuilder.build();

return runWithRetries(
"runDQL",
() -> {
StreamObserverBridge<Response> observerBridge = new StreamObserverBridge<>();
DgraphGrpc.DgraphStub localStub = getStubWithJwt(stub);
localStub.runDQL(request, observerBridge);
return observerBridge.getDelegate();
});
}

/**
* allocateUIDs allocates a given number of Node UIDs in the Graph and returns a start and end UIDs,
* end excluded. The UIDs in the range [start, end) can then be used by the client in the mutations
* going forward.
*
* @param howMany number of UIDs to allocate
* @return A CompletableFuture containing the AllocateIDsResponse with start and end UIDs
*/
public CompletableFuture<AllocateIDsResponse> allocateUIDs(long howMany) {
return allocateIDs(howMany, LeaseType.UID);
}

/**
* allocateTimestamps gets a sequence of timestamps allocated from Dgraph. These timestamps can be
* used in bulk loader and similar applications.
*
* @param howMany number of timestamps to allocate
* @return A CompletableFuture containing the AllocateIDsResponse with start and end timestamps
*/
public CompletableFuture<AllocateIDsResponse> allocateTimestamps(long howMany) {
return allocateIDs(howMany, LeaseType.TS);
}

/**
* allocateNamespaces allocates a given number of namespaces in the Graph and returns a start and end
* namespaces, end excluded. The namespaces in the range [start, end) can then be used by the client.
*
* @param howMany number of namespaces to allocate
* @return A CompletableFuture containing the AllocateIDsResponse with start and end namespaces
*/
public CompletableFuture<AllocateIDsResponse> allocateNamespaces(long howMany) {
return allocateIDs(howMany, LeaseType.NS);
}

/**
* Helper method to allocate IDs of different types (UIDs, timestamps, namespaces).
*
* @param howMany number of IDs to allocate
* @param leaseType type of lease (UID, TS, or NS)
* @return A CompletableFuture containing the AllocateIDsResponse
*/
private CompletableFuture<AllocateIDsResponse> allocateIDs(long howMany, LeaseType leaseType) {
if (howMany <= 0) {
CompletableFuture<AllocateIDsResponse> future = new CompletableFuture<>();
future.completeExceptionally(new IllegalArgumentException("howMany must be greater than 0"));
return future;
}

final DgraphGrpc.DgraphStub stub = anyClient();
final AllocateIDsRequest request = AllocateIDsRequest.newBuilder()
.setHowMany(howMany)
.setLeaseType(leaseType)
.build();

return runWithRetries(
"allocateIDs",
() -> {
StreamObserverBridge<AllocateIDsResponse> observerBridge = new StreamObserverBridge<>();
DgraphGrpc.DgraphStub localStub = getStubWithJwt(stub);
localStub.allocateIDs(request, observerBridge);
return observerBridge.getDelegate();
});
}

/**
* createNamespace creates a new namespace and returns its ID.
*
* @return A CompletableFuture containing the CreateNamespaceResponse with the new namespace ID
*/
public CompletableFuture<CreateNamespaceResponse> createNamespace() {
final DgraphGrpc.DgraphStub stub = anyClient();
final CreateNamespaceRequest request = CreateNamespaceRequest.newBuilder().build();

return runWithRetries(
"createNamespace",
() -> {
StreamObserverBridge<CreateNamespaceResponse> observerBridge = new StreamObserverBridge<>();
DgraphGrpc.DgraphStub localStub = getStubWithJwt(stub);
localStub.createNamespace(request, observerBridge);
return observerBridge.getDelegate();
});
}

/**
* dropNamespace drops the specified namespace. If the namespace does not exist, the request will still succeed.
*
* @param namespace the ID of the namespace to drop
* @return A CompletableFuture containing the DropNamespaceResponse
*/
public CompletableFuture<DropNamespaceResponse> dropNamespace(long namespace) {
final DgraphGrpc.DgraphStub stub = anyClient();
final DropNamespaceRequest request = DropNamespaceRequest.newBuilder()
.setNamespace(namespace)
.build();

return runWithRetries(
"dropNamespace",
() -> {
StreamObserverBridge<DropNamespaceResponse> observerBridge = new StreamObserverBridge<>();
DgraphGrpc.DgraphStub localStub = getStubWithJwt(stub);
localStub.dropNamespace(request, observerBridge);
return observerBridge.getDelegate();
});
}

/**
* listNamespaces lists all namespaces.
*
* @return A CompletableFuture containing the ListNamespacesResponse with all namespaces
*/
public CompletableFuture<ListNamespacesResponse> listNamespaces() {
final DgraphGrpc.DgraphStub stub = anyClient();
final ListNamespacesRequest request = ListNamespacesRequest.newBuilder().build();

return runWithRetries(
"listNamespaces",
() -> {
StreamObserverBridge<ListNamespacesResponse> observerBridge = new StreamObserverBridge<>();
DgraphGrpc.DgraphStub localStub = getStubWithJwt(stub);
localStub.listNamespaces(request, observerBridge);
return observerBridge.getDelegate();
});
}

private DgraphGrpc.DgraphStub anyClient() {
int index = ThreadLocalRandom.current().nextInt(stubs.size());
DgraphGrpc.DgraphStub rawStub = stubs.get(index);
Expand Down
Loading
Loading