Refactored repository structure.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package cypher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package cypher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
17
expanders.go
17
expanders.go
@@ -1,10 +1,11 @@
|
|||||||
package heartwood
|
package heartwood
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.wisehodl.dev/jay/go-heartwood/graph"
|
||||||
roots "git.wisehodl.dev/jay/go-roots/events"
|
roots "git.wisehodl.dev/jay/go-roots/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Expander func(e roots.Event, s *Subgraph)
|
type Expander func(e roots.Event, s *graph.Subgraph)
|
||||||
type ExpanderRegistry []Expander
|
type ExpanderRegistry []Expander
|
||||||
|
|
||||||
func NewExpanderRegistry() ExpanderRegistry {
|
func NewExpanderRegistry() ExpanderRegistry {
|
||||||
@@ -26,7 +27,7 @@ func (r *ExpanderRegistry) Add(m Expander) {
|
|||||||
|
|
||||||
// Default Expander Functions
|
// Default Expander Functions
|
||||||
|
|
||||||
func ExpandTaggedEvents(e roots.Event, s *Subgraph) {
|
func ExpandTaggedEvents(e roots.Event, s *graph.Subgraph) {
|
||||||
tagNodes := s.NodesByLabel("Tag")
|
tagNodes := s.NodesByLabel("Tag")
|
||||||
for _, tag := range e.Tags {
|
for _, tag := range e.Tags {
|
||||||
if !isValidTag(tag) {
|
if !isValidTag(tag) {
|
||||||
@@ -44,14 +45,14 @@ func ExpandTaggedEvents(e roots.Event, s *Subgraph) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
referencedEvent := NewEventNode(value)
|
referencedEvent := graph.NewEventNode(value)
|
||||||
|
|
||||||
s.AddNode(referencedEvent)
|
s.AddNode(referencedEvent)
|
||||||
s.AddRel(NewReferencesEventRel(tagNode, referencedEvent, nil))
|
s.AddRel(graph.NewReferencesEventRel(tagNode, referencedEvent, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpandTaggedUsers(e roots.Event, s *Subgraph) {
|
func ExpandTaggedUsers(e roots.Event, s *graph.Subgraph) {
|
||||||
tagNodes := s.NodesByLabel("Tag")
|
tagNodes := s.NodesByLabel("Tag")
|
||||||
for _, tag := range e.Tags {
|
for _, tag := range e.Tags {
|
||||||
if !isValidTag(tag) {
|
if !isValidTag(tag) {
|
||||||
@@ -69,16 +70,16 @@ func ExpandTaggedUsers(e roots.Event, s *Subgraph) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
referencedEvent := NewUserNode(value)
|
referencedEvent := graph.NewUserNode(value)
|
||||||
|
|
||||||
s.AddNode(referencedEvent)
|
s.AddNode(referencedEvent)
|
||||||
s.AddRel(NewReferencesUserRel(tagNode, referencedEvent, nil))
|
s.AddRel(graph.NewReferencesUserRel(tagNode, referencedEvent, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
func findTagNode(nodes []*Node, name, value string) *Node {
|
func findTagNode(nodes []*graph.Node, name, value string) *graph.Node {
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if node.Props["name"] == name && node.Props["value"] == value {
|
if node.Props["name"] == name && node.Props["value"] == value {
|
||||||
return node
|
return node
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -3,12 +3,17 @@ module git.wisehodl.dev/jay/go-heartwood
|
|||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.wisehodl.dev/jay/go-roots v0.3.1
|
||||||
github.com/neo4j/neo4j-go-driver/v6 v6.0.0
|
github.com/neo4j/neo4j-go-driver/v6 v6.0.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,5 +1,15 @@
|
|||||||
|
git.wisehodl.dev/jay/go-roots v0.3.1 h1:5UiG3g1S3XCkMB+W2rbNZGpl4IiSlRvae2cLHGfjVcA=
|
||||||
|
git.wisehodl.dev/jay/go-roots v0.3.1/go.mod h1:TQXk/V8MRSw4khMlNSINM8dU5/ARR1Wov+kGw0237rQ=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||||
github.com/neo4j/neo4j-go-driver/v6 v6.0.0 h1:xVAi6YLOfzXUx+1Lc/F2dUhpbN76BfKleZbAlnDFRiA=
|
github.com/neo4j/neo4j-go-driver/v6 v6.0.0 h1:xVAi6YLOfzXUx+1Lc/F2dUhpbN76BfKleZbAlnDFRiA=
|
||||||
github.com/neo4j/neo4j-go-driver/v6 v6.0.0/go.mod h1:hzSTfNfM31p1uRSzL1F/BAYOgaiTarE6OAQBajfsm+I=
|
github.com/neo4j/neo4j-go-driver/v6 v6.0.0/go.mod h1:hzSTfNfM31p1uRSzL1F/BAYOgaiTarE6OAQBajfsm+I=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// This module defines types and functions for working with Neo4j graph
|
// This module defines types and functions for working with Neo4j graph
|
||||||
// entities.
|
// entities.
|
||||||
|
|
||||||
package heartwood
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -33,19 +33,19 @@ type MatchKeysProvider interface {
|
|||||||
|
|
||||||
// MatchKeys is a simple implementation of the MatchKeysProvider interface.
|
// MatchKeys is a simple implementation of the MatchKeysProvider interface.
|
||||||
type MatchKeys struct {
|
type MatchKeys struct {
|
||||||
keys map[string][]string
|
Keys map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MatchKeys) GetLabels() []string {
|
func (p *MatchKeys) GetLabels() []string {
|
||||||
labels := []string{}
|
labels := []string{}
|
||||||
for l := range p.keys {
|
for l := range p.Keys {
|
||||||
labels = append(labels, l)
|
labels = append(labels, l)
|
||||||
}
|
}
|
||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MatchKeys) GetKeys(label string) ([]string, bool) {
|
func (p *MatchKeys) GetKeys(label string) ([]string, bool) {
|
||||||
if keys, exists := p.keys[label]; exists {
|
if keys, exists := p.Keys[label]; exists {
|
||||||
return keys, exists
|
return keys, exists
|
||||||
} else {
|
} else {
|
||||||
return nil, exists
|
return nil, exists
|
||||||
@@ -296,6 +296,10 @@ func (s *StructuredSubgraph) GetRels(relKey string) []*Relationship {
|
|||||||
return s.rels[relKey]
|
return s.rels[relKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StructuredSubgraph) MatchProvider() MatchKeysProvider {
|
||||||
|
return s.matchProvider
|
||||||
|
}
|
||||||
|
|
||||||
// NodeCount returns the number of nodes in the subgraph.
|
// NodeCount returns the number of nodes in the subgraph.
|
||||||
func (s *StructuredSubgraph) NodeCount() int {
|
func (s *StructuredSubgraph) NodeCount() int {
|
||||||
count := 0
|
count := 0
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
func TestMatchKeys(t *testing.T) {
|
func TestMatchKeys(t *testing.T) {
|
||||||
matchKeys := &MatchKeys{
|
matchKeys := &MatchKeys{
|
||||||
keys: map[string][]string{
|
Keys: map[string][]string{
|
||||||
"User": {"pubkey"},
|
"User": {"pubkey"},
|
||||||
"Event": {"id"},
|
"Event": {"id"},
|
||||||
"Tag": {"name", "value"},
|
"Tag": {"name", "value"},
|
||||||
@@ -70,7 +70,7 @@ func TestRelSortKey(t *testing.T) {
|
|||||||
|
|
||||||
func TestMatchProps(t *testing.T) {
|
func TestMatchProps(t *testing.T) {
|
||||||
matchKeys := &MatchKeys{
|
matchKeys := &MatchKeys{
|
||||||
keys: map[string][]string{
|
Keys: map[string][]string{
|
||||||
"User": {"pubkey"},
|
"User": {"pubkey"},
|
||||||
"Event": {"id"},
|
"Event": {"id"},
|
||||||
},
|
},
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
// This module provides methods for creating nodes and relationships according
|
// This module provides methods for creating nodes and relationships according
|
||||||
// to a defined schema.
|
// to a defined schema.
|
||||||
|
|
||||||
package heartwood
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/neo4j/neo4j-go-driver/v6/neo4j"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -15,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func NewMatchKeys() *MatchKeys {
|
func NewMatchKeys() *MatchKeys {
|
||||||
return &MatchKeys{
|
return &MatchKeys{
|
||||||
keys: map[string][]string{
|
Keys: map[string][]string{
|
||||||
"User": {"pubkey"},
|
"User": {"pubkey"},
|
||||||
"Relay": {"url"},
|
"Relay": {"url"},
|
||||||
"Event": {"id"},
|
"Event": {"id"},
|
||||||
@@ -101,43 +99,3 @@ func NewRelationshipWithValidation(
|
|||||||
|
|
||||||
return NewRelationship(rtype, start, end, props)
|
return NewRelationship(rtype, start, end, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// Schema Indexes and Constaints
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
// SetNeo4jSchema ensures that the necessary indexes and constraints exist in
|
|
||||||
// the database
|
|
||||||
func SetNeo4jSchema(ctx context.Context, driver neo4j.Driver) error {
|
|
||||||
schemaQueries := []string{
|
|
||||||
`CREATE CONSTRAINT user_pubkey IF NOT EXISTS
|
|
||||||
FOR (n:User) REQUIRE n.pubkey IS UNIQUE`,
|
|
||||||
|
|
||||||
`CREATE INDEX user_pubkey IF NOT EXISTS
|
|
||||||
FOR (n:User) ON (n.pubkey)`,
|
|
||||||
|
|
||||||
`CREATE INDEX event_id IF NOT EXISTS
|
|
||||||
FOR (n:Event) ON (n.id)`,
|
|
||||||
|
|
||||||
`CREATE INDEX event_kind IF NOT EXISTS
|
|
||||||
FOR (n:Event) ON (n.kind)`,
|
|
||||||
|
|
||||||
`CREATE INDEX tag_name_value IF NOT EXISTS
|
|
||||||
FOR (n:Tag) ON (n.name, n.value)`,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create indexes and constraints
|
|
||||||
for _, query := range schemaQueries {
|
|
||||||
_, err := neo4j.ExecuteQuery(ctx, driver,
|
|
||||||
query,
|
|
||||||
nil,
|
|
||||||
neo4j.EagerResultTransformer,
|
|
||||||
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package graph
|
||||||
|
|
||||||
// Sets
|
// Sets
|
||||||
|
|
||||||
@@ -52,15 +52,3 @@ func (s Set[T]) ToArray() []T {
|
|||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operations
|
|
||||||
|
|
||||||
func Flatten[K comparable, V comparable](mapping map[K][]V) []V {
|
|
||||||
var values []V
|
|
||||||
for _, array := range mapping {
|
|
||||||
for _, v := range array {
|
|
||||||
values = append(values, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
@@ -1,41 +1,43 @@
|
|||||||
package heartwood
|
package graphstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.wisehodl.dev/jay/go-heartwood/cypher"
|
||||||
|
"git.wisehodl.dev/jay/go-heartwood/graph"
|
||||||
"github.com/neo4j/neo4j-go-driver/v6/neo4j"
|
"github.com/neo4j/neo4j-go-driver/v6/neo4j"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MergeSubgraph(
|
func MergeSubgraph(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
driver neo4j.Driver,
|
driver neo4j.Driver,
|
||||||
subgraph *StructuredSubgraph,
|
subgraph *graph.StructuredSubgraph,
|
||||||
) ([]neo4j.ResultSummary, error) {
|
) ([]neo4j.ResultSummary, error) {
|
||||||
// Validate subgraph
|
// Validate subgraph
|
||||||
for _, nodeKey := range subgraph.NodeKeys() {
|
for _, nodeKey := range subgraph.NodeKeys() {
|
||||||
matchLabel, _, err := DeserializeNodeKey(nodeKey)
|
matchLabel, _, err := graph.DeserializeNodeKey(nodeKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := subgraph.matchProvider.GetKeys(matchLabel)
|
_, exists := subgraph.MatchProvider().GetKeys(matchLabel)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("unknown match label: %s", matchLabel)
|
return nil, fmt.Errorf("unknown match label: %s", matchLabel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, relKey := range subgraph.RelKeys() {
|
for _, relKey := range subgraph.RelKeys() {
|
||||||
_, startLabel, endLabel, err := DeserializeRelKey(relKey)
|
_, startLabel, endLabel, err := graph.DeserializeRelKey(relKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists := subgraph.matchProvider.GetKeys(startLabel)
|
_, exists := subgraph.MatchProvider().GetKeys(startLabel)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("unknown match label: %s", startLabel)
|
return nil, fmt.Errorf("unknown match label: %s", startLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, exists = subgraph.matchProvider.GetKeys(endLabel)
|
_, exists = subgraph.MatchProvider().GetKeys(endLabel)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("unknown match label: %s", endLabel)
|
return nil, fmt.Errorf("unknown match label: %s", endLabel)
|
||||||
}
|
}
|
||||||
@@ -49,12 +51,12 @@ func MergeSubgraph(
|
|||||||
var resultSummaries []neo4j.ResultSummary
|
var resultSummaries []neo4j.ResultSummary
|
||||||
|
|
||||||
for _, nodeKey := range subgraph.NodeKeys() {
|
for _, nodeKey := range subgraph.NodeKeys() {
|
||||||
matchLabel, labels, _ := DeserializeNodeKey(nodeKey)
|
matchLabel, labels, _ := graph.DeserializeNodeKey(nodeKey)
|
||||||
nodeResultSummary, err := MergeNodes(
|
nodeResultSummary, err := MergeNodes(
|
||||||
ctx, tx,
|
ctx, tx,
|
||||||
matchLabel,
|
matchLabel,
|
||||||
labels,
|
labels,
|
||||||
subgraph.matchProvider,
|
subgraph.MatchProvider(),
|
||||||
subgraph.GetNodes(nodeKey),
|
subgraph.GetNodes(nodeKey),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,13 +68,13 @@ func MergeSubgraph(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, relKey := range subgraph.RelKeys() {
|
for _, relKey := range subgraph.RelKeys() {
|
||||||
rtype, startLabel, endLabel, _ := DeserializeRelKey(relKey)
|
rtype, startLabel, endLabel, _ := graph.DeserializeRelKey(relKey)
|
||||||
relResultSummary, err := MergeRels(
|
relResultSummary, err := MergeRels(
|
||||||
ctx, tx,
|
ctx, tx,
|
||||||
rtype,
|
rtype,
|
||||||
startLabel,
|
startLabel,
|
||||||
endLabel,
|
endLabel,
|
||||||
subgraph.matchProvider,
|
subgraph.MatchProvider(),
|
||||||
subgraph.GetRels(relKey),
|
subgraph.GetRels(relKey),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,15 +105,15 @@ func MergeNodes(
|
|||||||
tx neo4j.ManagedTransaction,
|
tx neo4j.ManagedTransaction,
|
||||||
matchLabel string,
|
matchLabel string,
|
||||||
nodeLabels []string,
|
nodeLabels []string,
|
||||||
matchProvider MatchKeysProvider,
|
matchProvider graph.MatchKeysProvider,
|
||||||
nodes []*Node,
|
nodes []*graph.Node,
|
||||||
) (*neo4j.ResultSummary, error) {
|
) (*neo4j.ResultSummary, error) {
|
||||||
cypherLabels := ToCypherLabels(nodeLabels)
|
cypherLabels := cypher.ToCypherLabels(nodeLabels)
|
||||||
|
|
||||||
matchKeys, _ := matchProvider.GetKeys(matchLabel)
|
matchKeys, _ := matchProvider.GetKeys(matchLabel)
|
||||||
cypherProps := ToCypherProps(matchKeys, "node.")
|
cypherProps := cypher.ToCypherProps(matchKeys, "node.")
|
||||||
|
|
||||||
serializedNodes := []*SerializedNode{}
|
serializedNodes := []*graph.SerializedNode{}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
serializedNodes = append(serializedNodes, node.Serialize())
|
serializedNodes = append(serializedNodes, node.Serialize())
|
||||||
}
|
}
|
||||||
@@ -148,20 +150,20 @@ func MergeRels(
|
|||||||
rtype string,
|
rtype string,
|
||||||
startLabel string,
|
startLabel string,
|
||||||
endLabel string,
|
endLabel string,
|
||||||
matchProvider MatchKeysProvider,
|
matchProvider graph.MatchKeysProvider,
|
||||||
rels []*Relationship,
|
rels []*graph.Relationship,
|
||||||
) (*neo4j.ResultSummary, error) {
|
) (*neo4j.ResultSummary, error) {
|
||||||
cypherType := ToCypherLabel(rtype)
|
cypherType := cypher.ToCypherLabel(rtype)
|
||||||
startCypherLabel := ToCypherLabel(startLabel)
|
startCypherLabel := cypher.ToCypherLabel(startLabel)
|
||||||
endCypherLabel := ToCypherLabel(endLabel)
|
endCypherLabel := cypher.ToCypherLabel(endLabel)
|
||||||
|
|
||||||
matchKeys, _ := matchProvider.GetKeys(startLabel)
|
matchKeys, _ := matchProvider.GetKeys(startLabel)
|
||||||
startCypherProps := ToCypherProps(matchKeys, "rel.start.")
|
startCypherProps := cypher.ToCypherProps(matchKeys, "rel.start.")
|
||||||
|
|
||||||
matchKeys, _ = matchProvider.GetKeys(endLabel)
|
matchKeys, _ = matchProvider.GetKeys(endLabel)
|
||||||
endCypherProps := ToCypherProps(matchKeys, "rel.end.")
|
endCypherProps := cypher.ToCypherProps(matchKeys, "rel.end.")
|
||||||
|
|
||||||
serializedRels := []*SerializedRel{}
|
serializedRels := []*graph.SerializedRel{}
|
||||||
for _, rel := range rels {
|
for _, rel := range rels {
|
||||||
serializedRels = append(serializedRels, rel.Serialize())
|
serializedRels = append(serializedRels, rel.Serialize())
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package heartwood
|
package graphstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -10,10 +10,13 @@ func ConnectNeo4j(ctx context.Context, uri, user, password string) (neo4j.Driver
|
|||||||
driver, err := neo4j.NewDriver(
|
driver, err := neo4j.NewDriver(
|
||||||
uri,
|
uri,
|
||||||
neo4j.BasicAuth(user, password, ""))
|
neo4j.BasicAuth(user, password, ""))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = driver.VerifyConnectivity(ctx)
|
err = driver.VerifyConnectivity(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return driver, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return driver, nil
|
return driver, nil
|
||||||
42
graphstore/schema.go
Normal file
42
graphstore/schema.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package graphstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v6/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetNeo4jSchema ensures that the necessary indexes and constraints exist in
|
||||||
|
// the database
|
||||||
|
func SetNeo4jSchema(ctx context.Context, driver neo4j.Driver) error {
|
||||||
|
schemaQueries := []string{
|
||||||
|
`CREATE CONSTRAINT user_pubkey IF NOT EXISTS
|
||||||
|
FOR (n:User) REQUIRE n.pubkey IS UNIQUE`,
|
||||||
|
|
||||||
|
`CREATE INDEX user_pubkey IF NOT EXISTS
|
||||||
|
FOR (n:User) ON (n.pubkey)`,
|
||||||
|
|
||||||
|
`CREATE INDEX event_id IF NOT EXISTS
|
||||||
|
FOR (n:Event) ON (n.id)`,
|
||||||
|
|
||||||
|
`CREATE INDEX event_kind IF NOT EXISTS
|
||||||
|
FOR (n:Event) ON (n.kind)`,
|
||||||
|
|
||||||
|
`CREATE INDEX tag_name_value IF NOT EXISTS
|
||||||
|
FOR (n:Tag) ON (n.name, n.value)`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indexes and constraints
|
||||||
|
for _, query := range schemaQueries {
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver,
|
||||||
|
query,
|
||||||
|
nil,
|
||||||
|
neo4j.EagerResultTransformer,
|
||||||
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,37 +1,38 @@
|
|||||||
package heartwood
|
package heartwood
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.wisehodl.dev/jay/go-heartwood/graph"
|
||||||
roots "git.wisehodl.dev/jay/go-roots/events"
|
roots "git.wisehodl.dev/jay/go-roots/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EventToSubgraph(e roots.Event, exp ExpanderRegistry) *Subgraph {
|
func EventToSubgraph(e roots.Event, exp ExpanderRegistry) *graph.Subgraph {
|
||||||
subgraph := NewSubgraph()
|
subgraph := graph.NewSubgraph()
|
||||||
|
|
||||||
// Create Event node
|
// Create Event node
|
||||||
eventNode := NewEventNode(e.ID)
|
eventNode := graph.NewEventNode(e.ID)
|
||||||
eventNode.Props["created_at"] = e.CreatedAt
|
eventNode.Props["created_at"] = e.CreatedAt
|
||||||
eventNode.Props["kind"] = e.Kind
|
eventNode.Props["kind"] = e.Kind
|
||||||
eventNode.Props["content"] = e.Content
|
eventNode.Props["content"] = e.Content
|
||||||
|
|
||||||
// Create User node
|
// Create User node
|
||||||
userNode := NewUserNode(e.PubKey)
|
userNode := graph.NewUserNode(e.PubKey)
|
||||||
|
|
||||||
// Create SIGNED rel
|
// Create SIGNED rel
|
||||||
signedRel := NewSignedRel(userNode, eventNode, nil)
|
signedRel := graph.NewSignedRel(userNode, eventNode, nil)
|
||||||
|
|
||||||
// Create Tag nodes
|
// Create Tag nodes
|
||||||
tagNodes := []*Node{}
|
tagNodes := []*graph.Node{}
|
||||||
for _, tag := range e.Tags {
|
for _, tag := range e.Tags {
|
||||||
if !isValidTag(tag) {
|
if !isValidTag(tag) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tagNodes = append(tagNodes, NewTagNode(tag[0], tag[1]))
|
tagNodes = append(tagNodes, graph.NewTagNode(tag[0], tag[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Tag rels
|
// Create Tag rels
|
||||||
tagRels := []*Relationship{}
|
tagRels := []*graph.Relationship{}
|
||||||
for _, tagNode := range tagNodes {
|
for _, tagNode := range tagNodes {
|
||||||
tagRels = append(tagRels, NewTaggedRel(eventNode, tagNode, nil))
|
tagRels = append(tagRels, graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate subgraph
|
// Populate subgraph
|
||||||
@@ -2,6 +2,7 @@ package heartwood
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.wisehodl.dev/jay/go-heartwood/graph"
|
||||||
roots "git.wisehodl.dev/jay/go-roots/events"
|
roots "git.wisehodl.dev/jay/go-roots/events"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -21,21 +22,21 @@ var static = roots.Event{
|
|||||||
Content: "hello",
|
Content: "hello",
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFullEventNode(id string, createdAt, kind int, content string) *Node {
|
func newFullEventNode(id string, createdAt, kind int, content string) *graph.Node {
|
||||||
n := NewEventNode(id)
|
n := graph.NewEventNode(id)
|
||||||
n.Props["created_at"] = createdAt
|
n.Props["created_at"] = createdAt
|
||||||
n.Props["kind"] = kind
|
n.Props["kind"] = kind
|
||||||
n.Props["content"] = content
|
n.Props["content"] = content
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func baseSubgraph(eventID, pubkey string) (*Subgraph, *Node, *Node) {
|
func baseSubgraph(eventID, pubkey string) (*graph.Subgraph, *graph.Node, *graph.Node) {
|
||||||
s := NewSubgraph()
|
s := graph.NewSubgraph()
|
||||||
eventNode := newFullEventNode(eventID, static.CreatedAt, static.Kind, static.Content)
|
eventNode := newFullEventNode(eventID, static.CreatedAt, static.Kind, static.Content)
|
||||||
userNode := NewUserNode(pubkey)
|
userNode := graph.NewUserNode(pubkey)
|
||||||
s.AddNode(eventNode)
|
s.AddNode(eventNode)
|
||||||
s.AddNode(userNode)
|
s.AddNode(userNode)
|
||||||
s.AddRel(NewSignedRel(userNode, eventNode, nil))
|
s.AddRel(graph.NewSignedRel(userNode, eventNode, nil))
|
||||||
return s, eventNode, userNode
|
return s, eventNode, userNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
event roots.Event
|
event roots.Event
|
||||||
expected *Subgraph
|
expected *graph.Subgraph
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bare event",
|
name: "bare event",
|
||||||
@@ -51,7 +52,7 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
ID: ids["a"], PubKey: ids["b"],
|
ID: ids["a"], PubKey: ids["b"],
|
||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
@@ -63,11 +64,11 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"t", "bitcoin"}},
|
Tags: []roots.Tag{{"t", "bitcoin"}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
tagNode := NewTagNode("t", "bitcoin")
|
tagNode := graph.NewTagNode("t", "bitcoin")
|
||||||
s.AddNode(tagNode)
|
s.AddNode(tagNode)
|
||||||
s.AddRel(NewTaggedRel(eventNode, tagNode, nil))
|
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@@ -78,7 +79,7 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"t"}},
|
Tags: []roots.Tag{{"t"}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
@@ -90,14 +91,14 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"e", ids["c"]}},
|
Tags: []roots.Tag{{"e", ids["c"]}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
tagNode := NewTagNode("e", ids["c"])
|
tagNode := graph.NewTagNode("e", ids["c"])
|
||||||
referencedEvent := NewEventNode(ids["c"])
|
referencedEvent := graph.NewEventNode(ids["c"])
|
||||||
s.AddNode(tagNode)
|
s.AddNode(tagNode)
|
||||||
s.AddNode(referencedEvent)
|
s.AddNode(referencedEvent)
|
||||||
s.AddRel(NewTaggedRel(eventNode, tagNode, nil))
|
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
s.AddRel(NewReferencesEventRel(tagNode, referencedEvent, nil))
|
s.AddRel(graph.NewReferencesEventRel(tagNode, referencedEvent, nil))
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@@ -108,11 +109,11 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"e", "notvalid"}},
|
Tags: []roots.Tag{{"e", "notvalid"}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
tagNode := NewTagNode("e", "notvalid")
|
tagNode := graph.NewTagNode("e", "notvalid")
|
||||||
s.AddNode(tagNode)
|
s.AddNode(tagNode)
|
||||||
s.AddRel(NewTaggedRel(eventNode, tagNode, nil))
|
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@@ -123,14 +124,14 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"p", ids["d"]}},
|
Tags: []roots.Tag{{"p", ids["d"]}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
tagNode := NewTagNode("p", ids["d"])
|
tagNode := graph.NewTagNode("p", ids["d"])
|
||||||
referencedUser := NewUserNode(ids["d"])
|
referencedUser := graph.NewUserNode(ids["d"])
|
||||||
s.AddNode(tagNode)
|
s.AddNode(tagNode)
|
||||||
s.AddNode(referencedUser)
|
s.AddNode(referencedUser)
|
||||||
s.AddRel(NewTaggedRel(eventNode, tagNode, nil))
|
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
s.AddRel(NewReferencesUserRel(tagNode, referencedUser, nil))
|
s.AddRel(graph.NewReferencesUserRel(tagNode, referencedUser, nil))
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@@ -141,11 +142,11 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||||
Tags: []roots.Tag{{"p", "notvalid"}},
|
Tags: []roots.Tag{{"p", "notvalid"}},
|
||||||
},
|
},
|
||||||
expected: func() *Subgraph {
|
expected: func() *graph.Subgraph {
|
||||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||||
tagNode := NewTagNode("p", "notvalid")
|
tagNode := graph.NewTagNode("p", "notvalid")
|
||||||
s.AddNode(tagNode)
|
s.AddNode(tagNode)
|
||||||
s.AddRel(NewTaggedRel(eventNode, tagNode, nil))
|
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||||
return s
|
return s
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
@@ -163,7 +164,7 @@ func TestEventToSubgraph(t *testing.T) {
|
|||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
func nodesEqual(expected, got *Node) error {
|
func nodesEqual(expected, got *graph.Node) error {
|
||||||
// Compare label counts
|
// Compare label counts
|
||||||
if expected.Labels.Length() != got.Labels.Length() {
|
if expected.Labels.Length() != got.Labels.Length() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@@ -186,7 +187,7 @@ func nodesEqual(expected, got *Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func relsEqual(expected, got *Relationship) error {
|
func relsEqual(expected, got *graph.Relationship) error {
|
||||||
// Compare type
|
// Compare type
|
||||||
if expected.Type != got.Type {
|
if expected.Type != got.Type {
|
||||||
return fmt.Errorf("type: expected %q, got %q", expected.Type, got.Type)
|
return fmt.Errorf("type: expected %q, got %q", expected.Type, got.Type)
|
||||||
@@ -208,7 +209,7 @@ func relsEqual(expected, got *Relationship) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func propsEqual(expected, got Properties) error {
|
func propsEqual(expected, got graph.Properties) error {
|
||||||
if len(expected) != len(got) {
|
if len(expected) != len(got) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"number of props does not match. expected %d, got %d",
|
"number of props does not match. expected %d, got %d",
|
||||||
@@ -227,13 +228,13 @@ func propsEqual(expected, got Properties) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSubgraphsEqual(t *testing.T, expected, got *Subgraph) {
|
func assertSubgraphsEqual(t *testing.T, expected, got *graph.Subgraph) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
gotNodes := make([]*Node, len(got.Nodes()))
|
gotNodes := make([]*graph.Node, len(got.Nodes()))
|
||||||
copy(gotNodes, got.Nodes())
|
copy(gotNodes, got.Nodes())
|
||||||
|
|
||||||
gotRels := make([]*Relationship, len(got.Rels()))
|
gotRels := make([]*graph.Relationship, len(got.Rels()))
|
||||||
copy(gotRels, got.Rels())
|
copy(gotRels, got.Rels())
|
||||||
|
|
||||||
for _, expectedNode := range expected.Nodes() {
|
for _, expectedNode := range expected.Nodes() {
|
||||||
Reference in New Issue
Block a user