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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
81 changes: 41 additions & 40 deletions bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -118,13 +119,13 @@ private enum State {
INTERRUPTED
}

private final ArcadeDBServer server;
private volatile Socket socket; // Reassigned to the SSLSocket once TLS negotiation completes
private final BoltSslHelper sslHelper;
private BoltChunkedInput input;
private BoltChunkedOutput output;
private final boolean debug;
private final BoltNetworkListener listener; // For notifying when connection closes
private final ArcadeDBServer server;
private volatile Socket socket; // Reassigned to the SSLSocket once TLS negotiation completes
private final BoltSslHelper sslHelper;
private BoltChunkedInput input;
private BoltChunkedOutput output;
private final boolean debug;
private final BoltNetworkListener listener; // For notifying when connection closes

private State state = State.DISCONNECTED;
private int protocolVersion;
Expand All @@ -143,14 +144,14 @@ private enum State {
* Thread-safety: This class is designed to handle a single connection in a dedicated thread.
* All state variables are accessed only by the executor thread and do not require synchronization.
*/
private ResultSet currentResultSet;
private List<String> currentFields;
private Result firstResult; // Buffered first result for field name extraction
private List<List<Object>> syntheticResults; // For system queries that return synthetic data
private int recordsStreamed;
private long queryStartTime; // Nanosecond timestamp when query execution started
private long firstRecordTime; // Nanosecond timestamp when first record was retrieved
private boolean isWriteOperation; // Whether the current query performs writes
private ResultSet currentResultSet;
private List<String> currentFields;
private Result firstResult; // Buffered first result for field name extraction
private List<List<Object>> syntheticResults; // For system queries that return synthetic data
private int recordsStreamed;
private long queryStartTime; // Nanosecond timestamp when query execution started
private long firstRecordTime; // Nanosecond timestamp when first record was retrieved
private boolean isWriteOperation; // Whether the current query performs writes
// EXPLAIN / PROFILE state, populated in handleRun, surfaced in handlePull SUCCESS metadata
// so Neo4j drivers can read it via ResultSummary#plan() / #profile().
private Map<String, Object> currentPlanMetadata;
Expand Down Expand Up @@ -284,8 +285,8 @@ private boolean negotiateTransport() {
} else if (sslHelper.getTlsMode() == BoltSslHelper.TlsMode.REQUIRED) {
LogManager.instance().log(this, Level.WARNING,
"""
BOLT rejecting non-TLS connection from %s (TLS is REQUIRED). \
Configure the client to use bolt+s:// or bolt+ssc://""",
BOLT rejecting non-TLS connection from %s (TLS is REQUIRED). \
Configure the client to use bolt+s:// or bolt+ssc://""",
socket.getRemoteSocketAddress());
return false;
} else {
Expand Down Expand Up @@ -505,9 +506,9 @@ private void handleHello(final HelloMessage message) throws IOException {
* Insertion order (server first, then connection_id) is significant for wire equality.
*/
private Map<String, Object> buildHelloSuccessMetadata() {
final Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("server", "Neo4j/5.26.0 compatible (ArcadeDB " + Constants.getRawVersion() + ")");
metadata.put("connection_id", "bolt-" + Thread.currentThread().threadId());
final Map<String, Object> metadata = Map.of(
"server", "Neo4j/5.26.0 compatible (ArcadeDB " + Constants.getRawVersion() + ")",
"connection_id", "bolt-" + Thread.currentThread().threadId());
Comment on lines +509 to +511

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The comment explicitly states that the insertion order of the metadata is significant for wire equality. Replacing the LinkedHashMap with Map.of() breaks this guarantee because Map.of() has a randomized iteration order in Java. This will cause random failures in wire equality checks.

Suggested change
final Map<String, Object> metadata = Map.of(
"server", "Neo4j/5.26.0 compatible (ArcadeDB " + Constants.getRawVersion() + ")",
"connection_id", "bolt-" + Thread.currentThread().threadId());
final Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("server", "Neo4j/5.26.0 compatible (ArcadeDB " + Constants.getRawVersion() + ")");
metadata.put("connection_id", "bolt-" + Thread.currentThread().threadId());

return metadata;
}

Expand Down Expand Up @@ -638,9 +639,9 @@ private void handleRun(final RunMessage message) throws IOException {
recordsStreamed = 0;
isWriteOperation = false;

final Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("fields", currentFields);
metadata.put("t_first", 0L);
final Map<String, Object> metadata = Map.of(
"fields", currentFields,
"t_first", 0L);
sendSuccess(metadata);
state = explicitTransaction ? State.TX_STREAMING : State.STREAMING;
return;
Expand Down Expand Up @@ -746,7 +747,7 @@ private void handlePull(final PullMessage message) throws IOException {
// Handle synthetic results (from system queries)
if (syntheticResults != null) {
while (!syntheticResults.isEmpty() && (n < 0 || count < n)) {
sendRecord(syntheticResults.remove(0));
sendRecord(syntheticResults.removeFirst());
count++;
recordsStreamed++;
}
Expand Down Expand Up @@ -1014,9 +1015,9 @@ private void handleRoute(final RouteMessage message) throws IOException {
return;
}

final Map<String, Object> rt = new LinkedHashMap<>();
rt.put("ttl", GlobalConfiguration.BOLT_ROUTING_TTL.getValueAsLong());
rt.put("db", message.getDatabase() != null ? message.getDatabase() : databaseName);
final Map<String, Object> rt = Map.of(
"ttl", GlobalConfiguration.BOLT_ROUTING_TTL.getValueAsLong(),
"db", message.getDatabase() != null ? message.getDatabase() : databaseName);

final List<Map<String, Object>> servers = new ArrayList<>();

Expand Down Expand Up @@ -1065,9 +1066,9 @@ private void handleRoute(final RouteMessage message) throws IOException {
* a Bolt routing role (WRITE, READ, or ROUTE).
*/
private static Map<String, Object> roleEntry(final List<String> addresses, final String role) {
final Map<String, Object> entry = new LinkedHashMap<>();
entry.put("addresses", addresses);
entry.put("role", role);
final Map<String, Object> entry = Map.of(
"addresses", addresses,
"role", role);
return entry;
}

Expand Down Expand Up @@ -1176,11 +1177,11 @@ private boolean handleSystemQuery(final String query) throws IOException {
// SHOW CURRENT USER or CALL dbms.showCurrentUser()
currentFields = List.of("user", "roles", "passwordChangeRequired", "suspended", "home");
syntheticResults = new ArrayList<>();
final List<Object> userRecord = new ArrayList<>();
userRecord.add(user != null ? user.getName() : "anonymous");
userRecord.add(List.of("admin"));
userRecord.add(false);
userRecord.add(false);
final List<Object> userRecord = new ArrayList<>(List.of(
user != null ? user.getName() : "anonymous",
List.of("admin"),
false,
false));
userRecord.add(null); // home database (null = use default)
syntheticResults.add(userRecord);
return true;
Expand Down Expand Up @@ -1661,11 +1662,11 @@ private Map<String, Object> buildPlanMetadata(final ResultSet resultSet, final b
}
}

final Map<String, Object> root = new LinkedHashMap<>();
root.put("operatorType", profileMode ? "ArcadeDB.OpenCypher.ProfilePlan" : "ArcadeDB.OpenCypher.Plan");
root.put("identifiers", currentFields != null ? currentFields : List.<String>of());
root.put("args", args);
root.put("children", List.<Map<String, Object>>of());
final Map<String, Object> root = new HashMap<>(Map.of(
"operatorType", profileMode ? "ArcadeDB.OpenCypher.ProfilePlan" : "ArcadeDB.OpenCypher.Plan",
"identifiers", currentFields != null ? currentFields : List.<String>of(),
"args", args,
"children", List.<Map<String, Object>>of()));

if (profileMode) {
// ProfilePlan inherits Plan and adds dbHits/rows/pageCacheHits/etc. We do not yet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@
import com.arcadedb.server.network.ServerSocketFactory;

import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.*;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
Expand Down
8 changes: 1 addition & 7 deletions bolt/src/main/java/com/arcadedb/bolt/BoltSslHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@
import com.arcadedb.server.http.ssl.SslUtils;
import com.arcadedb.server.http.ssl.TlsProtocol;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
Expand Down
14 changes: 7 additions & 7 deletions bolt/src/main/java/com/arcadedb/bolt/message/BoltMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ public static BoltMessage parse(final PackStreamReader.StructureValue structure)

@SuppressWarnings("unchecked")
private static HelloMessage parseHello(final List<Object> fields) {
final Map<String, Object> extra = fields.isEmpty() ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> extra = fields.isEmpty() ? Map.of() : (Map<String, Object>) fields.getFirst();
return new HelloMessage(extra);
}

@SuppressWarnings("unchecked")
private static RunMessage parseRun(final List<Object> fields) {
final String query = (String) fields.get(0);
final String query = (String) fields.getFirst();
// Hydrate temporal PackStream structures (Date/Time/DateTime/...) into java.time values so
// native temporal query parameters bind correctly instead of being dropped (issue #4905).
final Map<String, Object> parameters = fields.size() > 1 && fields.get(1) != null ?
Expand All @@ -117,31 +117,31 @@ private static RunMessage parseRun(final List<Object> fields) {

@SuppressWarnings("unchecked")
private static BeginMessage parseBegin(final List<Object> fields) {
final Map<String, Object> extra = fields.isEmpty() || fields.get(0) == null ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> extra = fields.isEmpty() || fields.getFirst() == null ? Map.of() : (Map<String, Object>) fields.getFirst();
return new BeginMessage(extra);
}

@SuppressWarnings("unchecked")
private static DiscardMessage parseDiscard(final List<Object> fields) {
final Map<String, Object> extra = fields.isEmpty() || fields.get(0) == null ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> extra = fields.isEmpty() || fields.getFirst() == null ? Map.of() : (Map<String, Object>) fields.getFirst();
return new DiscardMessage(extra);
}

@SuppressWarnings("unchecked")
private static PullMessage parsePull(final List<Object> fields) {
final Map<String, Object> extra = fields.isEmpty() || fields.get(0) == null ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> extra = fields.isEmpty() || fields.getFirst() == null ? Map.of() : (Map<String, Object>) fields.getFirst();
return new PullMessage(extra);
}

@SuppressWarnings("unchecked")
private static LogonMessage parseLogon(final List<Object> fields) {
final Map<String, Object> auth = fields.isEmpty() || fields.get(0) == null ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> auth = fields.isEmpty() || fields.getFirst() == null ? Map.of() : (Map<String, Object>) fields.getFirst();
return new LogonMessage(auth);
}

@SuppressWarnings("unchecked")
private static RouteMessage parseRoute(final List<Object> fields) {
final Map<String, Object> routing = fields.isEmpty() || fields.get(0) == null ? Map.of() : (Map<String, Object>) fields.get(0);
final Map<String, Object> routing = fields.isEmpty() || fields.getFirst() == null ? Map.of() : (Map<String, Object>) fields.getFirst();
final List<String> bookmarks = fields.size() > 1 && fields.get(1) != null ? (List<String>) fields.get(1) : List.of();
final Object thirdField = fields.size() > 2 ? fields.get(2) : null;
// Bolt <=4.3 sends db::String; Bolt 4.4+ sends extra::Map{db, imp_user} (issue #4916).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public String getCredentials() {

public String getRouting() {
final Object routing = extra.get("routing");
if (routing instanceof Map) {
final Object address = ((Map<?, ?>) routing).get("address");
if (routing instanceof Map<?, ?> map) {
final Object address = map.get("address");
return address != null ? address.toString() : null;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,22 +278,22 @@ public void writeStructureHeader(final byte signature, final int fieldCount) thr
public void writeValue(final Object value) throws IOException {
if (value == null) {
writeNull();
} else if (value instanceof Boolean) {
writeBoolean((Boolean) value);
} else if (value instanceof Boolean boolean1) {
writeBoolean(boolean1);
} else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) {
writeInteger(((Number) value).longValue());
} else if (value instanceof Float || value instanceof Double) {
writeFloat(((Number) value).doubleValue());
} else if (value instanceof String) {
writeString((String) value);
} else if (value instanceof byte[]) {
writeBytes((byte[]) value);
} else if (value instanceof List) {
writeList((List<?>) value);
} else if (value instanceof String string) {
writeString(string);
} else if (value instanceof byte[] bytes) {
writeBytes(bytes);
} else if (value instanceof List<?> list) {
writeList(list);
} else if (value instanceof Map) {
writeMap((Map<String, Object>) value);
} else if (value instanceof PackStreamStructure) {
((PackStreamStructure) value).writeTo(this);
} else if (value instanceof PackStreamStructure structure) {
structure.writeTo(this);
} else {
// Default: convert to string
writeString(value.toString());
Expand Down
Loading
Loading