Статьи

Триггеры в Neo4j

Аль-Капоне-пушка

Одной из часто упускаемых функций в Neo4j являются возможности «TransactionEventHandler»… более известные в мире баз данных как « Триггеры» . Когда происходит транзакция, мы можем проанализировать это событие и принять решение о некоторых действиях. Для этого мы будем напишите «расширение ядра» (немного отличающееся от неуправляемых расширений, которые мы видели в этом блоге), чтобы связать наш триггер.

Представьте, что вы — специальный агент Эйвери Райан, работающий в команде ФБР по расследованию киберпреступлений, занимающейся расследованием убийств в Интернете, кражи, взлома, сексуальных преступлений, шантажа и любых других преступлений, которые считаются кибер-преступлениями в пределах юрисдикции ФБР.

ИСК-кибер

Ваши бывшие коллеги-хакеры Равен Рамирез и Броди Нельсон создали социальную сеть известных подозреваемых и их знакомых. Но бывший старший специальный агент морской пехоты США Элайджа Мундо не может потрудиться выучить Сайфера , все, что он хочет — это электронное письмо, когда будет выявлен новый подозреваемый или выяснится новая связь с известным подозреваемым. Итак, вы назначаете хакерского ученого Дэниела Крумица, чтобы добавить эту функцию в вашу систему.

Даниэль просматривает документацию TransactionEventHandler и узнает, что есть 3 хука: beforeCommit, afterCommit и afterRollback, которые можно использовать:

beforeCommit(TransactionData data)
Invoked when a transaction is about to be committed.

afterCommit(TransactionData data, T state)
Invoked after the transaction has been committed successfully.

afterRollback(TransactionData data, T state)
Invoked after the transaction has been rolled back if committing the transaction failed for some reason.

Поскольку мы хотим отправить электронное письмо после того, как новый подозреваемый будет идентифицирован или подключен, будет использован хук afterCommit. Однако мы не хотим помещать нашу логику здесь. Вместо этого мы создадим ExecutorService и Runnable для его обработки.

public class MyTransactionEventHandler implements TransactionEventHandler {

    public static GraphDatabaseService db;
    private static ExecutorService ex;

    public MyTransactionEventHandler(GraphDatabaseService graphDatabaseService, ExecutorService executor) {
        db = graphDatabaseService;
        ex = executor;
    }

    @Override
    public Object beforeCommit(TransactionData transactionData) throws Exception {
        return null;
    }

    @Override
    public void afterCommit(TransactionData transactionData, Object o) {
        ex.submit(new SuspectRunnable(transactionData, db));
    }

    @Override
    public void afterRollback(TransactionData transactionData, Object o) {

    }
}

Наш SuspectRunnable получает передаваемую базу данных, а также TransactionData, который содержит узлы и отношения, которые были созданы, метки, которые были назначены, свойства, которые были изменены, и т. Д. Давайте использовать их, начиная новую транзакцию и проверяя, если вновь созданные узлы иметь ярлык подозреваемого:

