Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
33 changes: 18 additions & 15 deletions bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,15 @@ public void run() {

final BoltMessage message = BoltMessage.parse(structure);
if (debug) {
LogManager.instance().log(this, Level.INFO, "BOLT << %s (state=%s)", message, state);
LogManager.instance().log(this, Level.FINE, "BOLT << %s (state=%s)", message, state);
}

processMessage(message);

} catch (final EOFException | SocketException e) {
// Client disconnected
if (debug) {
LogManager.instance().log(this, Level.INFO, "BOLT client disconnected: %s", e.getMessage());
LogManager.instance().log(this, Level.FINE, "BOLT client disconnected: %s", e.getMessage());
}
break;
} catch (final Exception e) {
Expand Down Expand Up @@ -220,7 +220,7 @@ private boolean performHandshake() throws IOException {
} catch (final EOFException e) {
// Client closed WebSocket without sending Bolt data (e.g. Neo4j Desktop health/SSO probe)
if (debug)
LogManager.instance().log(this, Level.INFO, "BOLT WebSocket closed without Bolt handshake from %s",
LogManager.instance().log(this, Level.FINE, "BOLT WebSocket closed without Bolt handshake from %s",
socket.getRemoteSocketAddress());
return false;
}
Expand Down Expand Up @@ -255,7 +255,7 @@ private boolean negotiateVersion() throws IOException {
clientVersions[i] = input.readRawInt();

if (debug)
LogManager.instance().log(this, Level.INFO, "BOLT client versions: %s",
LogManager.instance().log(this, Level.FINE, "BOLT client versions: %s",
Arrays.toString(Arrays.stream(clientVersions).mapToObj(v -> String.format("0x%08X", v)).toArray()));

// Select best matching version using Bolt version negotiation with range support.
Expand Down Expand Up @@ -291,8 +291,9 @@ private boolean negotiateVersion() throws IOException {
return false;
}

LogManager.instance().log(this, Level.INFO, "BOLT connection from %s, negotiated version %d.%d",
socket.getRemoteSocketAddress(), getMajorVersion(protocolVersion), getMinorVersion(protocolVersion));
if (debug)
LogManager.instance().log(this, Level.FINE, "BOLT connection from %s, negotiated version %d.%d",
socket.getRemoteSocketAddress(), getMajorVersion(protocolVersion), getMinorVersion(protocolVersion));

return true;
}
Expand Down Expand Up @@ -494,7 +495,7 @@ private void handleRun(final RunMessage message) throws IOException {
final Map<String, Object> params = message.getParameters();

if (debug)
LogManager.instance().log(this, Level.INFO, "BOLT executing: %s with params %s (db=%s)", query, params, databaseName);
LogManager.instance().log(this, Level.FINE, "BOLT executing: %s with params %s (db=%s)", query, params, databaseName);

// Start timing for performance metrics
queryStartTime = System.nanoTime();
Expand Down Expand Up @@ -534,7 +535,7 @@ private void handleRun(final RunMessage message) throws IOException {
recordsStreamed = 0;

if (debug) {
LogManager.instance().log(this, Level.INFO, "BOLT query fields=%s firstResult=%s", currentFields,
LogManager.instance().log(this, Level.FINE, "BOLT query fields=%s firstResult=%s", currentFields,
firstResult != null ? firstResult.toJSON() : "null");
}

Expand Down Expand Up @@ -1231,7 +1232,7 @@ private void sendRecord(final List<Object> data) throws IOException {
*/
private void sendMessage(final BoltMessage message) throws IOException {
if (debug) {
LogManager.instance().log(this, Level.INFO, "BOLT >> %s", message);
LogManager.instance().log(this, Level.FINE, "BOLT >> %s", message);
}

final PackStreamWriter writer = new PackStreamWriter();
Expand Down Expand Up @@ -1314,8 +1315,9 @@ private void completeWebSocketUpgrade(final Map<String, String> headers) throws
rawOut.write(response.toString().getBytes(StandardCharsets.UTF_8));
rawOut.flush();

LogManager.instance().log(this, Level.INFO, "BOLT WebSocket upgrade completed for %s (protocol=%s)",
socket.getRemoteSocketAddress(), protocol != null ? protocol : "none");
if (debug)
LogManager.instance().log(this, Level.FINE, "BOLT WebSocket upgrade completed for %s (protocol=%s)",
socket.getRemoteSocketAddress(), protocol != null ? protocol : "none");
}

/**
Expand Down Expand Up @@ -1351,9 +1353,10 @@ private void handleHttpOnBoltPort() throws IOException {

output.writeRaw(httpResponse.getBytes(StandardCharsets.UTF_8));

LogManager.instance().log(this, Level.INFO,
"HTTP request on BOLT port from %s, responded with Bolt endpoint info for %s",
socket.getRemoteSocketAddress(), address);
if (debug)
LogManager.instance().log(this, Level.FINE,
"HTTP request on BOLT port from %s, responded with Bolt endpoint info for %s",
socket.getRemoteSocketAddress(), address);
}

/**
Expand Down Expand Up @@ -1407,7 +1410,7 @@ private void cleanup() {
}

if (debug) {
LogManager.instance().log(this, Level.INFO, "BOLT connection closed");
LogManager.instance().log(this, Level.FINE, "BOLT connection closed");
}
}

Expand Down
33 changes: 33 additions & 0 deletions engine/src/main/java/com/arcadedb/query/OperationType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com)
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.query;

/**
* Classifies the type of operations a query or command performs.
* Used for semantic permission checking (e.g., MCP server authorization).
* A single query may involve multiple operation types (e.g., UPSERT = CREATE + UPDATE).
*/
public enum OperationType {
READ,
CREATE,
UPDATE,
DELETE,
SCHEMA,
ADMIN
}
26 changes: 26 additions & 0 deletions engine/src/main/java/com/arcadedb/query/QueryEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.arcadedb.ContextConfiguration;
import com.arcadedb.database.DatabaseInternal;
import com.arcadedb.query.sql.executor.ResultSet;
import com.arcadedb.utility.CollectionUtils;
import com.arcadedb.utility.ExcludeFromJacocoGeneratedReport;

import java.util.*;
Expand All @@ -31,6 +32,31 @@ interface AnalyzedQuery {
boolean isIdempotent();

boolean isDDL();

/**
* Returns the set of operation types this query performs.
* Provides semantic, parser-based classification of query operations
* for fine-grained permission checking.
*
* @return a non-empty set of {@link OperationType} values
*/
default Set<OperationType> getOperationTypes() {
if (isDDL())
return CollectionUtils.singletonSet(OperationType.SCHEMA);
if (isIdempotent())
return CollectionUtils.singletonSet(OperationType.READ);
// Fallback: non-idempotent, non-DDL commands that don't override this method
return Set.of(OperationType.CREATE, OperationType.UPDATE, OperationType.DELETE);
}

/**
* Executes this analyzed query, reusing the already-parsed AST to avoid double parsing.
* Returns null if direct execution is not supported, in which case the caller should
* fall back to the standard query/command methods.
*/
default ResultSet execute(final Map<String, Object> parameters) {
return null;
}
}

@ExcludeFromJacocoGeneratedReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import com.arcadedb.query.opencypher.executor.CypherExecutionPlan;
import com.arcadedb.query.opencypher.executor.CypherFunctionFactory;
import com.arcadedb.query.opencypher.executor.ExpressionEvaluator;
import com.arcadedb.query.OperationType;
import com.arcadedb.query.QueryEngine;
import com.arcadedb.utility.CollectionUtils;
import com.arcadedb.query.sql.executor.InternalResultSet;
import com.arcadedb.query.sql.executor.ResultInternal;
import com.arcadedb.query.sql.executor.ResultSet;
Expand All @@ -42,6 +44,7 @@
import com.arcadedb.security.SecurityManager;
import com.arcadedb.function.sql.DefaultSQLFunctionFactory;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -83,7 +86,32 @@ public boolean isIdempotent() {

@Override
public boolean isDDL() {
return statement instanceof CypherDDLStatement || statement instanceof CypherAdminStatement;
return statement instanceof CypherDDLStatement;
}

@Override
public Set<OperationType> getOperationTypes() {
if (statement instanceof CypherAdminStatement)
return CollectionUtils.singletonSet(OperationType.ADMIN);
if (statement instanceof CypherDDLStatement)
return CollectionUtils.singletonSet(OperationType.SCHEMA);
if (statement.isReadOnly())
return CollectionUtils.singletonSet(OperationType.READ);

final EnumSet<OperationType> ops = EnumSet.noneOf(OperationType.class);
if (statement.hasCreate())
ops.add(OperationType.CREATE);
if (statement.hasMerge()) {
ops.add(OperationType.CREATE);
ops.add(OperationType.UPDATE);
}
if (statement.hasDelete())
ops.add(OperationType.DELETE);
if (statement.getSetClause() != null && !statement.getSetClause().isEmpty())
ops.add(OperationType.UPDATE);
if (!statement.getRemoveClauses().isEmpty())
ops.add(OperationType.UPDATE);
return ops.isEmpty() ? Set.of(OperationType.CREATE, OperationType.UPDATE, OperationType.DELETE) : Set.copyOf(ops);
}
};
} catch (final Exception e) {
Expand Down
15 changes: 15 additions & 0 deletions engine/src/main/java/com/arcadedb/query/sql/SQLQueryEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import com.arcadedb.utility.Callable;
import com.arcadedb.utility.MultiIterator;

import com.arcadedb.query.OperationType;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -132,6 +134,19 @@ public boolean isIdempotent() {
public boolean isDDL() {
return statement.isDDL();
}

@Override
public Set<OperationType> getOperationTypes() {
return statement.getOperationTypes();
}

@Override
public ResultSet execute(final Map<String, Object> parameters) {
final long resultSetLimit = database.getResultSetLimit();
if (resultSetLimit > 0)
statement.setLimit(new Limit(JJTLIMIT).setValue((int) resultSetLimit));
return statement.execute(database, parameters);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import com.arcadedb.query.sql.executor.InsertExecutionPlan;
import com.arcadedb.query.sql.executor.ResultSet;

import com.arcadedb.query.OperationType;
import com.arcadedb.utility.CollectionUtils;

import java.util.*;

public class CreateEdgeStatement extends Statement {
Expand Down Expand Up @@ -174,5 +177,10 @@ public boolean isUnidirectional() {
public InsertBody getBody() {
return body;
}

@Override
public Set<OperationType> getOperationTypes() {
return CollectionUtils.singletonSet(OperationType.CREATE);
}
}
/* JavaCC - OriginalChecksum=2d3dc5693940ffa520146f8f7f505128 (do not edit this line) */
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import com.arcadedb.query.sql.executor.InternalExecutionPlan;
import com.arcadedb.query.sql.executor.ResultSet;

import com.arcadedb.query.OperationType;
import com.arcadedb.utility.CollectionUtils;

import java.util.*;

public class CreateVertexStatement extends Statement {
Expand Down Expand Up @@ -182,5 +185,10 @@ public Projection getReturnStatement() {
public InsertBody getInsertBody() {
return insertBody;
}

@Override
public Set<OperationType> getOperationTypes() {
return CollectionUtils.singletonSet(OperationType.CREATE);
}
}
/* JavaCC - OriginalChecksum=0ac3d3f09a76b9924a17fd05bc293863 (do not edit this line) */
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import com.arcadedb.query.sql.executor.DeleteExecutionPlanner;
import com.arcadedb.query.sql.executor.ResultSet;

import com.arcadedb.query.OperationType;
import com.arcadedb.utility.CollectionUtils;

import java.util.*;

public class DeleteStatement extends Statement {
Expand Down Expand Up @@ -120,5 +123,10 @@ public boolean isReturnBefore() {
public boolean isUnsafe() {
return unsafe;
}

@Override
public Set<OperationType> getOperationTypes() {
return CollectionUtils.singletonSet(OperationType.DELETE);
}
}
/* JavaCC - OriginalChecksum=5fb4ca5ba648e6c9110f41d806206a6f (do not edit this line) */
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import com.arcadedb.query.sql.executor.InsertExecutionPlanner;
import com.arcadedb.query.sql.executor.ResultSet;

import com.arcadedb.query.OperationType;
import com.arcadedb.utility.CollectionUtils;

import java.util.*;

public class InsertStatement extends Statement {
Expand Down Expand Up @@ -207,5 +210,10 @@ public boolean isSelectWithFrom() {
public boolean isUnsafe() {
return unsafe;
}

@Override
public Set<OperationType> getOperationTypes() {
return CollectionUtils.singletonSet(OperationType.CREATE);
}
}
/* JavaCC - OriginalChecksum=ccfabcf022d213caed873e6256cb26ad (do not edit this line) */
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.arcadedb.query.sql.executor.ResultSet;
import com.arcadedb.query.sql.executor.UpdateExecutionPlan;

import com.arcadedb.query.OperationType;

import java.util.*;

public class MoveVertexStatement extends Statement {
Expand Down Expand Up @@ -170,5 +172,10 @@ public Batch getBatch() {
public void setBatch(Batch batch) {
this.batch = batch;
}

@Override
public Set<OperationType> getOperationTypes() {
return EnumSet.of(OperationType.CREATE, OperationType.UPDATE, OperationType.DELETE);
}
}
/* JavaCC - OriginalChecksum=5cb0b9d3644fd28813ff615fe59d577d (do not edit this line) */
16 changes: 16 additions & 0 deletions engine/src/main/java/com/arcadedb/query/sql/parser/Statement.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import com.arcadedb.query.sql.executor.InternalExecutionPlan;
import com.arcadedb.query.sql.executor.ResultSet;

import com.arcadedb.query.OperationType;
import com.arcadedb.utility.CollectionUtils;

import java.util.*;

public class Statement extends SimpleNode {
Expand Down Expand Up @@ -132,6 +135,19 @@ public boolean isDDL() {
return this instanceof DDLStatement;
}

/**
* Returns the set of operation types this statement performs.
* Subclasses should override to provide accurate classification.
* Default: if idempotent returns READ, if DDL returns SCHEMA, otherwise CREATE+UPDATE+DELETE.
*/
public Set<OperationType> getOperationTypes() {
if (isDDL())
return CollectionUtils.singletonSet(OperationType.SCHEMA);
if (isIdempotent())
return CollectionUtils.singletonSet(OperationType.READ);
return Set.of(OperationType.CREATE, OperationType.UPDATE, OperationType.DELETE);
}

public boolean executionPlanCanBeCached() {
return false;
}
Expand Down
Loading
Loading