Skip to content
Merged
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
770 changes: 770 additions & 0 deletions docs/SQL-Triggers.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ FUNCTION: F U N C T I O N;
GLOBAL: G L O B A L;
PARAMETERS: P A R A M E T E R S;
LANGUAGE: L A N G U A G E;
TRIGGER: T R I G G E R;
FAIL: F A I L;
FIX: F I X;
SLEEP: S L E E P;
Expand Down
42 changes: 42 additions & 0 deletions engine/src/main/antlr4/com/arcadedb/query/sql/grammar/SQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ statement
| CREATE BUCKET createBucketBody # createBucketStmt
| CREATE VERTEX createVertexBody # createVertexStmt
| CREATE EDGE createEdgeBody # createEdgeStmt
| CREATE TRIGGER createTriggerBody # createTriggerStmt

// DDL Statements - ALTER variants
| ALTER TYPE alterTypeBody # alterTypeStmt
Expand All @@ -105,6 +106,7 @@ statement
| DROP PROPERTY dropPropertyBody # dropPropertyStmt
| DROP INDEX dropIndexBody # dropIndexStmt
| DROP BUCKET dropBucketBody # dropBucketStmt
| DROP TRIGGER dropTriggerBody # dropTriggerStmt

// DDL Statements - TRUNCATE variants
| TRUNCATE TYPE truncateTypeBody # truncateTypeStmt
Expand Down Expand Up @@ -586,6 +588,46 @@ dropBucketBody
: identifier (IF EXISTS)?
;

// ============================================================================
// TRIGGER MANAGEMENT
// ============================================================================

/**
* CREATE TRIGGER statement
* Syntax: CREATE TRIGGER [IF NOT EXISTS] name (BEFORE|AFTER) (CREATE|READ|UPDATE|DELETE)
* ON [TYPE] typeName (EXECUTE SQL 'statement' | EXECUTE JAVASCRIPT 'code' | EXECUTE JAVA 'className')
*/
createTriggerBody
: (IF NOT EXISTS)? identifier
triggerTiming triggerEvent
ON TYPE? identifier
triggerAction
;

triggerTiming
: BEFORE
| AFTER
;

triggerEvent
: CREATE
| READ
| UPDATE
| DELETE
;

triggerAction
: EXECUTE identifier STRING_LITERAL
;

/**
* DROP TRIGGER statement
* Syntax: DROP TRIGGER [IF EXISTS] name
*/
dropTriggerBody
: (IF EXISTS)? identifier
;

// ============================================================================
// DDL STATEMENTS - TRUNCATE
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import com.arcadedb.database.Database;
import com.arcadedb.function.FunctionLibraryDefinition;
import com.arcadedb.query.polyglot.GraalPolyglotEngine;
import org.graalvm.polyglot.Engine;
import com.arcadedb.query.polyglot.PolyglotEngineManager;

import java.util.*;
import java.util.concurrent.*;
Expand All @@ -42,7 +42,8 @@ protected PolyglotFunctionLibraryDefinition(final Database database, final Strin
this.libraryName = libraryName;
this.language = language;
this.allowedPackages = allowedPackages;
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, Engine.create()).setLanguage(language).setAllowedPackages(allowedPackages).build();
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, PolyglotEngineManager.getInstance().getSharedEngine())
.setLanguage(language).setAllowedPackages(allowedPackages).build();
}

