Skip to content
This repository was archived by the owner on Feb 20, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f625aff
Update to dgraph v2.0.0-rc1 and dependencies
liveforeverx Mar 2, 2020
457f0f9
Add IntelliJ specific .gitignore line
emhagman Apr 6, 2020
392bc0d
Update dgraph to a new version
liveforeverx Apr 13, 2020
c5e07df
Make port offset configurable
liveforeverx Apr 13, 2020
bbebc98
Return `queries` when doing upsert for mutation (#68)
emhagman May 1, 2020
d3543f2
Add "queries" to :json key in %Response{} for :http adapter (#69)
emhagman May 1, 2020
078a5aa
Add working relations and changeset validation for relations
jnylen Feb 1, 2020
160e133
Ignore .elixir_ls
jnylen Feb 1, 2020
455c0c5
Start on language support
jnylen Feb 1, 2020
6d5e1a7
Merge branch 'add-relations' into 'master'
jnylen Feb 1, 2020
e6c2b75
Merge branch 'master' into add-lang
jnylen May 2, 2020
27b2bf4
Fix lang
jnylen May 2, 2020
bc414f5
Merge branch 'add-lang' into 'master'
jnylen Feb 1, 2020
c798056
Add reverse
jnylen May 2, 2020
adb6ea9
Merge branch 'master' into add-relations
jnylen Feb 1, 2020
c439560
Fix reverse relations
jnylen May 2, 2020
5e6424c
Merge branch 'add-relations' into 'master'
jnylen Feb 2, 2020
dc2b8cd
Add a way to return all available languages and pass them to dgraph
jnylen May 2, 2020
fb16a09
No type needed for dgraph 1.2.0+
jnylen Feb 2, 2020
d492d20
Merge branch 'add-lang' into 'master'
jnylen Feb 2, 2020
70908ae
Up versions
jnylen May 2, 2020
8c3df32
Merge branch 'master' of gitlab.com:metadata-by-design/metagraph/dlex
jnylen Feb 2, 2020
8976d8a
Add shared name
jnylen May 2, 2020
eae21aa
Merge branch 'add-shared-name' into 'master'
jnylen Feb 2, 2020
fb737a9
Fix testing for shared/2 and split up the models for testing
jnylen May 2, 2020
4a7f767
Merge branch 'split-test' into 'master'
jnylen Feb 2, 2020
24f073f
Fix when it can get duplicated langs
jnylen Feb 2, 2020
a628418
Fix reverse relation
jnylen Feb 2, 2020
e14203d
Fix for merging multiple lists
jnylen Feb 2, 2020
fee172b
Fix?
jnylen Feb 7, 2020
d1ec34e
Merge branch 'fix-reverse-relation' into 'master'
jnylen Feb 2, 2020
9181403
Merge branch 'fix/single-relation-uid-issue' into 'master'
jnylen Feb 8, 2020
340e3a5
Add field depends on schema
jnylen Feb 9, 2020
b110706
Return opts instead
jnylen Feb 9, 2020
9b34003
How about this?
jnylen Feb 9, 2020
7e282b9
fix
jnylen Feb 9, 2020
6cb0809
Add renovate.json (#1)
renovate[bot] Apr 3, 2020
fc7c04b
Bump ecto from 3.3.2 to 3.4.0 (#4)
dependabot-preview[bot] Apr 3, 2020
e4243a5
Bump db_connection from 2.2.0 to 2.2.1 (#5)
dependabot-preview[bot] Apr 3, 2020
f2e0254
Bump ecto from 3.4.0 to 3.4.1 (#7)
dependabot-preview[bot] Apr 9, 2020
f06aaa0
Bump db_connection from 2.2.1 to 2.2.2 (#9)
dependabot-preview[bot] Apr 27, 2020
0bfcafb
Bump ecto from 3.4.1 to 3.4.2 (#8)
dependabot-preview[bot] Apr 27, 2020
35be846
Bump ecto from 3.4.2 to 3.4.3 (#10)
dependabot-preview[bot] May 1, 2020
4d99d89
Fixes
jnylen May 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ jobs:
build:
docker:
- image: elixir:1.9.1
- image: dgraph/dgraph:v1.1.1
- image: dgraph/dgraph:v20.03.0
command: dgraph zero --port_offset=-2000 --my=localhost:3080
- image: dgraph/dgraph:v1.1.1
- image: dgraph/dgraph:v20.03.0
command: dgraph alpha --bindall --port_offset=10 --my=localhost:7090 --zero=localhost:3080
working_directory: ~/repo
steps:
- checkout
- run: mix local.hex --force # install Hex locally (without prompt)
- run: mix local.rebar --force # fetch a copy of rebar (without prompt)
- run: mix deps.get
- run: mix test.all
- run: DLEX_PORT_OFFSET=10 mix test.all
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
dix-*.tar

.elixir_ls/
5 changes: 5 additions & 0 deletions .iex.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
c("test/models/ball.exs")
c("test/models/repo.exs")
c("test/models/social.exs")
c("test/models/team.exs")
c("test/models/user.exs")
53 changes: 32 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
[![CircleCI](https://circleci.com/gh/liveforeverx/dlex.svg?style=svg)](https://circleci.com/gh/liveforeverx/dlex)

Dlex is a gRPC based client for the [Dgraph](https://github.com/dgraph-io/dgraph) database in Elixir.
It uses the [DBConnection](https://hexdocs.pm/db_connection/DBConnection.html) behaviour to support
transactions and connection pooling.
It uses the [DBConnection](https://hexdocs.pm/db_connection/DBConnection.html) behaviour to support transactions and connection pooling.

Small, efficient codebase. Aims for a full Dgraph support. Supports transactions (starting from Dgraph version: `1.0.9`),
delete mutations and low-level parameterized queries. DSL is planned.
Small, efficient codebase. Aims for a full Dgraph support. Supports transactions (starting from Dgraph version: `1.0.9`), delete mutations and low-level parameterized queries. DSL is planned.

Now supports the new dgraph 1.1.x [Type System](https://docs.dgraph.io/master/query-language/#type-system).

Expand All @@ -17,7 +15,7 @@ Now supports the new dgraph 1.1.x [Type System](https://docs.dgraph.io/master/qu
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `dlex` to your list of dependencies in `mix.exs`:

Preffered and more performant option is to use `grpc`:
Preferred and more performant option is to use `grpc`:

```elixir
def deps do
Expand All @@ -41,29 +39,44 @@ def deps do
end
```

## Usage example
## Usage examples

```elixir
{:ok, conn} = Dlex.start_link(name: :example) # default try to connect `localhost:9080` by default
# try to connect to `localhost:9080` by default
{:ok, conn} = Dlex.start_link(name: :example)

# clear any data in the graph
Dlex.alter!(conn, %{drop_all: true})

# add a term index on then `name` predicate
{:ok, _} = Dlex.alter(conn, "name: string @index(term) .")
{:ok, %{"uid" => uid}} = Dlex.mutate(conn, %{

# add nodes, returning the uids in the response
mut = %{
"name" => "Alice",
"friends" => [%{"name" => "Betty"}, %{"name" => "Mark"}]
}, return_json: true) # return the same json with uids
Dlex.mutate(conn, ~s|_:foo <name> "Bar" .|) # or in nquads format
}
{:ok, %{json: %{"uid" => uid}}} = Dlex.mutate(conn, mut, return_json: true)

# use the nquad format for mutations instead if preferred
Dlex.mutate(conn, ~s|_:foo <name> "Bar" .|)

# basic query that shows Betty
by_name = "query by_name($name: string) {by_name(func: eq(name, $name)) {uid expand(_all_)}}"
Dlex.query(conn, by_name, %{"$name" => "Betty"})
Dlex.delete(conn, %{"uid" => uid}) # delete Alice node

# delete the Alice node
Dlex.delete(conn, %{"uid" => uid})
```

### Alter schema

Modification of schema supported with string and map form (which is returned by `query_schema`):

```
```elixir
Dlex.alter(conn, "name: string @index(term, fulltext, trigram) @lang .")
# equivalent to in map form

# equivalent map form
Dlex.alter(conn, [
%{
"predicate" => "name",
Expand All @@ -86,7 +99,7 @@ Dlex.alter(conn, [

NOTE: You may stop the server using `./stop-server.sh`

### By updating api.proto
### Updating GRPC stubs based on api.proto

#### Install development dependencies

Expand All @@ -97,7 +110,7 @@ NOTE: You may stop the server using `./stop-server.sh`
mix escript.install hex protobuf
```

#### By updating [api.proto](https://github.com/dgraph-io/dgo/blob/master/protos/api.proto), generate Elixir code
#### Generate Elixir code based on api.proto

3. Generate Elixir code using protoc

Expand All @@ -107,19 +120,17 @@ protoc --elixir_out=plugins=grpc:. lib/api.proto

4. Files `lib/api.pb.ex` will be generated

5. Rename `lib/api.pb.ex` to `lib/dlex/api.ex` and add `alias Dlex.Api` to be complient with Elixir naming
5. Rename `lib/api.pb.ex` to `lib/dlex/api.ex` and add `alias Dlex.Api` to be compliant with Elixir naming

## Credits

Inspired by [exdgraph](https://github.com/ospaarmann/exdgraph), but as I saw too many parts for changes or parts, which I would like to have completely different, so that it was easier to start from scratch with these goals: small codebase, small natural abstraction, efficient, less opionated, less dependencies.
Inspired by [exdgraph](https://github.com/ospaarmann/exdgraph), but as I saw too many parts for changes or parts, which I would like to have completely different, so that it was easier to start from scratch with these goals: small codebase, small natural abstraction, efficient, less opinionated, less dependencies.

So you can choose freely which pool implementation to use (poolboy or db_connection intern pool implementation) or
which JSON adapter to use. Fewer dependencies.
So you can choose freely which pool implementation to use (poolboy or db_connection intern pool implementation) or which JSON adapter to use. Fewer dependencies.

It seems for me more natural to have API names more or less matching actual query names.

For example `Dlex.mutate()` instead of `ExDgraph.set_map` for JSON-based mutations. Actually, `Dlex.mutate` infers
the type (JSON or nquads) from data passed to a function.
For example `Dlex.mutate()` instead of `ExDgraph.set_map` for JSON-based mutations. Actually, `Dlex.mutate` infers the type (JSON or nquads) from data passed to a function.

## License

Expand Down
32 changes: 10 additions & 22 deletions lib/dlex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,21 +148,20 @@ defmodule Dlex do
_:bar <name> "Bar" .
"
iex> Dlex.mutate(conn, mutation)
{:ok, %{"bar" => "0xfe04c", "foo" => "0xfe04b"}}
{:ok, %{uids: %{"bar" => "0xfe04c", "foo" => "0xfe04b"}, queries: %{}}}

Using `json`

iex> json = %{"name" => "Foo",
"owns" => [%{"name" => "Bar"}]}
iex> json = %{"name" => "Foo", "owns" => [%{"name" => "Bar"}]}
Dlex.mutate(conn, json)
{:ok, %{"blank-0" => "0xfe04d", "blank-1" => "0xfe04e"}}
{:ok, %{uids: %{"blank-0" => "0xfe04d", "blank-1" => "0xfe04e"}, queries: %{}}}
iex> Dlex.mutate(conn, json, return_json: true)
{:ok,
%{
%{ json: %{
"name" => "Foo",
"owns" => [%{"name" => "Bar", "uid" => "0xfe050"}],
"uid" => "0xfe04f"
}}
}}}

## Options

Expand Down Expand Up @@ -236,28 +235,17 @@ defmodule Dlex do

Example of usage

iex> mutation = "
_:foo <name> "Foo" .
_:foo <owns> _:bar .
_:bar <name> "Bar" .
"
iex> Dlex.delete(conn, mutation)
{:ok, %{"bar" => "0xfe04c", "foo" => "0xfe04b"}}
iex> Dlex.delete(conn, %{"uid" => "0xfe04c"})
{:ok, %{queries: %{}, uids: %{}}}

Using `json`

iex> json = %{"name" => "Foo",
"owns" => [%{"name" => "Bar"}]}
iex> json = %{"uid" => "0xfe04c"}
Dlex.delete(conn, json)
{:ok, %{"blank-0" => "0xfe04d", "blank-1" => "0xfe04e"}}
{:ok, %{queries: %{}, uids: %{}}}

iex> Dlex.delete(conn, json, return_json: true)
{:ok,
%{
"name" => "Foo",
"owns" => [%{"name" => "Bar", "uid" => "0xfe050"}],
"uid" => "0xfe04f"
}}
{:ok, %{json: %{"uid" => "0xfe04c"}, queries: %{}, uids: %{}}}

## Options

Expand Down
4 changes: 2 additions & 2 deletions lib/dlex/adapters/http.ex
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ if Code.ensure_loaded?(Mint.HTTP) do
Dlex.Api.Payload.new(Data: data)
end

defp parse_success(:mutate, %{"data" => %{"uids" => uids}} = response) do
Dlex.Api.Response.new(txn: parse_txn(response), uids: uids)
defp parse_success(:mutate, %{"data" => %{"uids" => uids, "queries" => queries}} = response) do
Dlex.Api.Response.new(txn: parse_txn(response), uids: uids, json: queries)
end

defp parse_success(:query, %{"data" => data} = response) do
Expand Down
37 changes: 37 additions & 0 deletions lib/dlex/changeset.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule Dlex.Changeset do
@moduledoc """
This is basically just a proxy to Ecto.Changeset, except it has some dgraph-specific validators.
"""

defmacro __using__(_) do
quote do
import Ecto.Changeset
import Dlex.Changeset
end
end

def validate_relation(%Ecto.Changeset{data: %{__struct__: struct}} = changeset, field)
when is_atom(field),
do: Ecto.Changeset.validate_change(changeset, field, &relation_valid?(&1, &2, struct))

defp relation_valid?(current_field, list, struct) when is_list(list) do
models = struct.__schema__(:models, current_field)

Enum.reduce_while(list, [], fn item, _ ->
if Enum.member?(models, item |> Map.get(:__struct__)),
do: {:cont, []},
else: {:halt, [{current_field, "not in one of the allowed models"}]}
end)
end

defp relation_valid?(current_field, value, struct) when is_map(value) do
models = struct.__schema__(:models, current_field)

if Enum.member?(models, value |> Map.get(:__struct__)),
do: [],
else: [{current_field, "not in one of the allowed models"}]
end

defp relation_valid?(current_field, _, _),
do: [{current_field, "value needs to be a list of structs or a single struct"}]
end
2 changes: 1 addition & 1 deletion lib/dlex/field.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Dlex.Field do
@type type :: :integer | :float | :string | :geo | :datetime | :uid | :auto
@type type :: :integer | :float | :string | :geo | :datetime | :uid | :auto | [:uid]

@type t :: %__MODULE__{
name: atom(),
Expand Down
14 changes: 14 additions & 0 deletions lib/dlex/lang.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Dlex.Lang do
def __schema__(:field, :language),
do: {:language, :string}

def __schema__(:field, :value),
do: {:value, :string}

@type t :: %__MODULE__{
value: String.t(),
language: String.t()
}

defstruct [:value, :language]
end
Loading