Skip to content
Open
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
5 changes: 4 additions & 1 deletion driver/src/main/java/oracle/nosql/driver/Nson.java
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,10 @@ public static String toJsonString(byte[] nson, JsonOptions options) {
if (nson == null) {
return null;
}
JsonSerializer jsonSerializer = new JsonSerializer(options);
JsonSerializer jsonSerializer =
(options != null && options.getPrettyPrint()) ?
new JsonPrettySerializer(options) :
new JsonSerializer(options);
try (NettyByteInputStream bis =
NettyByteInputStream.createFromBytes(nson)) {
Nson.generateEventsFromNson(jsonSerializer, bis, false);
Expand Down
97 changes: 87 additions & 10 deletions driver/src/main/java/oracle/nosql/driver/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -229,6 +232,9 @@ public class Client {

private volatile TopologyInfo topology;

/* Per store topology */
private final Map<String, TopologyInfo> storeTopologies;

/* for internal testing */
private final String prepareFilename;

Expand Down Expand Up @@ -300,6 +306,7 @@ public Client(Logger logger,
}

oneTimeMessages = new HashSet<String>();
storeTopologies = new ConcurrentHashMap<String, TopologyInfo>();
statsControl = new StatsControlImpl(config,
logger, httpClient, httpConfig.getRateLimitingEnabled());

Expand Down Expand Up @@ -360,6 +367,8 @@ public void shutdown() {
if (threadPool != null) {
threadPool.shutdown();
}
topology = null;
storeTopologies.clear();
}

public int getAcquiredChannelCount() {
Expand Down Expand Up @@ -441,9 +450,11 @@ public Result execute(Request kvRequest) {

QueryRequest qreq = (QueryRequest)kvRequest;

/* Set the topo seq num in the request, if it has not been set
* already */
kvRequest.setTopoSeqNum(getTopoSeqNum());
/*
* Stamp the query request with the cached legacy topology sequence
* and the per-store topology sequences.
*/
setRequestTopology(kvRequest);

statsControl.observeQuery(qreq);

Expand Down Expand Up @@ -655,11 +666,15 @@ public Result execute(Request kvRequest) {
*/
kvRequest.setCheckRequestSize(false);

/* Set the topo seq num in the request, if it has not been set
* already */
/*
* Stamp non-query and user query requests with the legacy
* topology sequence and the per-store topology sequences
* before serialization. The request preserves any topology
* captured earlier for a query execution.
*/
if (!(kvRequest instanceof QueryRequest) ||
kvRequest.isQueryRequest()) {
kvRequest.setTopoSeqNum(getTopoSeqNum());
setRequestTopology(kvRequest);
}

/*
Expand Down Expand Up @@ -776,7 +791,8 @@ public Result execute(Request kvRequest) {
long networkLatency =
(System.nanoTime() - latencyNanos) / 1_000_000;

setTopology(res.getTopology());
/* Update cached legacy or per-store topology from the response. */
updateCachedTopology(res);

if (serialVersionUsed < 3) {
/* so we can emit a one-time message if the app */
Expand Down Expand Up @@ -1189,8 +1205,8 @@ public boolean updateRateLimiters(String tableName, TableLimits limits) {
int durationSeconds = Integer.getInteger("test.rldurationsecs", 30)
.intValue();

double RUs = (double)limits.getReadUnits();
double WUs = (double)limits.getWriteUnits();
double RUs = limits.getReadUnits();
double WUs = limits.getWriteUnits();

/* if there's a specified rate limiter percentage, use that */
double rlPercent = config.getDefaultRateLimitingPercentage();
Expand Down Expand Up @@ -1412,7 +1428,6 @@ private void setEnabledFeatures(HttpHeaders headers) {
if (space <= 0) {
space = v.length();
}
long val = 0;
try {
this.features = Long.parseLong(v, eq+1, space, 16);
} catch (Exception e) {
Expand Down Expand Up @@ -2042,6 +2057,68 @@ public TopologyInfo getTopology() {
return topology;
}

/**
* @hidden
* Returns an immutable snapshot of the per-store topology cache.
*/
public Map<String, TopologyInfo> getStoreTopoSnapshot() {
return Collections.unmodifiableMap(new HashMap<>(storeTopologies));
}

/*
* Sets both the legacy topology sequence and a snapshot of the per-store
* topology sequences on the request before serialization. Both are set
* because the client cannot determine whether the proxy supports per-store
* topology sequences.
*/
private void setRequestTopology(Request kvRequest) {
/* legacy topology sequence */
kvRequest.setTopoSeqNum(getTopoSeqNum());

/* per store topology sequence */
Map<String, TopologyInfo> topos = getStoreTopoSnapshot();
Map<String, Integer> seqNums = new HashMap<>();
for (Map.Entry<String, TopologyInfo> e : topos.entrySet()) {
seqNums.put(e.getKey(), e.getValue().getSeqNum());
}
kvRequest.setStoreTopoSeqNums(seqNums);
}

/*
* Updates the cached topology information:
* - Updates the per-store cache from a response that carries per-store
* topology information, retaining a newer cached sequence for each
* store.
* - Responses without per-store information update the legacy topology.
*/
private void updateCachedTopology(Result ret) {
if (ret.getStoreTopologies() != null) {
setPerStoreTopology(ret.getStoreTopologies());
return;
}

setTopology(ret.getTopology());
}

private void setPerStoreTopology(List<TopologyInfo> storeTopos) {

if (storeTopos == null) {
return;
}

for (TopologyInfo topo : storeTopos) {
storeTopologies.compute(
topo.getStoreName(), (storeName, currentTopo) -> {
if (currentTopo == null ||
currentTopo.getSeqNum() < topo.getSeqNum()) {
trace("New store topology: " + topo, 1);
return topo;
}
return currentTopo;
});
}
}

private synchronized int getTopoSeqNum() {
return (topology == null ? -1 : topology.getSeqNum());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public class PreparedStatement {
*/
private final ArrayList<String> topTableNames;

/*
* The store names returned from a prepared query result. When present,
* this is branch-aligned with proxyStatements. Null means legacy plan
* metadata without per-store branch identity.
*/
private final ArrayList<String> branchStoreNames;

/*
* the operation code for the query.
*/
Expand Down Expand Up @@ -136,6 +143,8 @@ public class PreparedStatement {
* @param operation operation code for the query
* @param maxParallelism the maximum degree of parallelism possible for the
* query
* @param branchStoreNames optional store names aligned with each proxy
* statement. Null means legacy plan metadata.
* @hidden
*/
public PreparedStatement(
Expand All @@ -150,7 +159,8 @@ public PreparedStatement(
ArrayList<String> namespaces,
ArrayList<String> tableNames,
byte operation,
int maxParallelism) {
int maxParallelism,
ArrayList<String> branchStoreNames) {

if (proxyStatements == null || proxyStatements.isEmpty()) {
throw new IllegalArgumentException(
Expand All @@ -169,6 +179,7 @@ public PreparedStatement(
this.topTableNames = tableNames;
this.operation = operation;
this.maxParallelism = maxParallelism;
this.branchStoreNames = branchStoreNames;
}

/**
Expand All @@ -191,7 +202,8 @@ public PreparedStatement copyStatement() {
namespaces,
topTableNames,
operation,
maxParallelism);
maxParallelism,
branchStoreNames);
}

/**
Expand Down Expand Up @@ -330,6 +342,15 @@ public final byte[] getProxyStatement(int branch) {
return proxyStatements.get(branch).clone();
}

/**
* Internal use only
* @return the number of proxy-side query branches in this statement
* @hidden
*/
public int getNumBranches() {
return proxyStatements.size();
}

private static ArrayList<byte[]> copyProxyStatements(
ArrayList<byte[]> source) {

Expand Down Expand Up @@ -434,6 +455,18 @@ public String getTopTableName(int branch) {
return topTableNames.get(branch);
}

/**
* Internal use only
* @return store name from prepared statement if any
* @hidden
*/
public String getBranchStoreName(int branch) {
if (branchStoreNames == null) {
return null;
}
return branchStoreNames.get(branch);
}

/**
* Internal use only
* @return true if the query does writes
Expand Down
25 changes: 24 additions & 1 deletion driver/src/main/java/oracle/nosql/driver/ops/QueryRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ public class QueryRequest extends DurableRequest implements AutoCloseable {
public QueryRequest() {
}

/**
* Updates this request's topology with the snapshot selected for an
* advanced query execution. This is called only after the
* {@link QueryDriver} has selected the base topologies.
*
* @param topoSeqNum the legacy topology sequence number, or {@code -1}
* if no legacy topology is available
* @param storeTopoSeqNums the per-store topology sequences
* @hidden
*/
public void setExecutionTopology(int topoSeqNum,
Map<String, Integer> storeTopoSeqNums){

this.topoSeqNum = topoSeqNum;
this.storeTopoSeqNums = storeTopoSeqNums;
}

/**
* Creates an internal QueryRequest out of the application-provided request.
* @return a copy of the instance in a new object
Expand All @@ -233,6 +250,7 @@ public QueryRequest copyInternal() {
internalReq.isInternal = true;
internalReq.driver = driver;
internalReq.topoSeqNum = topoSeqNum;
internalReq.storeTopoSeqNums = storeTopoSeqNums;
internalReq.inTestMode = inTestMode;
internalReq.operationNumber = operationNumber;
internalReq.numberOfOperations = numberOfOperations;
Expand Down Expand Up @@ -1147,7 +1165,7 @@ public void validate() {
}
/*
* Parallel queries have multiple requirements:
* o only for prepared queries
* o only for simple prepared queries
* o if set, both number of operations and op number need to be set
* o operation number must be <= number of operations
* o number of operations must be <= the max
Expand All @@ -1159,6 +1177,11 @@ public void validate() {
throw new IllegalArgumentException(
"Parallel queries are only allowed on prepared queries");
}
if (!isSimpleQuery()) {
throw new IllegalArgumentException(
"Parallel queries are only allowed on simple " +
"prepared queries");
}
/* check both non-zero and value of operation number */
if (getOperationNumber() > getNumberOfOperations() ||
getOperationNumber() <= 0) {
Expand Down
31 changes: 31 additions & 0 deletions driver/src/main/java/oracle/nosql/driver/ops/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import static oracle.nosql.driver.util.HttpHeaderValidation.validateHttpHeaderValue;

import java.util.Map;

import oracle.nosql.driver.NoSQLHandleConfig;
import oracle.nosql.driver.RateLimiter;
import oracle.nosql.driver.ops.serde.Serializer;
Expand Down Expand Up @@ -85,6 +87,14 @@ public abstract class Request {

protected int topoSeqNum = -1;

/**
* @hidden
* Per-store topology sequences serialized as request header h.sts. The
* client initializes this map before sending a request. An empty map
* advertises per-store topology support with no cached store topology.
*/
protected Map<String, Integer> storeTopoSeqNums;

/**
* @hidden
* This is only required by Java SDK for internal cross-region request, not
Expand Down Expand Up @@ -535,6 +545,27 @@ public void setTopoSeqNum(int n) {
}
}

/**
* internal use only
* @return the per-store topology sequences for request
* @hidden
*/
public Map<String, Integer> getStoreTopoSeqNums() {
return storeTopoSeqNums;
}

/**
* internal use only
* @param topoSeqNums the per-store topology sequences
* @hidden
*/
public void setStoreTopoSeqNums(Map<String, Integer> topoSeqNums) {
if (storeTopoSeqNums != null) {
return;
}
storeTopoSeqNums = topoSeqNums;
}

/**
* Returns the type name of the request. This is used for stats.
*
Expand Down
Loading