Этот ответ основан на сообщении Яна Робинсона о основанных на времени версионных графах а>.
Я не знаю, охватывает ли этот ответ ВСЕ требования, связанные с вопросом, но я считаю, что это может дать некоторую информацию.
Кроме того, я считаю, что вас интересует только структурное управление версиями (то есть вас не интересуют запросы об изменениях имени пользователя домена с течением времени). Наконец, я использую частичное представление вашей модели графа, но считаю, что показанные здесь концепции можно применить ко всему графу.
Начальное состояние графа:
Рассмотрим этот Cypher для создания начального состояния графа:
CREATE (admin:Admin)
CREATE (person1:Person {person_id : 1})
CREATE (person2:Person {person_id : 2})
CREATE (person3:Person {person_id : 3})
CREATE (domain1:Domain {domain_id : 1})
CREATE (device1:Device {device_id : 1})
CREATE (person1)-[:ADMIN {from : 0, to : 1000}]->(admin)
CREATE (person1)-[:CONNECTED_DEVICE {from : 0, to : 1000}]->(device1)
CREATE (domain1)-[:MEMBER]->(person1)
CREATE (domain1)-[:MEMBER]->(person2)
CREATE (domain1)-[:MEMBER]->(person3)
Результат:
![Исходное состояние графика](https://i.stack.imgur.com/TajsU.png)
На приведенном выше графике есть узлы из 3 человек. Эти узлы являются членами узла домена. Узел пользователя с person_id = 1
подключен к устройству с device_id = 1
. Кроме того, person_id = 1
является текущим администратором. Свойства from
и to
внутри отношений :ADMIN
и :CONNECTED_DEVICE
используются для управления историей структуры графа. from
представляет начальную точку во времени, а to
- конечную точку во времени. Для упрощения я использую 0 как начальное время графика и 1000 как константу конца времени. На реальном графике текущее время в миллисекундах может использоваться для представления временных точек. Кроме того, вместо константы EOT можно использовать Long.MAX_VALUE
. Связь с to = 1000
означает, что нет текущей верхней границы связанного с ним периода.
Запросы:
С помощью этого графика, чтобы получить текущего администратора, я могу:
MATCH (person:Person)-[:ADMIN {to:1000}]->(:Admin)
RETURN person
В результате получится:
╒═══════════════╕
│"person" │
╞═══════════════╡
│{"person_id":1}│
└───────────────┘
Для данного устройства, чтобы получить текущего подключенного пользователя:
MATCH (:Device {device_id : 1})<-[:CONNECTED_DEVICE {to : 1000}]-(person:Person)
RETURN person
Результат:
╒═══════════════╕
│"person" │
╞═══════════════╡
│{"person_id":1}│
└───────────────┘
Для запроса текущего администратора и текущего человека, подключенного к устройству, используется константа конца времени.
Запросить события подключения / отключения устройства:
MATCH (device:Device {device_id : 1})<-[r:CONNECTED_DEVICE]-(person:Person)
RETURN person AS person, device AS device, r.from AS from, r.to AS to
ORDER BY r.from
Результат:
╒═══════════════╤═══════════════╤══════╤════╕
│"person" │"device" │"from"│"to"│
╞═══════════════╪═══════════════╪══════╪════╡
│{"person_id":1}│{"device_id":1}│0 │1000│
└───────────────┴───────────────┴──────┴────┘
Приведенный выше результат показывает, что person_id = 1
подключен к device_id = 1
от начала до сегодняшнего дня.
Изменение структуры графа
Учтите, что текущий момент времени - 30. Сейчас user_id = 1
отключается от device_id = 1
. user_id = 2
подключится к нему. Чтобы представить это структурное изменение, я выполню следующий запрос:
// Get the current connected person
MATCH (person1:Person)-[old:CONNECTED_DEVICE {to : 1000}]->(device:Device {device_id : 1})
// get person_id = 2
MATCH (person2:Person {person_id : 2})
// set 30 as the end time of the connection between person_id = 1 and device_id = 1
SET old.to = 30
// set person_id = 2 as the current connected user to device_id = 1
// (from time point 31 to now)
CREATE (person2)-[:CONNECTED_DEVICE {from : 31, to: 1000}]->(device)
Результирующий график будет:
![«График](https://i.stack.imgur.com/M94yt.png)
После этого структурного изменения история подключений device_id = 1
будет:
MATCH (device:Device {device_id : 1})<-[r:CONNECTED_DEVICE]-(person:Person)
RETURN person AS person, device AS device, r.from AS from, r.to AS to
ORDER BY r.from
╒═══════════════╤═══════════════╤══════╤════╕
│"person" │"device" │"from"│"to"│
╞═══════════════╪═══════════════╪══════╪════╡
│{"person_id":1}│{"device_id":1}│0 │30 │
├───────────────┼───────────────┼──────┼────┤
│{"person_id":2}│{"device_id":1}│31 │1000│
└───────────────┴───────────────┴──────┴────┘
Приведенный выше результат показывает, что user_id = 1
был подключен к device_id = 1
от 0 до 30 раз. person_id = 2
в настоящее время подключен к device_id = 1
.
Сейчас к device_id = 1
подключен человек person_id = 2
:
MATCH (:Device {device_id : 1})<-[:CONNECTED_DEVICE {to : 1000}]-(person:Person)
RETURN person
╒═══════════════╕
│"person" │
╞═══════════════╡
│{"person_id":2}│
└───────────────┘
Тот же подход можно применить для управления историей администратора.
Очевидно, у этого подхода есть свои недостатки:
- Необходимо управлять набором дополнительных отношений
- Более дорогие запросы
- Более сложные запросы
Но если вам действительно нужна схема управления версиями, я считаю, что этот подход - хороший вариант или (по крайней мере) хорошая отправная точка.
22.08.2017