commit d7cc27f2bd19cdbc08e6bd9ecdeb08556c6b5ae2 Author: Maksim Syomochkin Date: Thu Dec 12 15:52:34 2024 +0300 inital commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf0150a --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Neo4j golang example +Команда запуска сервера в Docker для локальной разработки: +```sh +docker run -d --name neo4j -p7474:7474 -p7687:7687 -e NEO4J_AUTH=neo4j/password neo4j +``` +- На порту `7474` будет доступен вебинтерфейс Neo4j: http://localhost:7474 +- Порт `7687` используется для подключения к базе данных через Bolt-протокол. + - Про Bolt можно детальнее почитать [тут](https://neo4j.com/docs/bolt/current/bolt/) + +Запросы к neo4j пишутся на языке [Cypher](https://opencypher.org). +Ниже приведены примеры запросов для создания узлов и связей между ними, а также для поиска связанных узлов. +## Создание узлов с помощью MERGE +Запрос: +```cypher +MERGE (a:Person {name: $name, age: $age}) +``` +**Объяснение**: +- `MERGE`: Проверяет, существует ли узел с указанными свойствами. Если не существует, создаёт его. Если существует, повторного создания не будет. +- `(a:Person ...)`: Создаётся или ищется узел с меткой Person. Метка (label) описывает тип сущности (например, Person для человека). +- `{name: $name, age: $age}`: Свойства узла. name и age задаются через параметры (обозначены как $name и $age), значения которых передаются из кода. + +## Создание уникальных связей с помощью MERGE +Запрос: +```cypher +MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) +MERGE (a)-[:FRIENDS]->(b) +``` +**Объяснение**: +- `MATCH (a:Person {name: $name1})`: Ищет узел с меткой Person, у которого свойство name равно значению параметра $name1. Этот узел связывается с переменной a. +- `MATCH (b:Person {name: $name2})`: Аналогично, ищет узел Person с именем $name2 и связывает его с переменной b. +- `MERGE (a)-[:FRIENDS]->(b`): Проверяет, существует ли связь FRIENDS от узла a к узлу b. Если такая связь существует, ничего не делает. Если не существует, создаёт её. +- `[:FRIENDS]:` Определяет тип связи. В данном случае это "дружба". +- `->`: Направление связи. Стрелка указывает, что a дружит с b. + +## Поиск друзей конкретного человека +```cypher +MATCH (a:Person {name: $name})-[:FRIENDS]->(friend) +RETURN friend.name AS name, friend.age AS age +``` +**Объяснение**: +- `MATCH (a:Person {name: $name})`: Ищет узел Person с именем, равным значению $name. Этот узел связывается с переменной a. +- `-[:FRIENDS]->(friend)`: Находит все узлы, связанные с узлом a связью типа FRIENDS. Эти узлы связываются с переменной friend. +- `RETURN friend.name AS name, friend.age AS age`: Возвращает свойства найденных узлов: + - `friend.name` — имя друга. + - `friend.age` — возраст друга. +- `AS` используется для задания алиасов, чтобы упростить доступ к возвращённым данным. + +## Поиск кратчайшего пути между двумя узлами +Запрос: +```cypher +MATCH p = shortestPath((a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end})) +RETURN p +``` +**Объяснение**: +- `MATCH p = shortestPath((a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end}))`: Находит кратчайший путь `p` между узлами `Person`, где начальный узел имеет имя, заданное параметром `$start`, и конечный узел имеет имя, заданное параметром `$end`. Связь между узлами должна быть типа `FRIENDS`. +- `RETURN p`: Возвращает найденный путь `p`. + +## Поиск самого длинного пути между двумя узлами +Запрос: +```cypher +MATCH p = (a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end}) +RETURN p, length(p) AS pathLength +ORDER BY pathLength DESC +LIMIT 1 +``` +**Объяснение**: +- `MATCH p = (a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end})`: Находит все пути `p` между узлами `Person`, где начальный узел имеет имя, заданное параметром `$start`, и конечный узел имеет имя, заданное параметром `$end`. Связь между узлами должна быть типа `FRIENDS`. +- `RETURN p, length(p) AS pathLength`: Возвращает каждый найденный путь `p` и его длину в виде `pathLength`. +- `ORDER BY pathLength DESC`: Сортирует пути по длине в убывающем порядке, чтобы самые длинные пути шли первыми. +- `LIMIT 1`: Ограничивает результат одним самым длинным найденным путем. + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9441e44 --- /dev/null +++ b/go.mod @@ -0,0 +1,30 @@ +module github.com/maksim77/neo4j_example + +go 1.23.0 + +require ( + github.com/neo4j/neo4j-go-driver/v5 v5.27.0 + github.com/raito-io/neo4j-tracing v0.0.5 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fdb043 --- /dev/null +++ b/go.sum @@ -0,0 +1,55 @@ +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/neo4j/neo4j-go-driver/v5 v5.27.0 h1:YdsIxDjAQbjlP/4Ha9B/gF8Y39UdgdTwCyihSxy8qTw= +github.com/neo4j/neo4j-go-driver/v5 v5.27.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/raito-io/neo4j-tracing v0.0.5 h1:Z1eEQl1Imm0DFkR2yfMc7jVW+ix4oZxAVOAWPQBgI2Q= +github.com/raito-io/neo4j-tracing v0.0.5/go.mod h1:m0utJXW1BPoBdKZ1cVhpyVZ1ChWttj8pSVtka/5j63s= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..8230b91 --- /dev/null +++ b/main.go @@ -0,0 +1,230 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + neo4j_tracing "github.com/raito-io/neo4j-tracing" + "go.opentelemetry.io/otel" +) + +var tracer = otel.Tracer("neo4j_example") + +func main() { + shutdown, err := InstallExportPipeline() + if err != nil { + log.Fatal(err.Error()) + } + defer func() { + if err := shutdown(context.Background()); err != nil { + log.Fatal(err.Error()) + } + }() + + // Создаём общий контекст с таймаутом на 10 секунд + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ctx, span := tracer.Start(ctx, "main") + defer span.End() + + // Параметры подключения + uri := "neo4j://localhost:7687" + username := "neo4j" + password := "password" + + driverFactory := neo4j_tracing.NewNeo4jTracer() + driver, err := driverFactory.NewDriverWithContext(uri, neo4j.BasicAuth(username, password, "")) + if err != nil { + panic(err) + } + + // Создаем драйвер + // driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(username, password, "")) + // if err != nil { + // log.Fatalf("Ошибка подключения к Neo4j: %v", err) + // } + defer func() { + _ = driver.Close(ctx) + }() + + // Создаем сессию + session := driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer func() { + _ = session.Close(ctx) + }() + + // Запись данных + _, err = session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (any, error) { + // Создание пользователей + queries := []struct { + query string + params map[string]interface{} + }{ + {query: "MERGE (a:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Alice", "age": 30}}, + {query: "MERGE (b:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Bob", "age": 25}}, + {query: "MERGE (c:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Charlie", "age": 35}}, + {query: "MERGE (d:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Diana", "age": 28}}, + {query: "MERGE (e:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Eve", "age": 22}}, + {query: "MERGE (f:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Frank", "age": 40}}, + {query: "MERGE (g:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Grace", "age": 33}}, + {query: "MERGE (h:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Hank", "age": 29}}, + {query: "MERGE (i:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Ivy", "age": 31}}, + {query: "MERGE (j:Person {name: $name, age: $age})", params: map[string]interface{}{"name": "Jack", "age": 26}}, + } + + for _, q := range queries { + _, err := tx.Run(ctx, q.query, q.params) + if err != nil { + return nil, err + } + } + + // Создание связей + relationships := []struct { + query string + params map[string]interface{} + }{ + // Основные связи + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Alice", "name2": "Bob"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Bob", "name2": "Charlie"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Charlie", "name2": "Diana"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Diana", "name2": "Eve"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Eve", "name2": "Frank"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Frank", "name2": "Grace"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Grace", "name2": "Hank"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Hank", "name2": "Ivy"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Ivy", "name2": "Jack"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Jack", "name2": "Alice"}}, + + // Дополнительные связи для усложнения графа + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Alice", "name2": "Charlie"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Bob", "name2": "Eve"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Charlie", "name2": "Frank"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Diana", "name2": "Hank"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Eve", "name2": "Ivy"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Frank", "name2": "Jack"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Grace", "name2": "Alice"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Hank", "name2": "Bob"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Ivy", "name2": "Charlie"}}, + {query: "MATCH (a:Person {name: $name1}), (b:Person {name: $name2}) MERGE (a)-[:FRIENDS]->(b)", params: map[string]interface{}{"name1": "Jack", "name2": "Diana"}}, + } + + for _, r := range relationships { + _, err := tx.Run(ctx, r.query, r.params) + if err != nil { + return nil, err + } + } + + return nil, nil + }) + if err != nil { + log.Fatalf("Ошибка выполнения записи: %v", err) + } + + // Чтение данных: находим друзей для Alice + _, err = session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, + "MATCH (a:Person {name: $name})-[:FRIENDS]->(friend) "+ + "RETURN friend.name AS name, friend.age AS age", + map[string]interface{}{"name": "Alice"}) + if err != nil { + return nil, err + } + + // Обработка результата + fmt.Println("Друзья Alice:") + for result.Next(ctx) { + record := result.Record() + name, _ := record.Get("name") + age, _ := record.Get("age") + fmt.Printf("- %s (Возраст: %d)\n", name, age) + } + + return nil, result.Err() + }) + if err != nil { + log.Fatalf("Ошибка выполнения чтения: %v", err) + } + + // Чтение данных: находим кратчайший путь от Alice до Diana + result, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (any, error) { + query := ` + MATCH p = shortestPath((a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end})) + RETURN p + ` + params := map[string]interface{}{ + "start": "Alice", + "end": "Diana", + } + res, err := tx.Run(ctx, query, params) + if err != nil { + return nil, err + } + if res.Next(ctx) { + path, _ := res.Record().Get("p") + return path, nil + } + return nil, res.Err() + }) + if err != nil { + log.Fatalf("Ошибка выполнения запроса: %v", err) + } + + // Вывод результата: + if path, ok := result.(neo4j.Path); ok { + fmt.Println("Кратчайший путь от Alice до Diana:") + for _, node := range path.Nodes { + if name, exists := node.Props["name"].(string); exists { + fmt.Printf(" - %s\n", name) + } + } + } else { + fmt.Println("Путь не найден.") + } + + // Чтение данных: находим самый длинный путь от Alice до Diana + result, err = session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (any, error) { + query := ` + MATCH p = (a:Person {name: $start})-[:FRIENDS*]-(b:Person {name: $end}) + RETURN p, length(p) AS pathLength + ORDER BY pathLength DESC + LIMIT 1 + ` + params := map[string]interface{}{ + "start": "Alice", + "end": "Diana", + } + res, err := tx.Run(ctx, query, params) + if err != nil { + return nil, err + } + if res.Next(ctx) { + record := res.Record() + path, _ := record.Get("p") + return path, nil + } + return nil, res.Err() + }) + if err != nil { + log.Fatalf("Ошибка выполнения запроса: %v", err) + } + + // Вывод результата: + if path, ok := result.(neo4j.Path); ok { + fmt.Println("Самый длинный путь от Alice до Diana:") + for _, node := range path.Nodes { + if name, exists := node.Props["name"].(string); exists { + fmt.Printf(" - %s\n", name) + } + } + } else { + fmt.Println("Путь не найден.") + } + + fmt.Println("Работа завершена успешно!") +} diff --git a/otel.go b/otel.go new file mode 100644 index 0000000..836a4a1 --- /dev/null +++ b/otel.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +func InstallExportPipeline() (func(context.Context) error, error) { + traceClient := otlptracegrpc.NewClient( + otlptracegrpc.WithInsecure(), + otlptracegrpc.WithEndpoint("127.0.0.1:4317"), + ) + sctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + traceExp, err := otlptrace.New(sctx, traceClient) + if err != nil { + log.Fatal(err) + } + if err != nil { + return nil, fmt.Errorf("creating stdout exporter: %w", err) + } + + res, err := resource.New(context.Background(), + resource.WithFromEnv(), + resource.WithProcess(), + resource.WithTelemetrySDK(), + resource.WithHost(), + resource.WithAttributes( + semconv.ServiceNameKey.String("neo4j_example"), + ), + ) + if err != nil { + log.Fatal(err) + } + + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(traceExp), + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tracerProvider) + otel.SetTextMapPropagator(propagation.TraceContext{}) + + return tracerProvider.Shutdown, nil +}