public PolyglotFunctionLibraryDefinition registerFunction(final T function) {
Expand All @@ -51,7 +52,8 @@ public PolyglotFunctionLibraryDefinition registerFunction(final T function) {

// REGISTER ALL THE FUNCTIONS UNDER THE NEW ENGINE INSTANCE
this.polyglotEngine.close();
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, Engine.create()).setLanguage(language).setAllowedPackages(allowedPackages).build();
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, PolyglotEngineManager.getInstance().getSharedEngine())
.setLanguage(language).setAllowedPackages(allowedPackages).build();
for (final T f : functions.values())
f.init(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public class GraalPolyglotEngine implements AutoCloseable {

static {
try {
supportedLanguages = Engine.create().getLanguages().keySet();
// Use the shared engine to discover supported languages
supportedLanguages = PolyglotEngineManager.getInstance().getSharedEngine().getLanguages().keySet();
} catch (Throwable e) {
LogManager.instance().log(GraalPolyglotEngine.class, Level.SEVERE, "GraalVM Polyglot Engine: no languages found");
supportedLanguages = Collections.emptySet();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.polyglot;

import com.arcadedb.log.LogManager;
import org.graalvm.polyglot.Engine;

import java.util.logging.Level;

/**
* Singleton manager for GraalVM Polyglot Engine instances.
* The Engine is a heavyweight object that should be shared across multiple Context instances
* for optimal performance. This manager provides a single shared Engine instance for the entire
* ArcadeDB process.
*
* @author Luca Garulli (l.garulli@arcadedata.com)
*/
public class PolyglotEngineManager {
private static final PolyglotEngineManager INSTANCE = new PolyglotEngineManager();
private volatile Engine sharedEngine;

private PolyglotEngineManager() {
// Private constructor for singleton
}

public static PolyglotEngineManager getInstance() {
return INSTANCE;
}

/**
* Returns the shared GraalVM Engine instance. Creates it on first access (lazy initialization).
* The Engine is thread-safe and can be used concurrently by multiple contexts.
*
* @return the shared Engine instance
*/
public Engine getSharedEngine() {
if (sharedEngine == null) {
synchronized (this) {
if (sharedEngine == null) {
LogManager.instance()
.log(this, Level.FINE, "Creating shared GraalVM Polyglot Engine for ArcadeDB process");
sharedEngine = Engine.create();
}
}
}
return sharedEngine;
}

/**
* Closes the shared Engine. This should only be called during application shutdown.
* After calling this method, subsequent calls to getSharedEngine() will create a new Engine.
*/
public synchronized void closeSharedEngine() {
if (sharedEngine != null) {
try {
LogManager.instance().log(this, Level.FINE, "Closing shared GraalVM Polyglot Engine");
sharedEngine.close();
} catch (final Exception e) {
LogManager.instance().log(this, Level.WARNING, "Error closing shared GraalVM Polyglot Engine", e);
} finally {
sharedEngine = null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.arcadedb.query.sql.executor.InternalResultSet;
import com.arcadedb.query.sql.executor.ResultInternal;
import com.arcadedb.query.sql.executor.ResultSet;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Value;

import java.util.*;
Expand Down Expand Up @@ -88,8 +87,8 @@ protected PolyglotQueryEngine(final DatabaseInternal database, final String lang
this.language = language;
this.database = database;
this.allowedPackages = allowedPackages;
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, Engine.create()).setLanguage(language)
.setAllowedPackages(allowedPackages).build();
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, PolyglotEngineManager.getInstance().getSharedEngine())
.setLanguage(language).setAllowedPackages(allowedPackages).build();
this.userCodeExecutorQueue = new ArrayBlockingQueue<>(10000);
this.userCodeExecutor = new ThreadPoolExecutor(8, 8, 30, TimeUnit.SECONDS, userCodeExecutorQueue,
new ThreadPoolExecutor.CallerRunsPolicy());
Expand Down Expand Up @@ -174,8 +173,8 @@ public QueryEngine registerFunctions(final String function) {

@Override
public QueryEngine unregisterFunctions() {
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, Engine.create()).setLanguage(language)
.setAllowedPackages(allowedPackages).build();
this.polyglotEngine = GraalPolyglotEngine.newBuilder(database, PolyglotEngineManager.getInstance().getSharedEngine())
.setLanguage(language).setAllowedPackages(allowedPackages).build();
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5595,6 +5595,90 @@ public DropBucketStatement visitDropBucketStmt(final SQLParser.DropBucketStmtCon
return stmt;
}

// TRIGGER MANAGEMENT

/**
* Visit CREATE TRIGGER statement.
*/
@Override
public CreateTriggerStatement visitCreateTriggerStmt(final SQLParser.CreateTriggerStmtContext ctx) {
final CreateTriggerStatement stmt = new CreateTriggerStatement(-1);
final SQLParser.CreateTriggerBodyContext bodyCtx = ctx.createTriggerBody();

// IF NOT EXISTS flag
stmt.ifNotExists = bodyCtx.IF() != null && bodyCtx.NOT() != null && bodyCtx.EXISTS() != null;

// Trigger name (first identifier)
stmt.name = (Identifier) visit(bodyCtx.identifier(0));

// Trigger timing (BEFORE or AFTER)
stmt.timing = (Identifier) visit(bodyCtx.triggerTiming());

// Trigger event (CREATE, READ, UPDATE, DELETE)
stmt.event = (Identifier) visit(bodyCtx.triggerEvent());

// Type name (second identifier - the one after ON)
stmt.typeName = (Identifier) visit(bodyCtx.identifier(1));

// Action type and code (SQL or JAVASCRIPT)
final SQLParser.TriggerActionContext actionCtx = bodyCtx.triggerAction();
final Identifier actionTypeId = (Identifier) visit(actionCtx.identifier());
stmt.actionType = actionTypeId;

// Extract string literal and remove quotes
final String rawText = actionCtx.STRING_LITERAL().getText();
stmt.actionCode = rawText.substring(1, rawText.length() - 1);

return stmt;
}

/**
* Visit DROP TRIGGER statement.
*/
@Override
public DropTriggerStatement visitDropTriggerStmt(final SQLParser.DropTriggerStmtContext ctx) {
final DropTriggerStatement stmt = new DropTriggerStatement(-1);
final SQLParser.DropTriggerBodyContext bodyCtx = ctx.dropTriggerBody();

// Trigger name
stmt.name = (Identifier) visit(bodyCtx.identifier());

// IF EXISTS
stmt.ifExists = bodyCtx.IF() != null && bodyCtx.EXISTS() != null;

return stmt;
}

/**
* Visit trigger timing (BEFORE or AFTER).
*/
@Override
public Identifier visitTriggerTiming(final SQLParser.TriggerTimingContext ctx) {
if (ctx.BEFORE() != null) {
return new Identifier("BEFORE");
} else if (ctx.AFTER() != null) {
return new Identifier("AFTER");
}
return null;
}

/**
* Visit trigger event (CREATE, READ, UPDATE, DELETE).
*/
@Override
public Identifier visitTriggerEvent(final SQLParser.TriggerEventContext ctx) {
if (ctx.CREATE() != null) {
return new Identifier("CREATE");
} else if (ctx.READ() != null) {
return new Identifier("READ");
} else if (ctx.UPDATE() != null) {
return new Identifier("UPDATE");
} else if (ctx.DELETE() != null) {
return new Identifier("DELETE");
}
return null;
}

// DDL STATEMENTS - TRUNCATE

/**
Expand Down
Loading
Loading