Showing preview only (548K chars total). Download the full file or copy to clipboard to get everything.
Repository: florinpatrascu/bolt_sips
Branch: master
Commit: b21901a46ed1
Files: 153
Total size: 507.4 KB
Directory structure:
gitextract_6ctcdb7g/
├── .credo.exs
├── .dialyzer_ignore.exs
├── .formatter.exs
├── .gitattributes
├── .gitignore
├── .iex.exs
├── .markdownlint.json
├── .prettierrc.yaml
├── .tool-versions
├── .travis.yml
├── CHANGELOG.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── benchees/
│ └── conn_to_local_bench.exs
├── config/
│ ├── config.exs
│ ├── dev.exs
│ └── test.exs
├── docker-compose.yml
├── docs/
│ ├── examples/
│ │ └── readme.md
│ ├── features/
│ │ ├── about-encoding.md
│ │ ├── about-transactions.md
│ │ ├── configuration.md
│ │ ├── multi-tenancy.md
│ │ ├── role-based-connections.md
│ │ ├── routing.md
│ │ ├── using-cypher.md
│ │ ├── using-temporal-and-spatial-types.md
│ │ └── using-with-phoenix.md
│ └── getting-started.md
├── lib/
│ ├── bolt_sips/
│ │ ├── application.ex
│ │ ├── enumerable_response.ex
│ │ ├── error.ex
│ │ ├── exception.ex
│ │ ├── internals/
│ │ │ ├── bolt_protocol.ex
│ │ │ ├── bolt_protocol_helper.ex
│ │ │ ├── bolt_protocol_v1.ex
│ │ │ ├── bolt_protocol_v2.ex
│ │ │ ├── bolt_protocol_v3.ex
│ │ │ ├── bolt_version_helper.ex
│ │ │ ├── error.ex
│ │ │ ├── logger.ex
│ │ │ ├── pack_stream/
│ │ │ │ ├── decoder.ex
│ │ │ │ ├── decoder_impl_v1.ex
│ │ │ │ ├── decoder_impl_v2.ex
│ │ │ │ ├── decoder_utils.ex
│ │ │ │ ├── decoder_v1.ex
│ │ │ │ ├── decoder_v2.ex
│ │ │ │ ├── decoder_v3.ex
│ │ │ │ ├── encoder.ex
│ │ │ │ ├── encoder_helper.ex
│ │ │ │ ├── encoder_v1.ex
│ │ │ │ ├── encoder_v2.ex
│ │ │ │ ├── encoder_v3.ex
│ │ │ │ ├── error.ex
│ │ │ │ ├── markers.ex
│ │ │ │ ├── message/
│ │ │ │ │ ├── decoder.ex
│ │ │ │ │ ├── encoder.ex
│ │ │ │ │ ├── encoder_v1.ex
│ │ │ │ │ ├── encoder_v2.ex
│ │ │ │ │ ├── encoder_v3.ex
│ │ │ │ │ └── signatures.ex
│ │ │ │ ├── message.ex
│ │ │ │ ├── utils.ex
│ │ │ │ ├── v1.ex
│ │ │ │ └── v2.ex
│ │ │ └── pack_stream.ex
│ │ ├── metadata.ex
│ │ ├── protocol.ex
│ │ ├── query.ex
│ │ ├── query_statement.ex
│ │ ├── response.ex
│ │ ├── response_encoder/
│ │ │ ├── json/
│ │ │ │ ├── jason.ex
│ │ │ │ └── poison.ex
│ │ │ └── json.ex
│ │ ├── response_encoder.ex
│ │ ├── router.ex
│ │ ├── routing/
│ │ │ ├── connection_supervisor.ex
│ │ │ ├── load_balancer.ex
│ │ │ └── routing_table.ex
│ │ ├── socket.ex
│ │ ├── types.ex
│ │ ├── types_helper.ex
│ │ └── utils.ex
│ ├── bolt_sips.ex
│ └── mix/
│ └── tasks/
│ └── cypher.ex
├── mix.exs
├── requirements.txt
└── test/
├── bolt_sips/
│ ├── internals/
│ │ ├── bolt_protocol_all_bolt_version_test.exs
│ │ ├── bolt_protocol_bolt_v1_test.exs
│ │ ├── bolt_protocol_bolt_v2_test.exs
│ │ ├── bolt_protocol_bolt_v3_test.exs
│ │ ├── bolt_protocol_v1_test.exs
│ │ ├── bolt_protocol_v3_test.exs
│ │ ├── bolt_version_helper_test.exs
│ │ ├── logger_test.exs
│ │ └── pack_stream/
│ │ ├── decoder_test.exs
│ │ ├── decoder_v1_test.exs
│ │ ├── decoder_v2_test.exs
│ │ ├── encoder_helper_test.exs
│ │ ├── encoder_test.exs
│ │ ├── encoder_v1_test.exs
│ │ ├── encoder_v2_test.exs
│ │ ├── message/
│ │ │ ├── decoder_test.exs
│ │ │ ├── encoder_test.exs
│ │ │ ├── encoder_v1_test.exs
│ │ │ └── encoder_v3_test.exs
│ │ └── message_test.exs
│ ├── metadata_test.exs
│ ├── performance_test.exs
│ ├── protocol_test.exs
│ ├── response_encoder/
│ │ ├── json_implementations_test.exs
│ │ └── json_test.exs
│ ├── response_encoder_test.exs
│ ├── types_helpers_test.exs
│ └── types_test.exs
├── boltkit_test.exs
├── config_test.exs
├── errors_test.exs
├── invalid_param_type_test.exs
├── one_test.exs
├── query_bolt_v2_test.exs
├── query_test.exs
├── response_test.exs
├── router_test.exs
├── routing/
│ ├── connections_test.exs
│ ├── crud_test.exs
│ ├── routing_table_parser_test.exs
│ ├── routing_test.exs
│ └── transaction_test.exs
├── scripts/
│ ├── count.bolt
│ ├── create_a.script
│ ├── forbidden_on_read_only_database.script
│ ├── get_routing_table.script
│ ├── get_routing_table_with_context.script
│ ├── non_router.script
│ ├── return_1.script
│ ├── return_1_in_tx_twice.script
│ ├── return_1_twice.script
│ ├── return_x.bolt
│ ├── router.script
│ ├── router_no_readers.script
│ └── router_no_writers.script
├── support/
│ ├── boltkit_case.ex
│ ├── conn_case.ex
│ ├── conn_routing_case.ex
│ ├── database.ex
│ ├── fixture.ex
│ └── internal_case.ex
├── test_helper.exs
├── test_large_param_set.exs
├── test_support.exs
└── transaction_test.exs
================================================
FILE CONTENTS
================================================
================================================
FILE: .credo.exs
================================================
# This file contains the configuration for Credo.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
name: "default",
#
# these are the files included in the analysis
files: %{
#
# you can give explicit globs or simply directories
# in the latter case `**/*.{ex,exs}` will be used
included: ["lib/", "src/", "web/", "apps/"],
excluded: []
},
#
# The `checks:` field contains all the checks that are run. You can
# customize the parameters of any given check by adding a second element
# to the tuple.
#
# There are two ways of deactivating a check:
# 1. deleting the check from this list
# 2. putting `false` as second element (to quickly "comment it out"):
#
# {Credo.Check.Consistency.ExceptionNames, false}
#
checks: [
{Credo.Check.Consistency.ExceptionNames},
{Credo.Check.Consistency.LineEndings, false},
{Credo.Check.Consistency.MultiAliasImportRequireUse, false},
{Credo.Check.Consistency.ParameterPatternMatching},
{Credo.Check.Consistency.SpaceAroundOperators},
{Credo.Check.Consistency.SpaceInParentheses, false},
{Credo.Check.Consistency.TabsOrSpaces},
# For some checks, like AliasUsage, you can only customize the priority
# Priority values are: `low, normal, high, higher`
{Credo.Check.Design.AliasUsage, priority: :low},
# For others you can set parameters
{Credo.Check.Design.DuplicatedCode, mass_threshold: 16, nodes_threshold: 2},
{Credo.Check.Design.TagTODO, false},
{Credo.Check.Design.TagFIXME, false},
{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
{Credo.Check.Readability.ModuleAttributeNames},
{Credo.Check.Readability.ModuleDoc},
{Credo.Check.Readability.ModuleNames},
{Credo.Check.Readability.ParenthesesInCondition},
{Credo.Check.Readability.PredicateFunctionNames},
{Credo.Check.Readability.PreferImplicitTry},
{Credo.Check.Readability.RedundantBlankLines},
{Credo.Check.Readability.Specs, false},
{Credo.Check.Readability.StringSigils},
{Credo.Check.Readability.TrailingBlankLine},
{Credo.Check.Readability.TrailingWhiteSpace},
{Credo.Check.Readability.VariableNames},
{Credo.Check.Refactor.DoubleBooleanNegation},
{Credo.Check.Refactor.ABCSize, max_size: 50},
{Credo.Check.Refactor.CaseTrivialMatches, false},
{Credo.Check.Refactor.CondStatements},
{Credo.Check.Refactor.CyclomaticComplexity},
{Credo.Check.Refactor.FunctionArity},
{Credo.Check.Refactor.MatchInCondition},
{Credo.Check.Refactor.NegatedConditionsInUnless},
{Credo.Check.Refactor.NegatedConditionsWithElse},
{Credo.Check.Refactor.Nesting},
{Credo.Check.Refactor.PipeChainStart},
{Credo.Check.Refactor.CyclomaticComplexity},
{Credo.Check.Refactor.NegatedConditionsInUnless},
{Credo.Check.Refactor.NegatedConditionsWithElse},
{Credo.Check.Refactor.Nesting},
{Credo.Check.Refactor.UnlessWithElse},
{Credo.Check.Refactor.VariableRebinding},
{Credo.Check.Warning.BoolOperationOnSameValues},
{Credo.Check.Warning.IExPry},
{Credo.Check.Warning.IoInspect},
{Credo.Check.Warning.OperationOnSameValues},
{Credo.Check.Warning.OperationWithConstantResult},
{Credo.Check.Warning.UnusedEnumOperation},
{Credo.Check.Warning.UnusedFileOperation},
{Credo.Check.Warning.UnusedKeywordOperation},
{Credo.Check.Warning.UnusedListOperation},
{Credo.Check.Warning.UnusedPathOperation},
{Credo.Check.Warning.UnusedRegexOperation},
{Credo.Check.Warning.UnusedStringOperation},
{Credo.Check.Warning.UnusedTupleOperation}
]
}
]
}
================================================
FILE: .dialyzer_ignore.exs
================================================
[
~r/__impl__.*does\ not\ exist\./
]
================================================
FILE: .formatter.exs
================================================
# Used by "mix format" and to export configuration.
export_locals_without_parens = [
plug: 1,
plug: 2,
adapter: 1,
adapter: 2
]
[
inputs: [
"lib/**/*.{ex,exs}",
"test/**/*.{ex,exs}",
"mix.exs"
],
locals_without_parens: export_locals_without_parens,
export: [locals_without_parens: export_locals_without_parens]
]
================================================
FILE: .gitattributes
================================================
* text=auto
================================================
FILE: .gitignore
================================================
### Elixir template
# The directory Mix will write compiled artifacts to.
/_build
# If you run "mix test --cover", coverage assets end up here.
/cover
# The directory Mix downloads your dependencies sources to.
/deps
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
/bench/snapshots
/bench/graphs
/cover
### Vim template
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
### SublimeText template
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
*.sublime-project
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Tags template
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
!TAGS/
tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
cscope.files
cscope.out
cscope.in.out
cscope.po.out
/tmp
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
## Directory-based project format:
.idea/
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
.tags
.tags_sorted_by_file
### Erlang template
.eunit
*.plt
ebin
rel/example_project
.concrete/DEV_MODE
logs/
.vscode/
.elixir_ls/
# py related
*~
*.py[co]
__pycache__
.pytest_cache
*.egg-info
================================================
FILE: .iex.exs
================================================
try do
Code.eval_file(".iex.exs", "~")
rescue
Code.LoadError -> :rescued
end
alias Bolt.Sips.{Utils, Protocol, Router, ConnectionSupervisor, Response}
alias Bolt.Sips
Application.put_env(:tzdata, :autoupdate, :disabled)
# default port considered to be: 7687
test_config = [
# url: 'localhost',
url: "bolt://localhost",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 5,
max_overflow: 1,
# retry the request, in case of error - in the example below the retry will
# linearly increase the delay from 150ms following a Fibonacci pattern,
# cap the delay at 15 seconds (the value defined by the default `:timeout`
# parameter) and giving up after 3 attempts
retry_linear_backoff: [delay: 150, factor: 2, tries: 3],
read: [pool_size: 5, pool_overflow: 0],
write: [pool_size: 1, pool_overflow: 0]
]
Mix.shell().info([
:green,
"""
Optional, if needed for development (Sips is the alias for Bolt.Sips):
{:ok, _neo} = Sips.start_link(url: "bolt://neo4j:test@localhost")
conn = Sips.conn()
Examples:
Sips.query!(conn, "UNWIND range(1, 10) AS n RETURN n")
Sips.query!(conn, "RETURN 1 as n")
--- ✄ -------------------------------------------------
"""
])
================================================
FILE: .markdownlint.json
================================================
{
"MD013": false,
"MD030": false
}
================================================
FILE: .prettierrc.yaml
================================================
# .prettierrc or .prettierrc.yaml
trailingComma: "es5"
tabWidth: 2
semi: false
singleQuote: true
MD013: false
MD030: false
================================================
FILE: .tool-versions
================================================
erlang 24.1.7
elixir 1.13.0-otp-24
nodejs 12.6.0
#python 3.7.3
python 3.7.3 2.7.16
ruby 2.7.5
lua 5.3.5
terraform 0.15.4
direnv 2.20.0
================================================
FILE: .travis.yml
================================================
sudo: required
services: docker
language: elixir
matrix:
include:
- elixir: 1.7.4
otp_release: 21.2
env:
- NEO4J_VERSION=3.2.14
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=true
- BOLT_V3_EXCLUDED=true
- elixir: 1.7.4
otp_release: 21.2
env:
- NEO4J_VERSION=3.2.14
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=true
- BOLT_V3_EXCLUDED=true
- elixir: 1.7.4
otp_release: 21.2
env:
- NEO4J_VERSION=3.4.17
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=false
- BOLT_V3_EXCLUDED=true
- elixir: 1.7.4
otp_release: 21.2
env:
- NEO4J_VERSION=3.5.14
- BOLT_V1_EXCLUDED=true
- BOLT_V2_EXCLUDED=false
- BOLT_V3_EXCLUDED=false
- elixir: 1.8.2
otp_release: 21.2
env:
- NEO4J_VERSION=3.2.14
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=true
- BOLT_V3_EXCLUDED=true
- elixir: 1.8.2
otp_release: 21.2
env:
- NEO4J_VERSION=3.2.14
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=true
- BOLT_V3_EXCLUDED=true
- elixir: 1.8.2
otp_release: 21.2
env:
- NEO4J_VERSION=3.4.17
- BOLT_V1_EXCLUDED=false
- BOLT_V2_EXCLUDED=false
- BOLT_V3_EXCLUDED=true
- elixir: 1.8.2
otp_release: 21.2
env:
- NEO4J_VERSION=3.5.14
- BOLT_V1_EXCLUDED=true
- BOLT_V2_EXCLUDED=false
- BOLT_V3_EXCLUDED=false
- elixir: 1.8.2
otp_release: 21.2
env:
- NEO4J_VERSION=4.2.1
- BOLT_V1_EXCLUDED=true
- BOLT_V2_EXCLUDED=false
- BOLT_V3_EXCLUDED=false
exclude:
- elixir: 1.8
otp_release: 19.3
- elixir: 1.8
otp_release: 18.3
- elixir: 1.7
otp_release: 18.3
- elixir: 1.6
otp_release: 18.3
- elixir: 1.5
otp_release: 21.2
- elixir: 1.4
otp_release: 21.2
- elixir: 1.3
otp_release: 21.2
- elixir: 1.3
otp_release: 20.3
- elixir: 1.2
otp_release: 21.2
- elixir: 1.2
otp_release: 20.3
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-6
- ninja-build
cache:
directories:
- $HOME/cmake
env:
global:
- ELIXIR_ERL_OPTIONS="+T 9"
- PATH=$HOME/cmake/bin:$PATH
- CXX=g++-6
- CC=gcc-6
branches:
only:
- master
before_install:
- if [ ! -d "$HOME/cmake/bin" ]; then wget --no-check-certificate https://cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.sh && sh cmake-3.5.2-Linux-x86_64.sh --prefix=$HOME/cmake --exclude-subdir; fi
- docker run --name neo4j -d -p 7687:7687 -e 'NEO4J_AUTH=neo4j/test' neo4j:$NEO4J_VERSION
- docker logs -f neo4j | sed /Bolt\ enabled/q
script:
- mix test --exclude routing --exclude bolt_v1:$BOLT_V1_EXCLUDED --exclude bolt_v2:$BOLT_V2_EXCLUDED --exclude bolt_v3:$BOLT_V3_EXCLUDED
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 2.1.0
Thank you https://github.com/zediogoviana, for the following improvements:
- Add configurable SSL options
- Fix local connection
- Keep the :ssl keyword to manage the options also
Dependencies updated, paving the road to switching to latest Elixir/Erlang combo.
## 2.0.11
- Issue #100: Timeout set in config in now used by queries
- DBConnection, bump dependencies
## 2.0.10
- Fix temporal types usage: microseconds are not fully available
- Review to pass test on Neo4j 4 :
- test and doctests to use new parameter syntax (using {} is deprecated in Neo4j 4)
- `toUpper` instead of `upper`
## 2.0.9
- fix: (Bolt.Sips.Exception) unable to encode value: -128, see: https://boltprotocol.org/v1/#ints, for details. Closes #93 Thank you, @kalamarski-marcin
## 2.0.8
- Fix Response.profile not being properly filled. Closes #91
## 2.0.7
- sometimes the server version is missing the patch number, and the router couldn't return the proper version. Thank you @barry-w-hill, for finding this bug and reporting it!
- remove the `basic_auth` when using the `&Bolt.Sips.info/0` function. Thanks @dominique-vassard, for suggestion. Closes: #89
## 2.0.6
- Fix 'unused alias' compilation warnings
- Fix Bolt.Sips.Response type: `stats` was a `list` instead of `list|map`
- Add typespec for Bolt.Sips.Types: Node, Relationship and UnboundRelationship
## 2.0.5
- fix #83. More details in commit: https://github.com/florinpatrascu/bolt_sips/commit/ebe17e62ab1d823e301b11d99d532663b0b25135 Thank you @kristofka!
## 2.0.4
- feature: support connection options in queries PR #82. Many thanks @tcrossland, for this contribution!
This PR adds support for passing options through to DBConnection.execute/4
- fix some broken links, in the docs; closes #76
- update some dependencies, including the DBConnection package.
- squashing some compile warnings; to be continued /attn: @team ;)
- please use Elixir 1.9 or 1.10, for test and development - where possible.
## 2.0.3
- refactoring the internals for achieving a better performance, while improving the code readability and extensibility - many thanks to @kristofka and @dominique-vassard. You guys are awesome!
- fix: Consistent bad connection state after malformed query [...] issue #78
## === 2.0.2 ===
- The 2.0, stable release. Thank you all for your feedback and for contributing to making this driver better. w⦿‿⦿t!
- fix: Simple Query taking too much time to process #73
## 2.0.0-rc.2
- swapping the assets around, for better organizing the docs
## 2.0.0-rc.1
- more documentation
- fix the TravisCi build
- min versions
erlang 21.2
elixir 1.7
## === 2.0.0-rc ===
## What's New?
### `bolt+routing://` is now supported
Read more what this schema is, as defined by the [Neo4j team](https://neo4j.com/developer/kb/how-neo4j-browser-bolt-routing/)
### Role-based connections
Until this version, Bolt.Sips was used for connecting to a single Neo4j server, aka: the "direct" mode. Basically you configure the driver with a url to a Neo4j server and Bolt.Sips will use that to attach itself to it, using a single configuration, remaining attached to that server until it is restarted (or reconfigured). In direct mode, bolt_sips "knows" only one server.
Starting with this version you can have as many distinct connection configurations, each of them dedicated to different Neo4j servers, as/if needed. We call these connections: "role-based connections". For example, when you'll connect to a Neo4j cluster using the new protocol, i.e. by using a configuration like this:
config :bolt_sips, Bolt,
# default port considered to be: 7687
url: "bolt+routing://localhost",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10
Bolt.Sips will automatically create three pools of size 10, with the following **reserved** names: `:read`, `:write` and `:route`. Now you can specify what type of connection you want to use, by its name (role). For example:
wconn = Bolt.Sips.conn(:write)
... = Bolt.Sips.query!(wconn, "CREATE (a:Person {name:'Bob'})")
rconn = Bolt.Sips.conn(:read)
... = Bolt.Sips.query!(rconn, "MATCH (a:Person {name: 'Bob'}) RETURN a.name AS name")
The roles above: `:read`, `:write` and `:route`, are reserved. Please do not name custom connections using the same names (atoms). And as you just realized, yes: now you can create as many Bolt.Sips **direct** "driver instances" as you want, or as many as your app/hardware supports.
Please see the documentation for much more details.
### Main breaking changes introduced in version 2.x
- the `hostname` config parameter is a string; used to be a charlist
- the `url` config parameter must start with a valid schema i.e. `bolt`, `bolt+routing` or `neo4j`.
Examples:
url: "bolt://localhost"
url: "bolt+routing://neo4j:password@neo01.graph.example.com:123456?policy=europe"
- Bolt.Sips.Query, will return a Bolt.Sips.Response now; it used to be a simple data structure.
## === 1.5 ===
## 1.5.1
- add a test alias for running the tests compatible with the most recent Neo4j server while
disabling the older/legacy ones
- cleanup some warning about unused aliases
## 1.5.0
- Bolt V3 support
- Decompose tests by bolt version
- Important note about transaction
## 1.4.0
- Encoding / Decoding types is now at the lowest possible level
- Decompose encoders / decoders by bolt version
- Expose only public API in docs
## 1.3.0
- 1.3.0 stable release. Many thanks to Dominique VASSARD, for his awesome contributions.
## 1.3.0-rc2
- Fix some typos
- add json encoding capability
## 1.2.2-rc2
- Bug fix: Nanoseconds formating was erroneous. Example: 54 nanoseconds was formated to "PT0.54S" instead of "PT0.000000054S"
- Bug fix: Large amount of nanoseconds (>= 1_000_000) wasn't treated and lead to Neo4j errors. Now large amount of nanoseconds are converted in seconds, with the remainder in nanoseconds.
## 1.2.1-rc2
- Bug fix: If a property contains a speciifc types (date, datetime, point, etc.), it wasn't decoded. see: https://github.com/florinpatrascu/bolt_sips/issues/55
## 1.2.0-rc2
- support for the spatial and temporal types.
## 1.1.0-rc2
- removed the `boltex` dependency and added all its "low-level" code to `internals`.
## 1.0.0-rc2
### Breaking changes introduced in version 1.x
- non-closure based transactions are not supported anymore. This is a change introduced in DBConnection 2.x. `Bolt.Sips` version tagged `v0.5.10` is the last version supporting open transactions.
- the support for ETLS was dropped. It was mostly used for development or hand-crafted deployments
This version is using the official [DBConnection 2.0.0-rc2](https://hex.pm/packages/db_connection/2.0.0-rc.0), from [hex.pm](https://hex.pm)
## 0.5.10
- update the links referencing the Bolt protocol documentation (types, etc)
## 0.5.9
- upgrade dependencies
- trading carefully around the new db_connection, as we're chasing the code from `master` currently, and there more changes in the pipe to come for the both projects; db_connection, and this one, respectively.
## 0.5.8
- dealing with negative integers see issue #42, for more details
## 0.5.7
- elixir 1.6 and code formatting, of course :)
- minor test updates
- update dependencies
- pending code for the newest `db_connection` (currently using db_connection from the master branch)
## 0.5.5
- using the [DBConnection](https://hexdocs.pm/db_connection/DBConnection.html), thanks to the work done by Dmitriy Nesteryuk.
## 0.4.11
- using Elixir 1.5
- not using the ConCache anymore. I initially intended to use its support throughout the driver, but it is not needed.
- README updated with a short snippet from a Phoenix web app demo, showing how to start Bolt.Sips, as a worker
- dependencies update
- minor code cleanup, to prep the code for receiving HA and Bolt routing capabilities
## v0.3.5
- better error messages; issue #33
- not retrying a connection when the server is not available/started
- incorrect number of retries, performed by the driver in case of errors; was one extra
## v0.3.4
- dependencies update, minor code cleanup, listening to Credo :) and finally using a Markdown linter
## v0.3.3
- Add link to travis build; #31 by vic
## v0.3.2
- Use the project's own configuration file when executing the `bolt.cypher` mix task. Fixes issue #20
## v0.3.1 Breaking changes
- rollback/refactor to optionally allow external configuration options to be defined at runtime. You must start the Bolt.Sips manually, when needed, i.e. `Bolt.Sips.start_link(url: "localhost")`, or by changing your app's mix config file, i.e.
```elixir
def application do
[applications: [:logger, :bolt_sips],
mod: {Bolt.Sips.Application, []}]
end
```
You can also specify custom configuration settings in you app's mix config file. These may overwrite your config file:
```elixir
def application do
[extra_applications: [:logger], mod:
{Bolt.Sips.Application, [url: 'localhost', pool_size: 15]}
]
end
```
- code cleanup
## v0.2.6 (2017-04-21)
- cleanup, and minor dependencies update
## v0.2.5 (2017-03-22)
- split multi-line Cypher statements containing semicolons only if the `;` character is at the end of the line, followed by \r\n on Windows and \n on Unix like system, otherwise it may break the Cypher statement when the semicolon appears somewhere else
## v0.2.4 (2017-02-26)
- add the fuzzyurl to the list of apps, for project using Elixir < 1.4 (thank you, @dnesteryuk!)
## v0.2.3 (2017-02-26)
- improved connection handling
## v0.2.2 (2017-02-24)
- PR #18; Bring up `:boltex` and `:retry` in `applications`, for Elixir < 1.4 (from: @wli0503, thank you!)
- PR #19; test for error message on invalid parameter types (from: @vic, thank you!).
## v0.2.1 (2017-02-20)
- stop retrying a request if the failure is an internal one (driver, or driver dependencies related).
- update the Boltex driver containing two important bug fixes: one where Boltex will fail when receiving too much data (florinpatrascu/bolt_sips/issues/16) and the other one, an improvement, make Boltex.Error.get_id/1 more resilient for new transports (details here: mschae/boltex/issues/14)
- changed the pool strategy to :fifo, and its timeout to :infinity, and let the (:gen_server) call timeout expire according to the user's :timeout configuration parameter
- added a test unit provided by @adri (thank you), for executing a Cypher query, with large set of parameters
## v0.2.0 Breaking changes
- Elixir 1.4 is now required.
- Using Boltex 0.2.0
- bugfix: invalid Cypher statements will now be properly handled when the request is retried automatically
## v0.1.11
- With a larger amount of parameters it seems like generating chunks isn't working correctly. This is a patch imported from Boltex, see: https://github.com/mschae/boltex/issues/13, for more info
## v0.1.10 (2017-02-11)
- accept Map and Struct for query parameters, transparently. Thank you [@wli0503], for the PR.
## v0.1.9 (2017-01-27)
Some of the users are encountering difficulties when trying to compile bolt_sips on Windows. This release is addressing their concern.
`Bolt.Sips` will use the optional System variable: `BOLT_WITH_ETLS`, for depending on the [ETLS](https://hex.pm/packages/etls) package. If that variable is not defined, then `Bolt.Sips` will use the standard Erlang [`:ssl` module](http://erlang.org/doc/man/ssl.html), for the SSL/TLS protocol; the default behavior, starting with this version.
Therefore, if you want the **much** faster ssl/tls support offered by ETLS, then use this: `export BOLT_WITH_ETLS=true` on Linux/OSX, for example. Then:
```elixir
mix deps.get
mix test
```
and so on.
(Don't forget to `mix deps.unlock --all`, if you plan to plan to further debugging/developing w/ or w/o the `BOLT_WITH_ETLS` support)
Many thanks to: [Ben Wilson](https://elixir-lang.slack.com/team/benwilson512), for advices.
## v0.1.8 (2017-01-07)
- using Elixir 1.4
- add more details to the README, about the components required to build ETLS, the TCP/TLS layer
- added newer Elixirs to the Travis CI configuration file
- minor code cleanups
## v0.1.7 (2017-01-02)
- Connection code refactored for capturing the errors when the remote server is not responding on the first request, or if the driver is misconfigured i.e. wrong port number, bad hostname ...
- updated the test configuration file with detailed info about the newly introduced option: `:retry_linear_backoff`, mostly as a reminder
## v0.1.6 (2017-01-01)
- we're already using configurable timeouts, when executing requests from the connection pool. But with Bolt, the initial handshake sequence (happening before sending any commands to the server) is represented by two important calls, executed in sequence: `handshake` and `init`, and they must both succeed, before sending any (Cypher) requests. You can see the details in the [Bolt protocol](http://boltprotocol.org/v1/#handshake) specs. This sequence is also sensitive to latencies, such as: network latencies, busy servers, etc., and because of that we're introducing a simple support for retrying the handshake (and the subsequent requests) with a linear backoff, and try the handshake sequence (or the request) a couple of times before giving up - all these as part of the exiting pool management, of course. This retry is configurable via a new configuration parameter, the: `:retry_linear_backoff`, respectively. For example:
```elixir
config :bolt_sips, Bolt,
url: "bolt://Bilbo:Baggins@hobby-hobbits.dbs.graphenedb.com:24786",
ssl: true,
timeout: 15_000,
retry_linear_backoff: [delay: 150, factor: 2, tries: 3]
```
In the example above the retry will linearly increase the delay from 150ms following a Fibonacci pattern, cap the delay at 15 seconds (the value defined by the `:timeout` parameter) and giving up after 3 attempts. The same retry mechanism (and configuration parameters) is also honored when we send requests to the neo4j server.
## v0.1.5 (2016-12-30)
- as requested by many users, this version is introducing the optional `url` configuration parameter. If present, it will be used for extracting the host name, the port and the authentication details. Please see the README, for a couple of examples. For brevity:
```elixir
config :bolt_sips, Bolt,
url: 'bolt://demo:demo@hobby-wowsoeasy.dbs.graphenedb.com:24786',
ssl: true
```
## v0.1.4 (Merry Christmas)
- add support for connecting to Neo4j servers on encrypted sockets. Currently only TLSv1.2 is supported, using the default [BoringSSL](https://boringssl.googlesource.com/boringssl/) cipher; via [:etls](https://github.com/kzemek/etls). To connect securely to a remote Neo4j server, such as the ones provided by graphenedb.com, modify your Bolt.Sips config file like this (example):
```elixir
config :bolt_sips, Bolt,
hostname: 'bolt://hobby-blah.dbs.graphenedb.com',
basic_auth: [username: "wow", password: "of_course_this_is_the_password"],
port: 24786,
pool_size: 5,
ssl: true,
max_overflow: 1
```
Observe the new flag: `ssl: true`
Please note this is work in progress
## v0.1.2 (2016-11-06)
- integrate the Boltex code from https://github.com/mschae/boltex, and let the Bolt.Sips wrapper to manage the connectivity, using a simple Poolboy implementation for connection pooling
## v0.1.1 (2016-09-09)
- a temporary solution for dealing with negative values while extracting a graph walk-through from a Path. Dealing with this in Boltex instead, but this fix should work for now.
## v0.1.0 (2016-08-31)
First release!
================================================
FILE: ISSUE_TEMPLATE.md
================================================
# Precheck
* For bugs, do a quick search and make sure the bug has not yet been reported.
* Finally, be nice and have fun!
## Environment
* Elixir version (elixir -v):
* Neo4j and version (Neo4j 3.5.3, etc.):
* Connection scheme (`bolt://`, `bolt+routing://` or `neo4j://`):
* Bolt.Sips version (mix deps):
* Operating system:
## Current behavior
Include code samples, errors and stacktraces if appropriate.
## Expected behavior
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
<img src="assets/logo_transparent.png" alt="logo" width="240"/>
# Neo4j driver for Elixir.
[](https://travis-ci.org/florinpatrascu/bolt_sips)
[](https://hex.pm/packages/bolt_sips)
[](https://hexdocs.pm/bolt_sips)
`Bolt.Sips` is an Elixir driver for [Neo4j](https://neo4j.com/developer/graph-database/), providing many useful features:
- using the Bolt protocol, the Elixir implementation - the Neo4j's newest network protocol, designed for high-performance; latest Bolt versions, are supported.
- Can connect to a standalone Neo4j server (`:direct` mode) or to a Neo4j causal cluster, using the `bolt+routing` or the newer `neo4j` schemes; connecting in `:routing` mode.
- Provides the user with the ability to create and manage distinct ad-hoc `role-based` connections to one or more Neo4j servers/databases
- Supports transactions, simple and complex Cypher queries with or w/o parameters
- Multi-tenancy
- Supports Neo4j versions: 3.0.x/3.1.x/3.2.x/3.4.x/3.5.x/4.0.x/4.1.x/4.2.x
Notes:
- Regarding Neo4j 4, stream capabilities are not yet supported.
- If you're seeking a substitute driver, here's a compilation of repositories:
- https://github.com/sagastume/boltx
## Table of Contents
- [Installation](#installation)
- [Getting Started](docs/getting-started.md#starting-the-driver)
- [Basic usage](docs/getting-started.md#usage)
- [Configuration](docs/features/configuration.md)
- [Direct mode](docs/features/configuration.md#direct-mode)
- [Routing](docs/features/configuration.md#routing-mode)
- [Role-based connections](docs/features/configuration.md#role-based-connections)
- [Multi tenancy](docs/features/configuration.md#multi-tenancy)
- [Using Cypher](docs/features/using-cypher.md)
- [Temporal and spatial types](docs/features/using-temporal-and-spatial-types.md)
- [Transactions](docs/features/about-transactions.md)
- [Encoding](docs/features/about-encoding.md)
- [Routing, in detail](docs/features/routing.md)
- [Multi tenancy, in detail](docs/features/multi-tenancy.md)
- [Using Bolt.Sips with Phoenix](docs/features/using-with-phoenix.md)
- [More examples](docs/examples/readme.md)
### Installation
[Available in Hex](https://hex.pm/packages/bolt_sips), the package can be added to your list of dependencies, in the: `mix.exs`:
```elixir
def deps do
[{:bolt_sips, "~> 2.0"}]
end
```
### Basic usage
Provided you have access to a running Neo4j server, and a project where you just added the `:bolt_sips` dependency, run an `iex` session inside the project's folder, and once inside the shell, follow this simple step-by-step example.
Start an iex session:
```elixir
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex> {:ok, _neo} = Bolt.Sips.start_link(url: "bolt://neo4j:test@localhost")
{:ok, #PID<0.237.0>}
iex> conn = Bolt.Sips.conn()
#PID<0.242.0>
iex> Bolt.Sips.query!(conn, "return 1 as n") |>
...> Bolt.Sips.Response.first()
%{"n" => 1}
```
Please see the docs for more examples and details about this driver.
### Testing
You'll need a running Neo4j server, for running the tests. Please verify that you do not store critical data on this server, as its contents will be wiped clean when the tests are running.
If you have docker available on your system, you can start an instance before running the test suite:
```shell
docker run --rm -p 7687:7687 -e 'NEO4J_AUTH=neo4j/test' neo4j:3.0.6
```
Neo4j versions used for test: 3.0, 3.1, 3.4, 3.5
```shell
mix test
```
For the stubs using [boltkit](https://github.com/neo4j-drivers/boltkit/), you will have to install Python 3.7 and run: `pip install boltkit`. After this you can run any tests tagged with `:boltkit`. Example:
```shell
mix test test/boltkit_test.exs --include boltkit
```
or:
```shell
mix test --only boltkit
```
### Special thanks
- Michael Schaefermeyer (@mschae), for the initial version of the Bolt protocol in Elixir: [mschae/boltex](https://github.com/mschae/boltex)
### Contributors
As reported by Github: [contributions to master, excluding merge commits](https://github.com/florinpatrascu/bolt_sips/graphs/contributors)
### Contributing
- [Fork it](https://github.com/florinpatrascu/bolt_sips/fork)
- Create your feature branch (`git checkout -b my-new-feature`)
- Test (`mix test`)
- Commit your changes (`git commit -am 'Add some feature'`)
- Push to the branch (`git push origin my-new-feature`)
- Create new Pull Request
### License
```txt
Copyright 2016-2020 the original author or authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
================================================
FILE: benchees/conn_to_local_bench.exs
================================================
# benchmark the time it takes to open connections to a local neo4j server.
# Misc...
#
# What I want:
#
# - I want to measure the time it takes to open a connection and run
# a simple query
# - I want to demonstrate how saving and reusing a connection (where
# applicable) takes less time compared with a similar code where
# I'm creating a new connection for every query
#
# Sample from a quick run:
#
# $ mix run benchees/conn_to_local_bench.exs
#
# Operating System: macOS
# CPU Information: Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
# Number of Available Cores: 8
# Available memory: 17.179869184 GB
# Elixir 1.5.0
# Erlang 20.0
# Benchmark suite executing with the following configuration:
# warmup: 2.00 s
# time: 1.00 s
# parallel: 1
# inputs: none specified
# Estimated total run time: 6.00 s
#
# Benchmarking new conn...
# Benchmarking same conn...
#
# Name ips average deviation median
# same conn 1.46 K 0.68 ms ±20.73% 0.64 ms
# new conn 0.47 K 2.15 ms ±67.17% 1.98 ms
#
# Comparison:
# same conn 1.46 K
# new conn 0.47 K - 3.14x slower
{:ok, _pid} = Bolt.Sips.start_link(url: "localhost")
simple_cypher = """
MATCH (p:Person)-[r:WROTE]->(b:Book {title: 'The Name of the Wind'})
RETURN p
"""
query = fn (conn, cypher) ->
Bolt.Sips.Query.query(conn, cypher)
end
conn = Bolt.Sips.conn()
Benchee.run(
%{
"same conn" => fn -> query.(conn, simple_cypher) end,
" new conn" => fn -> query.(Bolt.Sips.conn(), simple_cypher) end
}, time: 1)
================================================
FILE: config/config.exs
================================================
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
import Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure for your application as:
#
# config :bolt_sips, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:bolt_sips, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
import_config "#{Mix.env()}.exs"
================================================
FILE: config/dev.exs
================================================
import Config
config :mix_test_watch,
clear: true
level =
if System.get_env("DEBUG") do
:debug
else
:info
end
config :bolt_sips,
log: false,
log_hex: false
config :logger, :console,
level: level,
format: "$date $time [$level] $metadata$message\n"
config :tzdata, :autoupdate, :disabled
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
config :eye_drops,
tasks: [
%{
id: :docs,
name: "docs",
run_on_start: true,
cmd: "mix docs",
paths: ["lib/*", "README.md", "examples/*", "mix.exs"]
}
]
================================================
FILE: config/test.exs
================================================
import Config
config :bolt_sips, Bolt,
# default port considered to be: 7687
url: "bolt://localhost",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10,
max_overflow: 2,
queue_interval: 500,
queue_target: 1500,
prefix: :default
level =
if System.get_env("DEBUG") do
:debug
else
:info
end
config :bolt_sips,
log: true,
log_hex: false
config :logger, :console,
level: level,
format: "$date $time [$level] $metadata$message\n"
config :mix_test_watch,
clear: true
config :tzdata, :autoupdate, :disabled
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
config :porcelain, driver: Porcelain.Driver.Basic
================================================
FILE: docker-compose.yml
================================================
version: "3.0"
networks:
lan:
services:
core1:
container_name: core1
image: neo4j:3.5.3-enterprise
networks:
- lan
ports:
- 7474:7474
- 6477:6477
- 7687:7687
environment:
- NEO4J_AUTH=neo4j/test
- NEO4J_dbms_mode=CORE
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
- NEO4J_causalClustering_expectedCoreClusterSize=3
- NEO4J_causalClustering_initialDiscoveryMembers=core1:5000,core2:5000,core3:5000
- NEO4J_dbms_connector_http_listen__address=:7474
- NEO4J_dbms_connector_https_listen__address=:6477
- NEO4J_dbms_connector_bolt_listen__address=:7687
- NEO4J_dbms_memory_heap_initial__size=300m
- NEO4J_dbms_memory_heap_max__size=300m
- NEO4J_dbms_logs_query_enabled=true
- NEO4J_dbms_logs_query_page__logging__enabled=false
- NEO4J_dbms_logs_query_parameter__logging__enabled=true
- NEO4J_dbms_logs_query_threshold=0
core2:
container_name: core2
image: neo4j:3.5.3-enterprise
networks:
- lan
ports:
- 7475:7475
- 6478:6478
- 7688:7688
environment:
- NEO4J_AUTH=neo4j/test
- NEO4J_dbms_mode=CORE
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
- NEO4J_causalClustering_expectedCoreClusterSize=3
- NEO4J_causalClustering_initialDiscoveryMembers=core1:5000,core2:5000,core3:5000
- NEO4J_dbms_connector_http_listen__address=:7475
- NEO4J_dbms_connector_https_listen__address=:6478
- NEO4J_dbms_connector_bolt_listen__address=:7688
- NEO4J_dbms_memory_heap_initial__size=300m
- NEO4J_dbms_memory_heap_max__size=300m
- NEO4J_dbms_logs_query_enabled=true
- NEO4J_dbms_logs_query_page__logging__enabled=false
- NEO4J_dbms_logs_query_parameter__logging__enabled=true
- NEO4J_dbms_logs_query_threshold=0
core3:
container_name: core3
image: neo4j:3.5.3-enterprise
networks:
- lan
ports:
- 7476:7476
- 6479:6479
- 7689:7689
environment:
- NEO4J_AUTH=neo4j/test
- NEO4J_dbms_mode=CORE
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
- NEO4J_causalClustering_expectedCoreClusterSize=3
- NEO4J_causalClustering_initialDiscoveryMembers=core1:5000,core2:5000,core3:5000
- NEO4J_dbms_connector_http_listen__address=:7476
- NEO4J_dbms_connector_https_listen__address=:6479
- NEO4J_dbms_connector_bolt_listen__address=:7689
- NEO4J_dbms_memory_heap_initial__size=300m
- NEO4J_dbms_memory_heap_max__size=300m
- NEO4J_dbms_logs_query_enabled=true
- NEO4J_dbms_logs_query_page__logging__enabled=false
- NEO4J_dbms_logs_query_parameter__logging__enabled=true
- NEO4J_dbms_logs_query_threshold=0
================================================
FILE: docs/examples/readme.md
================================================
================================================
FILE: docs/features/about-encoding.md
================================================
# About encoding
Bolt.Sips provides support for encoding your query result in different formats.
For now, only JSON is supported.
There is two way of encoding data to json:
- By using the helpers provided by the module `Bolt.Sips.ResponseEncoder`
- Using your usual JSON encoding library. `Bolt.Sips` have implementation for: Jason and Poison. With this the query results can be automatically encoded by one of the libraries available: Jason or Poison. No further work is required when using a framework like: Phoenix, for example.
A few examples around the encoding suport:
```elixir
iex> query_result = [
%{
"t" => %Bolt.Sips.Types.Node{
id: 26,
labels: ["Test"],
properties: %{
"created_at" => "2019-08-03T12:34:56+01:00",
"name" => "A test node",
"uid" => 12345
}
}
}
]
# Using Bolt.Sips.ResponseEncoder
iex> Bolt.Sips.ResponseEncoder.encode(query_result, :json)
{:ok,
"[{\"t\":{\"id\":26,\"labels\":[\"Test\"],\"properties\":{\"created_at\":\"2019-08-03T12:34:56+01:00\",\"name\":\"A test node\",\"uid\":12345}}}]"}
iex(11)> Bolt.Sips.ResponseEncoder.encode!(query_result, :json)
"[{\"t\":{\"id\":26,\"labels\":[\"Test\"],\"properties\":{\"created_at\":\"2019-08-03T12:34:56+01:00\",\"name\":\"A test node\",\"uid\":12345}}}]"
# Using Jason
iex(14)> Jason.encode!(query_result)
"[{\"t\":{\"id\":26,\"labels\":[\"Test\"],\"properties\":{\"created_at\":\"2019-08-03T12:34:56+01:00\",\"name\":\"A test node\",\"uid\":12345}}}]"
# Using Poison
iex(13)> Poison.encode!(query_result)
"[{\"t\":{\"properties\":{\"uid\":12345,\"name\":\"A test node\",\"created_at\":\"2019-08-03T12:34:56+01:00\"},\"labels\":[\"Test\"],\"id\":26}}]"
```
Both solutions rely on protocols, then they can be easily overridden if needed.
More info in the modules `Bolt.Sips.ResponseEncoder.Json`, `Bolt.Sips.ResponseEncoder.Json.Jason`, `Bolt.Sips.ResponseEncoder.Json.Poison`
================================================
FILE: docs/features/about-transactions.md
================================================
# About transactions
Transaction management in Neo4j 3.5+ differs from what it was in prior versions.
The cypher keyword `BEGIN`, `COMMIT` and `ROLLBACK` are no longer available.
In order to have a query that runs fine in all versions, you should use the following pattern:
```elixir
# Commit is performed automatically if everythings went fine
conn = Bolt.Sips.conn()
Bolt.Sips.transaction(conn, fn conn ->
result = Bolt.Sips.query!(conn, "CREATE (m:Movie {title: "Matrix"}) RETURN m")
end)
# Rollback is performed automatically in case of error
Bolt.Sips.transaction(conn, fn conn ->
result =Bolt.Sips.query!(conn, "Invalid query")
end)
# Rollback can stil be forced
Bolt.Sips.transaction(conn, fn conn ->
result = Bolt.Sips.query!(conn, "CREATE (m:Movie {title: "Matrix"}) RETURN m")
Bolt.Sips.rollback(conn, :dont_save)
end)
```
================================================
FILE: docs/features/configuration.md
================================================
# Configuration
Bolt.Sips can be configured using the well known Mix config files, or by using simple keyword lists.
This is the most basic configuration:
```elixir
config :bolt_sips, Bolt,
url: "bolt://localhost:7687"
```
It tells Bolt.Sips your Neo4j server is available locally, and it listens on port 7687, expecting bolt commands.
These are the values you can configure, and their default values:
- `:url`- a full url to pointing to a running Neo4j server. Please remember you must specify the scheme used to connect to the server. Valid schemes:`bolt`,`bolt+routing`and`neo4j` - the last two being used for connecting to a Neo4j causal cluster.
- `:pool_size` - the size of the connection pool. Default: 15
- `:timeout` - a connection timeout value defined in milliseconds. Default: 15_000
- `:ssl`-`true`, if the connection must be encrypted. If the user wants to specify custom ssl options, just pass a keyword list with the options. More info [here](https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/ssl) Default:`false`
- `:prefix`- used for differentiating between multiple connections available in the same app. Default:`:default`
## Examples of configurations
Connecting to remote (hosted) Neo4j servers, such as the ones available (also for free) at [Neo4j/Sandbox](https://neo4j.com/sandbox-v2/):
```elixir
config :bolt_sips, Bolt,
url: "bolt://<ip_address>:<bolt_port>",
basic_auth: [username: "neo4j", password: "#######"]
ssl: true # or for example `[verify: :verify_none]` if custom ssl options
```
## Direct mode
Until this version, `Bolt.Sips` was used for connecting to a single Neo4j server from the moment the hosting app started, until the hosting app was terminated/restarted. This is known as the: `direct` mode. In `direct` mode, the `Bolt.Sips` driver has one configuration describing the connection to a single Neo4j server.
Since this connection mode is well known to our users, we'll not spend time on talking about it. It is sufficient to say that in direct mode, you have one configurable pool of connections, and the settings governing them i.e. timeout, size, etc., are all about this single connection.
Because starting with version 2.0 `Bolt.Sips` is supporting a new type of connectivity: `routing`, for connecting to multiple servers or to a Neo4j causal cluster, you must specify the `scheme` in the `url` parameter, of your configuration. Example, for configuring a connection in `direct` mode:
url: "bolt://localhost:7687"
We'll spend more ink on talking about the `routing` mode, next.
## Routing mode
With the 2.0 version, `Bolt.Sips` is implementing the ability to connect your app to a Neo4j causal cluster. You can read more about this, here: [Neo4j Causal Clustering](https://neo4j.com/docs/operations-manual/current/clustering/introduction/)
The features of using a causal cluster, in Neo4j's own words:
> Neo4j’s Causal Clustering provides three main features:
>
> - Safety: Core Servers provide a fault tolerant platform for transaction processing which will remain available while a simple majority of those Core Servers are functioning.
> - Scale: Read Replicas provide a massively scalable platform for graph queries that enables very large graph workloads to be executed in a widely distributed topology.
> - Causal consistency: when invoked, a client application is guaranteed to read at least its own writes.
To configure `Bolt.Sips` for connecting to a Neo4j Causal Cluster, you only need the specify the appropriate scheme, in the `url` configuration parameter:
url: "bolt+routing://localhost:7687"
or:
url: "neo4j://localhost:7687"
Prefer the latter, since `bolt+routing` appears to be soon deprecated, by Neo4j. We'll use `neo4j://` throughout the docs for referring to the `routing` mode, for brevity. Read more about `routing`, [here](routing.md).
## Role based connections
When we implemented the routing mode, we realized we could extend this ability to letting you define any number of connections, identified by a role name of your choice. For example, say your default configuration for `Bolt.Sips` looks like this:
```elixir
config :bolt_sips, Bolt,
url: "bolt://localhost:7687",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10,
max_overflow: 2,
```
`Bolt.Sips` will load it by default, when your application starts. And with a configuration like that, the default mode, you will continue to obtain connections using the default `Bolt.Sips.conn()` function.
However, if you require to have different connections, say: to a different Neo4j server that has some specific role, you could add a new configuration, for example:
```elixir
config :bolt_sips, :hidden_gems,
url: "bolt://localhost:1234",
pool_size: 50,
role: :hidden_gems
```
You'd have to load this config separately, after the starting the `Bolt.Sips`driver. Like this:
```elixir
iex> Bolt.Sips.start_link(Application.get_env(:bolt_sips, :hidden_gems))
{:ok, #PID<0.266.0>}
```
and the you can use connections from this new configuration, as easy as this:
```elixir
iex> conn = Bolt.Sips.conn(:hidden_gems)
#PID<0.324.0>
```
while for obtaing the connections from your default configuration, is business as usual:
```elixir
iex> conn = Bolt.Sips.conn()
#PID<0.309.0>
```
The new connection pool is supervised by the main `Bolt.Sips.ConnectionSupervisor`, you don't have to do anythings special for that.

In the final release, we'll add a friendlier api for adding role-based connections. More details about role-based-connections, [here](role-based-connections.md)
## Multi tenancy
Another important feature of the 2.0 version, is: **multi-tenancy**.
Starting with this version, your app can connect to any number of Neo4j servers, in `direct` mode or `routing`.
We introduced a new configurable parameter, named: `prefix`.
At this time, the only way to configure the driver for multi-tenancy, is programmatically, not via the configuration file. Example:
```elixir
my_secret_cluster_config [
url: "neo4j://localhost:9001",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10,
max_overflow: 2,
queue_interval: 500,
queue_target: 1500,
prefix: :secret_cluster
]
{:ok, _pid} = Bolt.Sips.start_link(@routing_connection_config)
conn = Bolt.Sips.conn(:write, prefix: :secret_cluster)
```
And you can start as many connections as needed, for as long as the `:prefix` has different names. These connections can be used for connecting to the same or different Neo4j servers.
More details about multi-tenancy, [here](multi-tenancy.md)
================================================
FILE: docs/features/multi-tenancy.md
================================================
# Multi tenancy
Very similar to the role-based connections, with multi-tenancy you will be able to connect to servers where the type of the server (role) is defined by the server itself, such as the Neo4j causal cluster. This setting is sill in its infancy, it works, but you'll have to be careful when using it.
For differentiating about Neo4j tenants, we introduced a new configurations parameter, named: `:prefix`. Example:
```elixir
monster_cluster_conf = [
url: "neo4j://localhost",
basic_auth: [username: "neo4j", password: "password"],
pool_size: 50,
prefix: :monster_cluster
baby_monster_cluster_conf = [
url: "neo4j://raspberry_π",
basic_auth: [username: "πs", password: "4VR"],
pool_size: 50,
prefix: :baby_monster_cluster
```
In the example above we defined two different connections, each of them pointing to different Neo4j clusters. As you know now, every cluster will have role-specific connections as defined by the routers, in those clusters. The connection roles will be: `:write`, `:read` and `:route`. To specify what connection you want and on what server, you will use the `:prefix` optional parameter of the new `Bolt.Sips.conn/2` method. Example:
```elixir
Bolt.Sips.conn(:read, prefix: :monster_cluster)
|> Bolt.Sips.query!("MATCH (n) RETURN n.name AS name")
```
or:
```elixir
Bolt.Sips.conn(:read, prefix: :baby_monster_cluster)
|> Bolt.Sips.query!("MATCH (n) RETURN n.name AS name")
```
(wip)
================================================
FILE: docs/features/role-based-connections.md
================================================
# Role-based connections
Starting with the 2.0 version, you can have distinct configurations that you can use in your app, concurrently. These configurations can connect to connect in `:direct` mode to different Neo4j servers, or the same but with different credentials, pool sizes, etc.
> This is not recommended for connecting to a causal cluster; the `:routing` mode, respectively.
To differentiate between multiple `:direct` configurations, you'll use a new parameter: `:role`. Let's see a some code examples, for brevity.
```elixir
frontend_config = [
url: "bolt://localhost",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10,
max_overflow: 2,
role: :frontend
]
backend_config = [
url: "bolt://not_my_localhost:12345",
basic_auth: [username: "xxxxx", password: "yyyyy"],
pool_size: 10,
max_overflow: 2,
role: :backend
]
{:ok, _pid} = Bolt.Sips.start_link(frontend_config)
{:ok, _pid} = Bolt.Sips.start_link(backend_config)
:frontend = Bolt.Sips.conn(:frontend)
:backend = Bolt.Sips.conn(:backend)
%Response{results: [%{"n" => 1}]} = Bolt.Sips.query!(:frontend, "RETURN 1 as n")
%Response{results: [%{"n" => 1}]} = Bolt.Sips.query!(:backend, "RETURN 1 as n")
```
The last two Cypher queries above will be executed on two different servers. And yes you can run them concurrently since their respective pools will not compete for the same resources.
If you desire to terminate a role-based connection, you can easily do so. Just like this: `:ok = Bolt.Sips.terminate_connections(:backend)`.
================================================
FILE: docs/features/routing.md
================================================
# Routing
When connecting to a Neo4j cluster, `Bolt.Sips` will create 3 distinct connection pools, each of them dedicated to one of the following connection types (**connection roles**):
- `:route` - used for getting information from the Neo4j router, such as: routing details about which server is handling what type of role: read/write, and more.
- `:read` - used for read-only connections
- `:write` - used for write-only connections.
Having the `Bolt.Sips` configured in `routing` mode, will enforce your code to clarify what type of connections you want, type you **must** specify when requesting a `Bolt.Sips` connection. Example:
```elixir
rconn = Bolt.Sips.conn(:read)
wconn = Bolt.Sips.conn(:write)
router_conn = Bolt.Sips.conn(:route)
```
Without being explicit about the connection type, you will receive errors, in case you'll attempt to execute a query that will say: create new nodes, on a server having the role: `read` or `route`. This is the only rule you must observe, when using the `Bolt.Sips` driver with a causal cluster.
## Routing walk-through
Let's walk-through a simple experiment with using `Bolt.Sips` in routing mode and a Neo4j cluster.
If you don't have a local server, or a remote Neo4j cluster available for your tests, you can easily setup your own local playground. All you need is Docker.
We'll use a [docker-compose.yml](../../docker-compose.yml) file that you can find in the `Bolt.Sips` main source repo.
If you have:
- [Docker](<https://en.wikipedia.org/wiki/Docker_(software)>) installed, and running. You can get Docker from here: https://docs.docker.com/installation/
- and a simple Elixir project having the `:bolt_sips` driver installer, as a dependency
### Start the Neo4j cluster
In a folder where you have the `docker-compose.yml` file, start a new shell session and run the following command:
docker-compose up
If this is the first time you run this command, or use Neo4j as a Docker image, then based on the quality of your Internet connection, you'll wait a few seconds while Docker downloads a Neo4j Enterprise image. You'll see something like this:
```sh
Creating network "neo4j_lan" with the default driver
Pulling core1 (neo4j:3.5.3-enterprise)...
3.5.3-enterprise: Pulling from library/neo4j
e7c96db7181b: Pull complete
f910a506b6cb: Pull complete
b6abafe80f63: Pull complete
b95a7fd32595: Pull complete
6c09128ad074: Pull complete
648805e5f471: Pull complete
e2790f69a70d: Pull complete
Creating core2 ... done
Creating core3 ... done
Creating core1 ... done
Attaching to core3, core1, core2
core3 | Changed password for user 'neo4j'.
core1 | Changed password for user 'neo4j'.
core2 | Changed password for user 'neo4j'.
core3 | Active database: graph.db
core3 | Directories in use:
core3 | home: /var/lib/neo4j
core3 | config: /var/lib/neo4j/conf
...
```
and towards the end of the starting sequence, this:
```sh
core2 | 2019-06-17 12:37:59.078+0000 INFO Remote interface available at http://localhost:7475/
core3 | 2019-06-17 12:37:59.165+0000 INFO Remote interface available at http://localhost:7476/
```
Check to see if you can connect to your local Neo4j cluster, as simple as pointing your Internet browser to this url: `http://localhost:7474`, and if everything was executed successfully, you'll be seeing the familiar Neo4j web interface.
Now let's play with the `Bolt.Sips`driver and our local Neo4j cluster.
Change your elixir test project configuration and modify the `config/config.exs` file like this (excerpt):
```elixir
use Mix.Config
config :bolt_sips, Bolt,
# bolt+routing will be deprecated?!
# url: "bolt+routing://localhost:7687",
url: "neo4j://localhost:7687",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10
```
then start a IEx shell session, from the projects'r main folder: `iex -S mix`. While inside the IEx session, let's see if our configuration is sound?
```elixir
iex> Bolt.Sips.info()
%{
default: %{
connections: %{
read: %{"localhost:7688" => 0, "localhost:7689" => 0},
route: %{
"localhost:7687" => 0,
"localhost:7688" => 0,
"localhost:7689" => 0
},
write: %{"localhost:7687" => 0},
routing_query: %{...},
ttl: 300,
updated_at: 1560775628
},
user_options: [
url: "neo4j://localhost:7687",
pool_size: 10,
....
]
}
}
```
if you see the response above, it means your settings are ready. Without going into much details about the data structure above, the routing details are these:
```elixir
read: %{"localhost:7688" => 0, "localhost:7689" => 0},
write: %{"localhost:7687" => 0},
route: %{
"localhost:7687" => 0,
"localhost:7688" => 0,
"localhost:7689" => 0
ttl: 300,
updated_at: ...
```
According to the routing information returned by our cluster, we have:
- two nodes accepting `:read` commands: `localhost:7688` and`localhost:7689`
- three nodes capabale of responding with routing specific details: `localhost:7687` `localhost:7688` and `localhost:7689`
- one node accepting `:write` commands; the `localhost:7687`, respectively.
But don't worry about the gory details, we got you covered :)
Let's run some Cypher queries.
```elixir
iex> alias Bolt.Sips.Response
iex> alias Bolt.Sips, as: Neo
# obtaining a read(only) connection:
iex> rconn = Neo.conn(:read)
#PID<0.324.0>
# checking if there are any Person nodes "named": Bob?
iex> %Response{results: r} = Neo.query!(rconn, "MATCH (p:Person{name: 'Bob'}) RETURN p")
%Bolt.Sips.Response{
bookmark: "neo4j:bookmark:v1:tx2",
fields: ["p"],
notifications: [],
plan: nil,
profile: nil,
records: [],
results: [],
stats: [],
type: "r"
}
# r is [], meaning: our query found none. So let's create one.
# First we obtain a connection suitable for `write` operations:
iex> wconn = Neo.conn(:write)
#PID<0.384.0>
# and now we can use it for creating a new node:
iex> %Response{results: r} = Neo.query!(wconn, "CREATE (p:Person{name:'Bob'})")
%Bolt.Sips.Response{
...
stats: %{"labels-added" => 1, "nodes-created" => 1, "properties-set" => 1},
type: "w"
}
# our node was created and has one property set, w⦿‿⦿t!
# but can we find it? Rerun the previous query using the `read` connection:
iex> Neo.query!(rconn, "MATCH (p:Person{name: 'Bob'}) RETURN p") |> Response.first()
%{
"p" => %Bolt.Sips.Types.Node{
id: 20,
labels: ["Person"],
properties: %{"name" => "Bob"}
}
}
# and yessss, our new Person node is in the cluster!
# Do you need its json form, instead? Easy:
iex> Neo.query!(rconn, "MATCH (p:Person{name: 'Bob'}) RETURN p") |>
...> Response.first() |>
...> Bolt.Sips.ResponseEncoder.encode!(:json)
"{\"p\":{\"id\":20,\"labels\":[\"Person\"],\"properties\":{\"name\":\"Bob\"}}}"
```
But what happens if we try to create a new Person, using our `read` connection?
```elixir
iex> Neo.query!(rconn, "CREATE (p:Person{name:'Alice'})")
** (Bolt.Sips.Exception) ... No write operations are allowed directly on this database. Writes must pass through the leader. The role of this server is: FOLLOWER
```
Neo4j will promptly let us know we can't use that connection for write operations. This is the main difference that you must consider when coding.
Same command executed on the proper (write) connection, will be successful:
```elixir
iex> Neo.query!(wconn, "CREATE (p:Person{name:'Alice'})")
%Bolt.Sips.Response{
...
stats: %{"labels-added" => 1, "nodes-created" => 1, "properties-set" => 1},
type: "w"
}
```
================================================
FILE: docs/features/using-cypher.md
================================================
# Using Bolt.Sips to query the Neo4j server
Let's talk about the basics of querying a Neo4j server, using `Bolt.Sips`, and a few methods you could use for using the data returned by the server, using the `Bolt.Sips.Response`. You can learn so much more from the official docs, available at [Neo4j](https://neo4j.com/developer/graph-database/), you should start from there, if you want to get a deep understanding about the Neo4j graph database and its query language: Cypher.
## What you need?
- a [Neo4j](https://neo4j.com/download/) server running locally and available at this `url`: `bolt://neo4j:test@localhost`
- a mix project with `:bolt_sips` available
## Simple queries, using Cypher
With the above prerequisites, let's drop into the IEx shell and start experimenting.
```sh
cd my_neo4j
iex -S mix
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex>
```
First we need to start the driver with a minimalist configuration (unless it is already started by your project?):
```elixir
iex> {:ok, _neo} = Bolt.Sips.start_link(url: "bolt://neo4j:test@localhost")
{:ok, #PID<0.243.0>}
iex>
```
Presuming your database is empty, you can still test your setup by running a simple Cypher query:
```elixir
iex> conn = Bolt.Sips.conn()
#PID<0.248.0>
iex> Bolt.Sips.query!(conn, "RETURN 1 as n") |>
...> Bolt.Sips.Response.first()
%{"n" => 1}
```
and we obtained our first response from the server: `%{"n" => 1}`, w⦿‿⦿t!! Now let's try some more complicated Cypher queries. We'll use examples that you may want to paste them in your `.exs/.ex` files rather than into the IEx shell, for readability.
While most of the Cypher querier fit on a simple row, and they look compact, you might encounter situations where you need to send multiple queries in a single trip, to the server. `Bolt.Sips` allows you do that.
Let's initialize our **test** database with some data.
```elixir
cypher = """
CREATE (BoltSips:BoltSips {title:'Elixir sipping from Neo4j, using Bolt', released:2016, license:'MIT', bolt_sips: true})
CREATE (TNOTW:Book {title:'The Name of the Wind', released:2007, genre:'fantasy', bolt_sips: true})
CREATE (Patrick:Person {name:'Patrick Rothfuss', bolt_sips: true})
CREATE (Kvothe:Person {name:'Kote', bolt_sips: true})
CREATE (Denna:Person {name:'Denna', bolt_sips: true})
CREATE (Chandrian:Deamon {name:'Chandrian', bolt_sips: true})
CREATE
(Kvothe)-[:ACTED_IN {roles:['sword fighter', 'magician', 'musician']}]->(TNOTW),
(Denna)-[:ACTED_IN {roles:['many talents']}]->(TNOTW),
(Chandrian)-[:ACTED_IN {roles:['killer']}]->(TNOTW),
(Patrick)-[:WROTE]->(TNOTW)
"""
{:ok, response} =
Bolt.Sips.conn()
|> Bolt.Sips.query(cypher)
```
According to the response from the server, this is what we did:
```elixir
iex> response
%Bolt.Sips.Response{
results: [],
stats: %{
"labels-added" => 6,
"nodes-created" => 6,
"properties-set" => 19,
"relationships-created" => 4
},
type: "w"
}
```
we have 6 new Nodes, 6 new labels and 4 new relationships.
At any time, if you want to clean up the data we're creating, you can use this query:
`MATCH (n {bolt_sips: true}) OPTIONAL MATCH (n)-[r]-() DELETE n,r`
Observe we're adding a `bolt_sips` property to the Nodes we're adding, so that it's easier to refer them in our tests.
Let's see how many nodes of "type" (`label`, according to Cypher's official terminology) `Person` having the property `bolt_sips` true, we have in our database:
```elixir
iex> query = """
...> MATCH (n:Person {bolt_sips: true})
...> RETURN n.name AS Name
...> ORDER BY Name DESC
...> LIMIT 5
...> """
iex> %Bolt.Sips.Response{} = response = Bolt.Sips.query!(conn, query)
%Bolt.Sips.Response{
bookmark: "neo4j:bookmark:v1:tx21613",
fields: ["Name"],
notifications: [],
plan: nil,
profile: nil,
records: [["Patrick Rothfuss"], ["Kote"], ["Denna"]],
results: [
%{"Name" => "Patrick Rothfuss"},
%{"Name" => "Kote"},
%{"Name" => "Denna"}
],
stats: [],
type: "r"
}
```
We have 3 of them, and we're only showing the `name` property! Above you see the full `Bolt.Sips.Response` returned by our driver based on the raw data returned by the Neo4j server. The `:results` key, contains the aggregated response you will use most of the time, and for that the `Bolt.Sips.Response` module has some useful helpers, for example:
```elixir
iex> response |>
...> Bolt.Sips.Response.first()
%{"Name" => "Patrick Rothfuss"}
```
and much more. Check the `Bolt.Sips.Response`'s own docs, for more.
================================================
FILE: docs/features/using-temporal-and-spatial-types.md
================================================
# Using temporal and spatial types
Temporal and spatial types are supported since Neo4J 3.4.
You can used the elixir structs: Time, NaiveDateTime, DateTime,
as well as the Bolt Sips structs: DateTimeWithTZOffset, TimeWithTZOffset, Duration, Point.
```elixir
$ MIX_ENV=test iex -S mix
Erlang/OTP 21 [erts-10.0.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex> alias Bolt.Sips.Types.{Duration, DateTimeWithTZOffset, Point, TimeWithTZOffset}
[Bolt.Sips.Types.Duration, Bolt.Sips.Types.DateTimeWithTZOffset,
Bolt.Sips.Types.Point, Bolt.Sips.Types.TimeWithTZOffset]
iex> alias Bolt.Sips.TypesHelper
Bolt.Sips.TypesHelper
iex> {:ok, pid} = Bolt.Sips.start_link(url: "localhost", basic_auth: [username: "neo4j", password: "test"])
{:ok, #PID<0.236.0>}
iex> conn = Bolt.Sips.conn
:bolt_sips_pool
# Date without timezone with Date
iex(8)> Bolt.Sips.query!(conn, "RETURN date($d) AS d", %{d: ~D[2019-02-04]})
[%{"d" => ~D[2019-02-04]}]
# Time without timezone with Time
iex> Bolt.Sips.query!(conn, "RETURN localtime($t) AS t", %{t: ~T[13:26:08.543440]})
[%{"t" => ~T[13:26:08.543440]}]
# Datetime without timezone with Naive DateTime
iex> Bolt.Sips.query!(conn, "RETURN localdatetime($ldt) AS ldt", %{ldt: ~N[2016-05-24 13:26:08.543]})
[%{"ldt" => ~N[2016-05-24 13:26:08.543]}]
# Datetime with timezone ID with DateTime (through Calendar)
iex> date_time_with_tz_id = TypesHelper.datetime_with_micro(~N[2016-05-24 13:26:08.543], "Europe/Paris")
#DateTime<2016-05-24 13:26:08.543+02:00 CEST Europe/Paris>
iex> Bolt.Sips.query!(conn, "RETURN datetime($dt) AS dt", %{dt: date_time_with_tz_id})
[%{"dt" => #DateTime<2016-05-24 13:26:08.543+02:00 CEST Europe/Paris>}]
# Datetime with timezone offset (seconds) with DateTimeWithTZOffset
iex(17)> date_time_with_tz = DateTimeWithTZOffset.create(~N[2016-05-24 13:26:08.543], 7200)
%Bolt.Sips.Types.DateTimeWithTZOffset{
naive_datetime: ~N[2016-05-24 13:26:08.543],
timezone_offset: 7200
}
iex(18)> Bolt.Sips.query!(conn, "RETURN datetime($dt) AS dt", %{dt: date_time_with_tz})
[
%{
"dt" => %Bolt.Sips.Types.DateTimeWithTZOffset{
naive_datetime: ~N[2016-05-24 13:26:08.543],
timezone_offset: 7200
}
}
]
# Datetime with timezone offset (seconds) with TimeWithTZOffset
iex> time_with_tz = TimeWithTZOffset.create(~T[12:45:30.250000], 3600)
%Bolt.Sips.Types.TimeWithTZOffset{
time: ~T[12:45:30.250000],
timezone_offset: 3600
}
iex> Bolt.Sips.query!(conn, "RETURN time($t) AS t", %{t: time_with_tz})
[
%{
"t" => %Bolt.Sips.Types.TimeWithTZOffset{
time: ~T[12:45:30.250000],
timezone_offset: 3600
}
}
]
# Cartesian 2D point with Point
iex> point_cartesian_2D = Point.create(:cartesian, 50, 60.5)
%Bolt.Sips.Types.Point{
crs: "cartesian",
height: nil,
latitude: nil,
longitude: nil,
srid: 7203,
x: 50.0,
y: 60.5,
z: nil
}
iex> Bolt.Sips.query!(conn, "RETURN point($pt) AS pt", %{pt: point_cartesian_2D})
[
%{
"pt" => %Bolt.Sips.Types.Point{
crs: "cartesian",
height: nil,
latitude: nil,
longitude: nil,
srid: 7203,
x: 50.0,
y: 60.5,
z: nil
}
}
]
# Geographic 2D point with Point
iex> point_geo_2D = Point.create(:wgs_84, 50, 60.5)
%Bolt.Sips.Types.Point{
crs: "wgs-84",
height: nil,
latitude: 60.5,
longitude: 50.0,
srid: 4326,
x: 50.0,
y: 60.5,
z: nil
}
iex> Bolt.Sips.query!(conn, "RETURN point($pt) AS pt", %{pt: point_geo_2D})
[
%{
"pt" => %Bolt.Sips.Types.Point{
crs: "wgs-84",
height: nil,
latitude: 60.5,
longitude: 50.0,
srid: 4326,
x: 50.0,
y: 60.5,
z: nil
}
}
]
# Cartesian 3D point with Point
iex> point_cartesian_3D = Point.create(:cartesian, 50, 60.5, 12.34)
%Bolt.Sips.Types.Point{
crs: "cartesian-3d",
height: nil,
latitude: nil,
longitude: nil,
srid: 9157,
x: 50.0,
y: 60.5,
z: 12.34
}
iex> Bolt.Sips.query!(conn, "RETURN point($pt) AS pt", %{pt: point_cartesian_3D})
[
%{
"pt" => %Bolt.Sips.Types.Point{
crs: "cartesian-3d",
height: nil,
latitude: nil,
longitude: nil,
srid: 9157,
x: 50.0,
y: 60.5,
z: 12.34
}
}
]
# Geographic 2D point with Point
iex> point_geo_3D = Point.create(:wgs_84, 50, 60.5, 12.34)
%Bolt.Sips.Types.Point{
crs: "wgs-84-3d",
height: 12.34,
latitude: 60.5,
longitude: 50.0,
srid: 4979,
x: 50.0,
y: 60.5,
z: 12.34
}
iex> Bolt.Sips.query!(conn, "RETURN point($pt) AS pt", %{pt: point_geo_2D})
[
%{
"pt" => %Bolt.Sips.Types.Point{
crs: "wgs-84",
height: nil,
latitude: 60.5,
longitude: 50.0,
srid: 4326,
x: 50.0,
y: 60.5,
z: nil
}
}
]
```
================================================
FILE: docs/features/using-with-phoenix.md
================================================
# Using Bolt.Sips with Phoenix, or similar
Don't forget to start the `Bolt.Sips` driver in your supervision tree. Example:
```elixir
defmodule MoviesElixirPhoenix do
use Application
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
# Define workers and child supervisors to be supervised
children = [
# Start the endpoint when the application starts
{Bolt.Sips, Application.get_env(:bolt_sips, Bolt)},
%{
id: MoviesElixirPhoenix.Endpoint,
start: {MoviesElixirPhoenix.Endpoint, :start_link, []}
}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: MoviesElixirPhoenix.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
MoviesElixirPhoenix.Endpoint.config_change(changed, removed)
:ok
end
end
```
The code above was extracted from [the Neo4j Movies Demo](https://github.com/florinpatrascu/bolt_movies_elixir_phoenix), a Phoenix web application using this driver and the well known [Dataset - Movie Database](https://neo4j.com/developer/movie-database/).
Note: as explained below, you don't need to convert your query result before having it encoded in JSON. BoltSips provides Jason and Poison implementation to tackle this problem automatically.
================================================
FILE: docs/getting-started.md
================================================
# Getting Started
Let's start by creating a simple Elixir project, as a playground for our tests.
```sh
mix new neo4j_demo --sup --app n4d --module N4D
cd neo4j_demo
```
Open the `mix.exs` and add the bolt_sips dependency.
```elixir
defmodule N4D.MixProject do
use Mix.Project
def project do
[
app: :n4d,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {N4D.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:bolt_sips, "~> 2.0.0-rc"},
{:jason, "~> 1.1"}
]
end
end
```
we added the [jason](https://hex.pm/packages/jason) library too, for converting the server responses to json. And then run:
```sh
mix do deps.get, compile
```
## Starting the driver
And our simple project is ready for us to start experimenting with it.
Let's first configure the connection to a running Neo4j server. We presume a standalone community edition server is started and available on the `localhost` interface, and having its Bolt port open at: `7687`. For simplicity edit the `config/config.exs`, and modify it to look like this:
```elixir
use Mix.Config
config :bolt_sips, Bolt,
url: "bolt://localhost:7687",
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10
```
With the project configured to connect to a Neo4j server, in direct mode, we can add `Bolt.Sips` to the app's main supervision tree, and let the OTP manage it.
```elixir
# lib/n4_d/application.ex
defmodule N4D.Application do
@moduledoc false
use Application
def start(_type, _args) do
children = [
{Bolt.Sips, Application.get_env(:bolt_sips, Bolt)}
]
opts = [strategy: :one_for_one, name: N4D.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
There are a couple of different other ways to start the driver but let's keep it simple for now.
The easiest way to start playing with the driver, in the current configuration, is to drop into the IEx shell and run simple Cypher commands through it.
```sh
cd neo4j_demo
iex -S mix
```
## Usage
A few examples:
```elixir
iex> alias Bolt.Sips, as: Neo
iex> alias Bolt.Sips.Response
# check the driver is up and running:
iex> Neo.info()
%{
default: %{
connections: %{direct: %{"localhost:7687" => 0}, routing_query: nil},
user_options: [
socket: Bolt.Sips.Socket,
port: 7687,
url: "bolt://localhost:7687",
# ...
basic_auth: [username: "neo4j", password: "test"],
pool_size: 10
]
}
}
# in direct mode, our current configuration, all the operations such as: read/write or
# delete, are sent to the Neo4j server using a common connection (pool).
# Let's obtain a connection:
iex> conn = Neo.conn()
#PID<0.308.0>
# a few examples:
iex> response = Neo.query!(conn, "CREATE (p:Person)-[:LIKES]->(t:Technology)")
%Response{
bookmark: nil,
fields: [],
notifications: [],
plan: nil,
profile: nil,
records: [],
results: [],
stats: %{
"labels-added" => 2,
"nodes-created" => 2,
"relationships-created" => 1
},
type: "w"
}
# query with undirected relationship unless sure of direction
%Bolt.Sips.Response{results: results} = response = Neo.query!(conn, "MATCH (p:Person)-[:LIKES]-(t:Technology) RETURN p")
# where `results` contain:
[%{"p" => %Bolt.Sips.Types.Node{id: 355, labels: ["Person"], properties: %{}}}]
# and we can also encode them to json, as simple as this:
iex> Jason.encode!(results)
"[{\"p\":{\"id\":355,\"labels\":[\"Person\"],\"properties\":{}}}]"
# of course you can do more:
iex> Bolt.Sips.query!(Bolt.Sips.conn(), "RETURN [10,11,21] AS arr", %{}, timeout: 19_000) |>
...> Enum.reduce(0, &(Enum.sum(&1["arr"]) + &2))
42
# see more examples and the tests, for getting familiar with what is possible.
# Enjoy!
```
Follow this link: [Cypher Basics](https://neo4j.com/developer/cypher-query-language/), for a gentle introduction to Cypher; Neo4j's query language. Throughout the code snippets we are often using examples copied from the original documentation published by Neo4j, so that you can feel comfortable with them.
================================================
FILE: lib/bolt_sips/application.ex
================================================
defmodule Bolt.Sips.Application do
@moduledoc false
use Application
alias Bolt.Sips
def start(_, start_args) do
Sips.start_link(start_args)
end
def stop(_state) do
:ok
end
end
================================================
FILE: lib/bolt_sips/enumerable_response.ex
================================================
defimpl Enumerable, for: Bolt.Sips.Response do
alias Bolt.Sips.Response
def count(%Response{results: nil}), do: {:ok, 0}
def count(%Response{results: []}), do: {:ok, 0}
def count(%Response{results: results}), do: {:ok, length(results)}
def member?(%Response{fields: fields}, field), do: {:ok, Enum.member?(fields, field)}
def slice(_response), do: {:error, __MODULE__}
def reduce(%Response{results: []}, acc, _fun), do: acc
def reduce(%Response{results: results}, acc, fun) when is_list(results),
do: reduce_list(results, acc, fun)
defp reduce_list(_, {:halt, acc}, _fun), do: {:halted, acc}
defp reduce_list(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce_list(list, &1, fun)}
defp reduce_list([], {:cont, acc}, _fun), do: {:done, acc}
defp reduce_list([h | t], {:cont, acc}, fun), do: reduce_list(t, fun.(h, acc), fun)
@doc false
def slice(%Response{results: []}, _start, _count), do: []
def slice(_response, _start, 0), do: []
def slice(%Response{results: [head | tail]}, 0, count), do: [head | slice(tail, 0, count - 1)]
def slice(%Response{results: [_head | tail]}, start, count), do: slice(tail, start - 1, count)
end
================================================
FILE: lib/bolt_sips/error.ex
================================================
defmodule Bolt.Sips.Error do
@moduledoc """
represents an error message
"""
alias __MODULE__
@type t :: %__MODULE__{}
defstruct [:code, :message]
def new(%Bolt.Sips.Internals.Error{
code: code,
connection_id: cid,
function: f,
message: message,
type: t
}) do
{:error,
%Error{
code: code,
message:
"Details: #{message}; connection_id: #{inspect(cid)}, function: #{inspect(f)}, type: #{
inspect(t)
}"
}}
end
def new({:ignored, f} = _r), do: new({:error, f})
def new({:failure, %{"code" => code, "message" => message}} = _r) do
{:error, %Error{code: code, message: message}}
end
def new(r), do: r
end
================================================
FILE: lib/bolt_sips/exception.ex
================================================
defmodule Bolt.Sips.Exception do
@moduledoc """
This module defines a `Bolt.Sips.Exception` structure containing two fields:
* `code` - the error code
* `message` - the error details
"""
@type t :: %Bolt.Sips.Exception{}
defexception [:code, :message]
end
================================================
FILE: lib/bolt_sips/internals/bolt_protocol.ex
================================================
defmodule Bolt.Sips.Internals.BoltProtocol do
@moduledoc false
# A library that handles Bolt Protocol (v1 and v2).
# Note that for now, only Neo4j implements Bolt v2.
# It handles all the protocol specific steps (i.e.
# handshake, init) as well as sending and receiving messages and wrapping
# them in chunks.
# It abstracts transportation, expecting the transport layer to define
# `send/2` and `recv/3` analogous to `:gen_tcp`.
# ## Logging configuration
# Logging can be enable / disable via config files (e.g, `config/config.exs`).
# - `:log`: (bool) wether Bolt.Sips.Internals. should produce logs or not. Defaults to `false`
# - `:log_hex`: (bool) wether Bolt.Sips.Internals. should produce logs hexadecimal counterparts. While this may be interesting,
# note that all the hexadecimal data will be written and this can be very long, and thus can seriously impact performances. Defaults to `false`
# For example, configuration to see the logs and their hexadecimal counterparts:
# ```
# config :Bolt.Sips.Internals.,
# log: true,
# log_hex: true
# ```
# # #### Examples of logging (without log_hex)
# iex> Bolt.Sips.Internals.test('localhost', 7687, "RETURN 1 as num", %{}, {"neo4j", "password"})
# C: HANDSHAKE ~ "<<0x60, 0x60, 0xB0, 0x17>> [2, 1, 0, 0]"
# S: HANDSHAKE ~ 2
# C: INIT ~ ["BoltSips/1.1.0.rc2", %{credentials: "password", principal: "neo4j", scheme: "basic"}]
# S: SUCCESS ~ %{"server" => "Neo4j/3.4.1"}
# C: RUN ~ ["RETURN 1 as num", %{}]
# S: SUCCESS ~ %{"fields" => ["num"], "result_available_after" => 1}
# C: PULL_ALL ~ []
# S: RECORD ~ [1]
# S: SUCCESS ~ %{"result_consumed_after" => 0, "type" => "r"}
# [
# success: %{"fields" => ["num"], "result_available_after" => 1},
# record: [1],
# success: %{"result_consumed_after" => 0, "type" => "r"}
# ]
# #### Examples of logging (with log_hex)
# iex> Bolt.Sips.Internals.test('localhost', 7687, "RETURN 1 as num", %{}, {"neo4j", "password"})
# 13:32:23.882 [debug] C: HANDSHAKE ~ "<<0x60, 0x60, 0xB0, 0x17>> [2, 1, 0, 0]"
# S: HANDSHAKE ~ <<0x0, 0x0, 0x0, 0x2>>
# S: HANDSHAKE ~ 2
# C: INIT ~ ["BoltSips/1.1.0.rc2", %{credentials: "password", principal: "neo4j", scheme: "basic"}]
# C: INIT ~ <<0x0, 0x42, 0xB2, 0x1, 0x8C, 0x42, 0x6F, 0x6C, 0x74, 0x65, 0x78, 0x2F, 0x30, 0x2E, 0x35, 0x2E, 0x30, 0xA3, 0x8B, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x73, 0x88, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x89, 0x70, 0x72, 0x69, 0x6E, 0x63, 0x69, 0x70, 0x61, 0x6C, 0x85, 0x6E, 0x65, 0x6F, 0x34, 0x6A, 0x86, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x65, 0x85, 0x62, 0x61, 0x73, 0x69, 0x63, 0x0, 0x0>>
# S: SUCCESS ~ <<0xA1, 0x86, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x8B, 0x4E, 0x65, 0x6F, 0x34, 0x6A, 0x2F, 0x33, 0x2E, 0x34, 0x2E, 0x31>>
# S: SUCCESS ~ %{"server" => "Neo4j/3.4.1"}
# C: RUN ~ ["RETURN 1 as num", %{}]
# C: RUN ~ <<0x0, 0x13, 0xB2, 0x10, 0x8F, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20, 0x31, 0x20, 0x61, 0x73, 0x20, 0x6E, 0x75, 0x6D, 0xA0, 0x0, 0x0>>
# S: SUCCESS ~ <<0xA2, 0xD0, 0x16, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x5F, 0x61, 0x76, 0x61, 0x69, 0x6C, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x61, 0x66, 0x74, 0x65, 0x72, 0x1, 0x86, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x73, 0x91, 0x83, 0x6E, 0x75, 0x6D>>
# S: SUCCESS ~ %{"fields" => ["num"], "result_available_after" => 1}
# C: PULL_ALL ~ []
# C: PULL_ALL ~ <<0x0, 0x2, 0xB0, 0x3F, 0x0, 0x0>>
# S: RECORD ~ <<0x91, 0x1>>
# S: RECORD ~ [1]
# S: SUCCESS ~ <<0xA2, 0xD0, 0x15, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x5F, 0x63, 0x6F, 0x6E, 0x73, 0x75, 0x6D, 0x65, 0x64, 0x5F, 0x61, 0x66, 0x74, 0x65, 0x72, 0x0, 0x84, 0x74, 0x79, 0x70, 0x65, 0x81, 0x72>>
# S: SUCCESS ~ %{"result_consumed_after" => 0, "type" => "r"}
# [
# success: %{"fields" => ["num"], "result_available_after" => 1},
# record: [1],
# success: %{"result_consumed_after" => 0, "type" => "r"}
# ]
# ## Shared options
# Functions that allow for options accept these default options:
# * `recv_timeout`: The timeout for receiving a response from the Neo4J s
# server (default: #{@recv_timeout})
alias Bolt.Sips.Metadata
alias Bolt.Sips.Internals.BoltProtocolV1
alias Bolt.Sips.Internals.BoltProtocolV3
defdelegate handshake(transport, port, options \\ []), to: BoltProtocolV1
defdelegate init(transport, port, version, auth \\ {}, options \\ []), to: BoltProtocolV1
defdelegate hello(transport, port, version, auth \\ {}, options \\ []), to: BoltProtocolV3
defdelegate goodbye(transport, port, version), to: BoltProtocolV3
defdelegate ack_failure(transport, port, bolt_version, options \\ []), to: BoltProtocolV1
defdelegate reset(transport, port, bolt_version, options \\ []), to: BoltProtocolV1
defdelegate discard_all(transport, port, bolt_version, options \\ []), to: BoltProtocolV1
defdelegate begin(transport, port, bolt_version, metadata \\ %Metadata{}, options \\ []),
to: BoltProtocolV3
defdelegate commit(transport, port, bolt_version, options \\ []), to: BoltProtocolV3
defdelegate rollback(transport, port, bolt_version, options \\ []), to: BoltProtocolV3
defdelegate pull_all(transport, port, bolt_version, options \\ []), to: BoltProtocolV1
@doc """
run for all Bolt version, but call differs.
For Bolt <= 2, use: run_statement(transport, port, bolt_version, statement, params, options)
For Bolt >=3: run_statement(transport, port, bolt_version, statement, params, metadata, options)
Note that Bolt V2 calls works with Bolt V3, but it is preferrable to update them.
"""
@spec run(
atom(),
port(),
integer(),
String.t(),
map(),
nil | Keyword.t() | Bolt.Sips.Metadata.t(),
nil | Keyword.t()
) ::
{:ok, tuple()}
| Bolt.Sips.Internals.Error.t()
def run(
transport,
port,
bolt_version,
statement,
params \\ %{},
options_or_metadata \\ [],
options \\ []
)
def run(transport, port, bolt_version, statement, params, options_or_metadata, _)
when bolt_version <= 2 do
BoltProtocolV1.run(
transport,
port,
bolt_version,
statement,
params,
options_or_metadata || []
)
end
def run(transport, port, bolt_version, statement, params, metadata, options)
when bolt_version >= 2 do
metadata =
case metadata do
[] -> %{}
metadata -> metadata
end
{metadata, options} = manage_metadata_and_options(metadata, options)
BoltProtocolV3.run(transport, port, bolt_version, statement, params, metadata, options)
end
defp manage_metadata_and_options([], options) do
{:ok, empty_metadata} = Metadata.new(%{})
{empty_metadata, options}
end
defp manage_metadata_and_options([_ | _] = metadata, options) do
{:ok, empty_metadata} = Metadata.new(%{})
{empty_metadata, metadata ++ options}
end
defp manage_metadata_and_options(metadata, options) do
{metadata, options}
end
@doc """
run_statement for all Bolt version, but call differs.
For Bolt <= 2, use: run_statement(transport, port, bolt_version, statement, params, options)
For Bolt >=3: run_statement(transport, port, bolt_version, statement, params, metadata, options)
Note that Bolt V2 calls works with Bolt V3, but it is preferrable to update them.
"""
@spec run_statement(
atom(),
port(),
integer(),
String.t(),
map(),
nil | Keyword.t() | Bolt.Sips.Metadata.t(),
nil | Keyword.t()
) ::
list()
| Bolt.Sips.Internals.Error.t()
def run_statement(
transport,
port,
bolt_version,
statement,
params \\ %{},
options_v2_or_metadata_v3 \\ [],
options_v3 \\ []
)
def run_statement(transport, port, bolt_version, statement, params, options_or_metadata, _)
when bolt_version <= 2 do
BoltProtocolV1.run_statement(
transport,
port,
bolt_version,
statement,
params,
options_or_metadata || []
)
end
def run_statement(transport, port, bolt_version, statement, params, metadata, options)
when bolt_version >= 2 do
metadata =
case metadata do
[] -> %{}
metadata -> metadata
end
BoltProtocolV3.run_statement(
transport,
port,
bolt_version,
statement,
params,
metadata,
options
)
end
end
================================================
FILE: lib/bolt_sips/internals/bolt_protocol_helper.ex
================================================
defmodule Bolt.Sips.Internals.BoltProtocolHelper do
@moduledoc false
alias Bolt.Sips.Internals.PackStream.Message
alias Bolt.Sips.Internals.Error
@recv_timeout :infinity #10_000
@zero_chunk <<0x00, 0x00>>
@summary ~w(success ignored failure)a
@doc """
Sends a message using the Bolt protocol and PackStream encoding.
Message have to be in the form of {message_type, [data]}.
"""
@spec send_message(atom(), port(), integer(), Bolt.Sips.Internals.PackStream.Message.raw()) ::
:ok | {:error, any()}
def send_message(transport, port, bolt_version, message) do
message
|> Message.encode(bolt_version)
|> (fn data -> transport.send(port, data) end).()
end
@doc """
Receives data.
This function is supposed to be called after a request to the server has been
made. It receives data chunks, mends them (if they were split between frames)
and decodes them using PackStream.
When just a single message is received (i.e. to acknowledge a command), this
function returns a tuple with two items, the first being the signature and the
second being the message(s) itself. If a list of messages is received it will
return a list of the former.
The same goes for the messages: If there was a single data point in a message
said data point will be returned by itself. If there were multiple data
points, the list will be returned.
The signature is represented as one of the following:
* `:success`
* `:record`
* `:ignored`
* `:failure`
## Options
See "Shared options" in the documentation of this module.
"""
@spec receive_data(atom(), port(), integer(), Keyword.t(), list()) ::
{atom(), Bolt.Sips.Internals.PackStream.value()}
| {:error, any()}
| Bolt.Sips.Internals.Error.t()
def receive_data(transport, port, bolt_version, options \\ [], previous \\ []) do
with {:ok, data} <- do_receive_data(transport, port, options) do
case Message.decode(data, bolt_version) do
{:record, _} = data ->
receive_data(transport, port, bolt_version, options, [data | previous])
{status, _} = data when status in @summary and previous == [] ->
data
{status, _} = data when status in @summary ->
Enum.reverse([data | previous])
other ->
{:error, Error.exception(other, port, :receive_data)}
end
else
other ->
# Should be the line below to have a cleaner typespec
# Keep the old return value to not break usage
# {:error, Error.exception(other, port, :receive_data)}
Error.exception(other, port, :receive_data)
end
end
@spec do_receive_data(atom(), port(), Keyword.t()) :: {:ok, binary()}
defp do_receive_data(transport, port, options) do
#recv_timeout = get_recv_timeout(options)
case transport.recv(port, 2, :infinity) do
{:ok, <<chunk_size::16>>} ->
do_receive_data_(transport, port, chunk_size, options, <<>>)
other ->
other
end
end
@spec do_receive_data_(atom(), port(), integer(), Keyword.t(), binary()) :: {:ok, binary()}
defp do_receive_data_(transport, port, chunk_size, options, old_data) do
recv_timeout = get_recv_timeout(options)
with {:ok, data} <- transport.recv(port, chunk_size, recv_timeout),
{:ok, marker} <- transport.recv(port, 2, recv_timeout) do
case marker do
@zero_chunk ->
{:ok, <<old_data::binary, data::binary>>}
<<chunk_size::16>> ->
data = <<old_data::binary, data::binary>>
do_receive_data_(transport, port, chunk_size, options, data)
end
else
other ->
Error.exception(other, port, :recv)
end
end
@doc """
Define timeout
"""
@spec get_recv_timeout(Keyword.t()) :: integer()
def get_recv_timeout(options) do
Keyword.get(options, :recv_timeout, @recv_timeout)
end
@doc """
Deal with message without data.
## Example
iex> BoltProtocolHelper.treat_simple_message(:reset, :gen_tcp, port, 1, [])
:ok
"""
@spec treat_simple_message(
Bolt.Sips.Internals.Message.out_signature(),
atom(),
port(),
integer(),
Keyword.t()
) :: :ok | Error.t()
def treat_simple_message(message, transport, port, bolt_version, options) do
send_message(transport, port, bolt_version, {message, []})
case receive_data(transport, port, bolt_version, options) do
{:success, %{}} ->
:ok
error ->
Error.exception(error, port, message)
end
end
end
================================================
FILE: lib/bolt_sips/internals/bolt_protocol_v1.ex
================================================
defmodule Bolt.Sips.Internals.BoltProtocolV1 do
@moduledoc false
alias Bolt.Sips.Internals.BoltProtocolHelper
alias Bolt.Sips.Internals.BoltVersionHelper
alias Bolt.Sips.Internals.Error
@hs_magic <<0x60, 0x60, 0xB0, 0x17>>
@doc """
Initiates the handshake between the client and the server.
See [http://boltprotocol.org/v1/#handshake](http://boltprotocol.org/v1/#handshake)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.handshake(:gen_tcp, port, [])
{:ok, bolt_version}
"""
@spec handshake(atom(), port(), Keyword.t()) ::
{:ok, integer()} | {:error, Bolt.Sips.Internals.Error.t()}
def handshake(transport, port, options \\ [recv_timeout: 15_000]) do
recv_timeout = BoltProtocolHelper.get_recv_timeout(options)
max_version = BoltVersionHelper.last()
# Define version list. Should be a 4 integer list
# Example: [1, 0, 0, 0]
versions =
((max_version..0
|> Enum.into([])) ++ [0, 0, 0])
|> Enum.take(4)
Bolt.Sips.Internals.Logger.log_message(
:client,
:handshake,
"#{inspect(@hs_magic, base: :hex)} #{inspect(versions)}"
)
data = @hs_magic <> Enum.into(versions, <<>>, fn version_ -> <<version_::32>> end)
transport.send(port, data)
case transport.recv(port, 4, recv_timeout) do
{:ok, <<version::32>> = packet} when version <= max_version ->
Bolt.Sips.Internals.Logger.log_message(:server, :handshake, packet, :hex)
Bolt.Sips.Internals.Logger.log_message(:server, :handshake, version)
{:ok, version}
{:ok, other} ->
{:error, Error.exception(other, port, :handshake)}
other ->
{:error, Error.exception(other, port, :handshake)}
end
end
@doc """
Initialises the connection.
Expects a transport module (i.e. `gen_tcp`) and a `Port`. Accepts
authorisation params in the form of {username, password}.
See [https://boltprotocol.org/v1/#message-init](https://boltprotocol.org/v1/#message-init)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Examples
iex> Bolt.Sips.Internals.BoltProtocol.init(:gen_tcp, port, 1, {}, [])
{:ok, info}
iex> Bolt.Sips.Internals.BoltProtocol.init(:gen_tcp, port, 1, {"username", "password"}, [])
{:ok, info}
"""
@spec init(atom(), port(), integer(), tuple(), Keyword.t()) ::
{:ok, any()} | {:error, Bolt.Sips.Internals.Error.t()}
def init(transport, port, bolt_version, auth, options \\ [recv_timeout: 15_000]) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:init, [auth]})
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, info} ->
{:ok, info}
{:failure, response} ->
{:error, Error.exception(response, port, :init)}
other ->
{:error, Error.exception(other, port, :init)}
end
end
@doc """
Implementation of Bolt's RUN. It passes a statement for execution on the server.
Note that this message doesn't return the statemetn result. For this purpose, use PULL_ALL.
See [https://boltprotocol.org/v1/#message-run](https://boltprotocol.org/v1/#message-run)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.run(:gen_tcp, port, 1, "RETURN $num AS num", %{num: 5}, [])
{:ok, {:success, %{"fields" => ["num"]}}}
"""
@spec run(atom(), port(), integer(), String.t(), map(), Keyword.t()) ::
{:ok, any()} | {:error, Bolt.Sips.Internals.Error.t()}
def run(transport, port, bolt_version, statement, params, options) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:run, [statement, params]})
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, _} = result ->
{:ok, result}
{:failure, response} ->
{:error, Error.exception(response, port, :run)}
%Error{} = error ->
{:error, error}
other ->
{:error, Error.exception(other, port, :run)}
end
end
@doc """
Implementation of Bolt's PULL_ALL. It retrieves all remaining items from the active result
stream.
See [https://boltprotocol.org/v1/#message-run](https://boltprotocol.org/v1/#message-run)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.run(:gen_tcp, port, 1, "RETURN $num AS num", %{num: 5}, [])
{:ok, {:success, %{"fields" => ["num"]}}}
iex> BoltProtocolV1.pull_all(:gen_tcp, port_, 1, [])
{:ok,
[
record: [5],
success: %{"type" => "r"}
]}
"""
@spec pull_all(atom(), port(), integer(), Keyword.t()) ::
{:ok, list()}
| {:failure, Bolt.Sips.Internals.Error.t()}
| {:failure, Bolt.Sips.Internals.Error.t()}
def pull_all(transport, port, bolt_version, options) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:pull_all, []})
with data <- BoltProtocolHelper.receive_data(transport, port, bolt_version, options),
data <- List.wrap(data),
{:success, _} <- List.last(data) do
{:ok, data}
else
{:failure, response} ->
{:failure, Error.exception(response, port, :pull_all)}
other ->
{:error, Error.exception(other, port, :pull_all)}
end
end
@doc """
Runs a statement (most likely Cypher statement) and returns a list of the
records and a summary (Act as as a RUN + PULL_ALL).
Records are represented using PackStream's record data type. Their Elixir
representation is a Keyword with the indexes `:sig` and `:fields`.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Examples
iex> Bolt.Sips.Internals.BoltProtocol.run_statement(:gen_tcp, port, 1, "MATCH (n) RETURN n")
[
{:success, %{"fields" => ["n"]}},
{:record, [sig: 1, fields: [1, "Example", "Labels", %{"some_attribute" => "some_value"}]]},
{:success, %{"type" => "r"}}
]
"""
@spec run_statement(atom(), port(), integer(), String.t(), map(), Keyword.t()) ::
[
Bolt.Sips.Internals.PackStream.Message.decoded()
]
| Bolt.Sips.Internals.Error.t()
def run_statement(transport, port, bolt_version, statement, params, options) do
with {:ok, run_data} <- run(transport, port, bolt_version, statement, params, options),
{:ok, result} <- pull_all(transport, port, bolt_version, options) do
[run_data | result]
else
{:error, %Error{} = error} ->
error
other ->
Error.exception(other, port, :run_statement)
end
end
@doc """
Implementation of Bolt's DISCARD_ALL. It discards all remaining items from the active result
stream.
See [https://boltprotocol.org/v1/#message-discard-all](https://boltprotocol.org/v1/#message-discard-all)
See http://boltprotocol.org/v1/#message-ack-failure
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.discard_all(:gen_tcp, port, 1, [])
:ok
"""
@spec discard_all(atom(), port(), integer(), Keyword.t()) :: :ok | Bolt.Sips.Internals.Error.t()
def discard_all(transport, port, bolt_version, options) do
BoltProtocolHelper.treat_simple_message(:discard_all, transport, port, bolt_version, options)
end
@doc """
Implementation of Bolt's ACK_FAILURE. It acknowledges a failure while keeping
transactions alive.
See [http://boltprotocol.org/v1/#message-ack-failure](http://boltprotocol.org/v1/#message-ack-failure)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.ack_failure(:gen_tcp, port, 1, [])
:ok
"""
@spec ack_failure(atom(), port(), integer(), Keyword.t()) :: :ok | Bolt.Sips.Internals.Error.t()
def ack_failure(transport, port, bolt_version, options) do
BoltProtocolHelper.treat_simple_message(:ack_failure, transport, port, bolt_version, options)
end
@doc """
Implementation of Bolt's RESET message. It resets a session to a "clean"
state.
See [http://boltprotocol.org/v1/#message-reset](http://boltprotocol.org/v1/#message-reset)
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.reset(:gen_tcp, port, 1, [])
:ok
"""
@spec reset(atom(), port(), integer(), Keyword.t()) :: :ok | Bolt.Sips.Internals.Error.t()
def reset(transport, port, bolt_version, options) do
BoltProtocolHelper.treat_simple_message(:reset, transport, port, bolt_version, options)
end
end
================================================
FILE: lib/bolt_sips/internals/bolt_protocol_v2.ex
================================================
defmodule Bolt.Sips.Internals.BoltProtocolV2 do
@moduledoc false
# There's no specific messagee for Bolt V2
# This file exists only to fill the gap between the 2 bolt protocol versions
end
================================================
FILE: lib/bolt_sips/internals/bolt_protocol_v3.ex
================================================
defmodule Bolt.Sips.Internals.BoltProtocolV3 do
alias Bolt.Sips.Internals.BoltProtocol
alias Bolt.Sips.Internals.BoltProtocolHelper
alias Bolt.Sips.Internals.Error
@doc """
Implementation of Bolt's HELLO. It initialises the connection.
Expects a transport module (i.e. `gen_tcp`) and a `Port`. Accepts
authorisation params in the form of {username, password}.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Examples
iex> Bolt.Sips.Internals.BoltProtocolV3.hello(:gen_tcp, port, 3, {}, [])
{:ok, info}
iex> Bolt.Sips.Internals.BoltProtocolV3.hello(:gen_tcp, port, 3, {"username", "password"}, [])
{:ok, info}
"""
@spec hello(atom(), port(), integer(), tuple(), Keyword.t()) ::
{:ok, any()} | {:error, Bolt.Sips.Internals.Error.t()}
def hello(transport, port, bolt_version, auth, options \\ [recv_timeout: 15_000]) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:hello, [auth]})
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, info} ->
{:ok, info}
{:failure, response} ->
{:error, Error.exception(response, port, :hello)}
other ->
{:error, Error.exception(other, port, :hello)}
end
end
@doc """
Implementation of Bolt's RUN. It closes the connection.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Examples
iex> Bolt.Sips.Internals.BoltProtocolV3.goodbye(:gen_tcp, port, 3)
:ok
iex> Bolt.Sips.Internals.BoltProtocolV3.goodbye(:gen_tcp, port, 3)
:ok
"""
def goodbye(transport, port, bolt_version) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:goodbye, []})
try do
Port.close(port)
:ok
rescue
ArgumentError -> Error.exception("Can't close port", port, :goodbye)
end
end
@doc """
Implementation of Bolt's RUN. It passes a statement for execution on the server.
Note that this message doesn't return the statement result. For this purpose, use PULL_ALL.
In bolt >= 3, run has an additional paramters; metadata
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV1.run(:gen_tcp, port, 1, "RETURN $num AS num", %{num: 5}, %{}, [])
{:ok, {:success, %{"fields" => ["num"]}}}
"""
@spec run(atom(), port(), integer(), String.t(), map(), Bolt.Sips.Metadata.t(), Keyword.t()) ::
{:ok, any()} | {:error, Bolt.Sips.Internals.Error.t()}
def run(transport, port, bolt_version, statement, params, metadata, options) do
BoltProtocolHelper.send_message(
transport,
port,
bolt_version,
{:run, [statement, params, metadata]}
)
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, _} = result ->
{:ok, result}
{:failure, response} ->
{:error, Error.exception(response, port, :run)}
%Error{} = error ->
{:error, error}
other ->
{:error, Error.exception(other, port, :run)}
end
end
@doc """
Runs a statement (most likely Cypher statement) and returns a list of the
records and a summary (Act as as a RUN + PULL_ALL).
Records are represented using PackStream's record data type. Their Elixir
representation is a Keyword with the indexes `:sig` and `:fields`.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Examples
iex> Bolt.Sips.Internals.BoltProtocol.run_statement(:gen_tcp, port, 1, "MATCH (n) RETURN n")
[
{:success, %{"fields" => ["n"]}},
{:record, [sig: 1, fields: [1, "Example", "Labels", %{"some_attribute" => "some_value"}]]},
{:success, %{"type" => "r"}}
]
"""
@spec run_statement(
atom(),
port(),
integer(),
String.t(),
map(),
Bolt.Sips.Metadata.t(),
Keyword.t()
) ::
[
Bolt.Sips.Internals.PackStream.Message.decoded()
]
| Bolt.Sips.Internals.Error.t()
def run_statement(transport, port, bolt_version, statement, params, metadata, options) do
with {:ok, run_data} <-
run(transport, port, bolt_version, statement, params, metadata, options),
{:ok, result} <- BoltProtocol.pull_all(transport, port, bolt_version, options) do
[run_data | result]
else
{:error, %Error{} = error} ->
error
other ->
Error.exception(other, port, :run_statement)
end
end
@doc """
Implementation of Bolt's BEGIN. It opens a transaction.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV3.begin(:gen_tcp, port, 3, [])
{:ok, metadata}
"""
@spec begin(atom(), port(), integer(), Bolt.Sips.Metadata.t() | map(), Keyword.t()) ::
{:ok, any()} | Bolt.Sips.Internals.Error.t()
def begin(transport, port, bolt_version, metadata, options) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:begin, [metadata]})
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, info} ->
{:ok, info}
{:failure, response} ->
{:error, Error.exception(response, port, :begin)}
other ->
{:error, Error.exception(other, port, :begin)}
end
end
@doc """
Implementation of Bolt's COMMIT. It commits the open transaction.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV3.commit(:gen_tcp, port, 3, [])
:ok
"""
@spec commit(atom(), port(), integer(), Keyword.t()) ::
{:ok, any()} | Bolt.Sips.Internals.Error.t()
def commit(transport, port, bolt_version, options) do
BoltProtocolHelper.send_message(transport, port, bolt_version, {:commit, []})
case BoltProtocolHelper.receive_data(transport, port, bolt_version, options) do
{:success, info} ->
{:ok, info}
{:failure, response} ->
{:error, Error.exception(response, port, :commit)}
other ->
{:error, Error.exception(other, port, :commit)}
end
end
@doc """
Implementation of Bolt's ROLLBACK. It rollbacks the open transaction.
## Options
See "Shared options" in `Bolt.Sips.Internals.BoltProtocolHelper` documentation.
## Example
iex> BoltProtocolV3.rollback(:gen_tcp, port, 3, [])
:ok
"""
@spec rollback(atom(), port(), integer(), Keyword.t()) :: :ok | Bolt.Sips.Internals.Error.t()
def rollback(transport, port, bolt_version, options) do
BoltProtocolHelper.treat_simple_message(:rollback, transport, port, bolt_version, options)
end
end
================================================
FILE: lib/bolt_sips/internals/bolt_version_helper.ex
================================================
defmodule Bolt.Sips.Internals.BoltVersionHelper do
@moduledoc false
@available_bolt_versions [1, 2, 3]
@doc """
List bolt versions.
Only bolt version that have specific encoding functions are listed.
"""
@spec available_versions() :: [integer()]
def available_versions(), do: @available_bolt_versions
@doc """
Retrieve previous valid version.
Return nil if there is no previous version.
## Example
iex> Bolt.Sips.Internals.BoltVersionHelper.previous(2)
1
iex> Bolt.Sips.Internals.BoltVersionHelper.previous(1)
nil
iex> Bolt.Sips.Internals.BoltVersionHelper.previous(15)
3
"""
@spec previous(integer()) :: nil | integer()
def previous(version) do
@available_bolt_versions
|> Enum.take_while(&(&1 < version))
|> List.last()
end
@doc """
Return the last available bolt version.
## Example:
iex> Bolt.Sips.Internals.BoltVersionHelper.last()
3
"""
def last() do
List.last(@available_bolt_versions)
end
end
================================================
FILE: lib/bolt_sips/internals/error.ex
================================================
defmodule Bolt.Sips.Internals.Error do
@moduledoc false
defexception [:message, :code, :connection_id, :function, :type]
@type t :: %__MODULE__{
message: String.t(),
code: nil | any(),
connection_id: nil | integer(),
function: atom(),
type: atom()
}
@doc false
# Produce a Bolt.Sips.Internals.Error depending on the context.
@spec exception(any(), nil | port(), atom()) :: Bolt.Sips.Internals.Error.t()
def exception(%{"message" => message, "code" => code}, pid, function) do
%Bolt.Sips.Internals.Error{
message: message,
code: code,
connection_id: get_id(pid),
function: function,
type: :cypher_error
}
end
def exception({:error, :closed}, pid, function) do
%Bolt.Sips.Internals.Error{
message: "Port #{inspect(pid)} is closed",
connection_id: get_id(pid),
function: function,
type: :connection_error
}
end
def exception({:failure, %Bolt.Sips.Internals.Error{message: _message, code: _code} = err}, _pid, _function) do
err
end
def exception(%Bolt.Sips.Internals.Error{} = err, _pid, _function), do: err
def exception(message, pid, function) do
%Bolt.Sips.Internals.Error{
message: message_for(function, message),
connection_id: get_id(pid),
function: function,
type: :protocol_error
}
end
@spec message_for(nil | atom(), any()) :: String.t()
defp message_for(:handshake, "HTTP") do
"""
Handshake failed.
The port expected a HTTP request.
This happens when trying to Neo4J using the REST API Port (default: 7474)
instead of the Bolt Port (default: 7687).
"""
end
defp message_for(:handshake, bin) when is_binary(bin) do
"""
Handshake failed.
Expected 01:00:00:00 as a result, received: #{inspect(bin, base: :hex)}.
"""
end
defp message_for(:handshake, other) do
"""
Handshake failed.
Expected 01:00:00:00 as a result, received: #{inspect(other)}.
"""
end
defp message_for(nil, message) do
"""
Unknown failure: #{inspect(message)}
"""
end
defp message_for(_function, {:error, error}) do
case error |> :inet.format_error() |> to_string do
"unknown POSIX error" -> to_string(error)
other -> other
end
end
defp message_for(_function, {:ignored, []}) do
"""
The session is in a failed state and ignores further messages. You need to
`ACK_FAILURE` or `RESET` in order to send new messages.
"""
end
defp message_for(function, message) do
"""
#{function}: Unknown failure: #{inspect(message)}
"""
end
@spec get_id(any()) :: nil | integer()
defp get_id({:sslsocket, {:gen_tcp, port, _tls, _unused_yet}, _pid}) do
get_id(port)
end
defp get_id(port) when is_port(port) do
case Port.info(port, :id) do
{:id, id} -> id
nil -> nil
end
end
defp get_id(_), do: nil
end
================================================
FILE: lib/bolt_sips/internals/logger.ex
================================================
defmodule Bolt.Sips.Internals.Logger do
@moduledoc false
# Designed to log Bolt protocol message between Client and Server.
#
# The `from` parameter must be a atom, either `:client` or `:server`
require Logger
@doc """
Produces a formatted Log for a message
## Example
iex> Logger.log_message(:client, {:init, []})
"""
def log_message(from, {type, data}) do
msg_type = type |> Atom.to_string() |> String.upcase()
do_log_message(from, fn -> "#{msg_type} ~ #{inspect(data)}" end)
end
@doc """
Produces a formatted Log
## Example
iex> Logger.log_message(:server, :handshake, 2)
"""
def log_message(from, type, data) do
if Application.get_env(:bolt_sips, :log) do
log_message(from, {type, data})
end
end
@doc """
Produces a formatted Log for a message
Data will be output in hexadecimal
## Example
iex> Logger.log_message(:server, :handshake, <<0x02>>)
"""
def log_message(from, type, data, :hex) do
if Application.get_env(:bolt_sips, :log_hex, false) do
msg_type = type |> Atom.to_string() |> String.upcase()
do_log_message(from, fn ->
"#{msg_type} ~ #{inspect(data, base: :hex, limit: :infinity)}"
end)
end
end
defp do_log_message(from, func) when is_function(func) do
from_txt =
case from do
:server -> "S"
:client -> "C"
end
Logger.debug(fn -> "#{from_txt}: #{func.()}" end)
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Decoder do
@moduledoc false
_moduledoc = """
This module is responsible for dispatching decoding amongst decoder depending on the
used bolt version.
Most of the documentation regarding Bolt binary format can be found in
`Bolt.Sips.Internals.PackStream.EncoderV1` and `Bolt.Sips.Internals.PackStream.EncoderV2`.
Here will be found ocumenation about data that are only availalbe for decoding::
- Node
- Relationship
- Unbound relationship
- Path
"""
use Bolt.Sips.Internals.PackStream.DecoderImplV1
use Bolt.Sips.Internals.PackStream.DecoderImplV2
use Bolt.Sips.Internals.PackStream.DecoderUtils
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_impl_v1.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderImplV1 do
alias Bolt.Sips.Types
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
@last_version Bolt.Sips.Internals.BoltVersionHelper.last()
# Null
@null_marker 0xC0
# Boolean
@true_marker 0xC3
@false_marker 0xC2
# String
@tiny_bitstring_marker 0x8
@bitstring8_marker 0xD0
@bitstring16_marker 0xD1
@bitstring32_marker 0xD2
# Integer
@int8_marker 0xC8
@int16_marker 0xC9
@int32_marker 0xCA
@int64_marker 0xCB
# Float
@float_marker 0xC1
# List
@tiny_list_marker 0x9
@list8_marker 0xD4
@list16_marker 0xD5
@list32_marker 0xD6
# Map
@tiny_map_marker 0xA
@map8_marker 0xD8
@map16_marker 0xD9
@map32_marker 0xDA
# Structure
@tiny_struct_marker 0xB
@struct8_marker 0xDC
@struct16_marker 0xDD
# Node
@node_marker 0x4E
# Relationship
@relationship_marker 0x52
# Unbounded relationship
@unbounded_relationship_marker 0x72
# Path
@path_marker 0x50
@spec decode(binary() | {integer(), binary(), integer()}, integer()) ::
list() | {:error, :not_implemented}
def decode(<<@null_marker, rest::binary>>, bolt_version) when bolt_version <= @last_version do
[nil | decode(rest, bolt_version)]
end
# Boolean
def decode(<<@true_marker, rest::binary>>, bolt_version) when bolt_version <= @last_version do
[true | decode(rest, bolt_version)]
end
def decode(<<@false_marker, rest::binary>>, bolt_version) when bolt_version <= @last_version do
[false | decode(rest, bolt_version)]
end
# Float
def decode(<<@float_marker, number::float, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[number | decode(rest, bolt_version)]
end
# Strings
def decode(<<@tiny_bitstring_marker::4, str_length::4, rest::bytes>>, bolt_version)
when bolt_version <= @last_version do
decode_string(rest, str_length, bolt_version)
end
def decode(<<@bitstring8_marker, str_length, rest::bytes>>, bolt_version)
when bolt_version <= @last_version do
decode_string(rest, str_length, bolt_version)
end
def decode(<<@bitstring16_marker, str_length::16, rest::bytes>>, bolt_version)
when bolt_version <= @last_version do
decode_string(rest, str_length, bolt_version)
end
def decode(<<@bitstring32_marker, str_length::32, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
decode_string(rest, str_length, bolt_version)
end
# Lists
def decode(<<@tiny_list_marker::4, list_size::4>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_list(bin, list_size, bolt_version)
end
def decode(<<@list8_marker, list_size::8>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_list(bin, list_size, bolt_version)
end
def decode(<<@list16_marker, list_size::16>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_list(bin, list_size, bolt_version)
end
def decode(<<@list32_marker, list_size::32>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_list(bin, list_size, bolt_version)
end
# Maps
def decode(<<@tiny_map_marker::4, entries::4>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_map(bin, entries, bolt_version)
end
def decode(<<@map8_marker, entries::8>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_map(bin, entries, bolt_version)
end
def decode(<<@map16_marker, entries::16>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_map(bin, entries, bolt_version)
end
def decode(<<@map32_marker, entries::32>> <> bin, bolt_version)
when bolt_version <= @last_version do
decode_map(bin, entries, bolt_version)
end
# Struct
def decode(<<@tiny_struct_marker::4, struct_size::4, sig::8>> <> struct, bolt_version)
when bolt_version <= @last_version do
decode({sig, struct, struct_size}, bolt_version)
end
def decode(<<@struct8_marker, struct_size::8, sig::8>> <> struct, bolt_version)
when bolt_version <= @last_version do
decode({sig, struct, struct_size}, bolt_version)
end
def decode(<<@struct16_marker, struct_size::16, sig::8>> <> struct, bolt_version)
when bolt_version <= @last_version do
decode({sig, struct, struct_size}, bolt_version)
end
######### SPECIAL STRUCTS
# Node
def decode({@node_marker, struct, struct_size}, bolt_version)
when bolt_version <= @last_version do
{[id, labels, props], rest} = decode_struct(struct, struct_size, bolt_version)
node = %Types.Node{id: id, labels: labels, properties: props}
[node | rest]
end
# Relationship
def decode({@relationship_marker, struct, struct_size}, bolt_version)
when bolt_version <= @last_version do
{[id, start_node, end_node, type, props], rest} =
decode_struct(struct, struct_size, bolt_version)
relationship = %Types.Relationship{
id: id,
start: start_node,
end: end_node,
type: type,
properties: props
}
[relationship | rest]
end
# UnboundedRelationship
def decode({@unbounded_relationship_marker, struct, struct_size}, bolt_version)
when bolt_version <= @last_version do
{[id, type, props], rest} = decode_struct(struct, struct_size, bolt_version)
unbounded_relationship = %Types.UnboundRelationship{
id: id,
type: type,
properties: props
}
[unbounded_relationship | rest]
end
# Path
def decode({@path_marker, struct, struct_size}, bolt_version)
when bolt_version <= @last_version do
{[nodes, relationships, sequence], rest} =
decode_struct(struct, struct_size, bolt_version)
path = %Types.Path{
nodes: nodes,
relationships: relationships,
sequence: sequence
}
[path | rest]
end
# Manage the end of data
def decode("", bolt_version) when bolt_version <= @last_version do
[]
end
# Integers
def decode(<<@int8_marker, int::signed-integer, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[int | decode(rest, bolt_version)]
end
def decode(<<@int16_marker, int::signed-integer-16, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[int | decode(rest, bolt_version)]
end
def decode(<<@int32_marker, int::signed-integer-32, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[int | decode(rest, bolt_version)]
end
def decode(<<@int64_marker, int::signed-integer-64, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[int | decode(rest, bolt_version)]
end
def decode(<<int::signed-integer, rest::binary>>, bolt_version)
when bolt_version <= @last_version do
[int | decode(rest, bolt_version)]
end
end
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_impl_v2.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderImplV2 do
alias Bolt.Sips.Types.{TimeWithTZOffset, DateTimeWithTZOffset, Duration, Point}
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
@last_version Bolt.Sips.Internals.BoltVersionHelper.last()
# Null
@null_marker 0xC0
# Boolean
@true_marker 0xC3
@false_marker 0xC2
# String
@tiny_bitstring_marker 0x8
@bitstring8_marker 0xD0
@bitstring16_marker 0xD1
@bitstring32_marker 0xD2
# Integer
@int8_marker 0xC8
@int16_marker 0xC9
@int32_marker 0xCA
@int64_marker 0xCB
# Float
@float_marker 0xC1
# List
@tiny_list_marker 0x9
@list8_marker 0xD4
@list16_marker 0xD5
@list32_marker 0xD6
# Map
@tiny_map_marker 0xA
@map8_marker 0xD8
@map16_marker 0xD9
@map32_marker 0xDA
# Structure
@tiny_struct_marker 0xB
@struct8_marker 0xDC
@struct16_marker 0xDD
# Node
@node_marker 0x4E
# Relationship
@relationship_marker 0x52
# Unbounded relationship
@unbounded_relationship_marker 0x72
# Path
@path_marker 0x50
# Local Time
@local_time_signature 0x74
@local_time_struct_size 1
# Time With TZ Offset
@time_with_tz_signature 0x54
@time_with_tz_struct_size 2
# Date
@date_signature 0x44
@date_struct_size 1
# Local DateTime
@local_datetime_signature 0x64
@local_datetime_struct_size 2
# Datetime with TZ offset
@datetime_with_zone_offset_signature 0x46
@datetime_with_zone_offset_struct_size 3
# Datetime with TZ id
@datetime_with_zone_id_signature 0x66
@datetime_with_zone_id_struct_size 3
# Duration
@duration_signature 0x45
@duration_struct_size 4
# Point 2D
@point2d_signature 0x58
@point2d_struct_size 3
# Point 3D
@point3d_signature 0x59
@point3d_struct_size 4
# Local Date
def decode({@date_signature, struct, @date_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[date], rest} = decode_struct(struct, @date_struct_size, bolt_version)
[Date.add(~D[1970-01-01], date) | rest]
end
# Local Time
def decode({@local_time_signature, struct, @local_time_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[time], rest} = decode_struct(struct, @local_time_struct_size, bolt_version)
[Time.add(~T[00:00:00.000000], time, :nanosecond) | rest]
end
# Local DateTime
def decode({@local_datetime_signature, struct, @local_datetime_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[seconds, nanoseconds], rest} =
decode_struct(struct, @local_datetime_struct_size, bolt_version)
ndt =
NaiveDateTime.add(
~N[1970-01-01 00:00:00.000000000],
seconds * 1_000_000_000 + nanoseconds,
:nanosecond
)
[ndt | rest]
end
# Time with Zone Offset
def decode({@time_with_tz_signature, struct, @time_with_tz_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[time, offset], rest} = decode_struct(struct, @time_with_tz_struct_size, bolt_version)
t = TimeWithTZOffset.create(Time.add(~T[00:00:00.000000], time, :nanosecond), offset)
[t | rest]
end
# Datetime with zone Id
def decode(
{@datetime_with_zone_id_signature, struct, @datetime_with_zone_id_struct_size},
bolt_version
)
when bolt_version >= 2 and bolt_version <= @last_version do
{[seconds, nanoseconds, zone_id], rest} =
decode_struct(struct, @datetime_with_zone_id_struct_size, bolt_version)
naive_dt =
NaiveDateTime.add(
~N[1970-01-01 00:00:00.000000],
seconds * 1_000_000_000 + nanoseconds,
:nanosecond
)
dt = Bolt.Sips.TypesHelper.datetime_with_micro(naive_dt, zone_id)
[dt | rest]
end
# Datetime with zone offset
def decode(
{@datetime_with_zone_offset_signature, struct,
@datetime_with_zone_offset_struct_size},
bolt_version
)
when bolt_version >= 2 and bolt_version <= @last_version do
{[seconds, nanoseconds, zone_offset], rest} =
decode_struct(struct, @datetime_with_zone_id_struct_size, bolt_version)
naive_dt =
NaiveDateTime.add(
~N[1970-01-01 00:00:00.000000],
seconds * 1_000_000_000 + nanoseconds,
:nanosecond
)
dt = DateTimeWithTZOffset.create(naive_dt, zone_offset)
[dt | rest]
end
# Duration
def decode({@duration_signature, struct, @duration_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[months, days, seconds, nanoseconds], rest} =
decode_struct(struct, @duration_struct_size, bolt_version)
duration = Duration.create(months, days, seconds, nanoseconds)
[duration | rest]
end
# Point2D
def decode({@point2d_signature, struct, @point2d_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[srid, x, y], rest} = decode_struct(struct, @point2d_struct_size, bolt_version)
point = Point.create(srid, x, y)
[point | rest]
end
# Point3D
def decode({@point3d_signature, struct, @point3d_struct_size}, bolt_version)
when bolt_version >= 2 and bolt_version <= @last_version do
{[srid, x, y, z], rest} = decode_struct(struct, @point3d_struct_size, bolt_version)
point = Point.create(srid, x, y, z)
[point | rest]
end
end
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_utils.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderUtils do
alias Bolt.Sips.Internals.PackStreamError
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
@last_version Bolt.Sips.Internals.BoltVersionHelper.last()
def decode(data, bolt_version) when is_integer(bolt_version) do
if bolt_version > @last_version do
decode(data, @last_version)
else
raise PackStreamError,
data: data,
bolt_version: bolt_version,
message: "Unsupported decoder version"
end
end
def decode(_, _) do
{:error, :not_implemented}
end
@doc """
Decodes a struct
"""
@spec decode_struct(binary(), integer(), integer()) :: {list(), list()}
def decode_struct(struct, struct_size, bolt_version) do
struct
|> decode(bolt_version)
|> Enum.split(struct_size)
end
@spec to_map(list()) :: map()
defp to_map(map) do
map
|> Enum.chunk_every(2)
|> Enum.map(&List.to_tuple/1)
|> Map.new()
end
@spec decode_string(binary(), integer(), integer()) :: list()
defp decode_string(bytes, str_length, bolt_version) do
<<string::binary-size(str_length), rest::binary>> = bytes
[string | decode(rest, bolt_version)]
end
@spec decode_list(binary(), integer(), integer()) :: list()
defp decode_list(list, list_size, bolt_version) do
{list, rest} = list |> decode(bolt_version) |> Enum.split(list_size)
[list | rest]
end
@spec decode_map(binary(), integer(), integer()) :: list()
defp decode_map(map, entries, bolt_version) do
{map, rest} = map |> decode(bolt_version) |> Enum.split(entries * 2)
[to_map(map) | rest]
end
end
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_v1.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderV1 do
@moduledoc false
_moduledoc = """
Bolt V1 can decode:
- Null
- Boolean
- Integer
- Float
- String
- List
- Map
- Struct
Functions from this module are not meant to be used directly.
Use `Decoder.decode(data, bolt_version)` for all decoding purposes.
"""
use Bolt.Sips.Internals.PackStream.Markers
alias Bolt.Sips.Internals.PackStream.Decoder
@spec decode(binary() | {integer(), binary(), integer()}, integer()) ::
list() | {:error, :not_implemented}
def decode(data, bolt_version), do: Decoder.decode(data, bolt_version)
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_v2.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderV2 do
@moduledoc false
_module_doc = """
Bolt V2 has specification for decoding:
- Temporal types:
- Local Date
- Local Time
- Local DateTime
- Time with Timezone Offset
- DateTime with Timezone Id
- DateTime with Timezone Offset
- Duration
- Spatial types:
- Point2D
- Point3D
For documentation about those typs representation in Bolt binary,
please see `Bolt.Sips.Internals.PackStream.EncoderV2`.
Functions from this module are not meant to be used directly.
Use `Decoder.decode(data, bolt_version)` for all decoding purposes.
"""
use Bolt.Sips.Internals.PackStream.Markers
alias Bolt.Sips.Internals.PackStream.Decoder
# Local Date
@spec decode({integer(), binary(), integer()}, integer()) :: list() | {:error, :not_implemented}
def decode(data, bolt_version), do: Decoder.decode(data, bolt_version)
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/decoder_v3.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.DecoderV3 do
def decode(_, _) do
{:error, :not_implemented}
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/encoder.ex
================================================
alias Bolt.Sips.Internals.PackStream
alias Bolt.Sips.Internals.PackStream.EncoderHelper
defprotocol Bolt.Sips.Internals.PackStream.Encoder do
@moduledoc false
# Encodes an item to its binary PackStream Representation
#
# Implementation exists for following types:
# - Integer
# - Float
# - List
# - Map
# - Struct (defined in the Bolt protocol)
@fallback_to_any true
@doc """
Encode entity into its Bolt binary represenation depending of the used bolt version
"""
@spec encode(any(), integer()) :: binary()
def encode(entity, bolt_version)
end
defimpl PackStream.Encoder, for: Atom do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:atom, data, bolt_version)
end
defimpl PackStream.Encoder, for: BitString do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:string, data, bolt_version)
end
defimpl PackStream.Encoder, for: Integer do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:integer, data, bolt_version)
end
defimpl PackStream.Encoder, for: Float do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:float, data, bolt_version)
end
defimpl PackStream.Encoder, for: List do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:list, data, bolt_version)
end
defimpl PackStream.Encoder, for: Map do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:map, data, bolt_version)
end
defimpl PackStream.Encoder, for: Time do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:local_time, data, bolt_version)
end
defimpl PackStream.Encoder, for: Bolt.Sips.Types.TimeWithTZOffset do
def encode(data, bolt_version) do
EncoderHelper.call_encode(:time_with_tz, data, bolt_version)
end
end
defimpl PackStream.Encoder, for: Date do
def encode(data, bolt_version), do: EncoderHelper.call_encode(:date, data, bolt_version)
end
defimpl PackStream.Encoder, for: NaiveDateTime do
def encode(data, bolt_version) do
EncoderHelper.call_encode(:local_datetime, data, bolt_version)
end
end
defimpl PackStream.Encoder, for: DateTime do
def encode(data, version) do
EncoderHelper.call_encode(:datetime_with_tz_id, data, version)
end
end
defimpl PackStream.Encoder, for: Bolt.Sips.Types.DateTimeWithTZOffset do
def encode(data, version) do
EncoderHelper.call_encode(:datetime_with_tz_offset, data, version)
end
end
defimpl PackStream.Encoder, for: Bolt.Sips.Types.Duration do
def encode(data, version), do: EncoderHelper.call_encode(:duration, data, version)
end
defimpl PackStream.Encoder, for: Bolt.Sips.Types.Point do
def encode(data, version), do: EncoderHelper.call_encode(:point, data, version)
end
defimpl PackStream.Encoder, for: Any do
@spec encode({integer(), list()} | %{:__struct__ => String.t()}, integer()) ::
Bolt.Sips.Internals.PackStream.value() | <<_::16, _::_*8>>
def encode({signature, data}, bolt_version) when is_list(data) do
valid_signatures =
PackStream.Message.Encoder.valid_signatures(bolt_version) ++
Bolt.Sips.Internals.PackStream.MarkersHelper.valid_signatures()
if signature in valid_signatures do
EncoderHelper.call_encode(:struct, {signature, data}, bolt_version)
else
raise Bolt.Sips.Internals.PackStreamError,
message: "Unable to encode",
data: data,
bolt_version: bolt_version
end
end
# Elixir structs just need to be convertedd to map befoare being encoded
def encode(%{__struct__: _} = data, bolt_version) do
map = Map.from_struct(data)
PackStream.Encoder.encode(map, bolt_version)
end
def encode(data, bolt_version) do
raise Bolt.Sips.Internals.PackStreamError,
message: "Unable to encode",
data: data,
bolt_version: bolt_version
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/encoder_helper.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.EncoderHelper do
@moduledoc false
alias Bolt.Sips.Internals.BoltVersionHelper
alias Bolt.Sips.Internals.PackStreamError
use Bolt.Sips.Internals.PackStream.V1
use Bolt.Sips.Internals.PackStream.V2
use Bolt.Sips.Internals.PackStream.Utils
@available_bolt_versions BoltVersionHelper.available_versions()
@last_version BoltVersionHelper.last()
@doc """
For the given `data_type` and `bolt_version`, determine the right enconding function
and call it agains `data`
"""
@spec call_encode(atom(), any(), any()) :: binary() | PackStreamError.t()
def call_encode(data_type, data, bolt_version)
when is_integer(bolt_version) and bolt_version in @available_bolt_versions do
do_call_encode(data_type, data, bolt_version)
end
def call_encode(data_type, data, bolt_version) when is_integer(bolt_version) do
if bolt_version > @last_version do
call_encode(data_type, data, @last_version)
else
raise PackStreamError,
data_type: data_type,
data: data,
bolt_version: bolt_version,
message: "Unsupported encoder version"
end
end
def call_encode(data_type, data, bolt_version) do
raise PackStreamError,
data_type: data_type,
data: data,
bolt_version: bolt_version,
message: "Unsupported encoder version"
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/encoder_v1.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.EncoderV1 do
@moduledoc false
alias Bolt.Sips.Internals.PackStream.EncoderHelper
use Bolt.Sips.Internals.PackStream.Markers
@doc """
Encode an atom into Bolt binary format.
Encoding:
`Marker`
with
| Value | Marker |
| ------- | -------- |
| nil | `0xC0` |
| false | `0xC2` |
| true | `0xC3` |
Other atoms are converted to string before encoding.
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_atom(nil, 1))
<<0xC0>>
iex> :erlang.iolist_to_binary(EncoderV1.encode_atom(true, 1))
<<0xC3>>
iex> :erlang.iolist_to_binary(EncoderV1.encode_atom(:guten_tag, 1))
<<0x89, 0x67, 0x75, 0x74, 0x65, 0x6E, 0x5F, 0x74, 0x61, 0x67>>
"""
@spec encode_atom(atom(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_atom(atom , bolt_version), do: EncoderHelper.call_encode(:atom, atom, bolt_version)
@doc """
Encode a string into Bolt binary format.
Encoding:
`Marker` `Size` `Content`
with
| Marker | Size | Max data size |
|--------|------|---------------|
| `0x80`..`0x8F` | None (contained in marker) | 15 bytes |
| `0xD0` | 8-bit integer | 255 bytes |
| `0xD1` | 16-bit integer | 65_535 bytes |
| `0xD2` | 32-bit integer | 4_294_967_295 bytes |
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_string("guten tag", 1))
<<0x89, 0x67, 0x75, 0x74, 0x65, 0x6E, 0x20, 0x74, 0x61, 0x67>>
"""
@spec encode_string(String.t(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_string(string, bolt_version), do: EncoderHelper.call_encode(:string, string, bolt_version)
@doc """
Encode an integer into Bolt binary format.
Encoding:
`Marker` `Value`
with
| | Marker |
|---|--------|
| tiny int | `0x2A` |
| int8 | `0xC8` |
| int16 | `0xC9` |
| int32 | `0xCA` |
| int64 | `0xCB` |
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_integer(74, 1))
<<0x4A>>
iex> :erlang.iolist_to_binary(EncoderV1.encode_integer(-74_789, 1))
<<0xCA, 0xFF, 0xFE, 0xDB, 0xDB>>
"""
@spec encode_integer(integer(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_integer(integer, bolt_version), do: EncoderHelper.call_encode(:integer, integer, bolt_version)
@doc """
Encode a float into Bolt binary format.
Encoding: `Marker` `8 byte Content`.
Marker: `0xC1`
Formated according to the IEEE 754 floating-point "double format" bit layout.
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_float(42.42, 1))
<<0xC1, 0x40, 0x45, 0x35, 0xC2, 0x8F, 0x5C, 0x28, 0xF6>>
"""
@spec encode_float(float(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_float(number, bolt_version), do: EncoderHelper.call_encode(:float, number, bolt_version)
@doc """
Encode a list into Bolt binary format.
Encoding:
`Marker` `Size` `Content`
with
| Marker | Size | Max list size |
|--------|------|---------------|
| `0x90`..`0x9F` | None (contained in marker) | 15 bytes |
| `0xD4` | 8-bit integer | 255 items |
| `0xD5` | 16-bit integer | 65_535 items |
| `0xD6` | 32-bit integer | 4_294_967_295 items |
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_list(["hello", "world"], 1))
<<0x92, 0x85, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x85, 0x77, 0x6F, 0x72, 0x6C, 0x64>>
"""
@spec encode_list(list(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_list(list, bolt_version), do: EncoderHelper.call_encode(:list, list, bolt_version)
@doc """
Encode a map into Bolt binary format.
Note that Elixir structs are converted to map for encoding purpose.
Encoding:
`Marker` `Size` `Content`
with
| Marker | Size | Max map size |
|--------|------|---------------|
| `0xA0`..`0xAF` | None (contained in marker) | 15 entries |
| `0xD8` | 8-bit integer | 255 entries |
| `0xD9` | 16-bit integer | 65_535 entries |
| `0xDA` | 32-bit integer | 4_294_967_295 entries |
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_map(%{id: 345, value: "hello world"}, 1))
<<0xA2, 0x82, 0x69, 0x64, 0xC9, 0x1, 0x59, 0x85, 0x76, 0x61, 0x6C, 0x75,
0x65, 0x8B, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64>>
"""
@spec encode_map(map(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_map(map, bolt_version), do: EncoderHelper.call_encode(:map, map, bolt_version)
@doc """
Encode a struct into Bolt binary format.
This concerns Bolt Structs as defined in []().
Elixir structs are just converted to regular maps before encoding
Encoding:
`Marker` `Size` `Signature` `Content`
with
| Marker | Size | Max structure size |
|--------|------|---------------|
| `0xB0`..`0xBF` | None (contained in marker) | 15 fields |
| `0xDC` | 8-bit integer | 255 fields |
| `0xDD` | 16-bit integer | 65_535 fields |
## Example
iex> alias Bolt.Sips.Internals.PackStream.EncoderV1
iex> :erlang.iolist_to_binary(EncoderV1.encode_struct({0x01, ["two", "params"]}, 1))
<<0xB2, 0x1, 0x83, 0x74, 0x77, 0x6F, 0x86, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x73>>
"""
@spec encode_struct({integer(), list()}, integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_struct(struct, bolt_version) , do: EncoderHelper.call_encode(:struct, struct, bolt_version)
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/encoder_v2.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.EncoderV2 do
@moduledoc false
use Bolt.Sips.Internals.PackStream.Markers
alias Bolt.Sips.Internals.PackStream.EncoderHelper
alias Bolt.Sips.Types.{TimeWithTZOffset, DateTimeWithTZOffset, Duration, Point}
@doc """
Encode a Time (represented by Time) into Bolt binary format.
Encoded in a structure.
Signature: `0x74`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nanoseconds_from_00:00:00`
## Example
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_local_time(~T[06:54:32.453], 2))
<<0xB1, 0x74, 0xCB, 0x0, 0x0, 0x16, 0x9F, 0x11, 0xB9, 0xCB, 0x40>>
"""
@spec encode_local_time(Time.t(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_local_time(local_time, bolt_version), do: EncoderHelper.call_encode(:local_time, local_time, bolt_version)
@doc """
Encode a TIME WITH TIMEZONE OFFSET (represented by TimeWithTZOffset) into Bolt binary format.
Encoded in a structure.
Signature: `0x54`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nanoseconds_from_00:00:00` `Offset_in_seconds`
## Example
iex> time_with_tz = Bolt.Sips.Types.TimeWithTZOffset.create(~T[06:54:32.453], 3600)
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_time_with_tz(time_with_tz, 2))
<<0xB2, 0x54, 0xCB, 0x0, 0x0, 0x16, 0x9F, 0x11, 0xB9, 0xCB, 0x40, 0xC9, 0xE, 0x10>>
"""
def encode_time_with_tz(%TimeWithTZOffset{time: time, timezone_offset: offset}, bolt_version), do: EncoderHelper.call_encode(:time_with_tz, %TimeWithTZOffset{time: time, timezone_offset: offset}, bolt_version )
@doc """
Encode a DATE (represented by Date) into Bolt binary format.
Encoded in a structure.
Signature: `0x44`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nb_days_since_epoch`
## Example
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_date(~D[2019-04-23], 2))
<<0xB1, 0x44, 0xC9, 0x46, 0x59>>
"""
@spec encode_date(Date.t(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_date(date, bolt_version), do: EncoderHelper.call_encode(:date, date, bolt_version)
@doc """
Encode a LOCAL DATETIME (Represented by NaiveDateTime) into Bolt binary format.
Encoded in a structure.
WARNING: Nanoseconds are left off as NaiveDateTime doesn't handle them.
A new Calendar should be implemented to manage them.
Signature: `0x64`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nb_seconds_since_epoch` `Remainder_in_nanoseconds`
## Example
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_local_datetime(~N[2019-04-23 13:45:52.678], 2))
<<0xB2, 0x64, 0xCA, 0x5C, 0xBF, 0x17, 0x10, 0xCA, 0x28, 0x69, 0x75, 0x80>>
"""
@spec encode_local_datetime(Calendar.naive_datetime(), integer()) ::
Bolt.Sips.Internals.PackStream.value()
def encode_local_datetime(local_datetime, bolt_version), do: EncoderHelper.call_encode(:local_datetime, local_datetime, bolt_version)
@doc """
Encode DATETIME WITH TIMEZONE ID (represented by Calendar.DateTime) into Bolt binary format.
Encoded in a structure.
WARNING: Nanoseconds are left off as NaiveDateTime doesn't handle them.
A new Calendar should be implemented to manage them.
Signature: `0x66`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nb_seconds_since_epoch` `Remainder_in_nanoseconds` `Zone_id`
## Example
iex> d = Bolt.Sips.TypesHelper.datetime_with_micro(~N[2013-11-12 07:32:02.003],
...> "Europe/Berlin")
#DateTime<2013-11-12 07:32:02.003+01:00 CET Europe/Berlin>
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_datetime_with_tz_id(d, 2))
<<0xB3, 0x66, 0xCA, 0x52, 0x81, 0xD9, 0x72, 0xCA, 0x0, 0x2D, 0xC6, 0xC0, 0x8D, 0x45, 0x75,
0x72, 0x6F, 0x70, 0x65, 0x2F, 0x42, 0x65, 0x72, 0x6C, 0x69, 0x6E>>
"""
@spec encode_datetime_with_tz_id(Calendar.datetime(), integer()) ::
Bolt.Sips.Internals.PackStream.value()
def encode_datetime_with_tz_id(datetime, bolt_version), do: EncoderHelper.call_encode(:datetime_with_tz_id, datetime, bolt_version)
@doc """
Encode DATETIME WITH TIMEZONE OFFSET (represented by DateTimeWithTZOffset) into Bolt binary format.
Encoded in a structure.
WARNING: Nanoseconds are left off as NaiveDateTime doesn't handle them.
A new Calendar should be implemented to manage them.
Signature: `0x46`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Nb_seconds_since_epoch` `Remainder_in_nanoseconds` `Zone_offset`
## Example
iex> d = Bolt.Sips.Types.DateTimeWithTZOffset.create(~N[2013-11-12 07:32:02.003], 7200)
%Bolt.Sips.Types.DateTimeWithTZOffset{
naive_datetime: ~N[2013-11-12 07:32:02.003],
timezone_offset: 7200
}
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_datetime_with_tz_offset(d, 2))
<<0xB3, 0x46, 0xCA, 0x52, 0x81, 0xD9, 0x72, 0xCA, 0x0, 0x2D, 0xC6, 0xC0, 0xC9, 0x1C, 0x20>>
"""
@spec encode_datetime_with_tz_offset(DateTimeWithTZOffset.t(), integer()) ::
Bolt.Sips.Internals.PackStream.value()
def encode_datetime_with_tz_offset(
%DateTimeWithTZOffset{naive_datetime: ndt, timezone_offset: tz_offset},
bolt_version
), do: EncoderHelper.call_encode(:datetime_with_tz_offset,
%DateTimeWithTZOffset{naive_datetime: ndt, timezone_offset: tz_offset},
bolt_version
)
@doc """
Encode DURATION (represented by Duration) into Bolt binary format.
Encoded in a structure.
Signature: `0x45`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`Months` `Days` `Seconds` `Nanoseconds`
## Example
iex(60)> d = %Bolt.Sips.Types.Duration{
...(60)> years: 3,
...(60)> months: 1,
...(60)> weeks: 7,
...(60)> days: 4,
...(60)> hours: 13,
...(60)> minutes: 2,
...(60)> seconds: 21,
...(60)> nanoseconds: 554
...(60)> }
%Bolt.Sips.Types.Duration{
days: 4,
hours: 13,
minutes: 2,
months: 1,
nanoseconds: 554,
seconds: 21,
weeks: 7,
years: 3
}
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_duration(d, 2))
<<0xB4, 0x45, 0x25, 0x35, 0xCA, 0x0, 0x0, 0xB7, 0x5D, 0xC9, 0x2, 0x2A>>
"""
@spec encode_duration(Duration.t(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_duration(%Duration{} = duration, bolt_version), do: EncoderHelper.call_encode(:duration, duration, bolt_version)
@doc """
Encode POINT 2D & 3D (represented by Point) into Bolt binary format.
Encoded in a structure.
## Point 2D
Signature: `0x58`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`SRID` `x_or_longitude` `y_or_latitude`
## Example
iex> p = Bolt.Sips.Types.Point.create(:wgs_84, 65.43, 12.54)
%Bolt.Sips.Types.Point{
crs: "wgs-84",
height: nil,
latitude: 12.54,
longitude: 65.43,
srid: 4326,
x: 65.43,
y: 12.54,
z: nil
}
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_point(p, 2))
<<0xB3, 0x58, 0xC9, 0x10, 0xE6, 0xC1, 0x40, 0x50, 0x5B, 0x85, 0x1E, 0xB8, 0x51, 0xEC, 0xC1,
0x40, 0x29, 0x14, 0x7A, 0xE1, 0x47, 0xAE, 0x14>>
## Point 3D
Signature: `0x58`
Encoding:
`Marker` `Size` `Signature` ` Content`
where `Content` is:
`SRID` `x_or_longitude` `y_or_latitude` `z_or_height`
## Example
iex> p = Bolt.Sips.Types.Point.create(:wgs_84, 45.0003, 40.3245, 23.1)
%Bolt.Sips.Types.Point{
crs: "wgs-84-3d",
height: 23.1,
latitude: 40.3245,
longitude: 45.0003,
srid: 4979,
x: 45.0003,
y: 40.3245,
z: 23.1
}
iex> :erlang.iolist_to_binary(Bolt.Sips.Internals.PackStream.EncoderV2.encode_point(p, 2))
<<0xB4, 0x59, 0xC9, 0x13, 0x73, 0xC1, 0x40, 0x46, 0x80, 0x9, 0xD4, 0x95, 0x18, 0x2B, 0xC1,
0x40, 0x44, 0x29, 0x89, 0x37, 0x4B, 0xC6, 0xA8, 0xC1, 0x40, 0x37, 0x19, 0x99, 0x99, 0x99,
0x99, 0x9A>>
"""
@spec encode_point(Point.t(), integer()) :: Bolt.Sips.Internals.PackStream.value()
def encode_point(%Point{z: nil} = point, bolt_version), do: EncoderHelper.call_encode(:point, point, bolt_version)
def encode_point(%Point{} = point, bolt_version), do: EncoderHelper.call_encode(:point, point, bolt_version)
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/encoder_v3.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.EncoderV3 do
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/error.ex
================================================
defmodule Bolt.Sips.Internals.PackStreamError do
@moduledoc false
# Represents an error when encoding data for the Bolt protocol.
defexception data_type: nil, data: nil, message: nil, bolt_version: nil
@typedoc """
Send back the `item` that cannot be encoded with a `message` explaining the reason why it
can't be successfully encoded.
"""
@type t :: %__MODULE__{
data_type: atom(),
data: any(),
message: String.t(),
bolt_version: integer()
}
def message(%{data_type: nil, data: data, message: message, bolt_version: bolt_version}) do
"#{message} [bolt_version: #{inspect(bolt_version)}, data: #{inspect(data)}]"
end
def message(%{data_type: data_type, data: data, message: message, bolt_version: bolt_version}) do
"#{message} [bolt_version: #{inspect(bolt_version)}, data_type: #{data_type}, data: #{
inspect(data)
}]"
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/markers.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Markers do
@moduledoc false
defmacro __using__(_opts) do
quote do
# Null
@null_marker 0xC0
# Boolean
@true_marker 0xC3
@false_marker 0xC2
# String
@tiny_bitstring_marker 0x8
@bitstring8_marker 0xD0
@bitstring16_marker 0xD1
@bitstring32_marker 0xD2
# Integer
@int8_marker 0xC8
@int16_marker 0xC9
@int32_marker 0xCA
@int64_marker 0xCB
# Float
@float_marker 0xC1
# List
@tiny_list_marker 0x9
@list8_marker 0xD4
@list16_marker 0xD5
@list32_marker 0xD6
# Map
@tiny_map_marker 0xA
@map8_marker 0xD8
@map16_marker 0xD9
@map32_marker 0xDA
# Structure
@tiny_struct_marker 0xB
@struct8_marker 0xDC
@struct16_marker 0xDD
# Node
@node_marker 0x4E
# Relationship
@relationship_marker 0x52
# Unbounded relationship
@unbounded_relationship_marker 0x72
# Path
@path_marker 0x50
# Local Time
@local_time_signature 0x74
@local_time_struct_size 1
# Time With TZ Offset
@time_with_tz_signature 0x54
@time_with_tz_struct_size 2
# Date
@date_signature 0x44
@date_struct_size 1
# Local DateTime
@local_datetime_signature 0x64
@local_datetime_struct_size 2
# Datetime with TZ offset
@datetime_with_zone_offset_signature 0x46
@datetime_with_zone_offset_struct_size 3
# Datetime with TZ id
@datetime_with_zone_id_signature 0x66
@datetime_with_zone_id_struct_size 3
# Duration
@duration_signature 0x45
@duration_struct_size 4
# Point 2D
@point2d_signature 0x58
@point2d_struct_size 3
# Point 3D
@point3d_signature 0x59
@point3d_struct_size 4
end
end
end
defmodule Bolt.Sips.Internals.PackStream.MarkersHelper do
@moduledoc false
use Bolt.Sips.Internals.PackStream.Markers
@doc """
Return the list of valid signatures (for data encoding).
"""
@spec valid_signatures() :: [integer()]
def valid_signatures() do
[
@local_time_signature,
@time_with_tz_signature,
@date_signature,
@local_datetime_signature,
@datetime_with_zone_offset_signature,
@datetime_with_zone_id_signature,
@duration_signature,
@point2d_signature,
@point3d_signature
]
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/decoder.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.Decoder do
@moduledoc false
@tiny_struct_marker 0xB
@success_signature 0x70
@failure_signature 0x7F
@record_signature 0x71
@ignored_signature 0x7E
# Decode SUCCESS message
@spec decode(Bolt.Sips.Internals.PackStream.Message.encoded(), integer()) ::
Bolt.Sips.Internals.PackStream.Message.decoded()
def decode(
<<@tiny_struct_marker::4, nb_entries::4, @success_signature, data::binary>>,
bolt_version
) do
build_response(:success, data, nb_entries, bolt_version)
end
# Decode FAILURE message
def decode(
<<@tiny_struct_marker::4, nb_entries::4, @failure_signature, data::binary>>,
bolt_version
) do
build_response(:failure, data, nb_entries, bolt_version)
end
# Decode RECORD message
def decode(
<<@tiny_struct_marker::4, nb_entries::4, @record_signature, data::binary>>,
bolt_version
) do
build_response(:record, data, nb_entries, bolt_version)
end
# Decode IGNORED message
def decode(
<<@tiny_struct_marker::4, nb_entries::4, @ignored_signature, data::binary>>,
bolt_version
) do
build_response(:ignored, data, nb_entries, bolt_version)
end
@spec build_response(
Bolt.Sips.Internals.PackStream.Message.in_signature(),
any(),
integer(),
integer()
) ::
Bolt.Sips.Internals.PackStream.Message.decoded()
defp build_response(message_type, data, nb_entries, bolt_version) do
Bolt.Sips.Internals.Logger.log_message(:server, message_type, data, :hex)
response =
case Bolt.Sips.Internals.PackStream.decode(data, bolt_version) do
response when nb_entries == 1 ->
List.first(response)
responses ->
responses
end
Bolt.Sips.Internals.Logger.log_message(:server, message_type, response)
{message_type, response}
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/encoder.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.Encoder do
@moduledoc false
_module_doc = """
Manages the message encoding.
A mesage is a tuple formated as:
`{message_type, data}`
with:
- message_type: atom amongst the valid message type (:init, :discard_all, :pull_all,
:ack_failure, :reset, :run)
- data: a list of data to be used by the message
Messages are passed in one or more chunk. The structure of a chunk is as follow: `chunk_size` `data`
with `chunk_size` beign a 16-bit integer.
A message always ends with the end marker `0x00 0x00`.
Thus the possible typologies of messages are:
- One-chunk message:
`chunk_size` `message_data` `end_marker`
- multiple-chunk message:
`chunk_1_size` `message_data` `chunk_n_size` `message_data`...`end_marker`
More documentation on message transfer encoding:
[https://boltprotocol.org/v1/#message_transfer_encoding](https://boltprotocol.org/v1/#message_transfer_encoding)
All messages are serialized structures. See `Bolt.Sips.Internals.PackStream.EncoderV1` for
more information about structure encoding).
An extensive documentation on messages can be found here:
[https://boltprotocol.org/v1/#messages](https://boltprotocol.org/v1/#messages)
"""
alias Bolt.Sips.Metadata
@max_chunk_size 65_535
@end_marker <<0x00, 0x00>>
@ack_failure_signature 0x0E
@begin_signature 0x11
@commit_signature 0x12
@discard_all_signature 0x2F
@goodbye_signature 0x02
@hello_signature 0x01
@init_signature 0x01
@pull_all_signature 0x3F
@reset_signature 0x0F
@rollback_signature 0x13
@run_signature 0x10
# OUT Signature
# TODO improve using macros?
@valid_signatures [
@ack_failure_signature,
@begin_signature,
@commit_signature,
@discard_all_signature,
@goodbye_signature,
@hello_signature,
@pull_all_signature,
@reset_signature,
@rollback_signature,
@run_signature
]
@valid_v1_signatures [
@ack_failure_signature,
@discard_all_signature,
@init_signature,
@pull_all_signature,
@reset_signature,
@run_signature
]
@valid_message_types [
:ack_failure,
:begin,
:commit,
:discard_all,
:goodbye,
:hello,
:rollback,
:pull_all,
:reset,
:run
]
@valid_v1_message_types [
:ack_failure,
:discard_all,
:init,
:pull_all,
:reset,
:run
]
@last_bolt_version 3
@spec signature(Bolt.Sips.Internals.PackStream.Message.out_signature()) :: integer()
defp signature(:ack_failure), do: @ack_failure_signature
defp signature(:discard_all), do: @discard_all_signature
defp signature(:pull_all), do: @pull_all_signature
defp signature(:reset), do: @reset_signature
defp signature(:begin), do: @begin_signature
defp signature(:commit), do: @commit_signature
defp signature(:goodbye), do: @goodbye_signature
defp signature(:hello), do: @hello_signature
defp signature(:rollback), do: @rollback_signature
defp signature(:run), do: @run_signature
defp signature(:init), do: @init_signature
@doc """
Return client name (based on bolt_sips version)
"""
def client_name() do
"BoltSips/" <> to_string(Application.spec(:bolt_sips, :vsn))
end
@doc """
Return the valid message signatures depending on the Bolt version
"""
@spec valid_signatures(integer()) :: [integer()]
def valid_signatures(bolt_version) when bolt_version <= 2 do
@valid_v1_signatures
end
def valid_signatures(3) do
@valid_signatures
end
# Encode messages for bolt version 3
# Encode HELLO message without auth token
@spec encode({Bolt.Sips.Internals.PackStream.Message.out_signature(), list()}, integer()) ::
Bolt.Sips.Internals.PackStream.Message.encoded()
| {:error, :not_implemented}
| {:error, :invalid_message}
def encode({:hello, []}, 3) do
encode({:hello, [{}]}, 3)
end
# Encode INIT message with a valid auth token.
# The auth token is tuple formated as: {user, password}
def encode({:hello, [auth]}, 3) do
do_encode(:hello, [auth_params(auth)], 3)
end
# Encode BEGIN message without metadata.
# BEGIN is used to open a transaction.
def encode({:begin, []}, 3) do
encode({:begin, [%{}]}, 3)
end
# Encode BEGIN message with metadata
def encode({:begin, [%Metadata{} = metadata]}, 3) do
do_encode(:begin, [Metadata.to_map(metadata)], 3)
end
def encode({:begin, [%{} = map]}, 3) when map_size(map) == 0 do
{:ok, metadata} = Metadata.new(%{})
encode({:begin, [metadata]}, 3)
end
def encode({:begin, _}, _) do
{:error, :invalid_data}
end
# Encode RUN without params nor metadata
def encode({:run, [statement]}, 3) do
do_encode(:run, [statement, %{}, %{}], 3)
end
# Encode RUN message with its data: statement and parameters
def encode({:run, [statement]}, bolt_version) when bolt_version <= 2 do
do_encode(:run, [statement, %{}], bolt_version)
end
# Encode RUN with params but without metadata
def encode({:run, [statement, params]}, 3) do
do_encode(:run, [statement, params, %{}], 3)
end
# Encode RUN with params and metadata
def encode({:run, [statement, params, %Metadata{} = metadata]}, 3) do
do_encode(:run, [statement, params, Metadata.to_map(metadata)], 3)
end
# INIT is no more a valid message in Bolt V3
def encode({:init, _}, 3) do
{:error, :invalid_message}
end
# Encode INIT message without auth token
def encode({:init, []}, bolt_version) when bolt_version <= 2 do
encode({:init, [{}]}, bolt_version)
end
# Encode INIT message with a valid auth token.
# The auth token is tuple formated as: {user, password}
def encode({:init, [auth]}, bolt_version) when bolt_version <= 2 do
do_encode(:init, [client_name(), auth_params_v1(auth)], bolt_version)
end
# Encode messages that don't need any data formating
def encode({message_type, data}, 3) when message_type in @valid_message_types do
do_encode(message_type, data, 3)
end
# Encode messages that don't need any data formating
def encode({message_type, data}, bolt_version)
when bolt_version <= 2 and message_type in @valid_v1_message_types do
do_encode(message_type, data, bolt_version)
end
@doc """
Encode Bolt V3 messages
Not that INIT is not valid in bolt V3, it is replaced by HELLO
## HELLO
Usage: intialize the session.
Signature: `0x01` (Same as INIT in previous bolt version)
Struct: `auth_parms`
with:
| data | type |
|-----|-----|
|auth_token | map: {scheme: string, principal: string, credentials: string, user_agent: string}|
Note: `user_agent` is equivalent to `client_name` in bolt previous version.
Examples (excluded from doctest because client_name changes at each bolt_sips version)
# without auth token
diex> :erlang.iolist_to_binary(Encoder.encode({:hello, []}, 3))
<<0x0, 0x1D, 0xB1, 0x1, 0xA1, 0x8A, 0x75, 0x73, 0x65, 0x72, 0x5F, 0x61, 0x67, 0x65, 0x6E,
0x74, 0x8E, 0x42, 0x6F, 0x6C, 0x74, 0x53, 0x69, 0x70, 0x73, 0x2F, 0x31, 0x2E, 0x34, 0x2E,
0x30, 0x0, 0x0>>
# with auth token
diex(20)> :erlang.iolist_to_binary(Encoder.encode({:hello, [{"neo4j", "test"}]}, 3))
<<0x0, 0x4B, 0xB1, 0x1, 0xA4, 0x8B, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x61,
0x6C, 0x73, 0x84, 0x74, 0x65, 0x73, 0x74, 0x89, 0x70, 0x72, 0x69, 0x6E, 0x63, 0x69, 0x70,
0x61, 0x6C, 0x85, 0x6E, 0x65, 0x6F, 0x34, 0x6A, 0x86, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x65,
0x85, 0x62, 0x61, 0x73, 0x69, ...>>
## GOODBYE
Usage: close the connection with the server
Signature: `0x02`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:goodbye, []}, 3))
<<0x0, 0x2, 0xB0, 0x2, 0x0, 0x0>>
## BEGIN
Usage: Open a transaction
Signature: `0x11`
Struct: `metadata`
with:
| data | type |
|------|------|
| metadata | See Bolt.Sips.Metadata
Example
# without metadata
# iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
# iex> :erlang.iolist_to_binary(Encoder.encode({:begin, []}, 3))
# <<0x0, 0x3, 0xB1, 0x11, 0xA0, 0x0, 0x0>>
# # with metadata
# iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
# iex> alias Bolt.Sips.Metadata
# iex> {:ok, metadata} = Metadata.new(%{tx_timeout: 5000})
# {:ok,
# %Bolt.Sips.Metadata{
# bookmarks: nil,
# metadata: nil,
# tx_timeout: 5000
# }}
# iex> :erlang.iolist_to_binary(Encoder.encode({:begin, [metadata]}, 3))
# <<0x0, 0x11, 0xB1, 0x11, 0xA1, 0x8A, 0x74, 0x78, 0x5F, 0x74, 0x69, 0x6D, 0x65, 0x6F, 0x75,
# 0x74, 0xC9, 0x13, 0x88, 0x0, 0x0>>
## COMMIT
Usage: commit the currently open transaction
Signature: `0x12`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:commit, []}, 3))
<<0x0, 0x2, 0xB0, 0x12, 0x0, 0x0>>
## ROLLBACK
Usage: rollback the currently open transaction
Signature: `0x13`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:rollback, []}, 3))
<<0x0, 0x2, 0xB0, 0x13, 0x0, 0x0>>
## RUN
Usage: pass statement for execution to the server. Same as in bolt previous version.
The only difference: `metadata` are passed as well since bolt v3.
Signature: `0x10`
Struct: `statement` `parameters` `metadata`
with:
| data | type |
|-----|-----|
| statement | string |
| parameters | map |
| metadata | See Bolt.Sips.Metadata
Example
# without params nor metadata
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN 'hello' AS str"]}, 3))
<<0x0, 0x1B, 0xB3, 0x10, 0xD0, 0x15, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20, 0x27, 0x68,
0x65, 0x6C, 0x6C, 0x6F, 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x74, 0x72, 0xA0, 0xA0, 0x0,
0x0>>
# without params but with metadata
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> alias Bolt.Sips.Metadata
iex> {:ok, metadata} = Metadata.new(%{tx_timeout: 4500})
{:ok,
%Bolt.Sips.Metadata{
bookmarks: nil,
metadata: nil,
tx_timeout: 4500
}}
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN 'hello' AS str", %{}, metadata]}, 3))
<<0x0, 0x29, 0xB3, 0x10, 0xD0, 0x15, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20, 0x27, 0x68,
0x65, 0x6C, 0x6C, 0x6F, 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x74, 0x72, 0xA0, 0xA1, 0x8A,
0x74, 0x78, 0x5F, 0x74, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0xC9, 0x11, 0x94, 0x0, 0x0>>
# with params but without metadata
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN $str AS str", %{str: "hello"}]}, 3))
<<0x0, 0x22, 0xB3, 0x10, 0xD0, 0x12, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20,
0x24, 0x73, 0x74, 0x72, 0x20, 0x41, 0x53, 0x20, 0x73, 0x74, 0x72, 0xA1, 0x83,
0x73, 0x74, 0x72, 0x85, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0xA0, 0x0, 0x0>>
# with params and metadata
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> alias Bolt.Sips.Metadata
iex> {:ok, metadata} = Metadata.new(%{tx_timeout: 4500})
{:ok,
%Bolt.Sips.Metadata{
bookmarks: nil,
metadata: nil,
tx_timeout: 4500
}}
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN $str AS str", %{str: "hello"}, metadata]}, 3))
<<0x0, 0x30, 0xB3, 0x10, 0xD0, 0x12, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20,
0x24, 0x73, 0x74, 0x72, 0x20, 0x41, 0x53, 0x20, 0x73, 0x74, 0x72, 0xA1, 0x83,
0x73, 0x74, 0x72, 0x85, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0xA1, 0x8A, 0x74, 0x78,
0x5F, 0x74, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0xC9, 0x11, 0x94, 0x0, 0x0>>
# Encode messages v1
# Supported messages
## INIT
Usage: intialize the session.
Signature: `0x01`
Struct: `client_name` `auth_token`
with:
| data | type |
|-----|-----|
|client_name | string|
|auth_token | map: {scheme: string, principal: string, credentials: string}|
Examples (excluded from doctest because client_name changes at each bolt_sips version)
# without auth token
diex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
:erlang.iolist_to_binary(Encoder.encode({:init, []}, 1))
<<0x0, 0x10, 0xB2, 0x1, 0x8C, 0x42, 0x6F, 0x6C, 0x74, 0x65, 0x78, 0x2F, 0x30, 0x2E, 0x34,
0x2E, 0x30, 0xA0, 0x0, 0x0>>
# with auth token
# The auth token is tuple formated as: {user, password}
diex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
diex> :erlang.iolist_to_binary(Encoder.encode({:init, [{"neo4j", "password"}]}))
<<0x0, 0x42, 0xB2, 0x1, 0x8C, 0x42, 0x6F, 0x6C, 0x74, 0x65, 0x78, 0x2F, 0x30, 0x2E, 0x34,
0x2E, 0x30, 0xA3, 0x8B, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x73,
0x88, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x89, 0x70, 0x72, 0x69, 0x6E, 0x63,
0x69, 0x70, 0x61, 0x6C, 0x85, ...>>
## RUN
Usage: pass statement for execution to the server.
Signature: `0x10`
Struct: `statement` `parameters`
with:
| data | type |
|-----|-----|
| statement | string |
| parameters | map |
Examples
# without parameters
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN 1 AS num"]}, 1))
<<0x0, 0x13, 0xB2, 0x10, 0x8F, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20, 0x31, 0x20, 0x41,
0x53, 0x20, 0x6E, 0x75, 0x6D, 0xA0, 0x0, 0x0>>
# with parameters
iex> :erlang.iolist_to_binary(Encoder.encode({:run, ["RETURN $num AS num", %{num: 1}]}, 1))
<<0x0, 0x1C, 0xB2, 0x10, 0xD0, 0x12, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4E, 0x20,
0x24, 0x6E, 0x75, 0x6D, 0x20, 0x41, 0x53, 0x20, 0x6E, 0x75, 0x6D, 0xA1, 0x83,
0x6E, 0x75, 0x6D, 0x1, 0x0, 0x0>>
## ACK_FAILURE
Usage: Acknowledge a failure the server has sent.
Signature: `0x0E`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:ack_failure, []}, 1))
<<0x0, 0x2, 0xB0, 0xE, 0x0, 0x0>>
## DISCARD_ALL
Uage: Discard all remaining items from the active result stream.
Signature: `0x2F`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:discard_all, []}, 1))
<<0x0, 0x2, 0xB0, 0x2F, 0x0, 0x0>>
## PULL_ALL
Usage: Retrieve all remaining items from the active result stream.
Signature: `0x3F`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:pull_all, []}, 1))
<<0x0, 0x2, 0xB0, 0x3F, 0x0, 0x0>>
## RESET
Usage: Return the current session to a "clean" state.
Signature: `0x0F`
Struct: no data
Example
iex> alias Bolt.Sips.Internals.PackStream.Message.Encoder
iex> :erlang.iolist_to_binary(Encoder.encode({:reset, []}, 1))
<<0x0, 0x2, 0xB0, 0xF, 0x0, 0x0>>
Check if the encoder for the given bolt version is capable of encoding the given message
If it is the case, the encoding function will be called
If not, fallback to previous bolt version
If encoding function is not present in any of the bolt version, an error will be raised
"""
def encode(data, bolt_version)
when is_integer(bolt_version) and bolt_version > @last_bolt_version do
encode(data, @last_bolt_version)
end
def encode(_data, _bolt_version) do
{:error, :not_implemented}
end
defp do_encode(message_type, data, bolt_version) do
signature = signature(message_type)
encode_message(message_type, signature, data, bolt_version)
end
# Format the auth params for v1 to v2
@spec auth_params_v1({} | {String.t(), String.t()}) :: map()
defp auth_params_v1({}), do: %{}
defp auth_params_v1({username, password}) do
%{
scheme: "basic",
principal: username,
credentials: password
}
end
# Format the auth params
@spec auth_params({} | {String.t(), String.t()}) :: map()
defp auth_params({}), do: user_agent()
defp auth_params({username, password}) do
%{
scheme: "basic",
principal: username,
credentials: password
}
|> Map.merge(user_agent())
end
defp user_agent() do
%{user_agent: client_name()}
end
@doc """
Perform the final message:
- add header
- manage chunk if necessary
- add end marker
"""
@spec encode_message(
Bolt.Sips.Internals.PackStream.Message.out_signature(),
integer(),
list(),
integer()
) ::
[[Bolt.Sips.Internals.PackStream.Message.encoded()]]
def encode_message(message_type, signature, data, bolt_version) do
Bolt.Sips.Internals.Logger.log_message(:client, message_type, data)
encoded =
{signature, data}
|> Bolt.Sips.Internals.PackStream.encode(bolt_version)
|> generate_chunks([])
Bolt.Sips.Internals.Logger.log_message(:client, message_type, encoded, :hex)
encoded
end
@spec generate_chunks(Bolt.Sips.Internals.PackStream.value() | <<>>, list()) ::
[[Bolt.Sips.Internals.PackStream.Message.encoded()]]
defp generate_chunks(<<>>, chunks) do
[chunks, [@end_marker], []]
end
defp generate_chunks(data, chunks) do
data_size = :erlang.iolist_size(data)
case data_size > @max_chunk_size do
true ->
bindata = :erlang.iolist_to_binary(data)
<<chunk::binary-@max_chunk_size, rest::binary>> = bindata
new_chunk = format_chunk(chunk)
# [new_chunk, generate_chunks(rest,[])]
generate_chunks(rest, [chunks, new_chunk])
# generate_chunks(<<rest>>, [new_chunk, chunks])
_ ->
generate_chunks(<<>>, [chunks, format_chunk(data)])
end
end
@spec format_chunk(Bolt.Sips.Internals.PackStream.value()) ::
[Bolt.Sips.Internals.PackStream.Message.encoded()]
defp format_chunk(chunk) do
[<<:erlang.iolist_size(chunk)::16>>, chunk]
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v1.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV1 do
@moduledoc false
use Bolt.Sips.Internals.PackStream.Message.Signatures
alias Bolt.Sips.Internals.PackStream.Message.Encoder
@doc """
Encode INIT message without auth token
"""
@spec encode({Bolt.Sips.Internals.PackStream.Message.out_signature(), list()}, integer()) ::
Bolt.Sips.Internals.PackStream.Message.encoded() | {:error, :not_implemented}
def encode(data, bolt_version) do
Encoder.encode(data, bolt_version)
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v2.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV2 do
def encode(_, _) do
{:error, :not_implemented}
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v3.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV3 do
@moduledoc false
use Bolt.Sips.Internals.PackStream.Message.Signatures
alias Bolt.Sips.Internals.PackStream.Message.Encoder
@valid_signatures [
@begin_signature,
@commit_signature,
@discard_all_signature,
@goodbye_signature,
@hello_signature,
@pull_all_signature,
@reset_signature,
@rollback_signature,
@run_signature
]
@doc """
Return the valid signatures for bolt V1
"""
@spec valid_signatures() :: [integer()]
def valid_signatures() do
@valid_signatures
end
@doc """
Encode HELLO message without auth token
"""
@spec encode({Bolt.Sips.Internals.PackStream.Message.out_signature(), list()}, integer()) ::
Bolt.Sips.Internals.PackStream.Message.encoded()
| {:error, :not_implemented}
| {:error, :invalid_message}
def encode(data, bolt_version) do
Encoder.encode(data, bolt_version)
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message/signatures.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message.Signatures do
@moduledoc false
defmacro __using__(_opts) do
quote do
# Message OUT
@ack_failure_signature 0x0E
@begin_signature 0x11
@commit_signature 0x12
@discard_all_signature 0x2F
@goodbye_signature 0x02
@hello_signature 0x01
@init_signature 0x01
@pull_all_signature 0x3F
@reset_signature 0x0F
@rollback_signature 0x13
@run_signature 0x10
# Message IN
@success_signature 0x70
@failure_signature 0x7F
@record_signature 0x71
@ignored_signature 0x7E
end
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/message.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Message do
@moduledoc false
# Manage the message encoding and decoding.
#
# Message encoding / decoding is the first step of encoding / decoding.
# The next step is the message data encoding /decoding (which is handled by packstream.ex)
alias Bolt.Sips.Internals.PackStream.Message.Encoder
alias Bolt.Sips.Internals.PackStream.Message.Decoder
@type in_signature :: :failure | :ignored | :record | :success
@type out_signature ::
:ack_failure
| :begin
| :commit
| :discard_all
| :goodbye
| :hello
| :init
| :pull_all
| :reset
| :rollback
| :run
@type raw :: {out_signature, list()}
@type decoded :: {in_signature(), any()}
@type encoded :: <<_::16, _::_*8>>
@doc """
Encode a message depending on its type
"""
@spec encode({Bolt.Sips.Internals.PackStream.Message.out_signature(), list()}, integer()) ::
Bolt.Sips.Internals.PackStream.Message.encoded()
def encode(message, bolt_version) do
Encoder.encode(message, bolt_version)
end
@doc """
Decode a message
"""
@spec decode(Bolt.Sips.Internals.PackStream.Message.encoded(), integer()) ::
Bolt.Sips.Internals.PackStream.Message.decoded()
def decode(message, bolt_version) do
Decoder.decode(message, bolt_version)
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/utils.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.Utils do
alias Bolt.Sips.Internals.PackStream.Encoder
alias Bolt.Sips.Types.Duration
alias Bolt.Sips.Internals.PackStreamError
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
# catch all clause for encoding implementation
defp do_call_encode(data_type, data, original_version) do
raise PackStreamError,
data_type: data_type,
data: data,
bolt_version: original_version,
message: "Encoding function not implemented for"
end
@spec encode_list_data(list(), integer()) :: [any()]
defp encode_list_data(data, bolt_version) do
Enum.map(
data,
&Encoder.encode(&1, bolt_version)
)
end
@spec encode_kv(map(), integer()) :: binary()
defp encode_kv(map, bolt_version) do
Enum.reduce(map, <<>>, fn data, acc -> [acc, do_reduce_kv(data, bolt_version)] end)
end
@spec do_reduce_kv({atom(), any()}, integer()) :: [binary()]
defp do_reduce_kv({key, value}, bolt_version) do
[
Encoder.encode(
key,
bolt_version
),
Encoder.encode(value, bolt_version)
]
end
@spec day_time(Time.t()) :: integer()
defp day_time(time) do
Time.diff(time, ~T[00:00:00.000], :nanosecond)
end
@spec decompose_datetime(Calendar.naive_datetime()) :: [integer()]
defp decompose_datetime(%NaiveDateTime{} = datetime) do
datetime_micros = NaiveDateTime.diff(datetime, ~N[1970-01-01 00:00:00.000], :microsecond)
seconds = div(datetime_micros, 1_000_000)
nanoseconds = rem(datetime_micros, 1_000_000) * 1_000
[seconds, nanoseconds]
end
@spec compact_duration(Duration.t()) :: [integer()]
defp compact_duration(%Duration{} = duration) do
months = 12 * duration.years + duration.months
days = 7 * duration.weeks + duration.days
seconds = 3600 * duration.hours + 60 * duration.minutes + duration.seconds
[months, days, seconds, duration.nanoseconds]
end
end
end
end
================================================
FILE: lib/bolt_sips/internals/pack_stream/v1.ex
================================================
defmodule Bolt.Sips.Internals.PackStream.V1 do
defmacro __using__(_options) do
quote do
import unquote(__MODULE__)
@last_version Bolt.Sips.Internals.BoltVersionHelper.last()
@int8 -128..-17
@int16_low -32_768..-129
@int16_high 128..32_767
@int32_low -2_147_483_648..-32_769
@int32_high 32_768..2_147_483_647
@int64_low -9_223_372_036_854_775_808..-2_147_483_649
@int64_high 2_147_483_648..9_223_372_036_854_775_807
# Null
@null_marker 0xC0
# Boolean
@true_marker 0xC3
@false_marker 0xC2
# String
@tiny_bitstring_marker 0x8
@bitstring8_marker 0xD0
@bitstring16_marker 0xD1
@bitstring32_marker 0xD2
# Integer
@int8_marker 0xC8
@int16_marker 0xC9
@int32_marker 0xCA
@int64_marker 0xCB
# Float
@float_marker 0xC1
# List
@tiny_list_marker 0x9
@list8_marker 0xD4
@list16_marker 0xD5
gitextract_6ctcdb7g/
├── .credo.exs
├── .dialyzer_ignore.exs
├── .formatter.exs
├── .gitattributes
├── .gitignore
├── .iex.exs
├── .markdownlint.json
├── .prettierrc.yaml
├── .tool-versions
├── .travis.yml
├── CHANGELOG.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── benchees/
│ └── conn_to_local_bench.exs
├── config/
│ ├── config.exs
│ ├── dev.exs
│ └── test.exs
├── docker-compose.yml
├── docs/
│ ├── examples/
│ │ └── readme.md
│ ├── features/
│ │ ├── about-encoding.md
│ │ ├── about-transactions.md
│ │ ├── configuration.md
│ │ ├── multi-tenancy.md
│ │ ├── role-based-connections.md
│ │ ├── routing.md
│ │ ├── using-cypher.md
│ │ ├── using-temporal-and-spatial-types.md
│ │ └── using-with-phoenix.md
│ └── getting-started.md
├── lib/
│ ├── bolt_sips/
│ │ ├── application.ex
│ │ ├── enumerable_response.ex
│ │ ├── error.ex
│ │ ├── exception.ex
│ │ ├── internals/
│ │ │ ├── bolt_protocol.ex
│ │ │ ├── bolt_protocol_helper.ex
│ │ │ ├── bolt_protocol_v1.ex
│ │ │ ├── bolt_protocol_v2.ex
│ │ │ ├── bolt_protocol_v3.ex
│ │ │ ├── bolt_version_helper.ex
│ │ │ ├── error.ex
│ │ │ ├── logger.ex
│ │ │ ├── pack_stream/
│ │ │ │ ├── decoder.ex
│ │ │ │ ├── decoder_impl_v1.ex
│ │ │ │ ├── decoder_impl_v2.ex
│ │ │ │ ├── decoder_utils.ex
│ │ │ │ ├── decoder_v1.ex
│ │ │ │ ├── decoder_v2.ex
│ │ │ │ ├── decoder_v3.ex
│ │ │ │ ├── encoder.ex
│ │ │ │ ├── encoder_helper.ex
│ │ │ │ ├── encoder_v1.ex
│ │ │ │ ├── encoder_v2.ex
│ │ │ │ ├── encoder_v3.ex
│ │ │ │ ├── error.ex
│ │ │ │ ├── markers.ex
│ │ │ │ ├── message/
│ │ │ │ │ ├── decoder.ex
│ │ │ │ │ ├── encoder.ex
│ │ │ │ │ ├── encoder_v1.ex
│ │ │ │ │ ├── encoder_v2.ex
│ │ │ │ │ ├── encoder_v3.ex
│ │ │ │ │ └── signatures.ex
│ │ │ │ ├── message.ex
│ │ │ │ ├── utils.ex
│ │ │ │ ├── v1.ex
│ │ │ │ └── v2.ex
│ │ │ └── pack_stream.ex
│ │ ├── metadata.ex
│ │ ├── protocol.ex
│ │ ├── query.ex
│ │ ├── query_statement.ex
│ │ ├── response.ex
│ │ ├── response_encoder/
│ │ │ ├── json/
│ │ │ │ ├── jason.ex
│ │ │ │ └── poison.ex
│ │ │ └── json.ex
│ │ ├── response_encoder.ex
│ │ ├── router.ex
│ │ ├── routing/
│ │ │ ├── connection_supervisor.ex
│ │ │ ├── load_balancer.ex
│ │ │ └── routing_table.ex
│ │ ├── socket.ex
│ │ ├── types.ex
│ │ ├── types_helper.ex
│ │ └── utils.ex
│ ├── bolt_sips.ex
│ └── mix/
│ └── tasks/
│ └── cypher.ex
├── mix.exs
├── requirements.txt
└── test/
├── bolt_sips/
│ ├── internals/
│ │ ├── bolt_protocol_all_bolt_version_test.exs
│ │ ├── bolt_protocol_bolt_v1_test.exs
│ │ ├── bolt_protocol_bolt_v2_test.exs
│ │ ├── bolt_protocol_bolt_v3_test.exs
│ │ ├── bolt_protocol_v1_test.exs
│ │ ├── bolt_protocol_v3_test.exs
│ │ ├── bolt_version_helper_test.exs
│ │ ├── logger_test.exs
│ │ └── pack_stream/
│ │ ├── decoder_test.exs
│ │ ├── decoder_v1_test.exs
│ │ ├── decoder_v2_test.exs
│ │ ├── encoder_helper_test.exs
│ │ ├── encoder_test.exs
│ │ ├── encoder_v1_test.exs
│ │ ├── encoder_v2_test.exs
│ │ ├── message/
│ │ │ ├── decoder_test.exs
│ │ │ ├── encoder_test.exs
│ │ │ ├── encoder_v1_test.exs
│ │ │ └── encoder_v3_test.exs
│ │ └── message_test.exs
│ ├── metadata_test.exs
│ ├── performance_test.exs
│ ├── protocol_test.exs
│ ├── response_encoder/
│ │ ├── json_implementations_test.exs
│ │ └── json_test.exs
│ ├── response_encoder_test.exs
│ ├── types_helpers_test.exs
│ └── types_test.exs
├── boltkit_test.exs
├── config_test.exs
├── errors_test.exs
├── invalid_param_type_test.exs
├── one_test.exs
├── query_bolt_v2_test.exs
├── query_test.exs
├── response_test.exs
├── router_test.exs
├── routing/
│ ├── connections_test.exs
│ ├── crud_test.exs
│ ├── routing_table_parser_test.exs
│ ├── routing_test.exs
│ └── transaction_test.exs
├── scripts/
│ ├── count.bolt
│ ├── create_a.script
│ ├── forbidden_on_read_only_database.script
│ ├── get_routing_table.script
│ ├── get_routing_table_with_context.script
│ ├── non_router.script
│ ├── return_1.script
│ ├── return_1_in_tx_twice.script
│ ├── return_1_twice.script
│ ├── return_x.bolt
│ ├── router.script
│ ├── router_no_readers.script
│ └── router_no_writers.script
├── support/
│ ├── boltkit_case.ex
│ ├── conn_case.ex
│ ├── conn_routing_case.ex
│ ├── database.ex
│ ├── fixture.ex
│ └── internal_case.ex
├── test_helper.exs
├── test_large_param_set.exs
├── test_support.exs
└── transaction_test.exs
SYMBOL INDEX (452 symbols across 104 files)
FILE: lib/bolt_sips.ex
class Bolt.Sips (line 1) | defmodule Bolt.Sips
method start_link (line 84) | def start_link(opts) do
method init (line 99) | def init(opts) do
method conn (line 113) | def conn(role \\ :direct, opts \\ [prefix: :default])
method conn (line 115) | def conn(role, opts) do
method info (line 241) | def info(), do: sanitized_info(Bolt.Sips.Router.info())
method routing_table (line 247) | def routing_table(prefix \\ :default)
method routing_table (line 249) | def routing_table(prefix) do
method registry_name (line 257) | def registry_name(), do: @registry_name
method sanitized_info (line 266) | defp sanitized_info(info), do: info
FILE: lib/bolt_sips/application.ex
class Bolt.Sips.Application (line 1) | defmodule Bolt.Sips.Application
method start (line 8) | def start(_, start_args) do
method stop (line 12) | def stop(_state) do
FILE: lib/bolt_sips/error.ex
class Bolt.Sips.Error (line 1) | defmodule Bolt.Sips.Error
method new (line 10) | def new(%Bolt.Sips.Internals.Error{
method new (line 27) | def new({:ignored, f} = _r), do: new({:error, f})
method new (line 29) | def new({:failure, %{"code" => code, "message" => message}} = _r) do
method new (line 33) | def new(r), do: r
FILE: lib/bolt_sips/exception.ex
class Bolt.Sips.Exception (line 1) | defmodule Bolt.Sips.Exception
FILE: lib/bolt_sips/internals/bolt_protocol.ex
class Bolt.Sips.Internals.BoltProtocol (line 1) | defmodule Bolt.Sips.Internals.BoltProtocol
method run (line 115) | def run(
method manage_metadata_and_options (line 150) | defp manage_metadata_and_options([], options) do
method manage_metadata_and_options (line 155) | defp manage_metadata_and_options([_ | _] = metadata, options) do
method manage_metadata_and_options (line 160) | defp manage_metadata_and_options(metadata, options) do
method run_statement (line 182) | def run_statement(
FILE: lib/bolt_sips/internals/bolt_protocol_helper.ex
class Bolt.Sips.Internals.BoltProtocolHelper (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolHelper
method send_message (line 18) | def send_message(transport, port, bolt_version, message) do
method receive_data (line 55) | def receive_data(transport, port, bolt_version, options \\ [], previou...
method do_receive_data (line 80) | defp do_receive_data(transport, port, options) do
method do_receive_data_ (line 93) | defp do_receive_data_(transport, port, chunk_size, options, old_data) do
method get_recv_timeout (line 116) | def get_recv_timeout(options) do
method treat_simple_message (line 135) | def treat_simple_message(message, transport, port, bolt_version, optio...
FILE: lib/bolt_sips/internals/bolt_protocol_v1.ex
class Bolt.Sips.Internals.BoltProtocolV1 (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolV1
method handshake (line 25) | def handshake(transport, port, options \\ [recv_timeout: 15_000]) do
method init (line 81) | def init(transport, port, bolt_version, auth, options \\ [recv_timeout...
method run (line 114) | def run(transport, port, bolt_version, statement, params, options) do
method pull_all (line 157) | def pull_all(transport, port, bolt_version, options) do
method run_statement (line 198) | def run_statement(transport, port, bolt_version, statement, params, op...
method discard_all (line 229) | def discard_all(transport, port, bolt_version, options) do
method ack_failure (line 249) | def ack_failure(transport, port, bolt_version, options) do
method reset (line 269) | def reset(transport, port, bolt_version, options) do
FILE: lib/bolt_sips/internals/bolt_protocol_v2.ex
class Bolt.Sips.Internals.BoltProtocolV2 (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolV2
FILE: lib/bolt_sips/internals/bolt_protocol_v3.ex
class Bolt.Sips.Internals.BoltProtocolV3 (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolV3
method hello (line 27) | def hello(transport, port, bolt_version, auth, options \\ [recv_timeou...
method goodbye (line 58) | def goodbye(transport, port, bolt_version) do
method run (line 86) | def run(transport, port, bolt_version, statement, params, metadata, op...
method run_statement (line 142) | def run_statement(transport, port, bolt_version, statement, params, me...
method begin (line 170) | def begin(transport, port, bolt_version, metadata, options) do
method commit (line 199) | def commit(transport, port, bolt_version, options) do
method rollback (line 227) | def rollback(transport, port, bolt_version, options) do
FILE: lib/bolt_sips/internals/bolt_version_helper.ex
class Bolt.Sips.Internals.BoltVersionHelper (line 1) | defmodule Bolt.Sips.Internals.BoltVersionHelper
method available_versions (line 11) | def available_versions(), do: @available_bolt_versions
method previous (line 27) | def previous(version) do
method last (line 41) | def last() do
FILE: lib/bolt_sips/internals/error.ex
class Bolt.Sips.Internals.Error (line 1) | defmodule Bolt.Sips.Internals.Error
method exception (line 16) | def exception(%{"message" => message, "code" => code}, pid, function) do
method exception (line 26) | def exception({:error, :closed}, pid, function) do
method exception (line 35) | def exception({:failure, %Bolt.Sips.Internals.Error{message: _message,...
method exception (line 39) | def exception(%Bolt.Sips.Internals.Error{} = err, _pid, _function), do...
method exception (line 41) | def exception(message, pid, function) do
method message_for (line 51) | defp message_for(:handshake, "HTTP") do
method message_for (line 67) | defp message_for(:handshake, other) do
method message_for (line 74) | defp message_for(nil, message) do
method message_for (line 80) | defp message_for(_function, {:error, error}) do
method message_for (line 87) | defp message_for(_function, {:ignored, []}) do
method message_for (line 94) | defp message_for(function, message) do
method get_id (line 101) | defp get_id({:sslsocket, {:gen_tcp, port, _tls, _unused_yet}, _pid}) do
method get_id (line 112) | defp get_id(_), do: nil
FILE: lib/bolt_sips/internals/logger.ex
class Bolt.Sips.Internals.Logger (line 1) | defmodule Bolt.Sips.Internals.Logger
method log_message (line 14) | def log_message(from, {type, data}) do
method log_message (line 25) | def log_message(from, type, data) do
method log_message (line 38) | def log_message(from, type, data, :hex) do
FILE: lib/bolt_sips/internals/pack_stream.ex
class Bolt.Sips.Internals.PackStream (line 1) | defmodule Bolt.Sips.Internals.PackStream
method encode (line 25) | def encode(item, bolt_version) do
method decode (line 38) | def decode(data, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/decoder.ex
class Bolt.Sips.Internals.PackStream.Decoder (line 1) | defmodule Bolt.Sips.Internals.PackStream.Decoder
FILE: lib/bolt_sips/internals/pack_stream/decoder_impl_v1.ex
class Bolt.Sips.Internals.PackStream.DecoderImplV1 (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderImplV1
FILE: lib/bolt_sips/internals/pack_stream/decoder_impl_v2.ex
class Bolt.Sips.Internals.PackStream.DecoderImplV2 (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderImplV2
FILE: lib/bolt_sips/internals/pack_stream/decoder_utils.ex
class Bolt.Sips.Internals.PackStream.DecoderUtils (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderUtils
FILE: lib/bolt_sips/internals/pack_stream/decoder_v1.ex
class Bolt.Sips.Internals.PackStream.DecoderV1 (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderV1
method decode (line 23) | def decode(data, bolt_version), do: Decoder.decode(data, bolt_version)
FILE: lib/bolt_sips/internals/pack_stream/decoder_v2.ex
class Bolt.Sips.Internals.PackStream.DecoderV2 (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderV2
method decode (line 29) | def decode(data, bolt_version), do: Decoder.decode(data, bolt_version)
FILE: lib/bolt_sips/internals/pack_stream/decoder_v3.ex
class Bolt.Sips.Internals.PackStream.DecoderV3 (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderV3
method decode (line 2) | def decode(_, _) do
FILE: lib/bolt_sips/internals/pack_stream/encoder_helper.ex
class Bolt.Sips.Internals.PackStream.EncoderHelper (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderHelper
method call_encode (line 36) | def call_encode(data_type, data, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/encoder_v1.ex
class Bolt.Sips.Internals.PackStream.EncoderV1 (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderV1
method encode_atom (line 34) | def encode_atom(atom , bolt_version), do: EncoderHelper.call_encode(:a...
method encode_string (line 58) | def encode_string(string, bolt_version), do: EncoderHelper.call_encode...
method encode_integer (line 86) | def encode_integer(integer, bolt_version), do: EncoderHelper.call_enco...
method encode_float (line 104) | def encode_float(number, bolt_version), do: EncoderHelper.call_encode(...
method encode_list (line 128) | def encode_list(list, bolt_version), do: EncoderHelper.call_encode(:li...
method encode_map (line 155) | def encode_map(map, bolt_version), do: EncoderHelper.call_encode(:map,...
method encode_struct (line 182) | def encode_struct(struct, bolt_version) , do: EncoderHelper.call_encod...
FILE: lib/bolt_sips/internals/pack_stream/encoder_v2.ex
class Bolt.Sips.Internals.PackStream.EncoderV2 (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderV2
method encode_local_time (line 25) | def encode_local_time(local_time, bolt_version), do: EncoderHelper.cal...
method encode_time_with_tz (line 45) | def encode_time_with_tz(%TimeWithTZOffset{time: time, timezone_offset:...
method encode_date (line 66) | def encode_date(date, bolt_version), do: EncoderHelper.call_encode(:da...
method encode_local_datetime (line 91) | def encode_local_datetime(local_datetime, bolt_version), do: EncoderHe...
method encode_datetime_with_tz_id (line 120) | def encode_datetime_with_tz_id(datetime, bolt_version), do: EncoderHel...
method encode_datetime_with_tz_offset (line 150) | def encode_datetime_with_tz_offset(
method encode_duration (line 198) | def encode_duration(%Duration{} = duration, bolt_version), do: Encoder...
method encode_point (line 261) | def encode_point(%Point{z: nil} = point, bolt_version), do: EncoderHel...
method encode_point (line 263) | def encode_point(%Point{} = point, bolt_version), do: EncoderHelper.ca...
FILE: lib/bolt_sips/internals/pack_stream/encoder_v3.ex
class Bolt.Sips.Internals.PackStream.EncoderV3 (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderV3
FILE: lib/bolt_sips/internals/pack_stream/error.ex
class Bolt.Sips.Internals.PackStreamError (line 1) | defmodule Bolt.Sips.Internals.PackStreamError
method message (line 19) | def message(%{data_type: nil, data: data, message: message, bolt_versi...
method message (line 23) | def message(%{data_type: data_type, data: data, message: message, bolt...
FILE: lib/bolt_sips/internals/pack_stream/markers.ex
class Bolt.Sips.Internals.PackStream.Markers (line 1) | defmodule Bolt.Sips.Internals.PackStream.Markers
class Bolt.Sips.Internals.PackStream.MarkersHelper (line 95) | defmodule Bolt.Sips.Internals.PackStream.MarkersHelper
method valid_signatures (line 103) | def valid_signatures() do
FILE: lib/bolt_sips/internals/pack_stream/message.ex
class Bolt.Sips.Internals.PackStream.Message (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message
method encode (line 34) | def encode(message, bolt_version) do
method decode (line 43) | def decode(message, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/message/decoder.ex
class Bolt.Sips.Internals.PackStream.Message.Decoder (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.Decoder
method decode (line 14) | def decode(
method decode (line 22) | def decode(
method decode (line 30) | def decode(
method decode (line 38) | def decode(
method build_response (line 52) | defp build_response(message_type, data, nb_entries, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/message/encoder.ex
class Bolt.Sips.Internals.PackStream.Message.Encoder (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.Encoder
method signature (line 98) | defp signature(:ack_failure), do: @ack_failure_signature
method signature (line 99) | defp signature(:discard_all), do: @discard_all_signature
method signature (line 100) | defp signature(:pull_all), do: @pull_all_signature
method signature (line 101) | defp signature(:reset), do: @reset_signature
method signature (line 102) | defp signature(:begin), do: @begin_signature
method signature (line 103) | defp signature(:commit), do: @commit_signature
method signature (line 104) | defp signature(:goodbye), do: @goodbye_signature
method signature (line 105) | defp signature(:hello), do: @hello_signature
method signature (line 106) | defp signature(:rollback), do: @rollback_signature
method signature (line 107) | defp signature(:run), do: @run_signature
method signature (line 108) | defp signature(:init), do: @init_signature
method client_name (line 113) | def client_name() do
method valid_signatures (line 125) | def valid_signatures(3) do
method encode (line 136) | def encode({:hello, []}, 3) do
method encode (line 142) | def encode({:hello, [auth]}, 3) do
method encode (line 149) | def encode({:begin, []}, 3) do
method encode (line 154) | def encode({:begin, [%Metadata{} = metadata]}, 3) do
method encode (line 163) | def encode({:begin, _}, _) do
method encode (line 168) | def encode({:run, [statement]}, 3) do
method encode (line 178) | def encode({:run, [statement, params]}, 3) do
method encode (line 183) | def encode({:run, [statement, params, %Metadata{} = metadata]}, 3) do
method encode (line 188) | def encode({:init, _}, 3) do
method encode (line 512) | def encode(_data, _bolt_version) do
method do_encode (line 516) | defp do_encode(message_type, data, bolt_version) do
method auth_params_v1 (line 523) | defp auth_params_v1({}), do: %{}
method auth_params_v1 (line 525) | defp auth_params_v1({username, password}) do
method auth_params (line 535) | defp auth_params({}), do: user_agent()
method auth_params (line 537) | defp auth_params({username, password}) do
method user_agent (line 546) | defp user_agent() do
method encode_message (line 564) | def encode_message(message_type, signature, data, bolt_version) do
method generate_chunks (line 578) | defp generate_chunks(<<>>, chunks) do
method generate_chunks (line 582) | defp generate_chunks(data, chunks) do
method format_chunk (line 602) | defp format_chunk(chunk) do
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v1.ex
class Bolt.Sips.Internals.PackStream.Message.EncoderV1 (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV1
method encode (line 12) | def encode(data, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v2.ex
class Bolt.Sips.Internals.PackStream.Message.EncoderV2 (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV2
method encode (line 2) | def encode(_, _) do
FILE: lib/bolt_sips/internals/pack_stream/message/encoder_v3.ex
class Bolt.Sips.Internals.PackStream.Message.EncoderV3 (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV3
method valid_signatures (line 23) | def valid_signatures() do
method encode (line 35) | def encode(data, bolt_version) do
FILE: lib/bolt_sips/internals/pack_stream/message/signatures.ex
class Bolt.Sips.Internals.PackStream.Message.Signatures (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.Signatures
FILE: lib/bolt_sips/internals/pack_stream/utils.ex
class Bolt.Sips.Internals.PackStream.Utils (line 1) | defmodule Bolt.Sips.Internals.PackStream.Utils
FILE: lib/bolt_sips/internals/pack_stream/v1.ex
class Bolt.Sips.Internals.PackStream.V1 (line 1) | defmodule Bolt.Sips.Internals.PackStream.V1
FILE: lib/bolt_sips/internals/pack_stream/v2.ex
class Bolt.Sips.Internals.PackStream.V2 (line 1) | defmodule Bolt.Sips.Internals.PackStream.V2
FILE: lib/bolt_sips/metadata.ex
class Bolt.Sips.Metadata (line 1) | defmodule Bolt.Sips.Metadata
method new (line 18) | def new(data) do
method to_map (line 39) | def to_map(metadata) do
method check_keys (line 50) | defp check_keys(data) do
method validate_bookmarks (line 64) | defp validate_bookmarks([]) do
method validate_bookmarks (line 68) | defp validate_bookmarks(_) do
method validate_timeout (line 77) | defp validate_timeout(nil) do
method validate_timeout (line 81) | defp validate_timeout(_) do
method validate_metadata (line 91) | defp validate_metadata(%{}) do
method validate_metadata (line 95) | defp validate_metadata(_) do
FILE: lib/bolt_sips/protocol.ex
class Bolt.Sips.Protocol (line 1) | defmodule Bolt.Sips.Protocol
method connect (line 28) | def connect(opts \\ [])
method connect (line 29) | def connect([]), do: connect(Bolt.Sips.Utils.default_config())
method connect (line 31) | def connect(opts) do
method do_init (line 65) | defp do_init(transport, port, 3, auth) do
method do_init (line 69) | defp do_init(transport, port, bolt_version, auth) do
method checkout (line 74) | def checkout(%ConnData{sock: sock, configuration: conf} = conn_data) do
method checkin (line 82) | def checkin(%ConnData{sock: sock, configuration: conf} = conn_data) do
method disconnect (line 89) | def disconnect(_err, %ConnData{sock: sock, bolt_version: 3, configurat...
method disconnect (line 98) | def disconnect(_err, %ConnData{sock: sock, configuration: conf}) do
method handle_begin (line 105) | def handle_begin(_, %ConnData{sock: sock, bolt_version: 3, configurati...
method handle_begin (line 110) | def handle_begin(_opts, conn_data) do
method handle_rollback (line 118) | def handle_rollback(_, %ConnData{sock: sock, bolt_version: 3, configur...
method handle_rollback (line 123) | def handle_rollback(_opts, conn_data) do
method handle_commit (line 131) | def handle_commit(_, %ConnData{sock: sock, bolt_version: 3, configurat...
method handle_commit (line 136) | def handle_commit(_opts, conn_data) do
method handle_execute (line 144) | def handle_execute(query, params, opts, conn_data) do
method handle_info (line 148) | def handle_info(msg, state) do
method ping (line 158) | def ping(state), do: {:ok, state}
method handle_prepare (line 159) | def handle_prepare(query, _opts, state), do: {:ok, query, state}
method handle_close (line 160) | def handle_close(query, _opts, state), do: {:ok, query, state}
method handle_deallocate (line 161) | def handle_deallocate(query, _cursor, _opts, state), do: {:ok, query, ...
method handle_declare (line 162) | def handle_declare(query, _params, _opts, state), do: {:ok, query, sta...
method handle_fetch (line 163) | def handle_fetch(query, _cursor, _opts, state), do: {:cont, query, state}
method handle_status (line 164) | def handle_status(_opts, state), do: {:idle, state}
method extract_auth (line 166) | defp extract_auth(nil), do: {}
method extract_auth (line 168) | defp extract_auth(basic_auth), do: {basic_auth[:username], basic_auth[...
method execute (line 170) | defp execute(q, params, _, conn_data) do
method _to_hostname (line 208) | defp _to_hostname(hostname), do: hostname
class ConnData (line 6) | defmodule ConnData
FILE: lib/bolt_sips/query.ex
class Bolt.Sips.Query (line 1) | defmodule Bolt.Sips.Query
method query! (line 54) | def query!(conn, statement), do: query!(conn, statement, %{})
method query (line 70) | def query(conn, statement), do: query(conn, statement, %{})
method query_commit (line 89) | defp query_commit(conn, statement, params, opts) do
method commit! (line 130) | defp commit!([], conn, statements, formatted_params, opts),
method commit! (line 133) | defp commit!(errors, _conn, _statements, _formatted_params, _opts),
method tx! (line 136) | defp tx!(conn, [statement], params, opts), do: hd(send!(conn, statemen...
method send! (line 142) | defp send!(conn, statement, params, opts, acc \\ [])
method send! (line 144) | defp send!(conn, statement, params, opts, acc) do
method format_param (line 175) | defp format_param({name, %Types.Duration{} = duration}),
method format_param (line 178) | defp format_param({name, %Types.Point{} = point}), do: {name, Types.Po...
method format_param (line 180) | defp format_param({name, value}), do: {name, {:ok, value}}
FILE: lib/bolt_sips/query_statement.ex
class Bolt.Sips.QueryStatement (line 1) | defmodule Bolt.Sips.QueryStatement
FILE: lib/bolt_sips/response.ex
class Bolt.Sips.Response (line 1) | defmodule Bolt.Sips.Response
method first (line 92) | def first(%__MODULE__{results: []}), do: nil
method first (line 93) | def first(%__MODULE__{results: [head | _tail]}), do: head
method transform! (line 97) | def transform!(records, stats \\ :no) do
method transform (line 105) | def transform(records, _stats \\ :no) do
method fetch (line 118) | def fetch(%Bolt.Sips.Response{fields: fields, results: results}, key) do
method fetch! (line 132) | def fetch!(%Bolt.Sips.Response{} = r, key) do
method parse (line 138) | defp parse(records) do
method parse_record (line 158) | defp parse_record(:success, %{"fields" => fields}, response) do
method parse_record (line 162) | defp parse_record(:success, %{"profile" => profile, "type" => type} = ...
method parse_record (line 166) | defp parse_record(:success, %{"notifications" => n, "plan" => plan, "t...
method parse_record (line 170) | defp parse_record(:success, %{"plan" => plan, "type" => type}, respons...
method parse_record (line 174) | defp parse_record(:success, %{"stats" => stats, "type" => type}, respo...
method parse_record (line 178) | defp parse_record(:success, %{"bookmark" => bookmark, "type" => type},...
method parse_record (line 182) | defp parse_record(:success, %{"type" => type}, response) do
method parse_record (line 186) | defp parse_record(:success, %{}, response) do
method parse_record (line 190) | defp parse_record(:success, record, _response) do
method parse_record (line 201) | defp parse_record(:record, record, response) do
method parse_record (line 205) | defp parse_record(_type, record, _response) do
method create_results (line 214) | defp create_results(fields, records) do
FILE: lib/bolt_sips/response_encoder.ex
class Bolt.Sips.ResponseEncoder (line 1) | defmodule Bolt.Sips.ResponseEncoder
method encode (line 48) | def encode(response, :json) do
method encode! (line 80) | def encode!(response, :json) do
method jsonable_response (line 86) | defp jsonable_response(response) do
FILE: lib/bolt_sips/router.ex
class Bolt.Sips.Router (line 1) | defmodule Bolt.Sips.Router
method configure (line 33) | def configure(opts), do: GenServer.call(__MODULE__, {:configure, opts})
method get_connection (line 35) | def get_connection(role, prefix \\ :direct)
method get_connection (line 37) | def get_connection(role, prefix),
method terminate_connections (line 40) | def terminate_connections(role, prefix \\ :default)
method terminate_connections (line 42) | def terminate_connections(role, prefix),
method info (line 45) | def info(), do: GenServer.call(__MODULE__, :info)
method routing_table (line 46) | def routing_table(prefix), do: GenServer.call(__MODULE__, {:routing_ta...
method start_link (line 49) | def start_link(init_args) do
method init (line 55) | def init(options) do
method handle_call (line 60) | def handle_call({:configure, opts}, _from, state) do
method handle_call (line 98) | def handle_call({:get_connection, role, prefix}, _from, state) do
method handle_call (line 112) | def handle_call({:terminate_connections, role, prefix}, _from, state) do
method handle_call (line 131) | def handle_call(:info, _from, state), do: {:reply, state, state}
method handle_call (line 133) | def handle_call({:routing_table_info, prefix}, _from, state) do
method handle_continue (line 144) | def handle_continue(:post_init, opts), do: {:noreply, _configure(opts)}
method _configure (line 146) | defp _configure(opts) do
method get_routing_table (line 168) | defp get_routing_table(
method get_routing_table (line 192) | defp get_routing_table(_opts, false), do: {:ok, @no_routing}
method get_routing_table (line 194) | defp get_routing_table(opts, _) do
method start_connections (line 244) | def start_connections(opts, routing_table)
method start_connections (line 255) | def start_connections(opts, routing_table) do
method handle_info (line 295) | def handle_info({:refresh, prefix}, state) do
method handle_info (line 326) | def handle_info(req, state) do
method parse_server_version (line 352) | def parse_server_version(%{"server" => server_version_string}) do
method parse_server_version (line 360) | def parse_server_version(some_version),
method error_no_connection_available_for_role (line 363) | defp error_no_connection_available_for_role(role, _e, prefix \\ :default)
method error_no_connection_available_for_role (line 365) | defp error_no_connection_available_for_role(role, _e, prefix) do
method merge_connections_maps (line 371) | def merge_connections_maps(current_connections, new_connections, prefi...
method merge_connections_maps (line 373) | def merge_connections_maps(current_connections, new_connections, prefi...
method remove_old_urls (line 390) | defp remove_old_urls(role, url, urls), do: if(url in urls, do: [], els...
method close_connections (line 399) | defp close_connections(connections, prefix) do
method _get_connection (line 413) | defp _get_connection(role, connections, prefix) do
class State (line 11) | defmodule State
FILE: lib/bolt_sips/routing/connection_supervisor.ex
class Bolt.Sips.ConnectionSupervisor (line 1) | defmodule Bolt.Sips.ConnectionSupervisor
method start_link (line 12) | def start_link(init_args) do
method init (line 17) | def init(_args) do
method start_child (line 25) | def start_child(role, url, config) do
method find_connection (line 57) | def find_connection(role, url, prefix), do: find_connection("#{prefix}...
method find_connection (line 60) | def find_connection(name) do
method terminate_connection (line 68) | def terminate_connection(role, url, prefix \\ :default) do
method connections (line 76) | def connections() do
method _connections (line 85) | defp _connections() do
method via_tuple (line 99) | def via_tuple(name) do
FILE: lib/bolt_sips/routing/load_balancer.ex
class Bolt.Sips.LoadBalancer (line 1) | defmodule Bolt.Sips.LoadBalancer
method least_reused_url (line 19) | def least_reused_url(urls) do
FILE: lib/bolt_sips/routing/routing_table.ex
class Bolt.Sips.Routing.RoutingTable (line 1) | defmodule Bolt.Sips.Routing.RoutingTable
method parse (line 40) | def parse(%{"servers" => servers, "ttl" => ttl}) do
method parse (line 47) | def parse(map),
method parse_servers (line 51) | defp parse_servers(servers) do
method to_atomic_role (line 83) | defp to_atomic_role(_), do: {:error, :alien_role}
method parse_ttl (line 85) | def parse_ttl(ttl), do: {:ok, ensure_integer(ttl)}
method ttl_expired? (line 88) | def ttl_expired?(updated_at, ttl) do
method ensure_integer (line 95) | defp ensure_integer(ttl), do: raise(ArgumentError, "invalid ttl: " <> ...
FILE: lib/bolt_sips/socket.ex
class Bolt.Sips.Socket (line 1) | defmodule Bolt.Sips.Socket
FILE: lib/bolt_sips/types.ex
class Bolt.Sips.Types (line 1) | defmodule Bolt.Sips.Types
class Entity (line 34) | defmodule Entity
class Node (line 48) | defmodule Node
class Relationship (line 68) | defmodule Relationship
class UnboundRelationship (line 87) | defmodule UnboundRelationship
class Path (line 104) | defmodule Path
method graph (line 128) | def graph(path) do
method draw_path (line 146) | defp draw_path(_n, _r, _s, _i, [], acc, _ln, _nn), do: acc
method draw_path (line 148) | defp draw_path(n, r, s, i, [h | t] = _rel_index, acc, ln, _nn) do
class TimeWithTZOffset (line 180) | defmodule TimeWithTZOffset
method format_param (line 214) | def format_param(param) do
class DateTimeWithTZOffset (line 219) | defmodule DateTimeWithTZOffset
method format_param (line 253) | def format_param(param) do
class Duration (line 258) | defmodule Duration
method manage_nanoseconds (line 350) | defp manage_nanoseconds(seconds, nanoseconds) do
method format_param (line 384) | def format_param(param) do
method format_date (line 389) | defp format_date(%Duration{years: years, months: months, weeks: weeks,...
method format_time (line 413) | defp format_time(_) do
method format_duration_part (line 423) | defp format_duration_part(_, _) do
method stringify_number (line 432) | defp stringify_number(number) do
class Point (line 437) | defmodule Point
method create (line 508) | def create(:cartesian, x, y) do
method create (line 512) | def create(:wgs_84, longitude, latitude) do
method create (line 568) | def create(:cartesian, x, y, z) do
method create (line 572) | def create(:wgs_84, longitude, latitude, height) do
method crs (line 601) | defp crs(@srid_cartesian), do: "cartesian"
method crs (line 602) | defp crs(@srid_cartesian_3d), do: "cartesian-3d"
method crs (line 603) | defp crs(@srid_wgs_84), do: "wgs-84"
method crs (line 604) | defp crs(@srid_wgs_84_3d), do: "wgs-84-3d"
method format_coord (line 607) | defp format_coord(coord), do: coord
method format_param (line 632) | def format_param(param) do
FILE: lib/bolt_sips/types_helper.ex
class Bolt.Sips.TypesHelper (line 1) | defmodule Bolt.Sips.TypesHelper
method decompose_in_hms (line 6) | def decompose_in_hms(seconds) do
method datetime_with_micro (line 23) | def datetime_with_micro(%NaiveDateTime{} = naive_dt, timezone) do
method formated_time_offset (line 31) | def formated_time_offset(offset_seconds) do
method get_sign_string (line 40) | defp get_sign_string(_) do
method format_time_part (line 48) | defp format_time_part(time_part) do
FILE: lib/bolt_sips/utils.ex
class Bolt.Sips.Utils (line 1) | defmodule Bolt.Sips.Utils
method random_id (line 24) | def random_id, do: :rand.uniform() |> Float.to_string() |> String.slic...
method default_config (line 30) | def default_config(), do: Application.get_env(:bolt_sips, Bolt, []) |>...
method default_config (line 32) | def default_config(opts) do
method or_use_url_if_present (line 102) | defp or_use_url_if_present(config) do
method username_and_password (line 145) | defp username_and_password(config, _), do: config
method port_from_url (line 155) | defp port_from_url(_port), do: @default_bolt_port
method now (line 164) | def now(unit \\ :seconds)
method now (line 167) | def now(unit),
method routing_context (line 174) | defp routing_context(nil), do: decode("")
method routing_context (line 175) | defp routing_context(query), do: decode(query)
method decode (line 177) | def decode(query) do
method do_decode (line 181) | defp do_decode([], acc), do: acc
method do_decode (line 183) | defp do_decode([h | t], acc) do
method decode_kv (line 191) | defp decode_kv(""), do: false
method decode_kv (line 192) | defp decode_kv(<<?$, _::binary>>), do: false
method decode_kv (line 194) | defp decode_kv(kv), do: decode_key(kv, "")
method decode_key (line 196) | defp decode_key("", _key), do: false
method decode_key (line 197) | defp decode_key(<<?=, _::binary>>, ""), do: false
method decode_key (line 198) | defp decode_key(<<?=, t::binary>>, key), do: decode_value(t, "", key, "")
method decode_key (line 200) | defp decode_key(<<h, t::binary>>, key), do: decode_key(t, <<key::binar...
method decode_value (line 202) | defp decode_value("", _spaces, key, value), do: {key, value}
method decode_value (line 204) | defp decode_value(<<?\s, t::binary>>, spaces, key, value),
method decode_value (line 210) | defp decode_value(<<h, t::binary>>, spaces, key, value),
FILE: lib/mix/tasks/cypher.ex
class Mix.Tasks.Bolt.Cypher (line 1) | defmodule Mix.Tasks.Bolt.Cypher
method run (line 33) | def run(args) do
method run_options (line 61) | defp run_options(_, nil) do
method run_options (line 67) | defp run_options(args, config) do
method log_cypher (line 71) | defp log_cypher(msg), do: Mix.shell().info([:green, "#{inspect(msg)}"])
method log_response (line 72) | defp log_response(msg), do: Mix.shell().info([:yellow, "#{inspect(msg)...
method log_error (line 73) | defp log_error(msg), do: Mix.shell().info([:white, "#{msg}"])
FILE: mix.exs
class BoltSips.Mixfile (line 1) | defmodule BoltSips.Mixfile
method project (line 8) | def project do
method application (line 37) | def application do
method aliases (line 43) | defp aliases do
method elixirc_paths (line 51) | defp elixirc_paths(:test), do: ["lib", "test/support"]
method elixirc_paths (line 52) | defp elixirc_paths(_), do: ["lib"]
method package (line 54) | defp package do
method docs (line 75) | defp docs do
method deps (line 101) | defp deps do
FILE: test/bolt_sips/internals/bolt_protocol_all_bolt_version_test.exs
class Bolt.Sips.Internals.BoltProtocolAllBoltVersionTest (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolAllBoltVersionTest
FILE: test/bolt_sips/internals/bolt_protocol_bolt_v1_test.exs
class Bolt.Sips.Internals.BoltProtocolV1Test (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolV1Test
FILE: test/bolt_sips/internals/bolt_protocol_bolt_v2_test.exs
class Bolt.Sips.Internals.BoltProtoolBoltV2Test (line 1) | defmodule Bolt.Sips.Internals.BoltProtoolBoltV2Test
FILE: test/bolt_sips/internals/bolt_protocol_bolt_v3_test.exs
class Bolt.Sips.Internals.BoltProtocolBoltV3Test (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolBoltV3Test
FILE: test/bolt_sips/internals/bolt_protocol_v1_test.exs
class BoltProtocolV1.Sips.Internals.BoltProtocolV1Test (line 1) | defmodule BoltProtocolV1.Sips.Internals.BoltProtocolV1Test
FILE: test/bolt_sips/internals/bolt_protocol_v3_test.exs
class Bolt.Sips.Internals.BoltProtocolV3Test (line 1) | defmodule Bolt.Sips.Internals.BoltProtocolV3Test
FILE: test/bolt_sips/internals/bolt_version_helper_test.exs
class Bolt.Sips.Internals.BoltVersionHelperTest (line 1) | defmodule Bolt.Sips.Internals.BoltVersionHelperTest
FILE: test/bolt_sips/internals/logger_test.exs
class Bolt.Sips.Internals.LoggerTest (line 1) | defmodule Bolt.Sips.Internals.LoggerTest
FILE: test/bolt_sips/internals/pack_stream/decoder_test.exs
class Bolt.Sips.Internals.PackStream.DecoderTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderTest
FILE: test/bolt_sips/internals/pack_stream/decoder_v1_test.exs
class Bolt.Sips.Internals.PackStream.DecoderV1Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderV1Test
FILE: test/bolt_sips/internals/pack_stream/decoder_v2_test.exs
class Bolt.Sips.Internals.PackStream.DecoderV2Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.DecoderV2Test
FILE: test/bolt_sips/internals/pack_stream/encoder_helper_test.exs
class Bolt.Sips.Internals.PackStream.EncoderHelperTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderHelperTest
FILE: test/bolt_sips/internals/pack_stream/encoder_test.exs
class Bolt.Sips.Internals.PackStream.EncoderTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderTest
class TestStruct (line 9) | defmodule TestStruct
FILE: test/bolt_sips/internals/pack_stream/encoder_v1_test.exs
class Bolt.Sips.Internals.PackStream.EncoderV1Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderV1Test
class TestStruct (line 6) | defmodule TestStruct
FILE: test/bolt_sips/internals/pack_stream/encoder_v2_test.exs
class Bolt.Sips.Internals.PackStream.EncoderV2Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.EncoderV2Test
FILE: test/bolt_sips/internals/pack_stream/message/decoder_test.exs
class Bolt.Sips.Internals.PackStream.Message.DecoderTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.DecoderTest
FILE: test/bolt_sips/internals/pack_stream/message/encoder_test.exs
class Bolt.Sips.Internals.PackStream.Message.EncoderTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderTest
class TestUser (line 10) | defmodule TestUser
FILE: test/bolt_sips/internals/pack_stream/message/encoder_v1_test.exs
class Bolt.Sips.Internals.PackStream.Message.EncoderV1Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV1Test
FILE: test/bolt_sips/internals/pack_stream/message/encoder_v3_test.exs
class Bolt.Sips.Internals.PackStream.Message.EncoderV3Test (line 1) | defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV3Test
FILE: test/bolt_sips/internals/pack_stream/message_test.exs
class Bolt.Sips.Internals.PackStream.MessageTest (line 1) | defmodule Bolt.Sips.Internals.PackStream.MessageTest
FILE: test/bolt_sips/metadata_test.exs
class Bolt.Sips.MetadataTest (line 1) | defmodule Bolt.Sips.MetadataTest
FILE: test/bolt_sips/performance_test.exs
class Bolt.Sips.PerformanceTest (line 1) | defmodule Bolt.Sips.PerformanceTest
FILE: test/bolt_sips/protocol_test.exs
class Bolt.Sips.ProtocolTest (line 1) | defmodule Bolt.Sips.ProtocolTest
FILE: test/bolt_sips/response_encoder/json_implementations_test.exs
class Bolt.Sips.JsonImplementationsTest (line 1) | defmodule Bolt.Sips.JsonImplementationsTest
method fixture (line 27) | defp fixture() do
method result (line 83) | defp result(:jason) do
method result (line 146) | defp result(:poison) do
class TestStruct (line 15) | defmodule TestStruct
FILE: test/bolt_sips/response_encoder/json_test.exs
class Bolt.Sips.ResponseEncode.JsonTest (line 1) | defmodule Bolt.Sips.ResponseEncode.JsonTest
class TestStruct (line 17) | defmodule TestStruct
FILE: test/bolt_sips/response_encoder_test.exs
class Bolt.Sips.ResponseEncoderTest (line 1) | defmodule Bolt.Sips.ResponseEncoderTest
FILE: test/bolt_sips/types_helpers_test.exs
class Bolt.Sips.TypesHelperTest (line 1) | defmodule Bolt.Sips.TypesHelperTest
FILE: test/bolt_sips/types_test.exs
class Bolt.Sips.TypesTest (line 1) | defmodule Bolt.Sips.TypesTest
FILE: test/boltkit_test.exs
class Bolt.Sips.BoltStubTest (line 1) | defmodule Bolt.Sips.BoltStubTest
FILE: test/config_test.exs
class Config.Test (line 1) | defmodule Config.Test
FILE: test/errors_test.exs
class ErrorsTest (line 1) | defmodule ErrorsTest
FILE: test/invalid_param_type_test.exs
class Bolt.Sips.InvalidParamType.Test (line 1) | defmodule Bolt.Sips.InvalidParamType.Test
FILE: test/one_test.exs
class One.Test (line 1) | defmodule One.Test
FILE: test/query_bolt_v2_test.exs
class Bolt.Sips.QueryBoltV2Test (line 1) | defmodule Bolt.Sips.QueryBoltV2Test
FILE: test/query_test.exs
class Query.Test (line 1) | defmodule Query.Test
method rebuild_fixtures (line 12) | defp rebuild_fixtures(conn) do
class TestUser (line 8) | defmodule TestUser
FILE: test/response_test.exs
class ResponseTest (line 1) | defmodule ResponseTest
FILE: test/router_test.exs
class Bolt.Sips.Routing.RouterTest (line 1) | defmodule Bolt.Sips.Routing.RouterTest
FILE: test/routing/connections_test.exs
class Bolt.Sips.Routing.ConnectionsTest (line 1) | defmodule Bolt.Sips.Routing.ConnectionsTest
FILE: test/routing/crud_test.exs
class Bolt.Sips.Routing.CrudTest (line 1) | defmodule Bolt.Sips.Routing.CrudTest
FILE: test/routing/routing_table_parser_test.exs
class Routing.Routing.TableParserTest (line 1) | defmodule Routing.Routing.TableParserTest
FILE: test/routing/routing_test.exs
class Bolt.Sips.RoutingTest (line 1) | defmodule Bolt.Sips.RoutingTest
FILE: test/routing/transaction_test.exs
class Bolt.Sips.Routing.TransactionTest (line 1) | defmodule Bolt.Sips.Routing.TransactionTest
FILE: test/support/boltkit_case.ex
class Bolt.Sips.BoltKitCase (line 1) | defmodule Bolt.Sips.BoltKitCase
method stub_servers (line 73) | defp stub_servers(%{scripts: scripts} = args) do
method wait_for_socket (line 95) | defp wait_for_socket(address, port) do
method connect (line 102) | defp connect(url, prefix) do
FILE: test/support/conn_case.ex
class Bolt.Sips.ConnCase (line 1) | defmodule Bolt.Sips.ConnCase
FILE: test/support/conn_routing_case.ex
class Bolt.Sips.RoutingConnCase (line 1) | defmodule Bolt.Sips.RoutingConnCase
FILE: test/support/database.ex
class Bolt.Sips.Test.Support.Database (line 1) | defmodule Bolt.Sips.Test.Support.Database
method clear (line 2) | def clear(conn) do
FILE: test/support/fixture.ex
class Bolt.Sips.Fixture (line 1) | defmodule Bolt.Sips.Fixture
method create_graph (line 2) | def create_graph(conn, :movie) do
method create_graph (line 6) | def create_graph(conn, :bolt_sips) do
method bolt_sips_cypher (line 10) | def bolt_sips_cypher() do
method movie_cypher (line 29) | def movie_cypher() do
FILE: test/support/internal_case.ex
class Bolt.Sips.InternalCase (line 1) | defmodule Bolt.Sips.InternalCase
method neo4j_uri (line 20) | defp neo4j_uri do
method init (line 36) | defp init(transport, port, 3, auth) do
method init (line 40) | defp init(transport, port, bolt_version, auth) do
FILE: test/test_helper.exs
class Bolt.Sips.TestHelper (line 7) | defmodule Bolt.Sips.TestHelper
method read_whole_file (line 12) | def read_whole_file(path) do
method stream_file_join (line 22) | def stream_file_join(filename) do
method file_error_description (line 27) | defp file_error_description(:enoent), do: "because the file does not e...
method file_error_description (line 28) | defp file_error_description(reason), do: "due to #{reason}."
FILE: test/test_large_param_set.exs
class Large.Param.Set.Test (line 1) | defmodule Large.Param.Set.Test
FILE: test/test_support.exs
class TestSupport (line 1) | defmodule TestSupport
FILE: test/transaction_test.exs
class Transaction.Test (line 1) | defmodule Transaction.Test
Condensed preview — 153 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (552K chars).
[
{
"path": ".credo.exs",
"chars": 4329,
"preview": "# This file contains the configuration for Credo.\n#\n# If you find anything wrong or unclear in this file, please report "
},
{
"path": ".dialyzer_ignore.exs",
"chars": 39,
"preview": "[\n ~r/__impl__.*does\\ not\\ exist\\./\n]\n"
},
{
"path": ".formatter.exs",
"chars": 342,
"preview": "# Used by \"mix format\" and to export configuration.\nexport_locals_without_parens = [\n plug: 1,\n plug: 2,\n adapter: 1,"
},
{
"path": ".gitattributes",
"chars": 12,
"preview": "* text=auto\n"
},
{
"path": ".gitignore",
"chars": 2063,
"preview": "### Elixir template\n# The directory Mix will write compiled artifacts to.\n/_build\n\n# If you run \"mix test --cover\", cove"
},
{
"path": ".iex.exs",
"chars": 1219,
"preview": "try do\n Code.eval_file(\".iex.exs\", \"~\")\nrescue\n Code.LoadError -> :rescued\nend\n\nalias Bolt.Sips.{Utils, Protocol, Rout"
},
{
"path": ".markdownlint.json",
"chars": 42,
"preview": "{\n \"MD013\": false,\n \"MD030\": false\n}"
},
{
"path": ".prettierrc.yaml",
"chars": 123,
"preview": "# .prettierrc or .prettierrc.yaml\ntrailingComma: \"es5\"\ntabWidth: 2\nsemi: false\nsingleQuote: true\nMD013: false\nMD030: fal"
},
{
"path": ".tool-versions",
"chars": 136,
"preview": "erlang 24.1.7\nelixir 1.13.0-otp-24\nnodejs 12.6.0\n#python 3.7.3\npython 3.7.3 2.7.16\n\nruby 2.7.5\nlua 5.3.5\nterraform 0.15."
},
{
"path": ".travis.yml",
"chars": 2947,
"preview": "sudo: required\nservices: docker\nlanguage: elixir\nmatrix:\n include:\n - elixir: 1.7.4\n otp_release: 21.2\n en"
},
{
"path": "CHANGELOG.md",
"chars": 15584,
"preview": "# Changelog\n\n## 2.1.0\n\nThank you https://github.com/zediogoviana, for the following improvements:\n\n- Add configurable SS"
},
{
"path": "ISSUE_TEMPLATE.md",
"chars": 435,
"preview": "# Precheck\n\n* For bugs, do a quick search and make sure the bug has not yet been reported.\n* Finally, be nice and have f"
},
{
"path": "LICENSE",
"chars": 10850,
"preview": " Apache License\n Version 2.0, January 2004\n http"
},
{
"path": "README.md",
"chars": 5328,
"preview": "<img src=\"assets/logo_transparent.png\" alt=\"logo\" width=\"240\"/>\n\n# Neo4j driver for Elixir.\n\n[ do\n :debug\n else\n :inf"
},
{
"path": "config/test.exs",
"chars": 672,
"preview": "import Config\n\nconfig :bolt_sips, Bolt,\n # default port considered to be: 7687\n url: \"bolt://localhost\",\n basic_auth:"
},
{
"path": "docker-compose.yml",
"chars": 2734,
"preview": "version: \"3.0\"\n\nnetworks:\n lan:\n\nservices:\n core1:\n container_name: core1\n image: neo4j:3.5.3-enterprise\n net"
},
{
"path": "docs/examples/readme.md",
"chars": 0,
"preview": ""
},
{
"path": "docs/features/about-encoding.md",
"chars": 1936,
"preview": "# About encoding\n\nBolt.Sips provides support for encoding your query result in different formats.\nFor now, only JSON is "
},
{
"path": "docs/features/about-transactions.md",
"chars": 846,
"preview": "# About transactions\n\nTransaction management in Neo4j 3.5+ differs from what it was in prior versions.\nThe cypher keywor"
},
{
"path": "docs/features/configuration.md",
"chars": 6672,
"preview": "# Configuration\n\nBolt.Sips can be configured using the well known Mix config files, or by using simple keyword lists.\n\nT"
},
{
"path": "docs/features/multi-tenancy.md",
"chars": 1449,
"preview": "# Multi tenancy\n\nVery similar to the role-based connections, with multi-tenancy you will be able to connect to servers w"
},
{
"path": "docs/features/role-based-connections.md",
"chars": 1565,
"preview": "# Role-based connections\n\nStarting with the 2.0 version, you can have distinct configurations that you can use in your a"
},
{
"path": "docs/features/routing.md",
"chars": 7538,
"preview": "# Routing\n\nWhen connecting to a Neo4j cluster, `Bolt.Sips` will create 3 distinct connection pools, each of them dedicat"
},
{
"path": "docs/features/using-cypher.md",
"chars": 4643,
"preview": "# Using Bolt.Sips to query the Neo4j server\n\nLet's talk about the basics of querying a Neo4j server, using `Bolt.Sips`, "
},
{
"path": "docs/features/using-temporal-and-spatial-types.md",
"chars": 4776,
"preview": "# Using temporal and spatial types\n\nTemporal and spatial types are supported since Neo4J 3.4.\nYou can used the elixir st"
},
{
"path": "docs/features/using-with-phoenix.md",
"chars": 1536,
"preview": "# Using Bolt.Sips with Phoenix, or similar\n\nDon't forget to start the `Bolt.Sips` driver in your supervision tree. Examp"
},
{
"path": "docs/getting-started.md",
"chars": 4297,
"preview": "# Getting Started\n\nLet's start by creating a simple Elixir project, as a playground for our tests.\n\n```sh\nmix new neo4j_"
},
{
"path": "lib/bolt_sips/application.ex",
"chars": 202,
"preview": "defmodule Bolt.Sips.Application do\n @moduledoc false\n\n use Application\n\n alias Bolt.Sips\n\n def start(_, start_args) "
},
{
"path": "lib/bolt_sips/enumerable_response.ex",
"chars": 1179,
"preview": "defimpl Enumerable, for: Bolt.Sips.Response do\n alias Bolt.Sips.Response\n\n def count(%Response{results: nil}), do: {:o"
},
{
"path": "lib/bolt_sips/error.ex",
"chars": 734,
"preview": "defmodule Bolt.Sips.Error do\n @moduledoc \"\"\"\n represents an error message\n \"\"\"\n alias __MODULE__\n @type t :: %__MOD"
},
{
"path": "lib/bolt_sips/exception.ex",
"chars": 272,
"preview": "defmodule Bolt.Sips.Exception do\n @moduledoc \"\"\"\n This module defines a `Bolt.Sips.Exception` structure containing two"
},
{
"path": "lib/bolt_sips/internals/bolt_protocol.ex",
"chars": 8715,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocol do\n @moduledoc false\n # A library that handles Bolt Protocol (v1 and v2).\n "
},
{
"path": "lib/bolt_sips/internals/bolt_protocol_helper.ex",
"chars": 4580,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolHelper do\n @moduledoc false\n\n alias Bolt.Sips.Internals.PackStream.Message\n "
},
{
"path": "lib/bolt_sips/internals/bolt_protocol_v1.ex",
"chars": 8894,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolV1 do\n @moduledoc false\n alias Bolt.Sips.Internals.BoltProtocolHelper\n alia"
},
{
"path": "lib/bolt_sips/internals/bolt_protocol_v2.ex",
"chars": 195,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolV2 do\n @moduledoc false\n # There's no specific messagee for Bolt V2\n # This"
},
{
"path": "lib/bolt_sips/internals/bolt_protocol_v3.ex",
"chars": 6899,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolV3 do\n alias Bolt.Sips.Internals.BoltProtocol\n alias Bolt.Sips.Internals.Bol"
},
{
"path": "lib/bolt_sips/internals/bolt_version_helper.ex",
"chars": 1017,
"preview": "defmodule Bolt.Sips.Internals.BoltVersionHelper do\n @moduledoc false\n @available_bolt_versions [1, 2, 3]\n\n @doc \"\"\"\n "
},
{
"path": "lib/bolt_sips/internals/error.ex",
"chars": 2948,
"preview": "defmodule Bolt.Sips.Internals.Error do\n @moduledoc false\n defexception [:message, :code, :connection_id, :function, :t"
},
{
"path": "lib/bolt_sips/internals/logger.ex",
"chars": 1454,
"preview": "defmodule Bolt.Sips.Internals.Logger do\n @moduledoc false\n # Designed to log Bolt protocol message between Client and "
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder.ex",
"chars": 672,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Decoder do\n @moduledoc false\n _moduledoc = \"\"\"\n This module is responsible f"
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_impl_v1.ex",
"chars": 7656,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderImplV1 do\n alias Bolt.Sips.Types\n\n defmacro __using__(_options) do\n "
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_impl_v2.ex",
"chars": 6077,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderImplV2 do\n alias Bolt.Sips.Types.{TimeWithTZOffset, DateTimeWithTZOffse"
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_utils.ex",
"chars": 1851,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderUtils do\n alias Bolt.Sips.Internals.PackStreamError\n\n defmacro __using"
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_v1.ex",
"chars": 628,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderV1 do\n @moduledoc false\n _moduledoc = \"\"\"\n Bolt V1 can decode:\n - Nu"
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_v2.ex",
"chars": 925,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderV2 do\n @moduledoc false\n _module_doc = \"\"\"\n Bolt V2 has specification"
},
{
"path": "lib/bolt_sips/internals/pack_stream/decoder_v3.ex",
"chars": 117,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderV3 do\n def decode(_, _) do\n {:error, :not_implemented}\n end\nend\n"
},
{
"path": "lib/bolt_sips/internals/pack_stream/encoder.ex",
"chars": 3782,
"preview": "alias Bolt.Sips.Internals.PackStream\nalias Bolt.Sips.Internals.PackStream.EncoderHelper\n\ndefprotocol Bolt.Sips.Internals"
},
{
"path": "lib/bolt_sips/internals/pack_stream/encoder_helper.ex",
"chars": 1371,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderHelper do\n @moduledoc false\n alias Bolt.Sips.Internals.BoltVersionHelp"
},
{
"path": "lib/bolt_sips/internals/pack_stream/encoder_v1.ex",
"chars": 5764,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderV1 do\n @moduledoc false\n alias Bolt.Sips.Internals.PackStream.EncoderH"
},
{
"path": "lib/bolt_sips/internals/pack_stream/encoder_v2.ex",
"chars": 8866,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderV2 do\n @moduledoc false\n use Bolt.Sips.Internals.PackStream.Markers\n "
},
{
"path": "lib/bolt_sips/internals/pack_stream/encoder_v3.ex",
"chars": 58,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderV3 do\nend\n"
},
{
"path": "lib/bolt_sips/internals/pack_stream/error.ex",
"chars": 924,
"preview": "defmodule Bolt.Sips.Internals.PackStreamError do\n @moduledoc false\n\n # Represents an error when encoding data for the "
},
{
"path": "lib/bolt_sips/internals/pack_stream/markers.ex",
"chars": 2468,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Markers do\n @moduledoc false\n defmacro __using__(_opts) do\n quote do\n "
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/decoder.ex",
"chars": 1946,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.Decoder do\n @moduledoc false\n\n @tiny_struct_marker 0xB\n\n @success_si"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/encoder.ex",
"chars": 18418,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.Encoder do\n @moduledoc false\n _module_doc = \"\"\"\n Manages the message"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/encoder_v1.ex",
"chars": 520,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV1 do\n @moduledoc false\n use Bolt.Sips.Internals.PackStream.Me"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/encoder_v2.ex",
"chars": 125,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV2 do\n def encode(_, _) do\n {:error, :not_implemented}\n end"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/encoder_v3.ex",
"chars": 967,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV3 do\n @moduledoc false\n use Bolt.Sips.Internals.PackStream.Me"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message/signatures.ex",
"chars": 629,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.Signatures do\n @moduledoc false\n defmacro __using__(_opts) do\n quo"
},
{
"path": "lib/bolt_sips/internals/pack_stream/message.ex",
"chars": 1401,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message do\n @moduledoc false\n\n # Manage the message encoding and decoding.\n "
},
{
"path": "lib/bolt_sips/internals/pack_stream/utils.ex",
"chars": 2169,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Utils do\n alias Bolt.Sips.Internals.PackStream.Encoder\n alias Bolt.Sips.Types"
},
{
"path": "lib/bolt_sips/internals/pack_stream/v1.ex",
"chars": 6685,
"preview": "defmodule Bolt.Sips.Internals.PackStream.V1 do\n defmacro __using__(_options) do\n quote do\n import unquote(__MOD"
},
{
"path": "lib/bolt_sips/internals/pack_stream/v2.ex",
"chars": 3461,
"preview": "defmodule Bolt.Sips.Internals.PackStream.V2 do\n alias Bolt.Sips.Types.{TimeWithTZOffset, DateTimeWithTZOffset, Duration"
},
{
"path": "lib/bolt_sips/internals/pack_stream.ex",
"chars": 1274,
"preview": "defmodule Bolt.Sips.Internals.PackStream do\n @moduledoc false\n\n # The PackStream implementation for Bolt.\n #\n # This"
},
{
"path": "lib/bolt_sips/metadata.ex",
"chars": 2605,
"preview": "defmodule Bolt.Sips.Metadata do\n @moduledoc false\n defstruct [:bookmarks, :tx_timeout, :metadata]\n\n @type t :: %__MOD"
},
{
"path": "lib/bolt_sips/protocol.ex",
"chars": 6478,
"preview": "defmodule Bolt.Sips.Protocol do\n @moduledoc false\n # Implements callbacks required by DBConnection.\n # Each callback "
},
{
"path": "lib/bolt_sips/query.ex",
"chars": 6676,
"preview": "defmodule Bolt.Sips.Query do\n @moduledoc \"\"\"\n Provides a simple Query DSL.\n\n You can run simple Cypher queries with o"
},
{
"path": "lib/bolt_sips/query_statement.ex",
"chars": 304,
"preview": "defmodule Bolt.Sips.QueryStatement do\n @moduledoc false\n defstruct statement: \"\"\nend\n\ndefimpl DBConnection.Query, for:"
},
{
"path": "lib/bolt_sips/response.ex",
"chars": 6651,
"preview": "defmodule Bolt.Sips.Response do\n @moduledoc \"\"\"\n Support for transforming a Bolt response to a list of Bolt.Sips.Types"
},
{
"path": "lib/bolt_sips/response_encoder/json/jason.ex",
"chars": 1500,
"preview": "if Code.ensure_loaded?(Jason) do\n defmodule Bolt.Sips.ResponseEncoder.Json.Jason do\n @moduledoc \"\"\"\n A default im"
},
{
"path": "lib/bolt_sips/response_encoder/json/poison.ex",
"chars": 1537,
"preview": "if Code.ensure_loaded?(Poison) do\n defmodule Bolt.Sips.ResponseEncoder.Json.Poison do\n @moduledoc \"\"\"\n A default "
},
{
"path": "lib/bolt_sips/response_encoder/json.ex",
"chars": 3357,
"preview": "defprotocol Bolt.Sips.ResponseEncoder.Json do\n @moduledoc \"\"\"\n Protocol controlling how a value is made jsonable.\n\n I"
},
{
"path": "lib/bolt_sips/response_encoder.ex",
"chars": 2867,
"preview": "defmodule Bolt.Sips.ResponseEncoder do\n @moduledoc \"\"\"\n This module provides functions to encode a query result or dat"
},
{
"path": "lib/bolt_sips/router.ex",
"chars": 15179,
"preview": "defmodule Bolt.Sips.Router do\n @moduledoc \"\"\"\n This \"driver\" works in tandem with Neo4j's [Causal Clustering](https://"
},
{
"path": "lib/bolt_sips/routing/connection_supervisor.ex",
"chars": 2597,
"preview": "defmodule Bolt.Sips.ConnectionSupervisor do\n @moduledoc false\n\n use DynamicSupervisor\n\n alias Bolt.Sips.Protocol\n al"
},
{
"path": "lib/bolt_sips/routing/load_balancer.ex",
"chars": 693,
"preview": "defmodule Bolt.Sips.LoadBalancer do\n @moduledoc \"\"\"\n a simple load balancer used for selecting a server address from a"
},
{
"path": "lib/bolt_sips/routing/routing_table.ex",
"chars": 2754,
"preview": "defmodule Bolt.Sips.Routing.RoutingTable do\n @moduledoc ~S\"\"\"\n representing the routing table elements\n\n There are a "
},
{
"path": "lib/bolt_sips/socket.ex",
"chars": 1056,
"preview": "defmodule Bolt.Sips.Socket do\n @moduledoc \"\"\"\n A default socket interface used to communicate to a Neo4j instance.\n\n "
},
{
"path": "lib/bolt_sips/types.ex",
"chars": 19024,
"preview": "defmodule Bolt.Sips.Types do\n @moduledoc \"\"\"\n Basic support for representing nodes, relationships and paths belonging "
},
{
"path": "lib/bolt_sips/types_helper.ex",
"chars": 1462,
"preview": "defmodule Bolt.Sips.TypesHelper do\n @doc \"\"\"\n Decompose an amount seconds into the tuple {hours, minutes, seconds}\n \""
},
{
"path": "lib/bolt_sips/utils.ex",
"chars": 6360,
"preview": "defmodule Bolt.Sips.Utils do\n @moduledoc false\n # Common utilities\n\n @default_hostname \"localhost\"\n @default_bolt_po"
},
{
"path": "lib/bolt_sips.ex",
"chars": 9328,
"preview": "defmodule Bolt.Sips do\n @moduledoc \"\"\"\n A Neo4j driver for Elixir providing many useful features:\n\n - using the Bolt "
},
{
"path": "lib/mix/tasks/cypher.ex",
"chars": 2049,
"preview": "defmodule Mix.Tasks.Bolt.Cypher do\n use Mix.Task\n\n @shortdoc \"Execute a Cypher command\"\n @recursive true\n\n @moduledo"
},
{
"path": "mix.exs",
"chars": 3471,
"preview": "defmodule BoltSips.Mixfile do\n use Mix.Project\n\n @version \"2.1.0\"\n @url_docs \"https://hexdocs.pm/bolt_sips\"\n @url_gi"
},
{
"path": "requirements.txt",
"chars": 48,
"preview": "boto==2.48.0\ncertifi\nclick<8,>=7\ndocker\nurllib3\n"
},
{
"path": "test/bolt_sips/internals/bolt_protocol_all_bolt_version_test.exs",
"chars": 3441,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolAllBoltVersionTest do\n use Bolt.Sips.InternalCase\n alias Bolt.Sips.Internals"
},
{
"path": "test/bolt_sips/internals/bolt_protocol_bolt_v1_test.exs",
"chars": 1751,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolV1Test do\n use Bolt.Sips.InternalCase\n @moduletag :bolt_v1\n alias Bolt.Sips"
},
{
"path": "test/bolt_sips/internals/bolt_protocol_bolt_v2_test.exs",
"chars": 7233,
"preview": "defmodule Bolt.Sips.Internals.BoltProtoolBoltV2Test do\n use Bolt.Sips.InternalCase\n @moduletag :bolt_v2\n\n alias Bolt."
},
{
"path": "test/bolt_sips/internals/bolt_protocol_bolt_v3_test.exs",
"chars": 8889,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolBoltV3Test do\n use ExUnit.Case, async: true\n @moduletag :bolt_v3\n\n alias Bo"
},
{
"path": "test/bolt_sips/internals/bolt_protocol_v1_test.exs",
"chars": 6548,
"preview": "defmodule BoltProtocolV1.Sips.Internals.BoltProtocolV1Test do\n use ExUnit.Case, async: true\n @moduletag :bolt_v1\n\n al"
},
{
"path": "test/bolt_sips/internals/bolt_protocol_v3_test.exs",
"chars": 7899,
"preview": "defmodule Bolt.Sips.Internals.BoltProtocolV3Test do\n use ExUnit.Case, async: true\n @moduletag :bolt_v3\n\n alias Bolt.S"
},
{
"path": "test/bolt_sips/internals/bolt_version_helper_test.exs",
"chars": 663,
"preview": "defmodule Bolt.Sips.Internals.BoltVersionHelperTest do\n use ExUnit.Case, async: true\n\n doctest Bolt.Sips.Internals.Bol"
},
{
"path": "test/bolt_sips/internals/logger_test.exs",
"chars": 760,
"preview": "defmodule Bolt.Sips.Internals.LoggerTest do\n use ExUnit.Case\n import ExUnit.CaptureLog\n\n alias Bolt.Sips.Internals.Lo"
},
{
"path": "test/bolt_sips/internals/pack_stream/decoder_test.exs",
"chars": 10381,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internals.Pack"
},
{
"path": "test/bolt_sips/internals/pack_stream/decoder_v1_test.exs",
"chars": 5898,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderV1Test do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internals.Pa"
},
{
"path": "test/bolt_sips/internals/pack_stream/decoder_v2_test.exs",
"chars": 5371,
"preview": "defmodule Bolt.Sips.Internals.PackStream.DecoderV2Test do\n use ExUnit.Case, async: true\n alias Bolt.Sips.Internals.Pac"
},
{
"path": "test/bolt_sips/internals/pack_stream/encoder_helper_test.exs",
"chars": 1043,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderHelperTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internal"
},
{
"path": "test/bolt_sips/internals/pack_stream/encoder_test.exs",
"chars": 6480,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderTest do\n use ExUnit.Case, async: false\n\n alias Bolt.Sips.Internals.Pac"
},
{
"path": "test/bolt_sips/internals/pack_stream/encoder_v1_test.exs",
"chars": 4765,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderV1Test do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internals.Pa"
},
{
"path": "test/bolt_sips/internals/pack_stream/encoder_v2_test.exs",
"chars": 4144,
"preview": "defmodule Bolt.Sips.Internals.PackStream.EncoderV2Test do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internals.Pa"
},
{
"path": "test/bolt_sips/internals/pack_stream/message/decoder_test.exs",
"chars": 2195,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.DecoderTest do\n use ExUnit.Case, async: true\n alias Bolt.Sips.Interna"
},
{
"path": "test/bolt_sips/internals/pack_stream/message/encoder_test.exs",
"chars": 9262,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderTest do\n use ExUnit.Case, async: true\n\n doctest Bolt.Sips.Inte"
},
{
"path": "test/bolt_sips/internals/pack_stream/message/encoder_v1_test.exs",
"chars": 2152,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV1Test do\n use ExUnit.Case, async: true\n\n doctest Bolt.Sips.In"
},
{
"path": "test/bolt_sips/internals/pack_stream/message/encoder_v3_test.exs",
"chars": 3911,
"preview": "defmodule Bolt.Sips.Internals.PackStream.Message.EncoderV3Test do\n use ExUnit.Case, async: true\n\n doctest Bolt.Sips.In"
},
{
"path": "test/bolt_sips/internals/pack_stream/message_test.exs",
"chars": 7094,
"preview": "defmodule Bolt.Sips.Internals.PackStream.MessageTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Internals.Pack"
},
{
"path": "test/bolt_sips/metadata_test.exs",
"chars": 2374,
"preview": "defmodule Bolt.Sips.MetadataTest do\n use ExUnit.Case, async: true\n alias Bolt.Sips.Metadata\n\n @valid_metadata %{\n "
},
{
"path": "test/bolt_sips/performance_test.exs",
"chars": 1899,
"preview": "defmodule Bolt.Sips.PerformanceTest do\n use Bolt.Sips.ConnCase, async: false\n\n setup(%{conn: conn} = context) do\n B"
},
{
"path": "test/bolt_sips/protocol_test.exs",
"chars": 1118,
"preview": "defmodule Bolt.Sips.ProtocolTest do\n use ExUnit.Case, async: false\n\n alias Bolt.Sips.Protocol\n\n # Transactions are no"
},
{
"path": "test/bolt_sips/response_encoder/json_implementations_test.exs",
"chars": 4375,
"preview": "defmodule Bolt.Sips.JsonImplementationsTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Types.{\n DateTimeWit"
},
{
"path": "test/bolt_sips/response_encoder/json_test.exs",
"chars": 4527,
"preview": "defmodule Bolt.Sips.ResponseEncode.JsonTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Types.{\n DateTimeWit"
},
{
"path": "test/bolt_sips/response_encoder_test.exs",
"chars": 115,
"preview": "defmodule Bolt.Sips.ResponseEncoderTest do\n use ExUnit.Case, async: true\n\n doctest Bolt.Sips.ResponseEncoder\nend\n"
},
{
"path": "test/bolt_sips/types_helpers_test.exs",
"chars": 1622,
"preview": "defmodule Bolt.Sips.TypesHelperTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.TypesHelper\n\n describe \"decomp"
},
{
"path": "test/bolt_sips/types_test.exs",
"chars": 5173,
"preview": "defmodule Bolt.Sips.TypesTest do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Types.{DateTimeWithTZOffset, Duration"
},
{
"path": "test/boltkit_test.exs",
"chars": 1369,
"preview": "defmodule Bolt.Sips.BoltStubTest do\n @moduledoc \"\"\"\n !!Remember!!\n you cannot reuse the boltstub across the tests, "
},
{
"path": "test/config_test.exs",
"chars": 3079,
"preview": "defmodule Config.Test do\n use ExUnit.Case\n alias Bolt.Sips.Utils\n\n doctest Bolt.Sips\n\n @graphenedb_like_url \"bolt://"
},
{
"path": "test/errors_test.exs",
"chars": 1281,
"preview": "defmodule ErrorsTest do\n @moduledoc \"\"\"\n every new error, and related tests\n \"\"\"\n use ExUnit.Case, async: true\n\n @s"
},
{
"path": "test/invalid_param_type_test.exs",
"chars": 596,
"preview": "defmodule Bolt.Sips.InvalidParamType.Test do\n use ExUnit.Case\n\n setup_all do\n Bolt.Sips.ConnectionSupervisor.connec"
},
{
"path": "test/one_test.exs",
"chars": 1418,
"preview": "defmodule One.Test do\n # use Bolt.Sips.RoutingConnCase\n # @moduletag :routing\n\n # # alias Bolt.Sips.{Success, Error, "
},
{
"path": "test/query_bolt_v2_test.exs",
"chars": 4205,
"preview": "defmodule Bolt.Sips.QueryBoltV2Test do\n use Bolt.Sips.ConnCase, async: true\n @moduletag :bolt_v2\n\n alias Bolt.Sips.Ty"
},
{
"path": "test/query_test.exs",
"chars": 13440,
"preview": "defmodule Query.Test do\n use Bolt.Sips.ConnCase, async: true\n\n alias Query.Test\n alias Bolt.Sips.Test.Support.Databas"
},
{
"path": "test/response_test.exs",
"chars": 13918,
"preview": "defmodule ResponseTest do\n use ExUnit.Case\n\n alias Bolt.Sips.Response\n # import ExUnit.CaptureLog\n\n @explain [\n s"
},
{
"path": "test/router_test.exs",
"chars": 3057,
"preview": "defmodule Bolt.Sips.Routing.RouterTest do\n use ExUnit.Case\n doctest Bolt.Sips.Router\n\n alias Bolt.Sips.Response\n\n # "
},
{
"path": "test/routing/connections_test.exs",
"chars": 1223,
"preview": "defmodule Bolt.Sips.Routing.ConnectionsTest do\n use ExUnit.Case, async: true\n @moduletag :routing\n\n alias Bolt.Sips.R"
},
{
"path": "test/routing/crud_test.exs",
"chars": 2035,
"preview": "defmodule Bolt.Sips.Routing.CrudTest do\n use Bolt.Sips.RoutingConnCase\n @moduletag :routing\n\n alias Bolt.Sips\n\n desc"
},
{
"path": "test/routing/routing_table_parser_test.exs",
"chars": 2282,
"preview": "defmodule Routing.Routing.TableParserTest do\n use ExUnit.Case, async: true\n @moduletag :routing\n\n alias Bolt.Sips.Rou"
},
{
"path": "test/routing/routing_test.exs",
"chars": 4413,
"preview": "defmodule Bolt.Sips.RoutingTest do\n @moduledoc \"\"\"\n\n \"\"\"\n use Bolt.Sips.BoltKitCase, async: false\n\n alias Bolt.Sips."
},
{
"path": "test/routing/transaction_test.exs",
"chars": 3376,
"preview": "defmodule Bolt.Sips.Routing.TransactionTest do\n use Bolt.Sips.RoutingConnCase\n @moduletag :routing\n\n setup do\n {:o"
},
{
"path": "test/scripts/count.bolt",
"chars": 297,
"preview": "!: AUTO INIT\n!: AUTO RESET\n!: AUTO PULL_ALL\n\nC: RUN \"UNWIND range(1, 10) AS n RETURN n\" {}\nS: SUCCESS {\"fields\": [\"n\"]}\n"
},
{
"path": "test/scripts/create_a.script",
"chars": 116,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"CREATE (a $x)\" {\"x\": {\"name\": \"Alice\"}}\nS: SUCCESS {\"fields\": []}\n SUCCESS {}\n"
},
{
"path": "test/scripts/forbidden_on_read_only_database.script",
"chars": 273,
"preview": "!: AUTO INIT\n!: AUTO RESET\n!: AUTO DISCARD_ALL\n!: AUTO RUN \"ROLLBACK\" {}\n!: AUTO RUN \"BEGIN\" {}\n!: AUTO RUN \"COMMIT\" {}\n"
},
{
"path": "test/scripts/get_routing_table.script",
"chars": 598,
"preview": "!: AUTO INIT\n!: AUTO RESET\n!: AUTO PULL_ALL\n\nS: SUCCESS {\"server\": \"Neo4j/3.2.3\"}\nC: RUN \"CALL dbms.cluster.routing.getR"
},
{
"path": "test/scripts/get_routing_table_with_context.script",
"chars": 446,
"preview": "!: AUTO INIT\n!: AUTO RESET\n!: AUTO PULL_ALL\n\nS: SUCCESS {\"server\": \"Neo4j/3.2.3\"}\nC: RUN \"CALL dbms.cluster.routing.getR"
},
{
"path": "test/scripts/non_router.script",
"chars": 233,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"CALL dbms.cluster.routing.getRoutingTable($context)\" {\"context\": {}}\nS: FAILURE {\"co"
},
{
"path": "test/scripts/return_1.script",
"chars": 113,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"RETURN $x\" {\"x\": 1}\nS: SUCCESS {\"fields\": [\"x\"]}\n RECORD [1]\n SUCCESS {}\n"
},
{
"path": "test/scripts/return_1_in_tx_twice.script",
"chars": 595,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"BEGIN\" {}\nS: SUCCESS {\"fields\": []}\n SUCCESS {}\n\nC: RUN \"RETURN 1\" {}\nS: SUCCESS {"
},
{
"path": "test/scripts/return_1_twice.script",
"chars": 199,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"RETURN $x\" {\"x\": 1}\nS: SUCCESS {\"fields\": [\"x\"]}\n RECORD [1]\n SUCCESS {}\n\nC: RUN"
},
{
"path": "test/scripts/return_x.bolt",
"chars": 129,
"preview": "!: AUTO INIT\n!: AUTO RESET\n!: AUTO PULL_ALL\n\nC: RUN \"RETURN $x\" {\"x\": 1}\nS: SUCCESS {\"fields\": [\"x\"]}\n RECORD [1]\n S"
},
{
"path": "test/scripts/router.script",
"chars": 374,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"CALL dbms.cluster.routing.getRoutingTable($context)\" {\"context\": {}}\nS: SUCCESS {\"fi"
},
{
"path": "test/scripts/router_no_readers.script",
"chars": 353,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"CALL dbms.cluster.routing.getRoutingTable($context)\" {\"context\": {}}\n PULL_ALL\nS: "
},
{
"path": "test/scripts/router_no_writers.script",
"chars": 370,
"preview": "!: AUTO INIT\n!: AUTO RESET\n\nC: RUN \"CALL dbms.cluster.routing.getRoutingTable($context)\" {\"context\": {}}\n PULL_ALL\nS: "
},
{
"path": "test/support/boltkit_case.ex",
"chars": 2946,
"preview": "defmodule Bolt.Sips.BoltKitCase do\n _doc = \"\"\"\n tag your tests with `boltkit`, like this:\n\n @tag boltkit: %{\n "
},
{
"path": "test/support/conn_case.ex",
"chars": 277,
"preview": "defmodule Bolt.Sips.ConnCase do\n use ExUnit.CaseTemplate\n\n setup_all do\n Bolt.Sips.start_link(Application.get_env(:"
},
{
"path": "test/support/conn_routing_case.ex",
"chars": 678,
"preview": "defmodule Bolt.Sips.RoutingConnCase do\n @moduletag :routing\n\n use ExUnit.CaseTemplate\n\n alias Bolt.Sips\n\n @routing_c"
},
{
"path": "test/support/database.ex",
"chars": 132,
"preview": "defmodule Bolt.Sips.Test.Support.Database do\n def clear(conn) do\n Bolt.Sips.query!(conn, \"MATCH (n) DETACH DELETE n\""
},
{
"path": "test/support/fixture.ex",
"chars": 30500,
"preview": "defmodule Bolt.Sips.Fixture do\n def create_graph(conn, :movie) do\n Bolt.Sips.query!(conn, movie_cypher())\n end\n\n d"
},
{
"path": "test/support/internal_case.ex",
"chars": 1105,
"preview": "defmodule Bolt.Sips.InternalCase do\n use ExUnit.CaseTemplate\n\n alias Bolt.Sips.Internals.BoltProtocol\n\n setup do\n "
},
{
"path": "test/test_helper.exs",
"chars": 1176,
"preview": "Logger.configure(level: :debug)\nExUnit.start(capture_log: true, assert_receive_timeout: 500, exclude: [:skip, :bench, :a"
},
{
"path": "test/test_large_param_set.exs",
"chars": 741,
"preview": "defmodule Large.Param.Set.Test do\n use ExUnit.Case\n doctest Bolt.Sips\n\n setup_all do\n {:ok, [conn: Bolt.Sips.conn("
},
{
"path": "test/test_support.exs",
"chars": 48,
"preview": "defmodule TestSupport do\n @moduledoc false\nend\n"
},
{
"path": "test/transaction_test.exs",
"chars": 3831,
"preview": "defmodule Transaction.Test do\n use ExUnit.Case, async: true\n\n alias Bolt.Sips.Response\n\n setup do\n {:ok, [main_con"
}
]
About this extraction
This page contains the full source code of the florinpatrascu/bolt_sips GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 153 files (507.4 KB), approximately 159.5k tokens, and a symbol index with 452 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.