Разрешить neo4j сопоставлять один и тот же узел с несколькими разными именами

#neo4j #erlang #cypher

#neo4j #erlang #шифр

Вопрос:

Я работаю над проектом (если быть точным, refactorererl), где мы храним синтаксические деревья и семантические графы программ erlang для выполнения рефакторинга и других видов анализа, преобразований и запросов к ним. Ранее они хранились и сравнивались с различными хранилищами значений ключей, реляционными базами данных и базами данных на основе документов, но я решил попробовать и реализовать серверную часть neo4j, поскольку она казалась подходящей для запросов типа path, таких как «перечислите все переменные функций, которые находятся в вызываемом модуле xyz » и аналогичные действия.

Я написал библиотеку-оболочку neo4j (neo4j_driver), поскольку более старые, которые я нашел, использовали API, который сейчас устарел, и дошел до того, что он почти работает с соответствующими фильтрами и всем прочим, но одна вещь, которую я просто не могу решить, — это отсутствие возможности сопоставить один и тот же узел поднесколько имен в запросе. Это кажется необходимым, поскольку бывают случаи, когда поиск в обратном направлении по связям неизбежен. Например, когда программа (например, потому, что это необходимо для рефакторинга) пытается выполнить поиск всех переменных, которые находятся в той же функции, что и, скажем, заданный X (идентифицируемый путем указания параметра fromId ), он преобразует это в Cypher и выдает что-то вроде этого:

 MATCH (from:var)<-[edge1:vardef]-(node1)-[edge2:vardef]->(node2) WHERE id(from) = $fromId RETURN labels(node2), id(node2) ORDER BY edge1.orderId, edge2.orderId"
  

(Вот orderId лексический порядок элементов в файле.)

Проблема в том, что это дает только переменные внутри функции, которые содержат X другие, чем X она сама, поскольку они должны совпадать с именами «from» и «node2» одновременно. Есть ли способ создать такой запрос, который позволяет избежать этой проблемы? Я знаю, что я мог бы попытаться избежать последовательного присвоения имен узлам и фильтрации с помощью фигурных скобок внутри узла, но таким образом я мог бы выполнять только точные совпадения, а не, например, диапазоны.

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

 filter_to_where_clause_element(NodeName, EdgeName, empty_filter) -> "";
filter_to_where_clause_element(NodeName, EdgeName, Filter) ->
    case Filter of
        {'not', InnerFilter} ->
            "NOT ("    filter_to_where_clause_element(NodeName, EdgeName, InnerFilter)    ")";
        {LeftFilter, 'and', RightFilter} ->
            "(("    filter_to_where_clause_element(NodeName, EdgeName, LeftFilter)   
            ") AND ("    filter_to_where_clause_element(NodeName, EdgeName, RightFilter)    "))";
        {LeftFilter, 'or', RightFilter} ->
            "(("    filter_to_where_clause_element(NodeName, EdgeName, LeftFilter)   
            ") OR ("    filter_to_where_clause_element(NodeName, EdgeName, RightFilter)    "))";
        {Property, '==', Value} ->
            NodeName    "."    to_str(Property)    " = '"    to_str(Value)    "'";
        {Property, '/=', Value} ->
            NodeName    "."    to_str(Property)    " <> '"    to_str(Value)    "'";
        {Property, '<', Value} ->
            NodeName    "."    to_str(Property)    " < '"    to_str(Value)    "'";
        {Property, '>', Value} ->
            NodeName    "."    to_str(Property)    " > '"    to_str(Value)    "'";
        {Property, '<=', Value} ->
            NodeName    "."    to_str(Property)    " <= '"    to_str(Value)    "'";
        {Property, '>=', Value} ->
            NodeName    "."    to_str(Property)    " >= '"    to_str(Value)    "'";
        % last ->
        %     EdgeName    ".orderId = "    to_str(get_last_id());
        Id when is_integer(Id) ->
            EdgeName    ".orderId = "    to_str(Id);
        Other -> own_debug("Unknown filter", [Filter]), ""
    end.

path_element_to_query_part({Id, {Tag, Direction, Filter}}) ->
    NodeName = "node"    to_str(Id),
    EdgeName = "edge"    to_str(Id),

    Edge = "["    EdgeName    ":"    to_str(Tag)    "]",

    MatchClauseElement =
        case Direction of
            back -> "<-"    Edge    "-";
            fwd -> "-"    Edge    "->"
        end    "("    NodeName    ")",

    WhereClauseElement = filter_to_where_clause_element(NodeName, EdgeName, Filter),

    OrderByClauseElement = EdgeName    ".orderId",

    { MatchClauseElement, WhereClauseElement, OrderByClauseElement }.

path(Node, Path) ->
    {?NODETAG, Class, Id} = Node,

    NormalizedIndexedPath = lists:zip(
        lists:seq(1, length(Path)),
        lists:map(fun(PE) -> normalize_path_element(PE) end, Path)
    ),

    QueryPathElements = lists:map(
        fun(IndexedPathElement) ->
            path_element_to_query_part(IndexedPathElement)
        end,
        NormalizedIndexedPath
    ),

    { MatchClauseElements, WhereClauseElements, OrderByClauseElements } = {
        lists:map(
            fun(QueryPathElement) -> element(1, QueryPathElement) end,
            QueryPathElements
        ),
        lists:map(
            fun(QueryPathElement) -> element(2, QueryPathElement) end,
            QueryPathElements
        ),
        lists:map(
            fun(QueryPathElement) -> element(3, QueryPathElement) end,
            QueryPathElements
        )
    },

    JoinedMatchClause = string:join(MatchClauseElements, ""),
    LastNodeName = "node"    to_str(length(Path)),

    JoinedWhereClause = string:join(
        lists:filter(
            fun(WhereClauseElement) -> WhereClauseElement /= "" end,
            ["id(from) = $fromId" | WhereClauseElements]
        ),
        " AND "
    ),

    JoinedOrderByClause = string:join(OrderByClauseElements, ", "),

    Statements = [
        [
            { statement,
                "MATCH (from:"    to_str(Class)    ")"   
                JoinedMatchClause   
                " WHERE "    JoinedWhereClause   
                " RETURN labels("    LastNodeName    "), id("    LastNodeName    ")"   
                " ORDER BY "    JoinedOrderByClause
            },
            { parameters, [
                { "fromId", Id }
            ] }
        ]
    ],
    Result = run_cypher_query(Statements),
    MatchedNodes = lists:map(fun (Row) ->
        [[TargetClass], TargetId] = Row,
        {?NODETAG, parse(TargetClass), TargetId}
    end, get_rows(Result)),
    { ok, MatchedNodes }.
  

Комментарии:

1. Возможно, вы хотите разделить свое отдельное MATCH предложение на предложения, разделенные запятыми, или MATCH на несколько предложений?

2. Спасибо за ответ! Насколько я могу судить по чтению в Интернете, предложения, разделенные запятыми, не решат эту проблему, потому что они по-прежнему используют одно и то же пространство имен, просто разрешают ветвление. MATCH Может работать несколько предложений, но тогда мне нужно будет соединить их вручную, чтобы связать цепочку вместе. Разве это не разрушит их производительность?

3. MATCH Я полагаю, что несколько предложений создают перекрестный продукт, но мне обычно легче оптимизировать запрос (и / или дизайн базы данных) после получения правильных результатов.