@Override
public void run() {
    try (Transaction tx = db.beginTx()) {
        Set<Node> suspects = new HashSet<>();
        for (Node node : td.createdNodes()) {
            if (node.hasLabel(Labels.Suspect)) {
                suspects.add(node);
                System.out.println("A new Suspect has been created!");
            }
        }

Мы просто печатаем сообщение в файл «neo4j / data / log / console.log» для нашей демонстрации, но в реальном приложении оно отправит электронное письмо, или перейдет в очередь сообщений, или что вы захотите. Таким образом, мы фиксируем любые новые Подозреваемые, но мы также должны собирать, если каким-либо узлам присваивается метка Подозреваемый:

for (LabelEntry labelEntry : td.assignedLabels()) {
    if (labelEntry.label().equals(Labels.Suspect) && !suspects.contains(labelEntry.node())) {
        System.out.println("A new Suspect has been identified!");
        suspects.add(labelEntry.node());
    }
}

Теперь нам нужно определить любые прямые или косвенные отношения с подозреваемыми, которые могли быть созданы. Мы возьмем недавно созданные отношения и проверим, является ли один из узлов отношений подозреваемым. Но мы не будем останавливаться на достигнутом. Мы будем использовать эти узлы, чтобы пройти еще один уровень и посмотреть, есть ли у нас новое косвенное отношение к Подозреваемому:

for (Relationship relationship : td.createdRelationships()) {
    if (relationship.isType(RelationshipTypes.KNOWS)) {
        for (Node user : relationship.getNodes()) {
            if (user.hasLabel(Labels.Suspect)) {
                System.out.println("A new direct relationship to a Suspect has been created!");
            }

            for (Relationship knows : user.getRelationships(Direction.BOTH, 
                                                            RelationshipTypes.KNOWS)) {
                Node otherUser = knows.getOtherNode(user);
                if (otherUser.hasLabel(Labels.Suspect) &&
                   !otherUser.equals(relationship.getOtherNode(user))) {
                    System.out.println("A new indirect relationship to a Suspect has been created!");
                }
            }
        }
    }
}

Вот и все для нашего Runnable, теперь нам нужно подключить это к Neo4j, создав наш KernelExtension, который регистрирует наш обработчик события транзакции. В нашем методе запуска мы создадим нашего исполнителя и обработчик. В нашем методе выключения (который вызывается при выключении Neo4j) мы выключим исполнителя и отменим регистрацию обработчика.

@Override
public Lifecycle newKernelExtension(final Dependencies dependencies) throws Throwable {
    return new LifecycleAdapter() {

        private MyTransactionEventHandler handler;
        private ExecutorService executor;

        @Override
        public void start() throws Throwable {
            executor = Executors.newFixedThreadPool(2);
            handler = new MyTransactionEventHandler(dependencies.getGraphDatabaseService(), executor);
            dependencies.getGraphDatabaseService().registerTransactionEventHandler(handler);
        }

        @Override
        public void shutdown() throws Throwable {
            executor.shutdown();
            dependencies.getGraphDatabaseService().unregisterTransactionEventHandler(handler);
        }
    };
}

Чтобы Neo4j мог получить этот класс, нам нужно создать файл «org.neo4j.kernel.extension.KernelExtensionFactory» в каталоге «resources / META-INF / services» с записью «com.maxdemarzi.RegisterTransactionEventHandlerExtensionFactory». ,

Полный исходный код доступен на github.

Чтобы развернуть это на нашем сервере, мы создадим это:

mvn clean package

Затем скопируйте target / triggers-1.0.jar в каталог neo4j / plugins нашего сервера Neo4j. Мы запустим наш сервер и подключим файл neo4j / data / log / console.log. Давайте попробуем несколько запросов:

CREATE (max:User {name:"Max"}) RETURN max;

Ничего не произошло, потому что созданный нами узел ничего не вызывает.

CREATE (al:Suspect {name:"Al Capone"}) RETURN al;

Результаты в «Новый подозреваемый был создан!» появляется в нашем файле журнала.

MATCH (max:User),(al:Suspect)
WHERE max.name = "Max" AND al.name = "Al Capone"
CREATE (max)-[r:KNOWS]->(al)
RETURN r;

Дает нам «Новое прямое отношение к подозреваемому было создано!».

CREATE (monica:User {name:"Monica"}) RETURN monica;

Ничего не произошло.

MATCH (max:User),(monica:User)
WHERE max.name = "Max" AND monica.name = "Monica"
CREATE (max)-[r:KNOWS]->(monica)
RETURN r;

Дает нам «Созданы новые косвенные отношения с подозреваемым!» поскольку Моника теперь связана с Максом, который уже был связан с нашим подозреваемым Аль Капоне … и наконец:

MATCH (monica:User)
WHERE monica.name = "Monica"
SET monica :Suspect
RETURN monica;

Показывает нам «Новый подозреваемый был идентифицирован!» с тех пор как Моника превратилась из Пользователя в обычного Подозреваемого.

Обычные подозреваемые

Вы видите, что добавление триггеров в Neo4j дает вам совершенно новый набор возможностей. Механизм, который мы использовали для добавления их на наш сервер, достаточно мощный. Они позволяют вам делать все, что могут делать люди, использующие встроенный Neo4j, но в аккуратном небольшом пакете, который легко развернуть.