Skip to content

Commit

Permalink
binary_id custom autogeneration
Browse files Browse the repository at this point in the history
* `binary_id` custom autogeneration added
* `ensure_all_started` added
  • Loading branch information
vintikzzz committed Dec 24, 2016
1 parent b368561 commit 99c839d
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Tested against 3.7+.
```elixir
def deps do
[{:cqerl, github: "matehat/cqerl", tag: "v1.0.2", only: :test},
{:cassandra_ecto, "~> 0.2.1"}]
{:cassandra_ecto, "~> 0.3.0"}]
end
```

Expand Down
2 changes: 2 additions & 0 deletions lib/cassandra_ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ defmodule Cassandra.Ecto do

def child_spec(repo, opts), do: Connection.child_spec(repo, opts)

def ensure_all_started(_repo, _type), do: {:ok, []}

## Adapter
alias Cassandra.Ecto.Adapter
defdelegate prepare(func, query), to: Adapter
Expand Down
22 changes: 22 additions & 0 deletions lib/cassandra_ecto/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ defmodule Cassandra.Ecto.Adapter do
You can specify TTL and TIMESTAMP with `:ttl` and `:timestamp` respectively
on query level.
## `:binary_id` autogeneration
By default Cassandra adapter generates `:binary_id` with `Ecto.UUID.bingenerate()`.
But it is also possible to override this behaviour and generate id natively
on Cassandra side with build-in functions `now()` or `uuid()` like so:
post = Repo.insert!(post, binary_id: :now)
If you have decided to make it default for repo:
config :my_app, Repo,
keyspace: "my_keyspace"
binary_id: :now
Possible option variants:
:now
:uuid
:default
## Transactions
CASSANDRA DOESN'T SUPPORT TRANSACTIONS!
Expand Down Expand Up @@ -170,6 +191,7 @@ defmodule Cassandra.Ecto.Adapter do
def insert(_repo, meta, _params, _on_conflict, [_|_] = returning, _opts), do:
read_write_error!(meta, returning)
def insert(repo, meta, fields, on_conflict, [], opts) do
opts = Keyword.put_new(opts, :binary_id, Keyword.get(repo.__pool__, :binary_id, :default))
on_conflict = prepare_on_conflict(repo, on_conflict)
cql = to_cql(:insert, meta, fields, on_conflict, opts)
fields = fields ++ if_fields(opts)
Expand Down
10 changes: 8 additions & 2 deletions lib/cassandra_ecto/adapter/cql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ defmodule Cassandra.Ecto.Adapter.CQL do
where = where(query)
assemble(["UPDATE", quote_table(prefix, from), using(opts), "SET", fields, where, if_op(opts)])
end
def to_cql(:insert, %{source: {prefix, source}}, fields, on_conflict, opts) do
def to_cql(:insert, %{autogenerate_id: autogenerate, source: {prefix, source}}, fields, on_conflict, opts) do
header = fields |> Keyword.keys
values = "(" <> Enum.map_join(header, ", ", &quote_name/1) <> ") " <>
"VALUES " <> "(" <> Enum.map_join(header, ", ", fn _arg -> "?" end) <> ")"
"VALUES " <> "(" <> Enum.map_join(header, ", ", &insert_value(autogenerate, &1, Keyword.get(opts, :binary_id, :default))) <> ")"
assemble(["INSERT INTO", quote_table(prefix, source), values, on_conflict(on_conflict, opts), using(opts)])
end
def to_cql(:update, %{source: {prefix, source}}, fields, filters, opts), do:
Expand All @@ -45,6 +45,12 @@ defmodule Cassandra.Ecto.Adapter.CQL do
def to_cql(:delete, %{source: {prefix, source}}, fields, opts), do:
assemble(["DELETE FROM", quote_table(prefix, source), using(opts), "WHERE", filter(fields)])

defp insert_value({id, :binary_id}, field, type) when field == id and type in [:now, :uuid], do:
Atom.to_string(type) <> "()"
defp insert_value({id, :id}, field, _) when field == id, do: error! nil,
"Cassandra adapter supports only :binary_id"
defp insert_value(_, field, _), do: "?"

defp update_fields(%Query{updates: updates} = query), do:
for(%{expr: expr} <- updates,
{op, kw} <- expr,
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule CassandraEcto.Mixfile do
use Mix.Project

@version "0.2.1"
@version "0.3.0"

def project do
[app: :cassandra_ecto,
Expand Down
24 changes: 19 additions & 5 deletions spec/cassandra_ecto/adapter/cql_spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -133,33 +133,47 @@ defmodule CassandroEctoAdapterCQLSpec do
end
end
context "with :insert" do
context "with :autogenerate_id" do
it "generates cql with now() for option binary_id: :now" do
expect(to_cql(:insert, %{autogenerate_id: {:id, :binary_id}, source: {nil, "posts"}}, [id: nil, title: "a", text: "b"], {:raise, [], []}, [binary_id: :now]))
|> to(eq "INSERT INTO \"posts\" (\"id\", \"title\", \"text\") VALUES (now(), ?, ?) IF NOT EXISTS")
end
it "generates cql with uuid() for option binary_id: :uuid" do
expect(to_cql(:insert, %{autogenerate_id: {:id, :binary_id}, source: {nil, "posts"}}, [id: nil, title: "a", text: "b"], {:raise, [], []}, [binary_id: :uuid]))
|> to(eq "INSERT INTO \"posts\" (\"id\", \"title\", \"text\") VALUES (uuid(), ?, ?) IF NOT EXISTS")
end
it "fails with autogenerate_id: {_, :id}" do
expect(fn -> to_cql(:insert, %{autogenerate_id: {:id, :id}, source: {nil, "posts"}}, [id: nil, title: "a", text: "b"], {:raise, [], []}, []) end)
|> to(raise_exception(ArgumentError))
end
end
context "with on_conflict :raise" do
it "generates cql with \"IF NOT EXISTS\"" do
expect(to_cql(:insert, %{source: {nil, "posts"}}, [title: "a", text: "b"], {:raise, [], []}, []))
expect(to_cql(:insert, %{autogenerate_id: nil, source: {nil, "posts"}}, [title: "a", text: "b"], {:raise, [], []}, []))
|> to(eq "INSERT INTO \"posts\" (\"title\", \"text\") VALUES (?, ?) IF NOT EXISTS")
end
end
context "with on_conflict :nothing" do
it "generates cql for Cassandra upsert" do
expect(to_cql(:insert, %{source: {nil, "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, []))
expect(to_cql(:insert, %{autogenerate_id: nil, source: {nil, "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, []))
|> to(eq "INSERT INTO \"posts\" (\"title\", \"text\") VALUES (?, ?)")
end
end
context "with on_conflict :nothing and if: :not_exists" do
it "generates cql with \"IF NOT EXISTS\"" do
expect(to_cql(:insert, %{source: {nil, "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, if: :not_exists))
expect(to_cql(:insert, %{autogenerate_id: nil, source: {nil, "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, if: :not_exists))
|> to(eq "INSERT INTO \"posts\" (\"title\", \"text\") VALUES (?, ?) IF NOT EXISTS")
end
end
context "with :prefix" do
it "generates cql with Cassandra keyspace" do
expect(to_cql(:insert, %{source: {"test", "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, []))
expect(to_cql(:insert, %{autogenerate_id: nil, source: {"test", "posts"}}, [title: "a", text: "b"], {:nothing, [], []}, []))
|> to(eq "INSERT INTO \"test\".\"posts\" (\"title\", \"text\") VALUES (?, ?)")
end
end
context "with :timestamp and :ttl" do
it "generates cql" do
expect(to_cql(:insert, %{source: {nil, "posts"}}, [title: "a", text: "b"], {:raise, [], []}, ttl: 86400, timestamp: 123456789))
expect(to_cql(:insert, %{autogenerate_id: nil, source: {nil, "posts"}}, [title: "a", text: "b"], {:raise, [], []}, ttl: 86400, timestamp: 123456789))
|> to(eq "INSERT INTO \"posts\" (\"title\", \"text\") VALUES (?, ?) IF NOT EXISTS USING TTL 86400 AND TIMESTAMP 123456789")
end
end
Expand Down
12 changes: 12 additions & 0 deletions spec/cassandra_ecto/adapter_spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ defmodule CassandraEctoAdapterSpec do
fetched_post = TestRepo.one(Post)
expect(fetched_post) |> to(eq post)
end
it "inserts record with now() if has option binary_id: :now" do
post = factory(:post)
post = TestNowRepo.insert!(post, binary_id: :now)
fetched_post = TestNowRepo.one(Post)
expect(fetched_post) |> to_not(eq post)
end
it "inserts record with now() if repo has option binary_id: :now" do
post = factory(:post)
post = TestNowRepo.insert!(post)
fetched_post = TestNowRepo.one(Post)
expect(fetched_post) |> to_not(eq post)
end
it "inserts record with ttl and timestamp" do
post = factory(:post)
post = TestRepo.insert!(post, on_conflict: :nothing, ttl: 1000, timestamp: :os.system_time(:micro_seconds))
Expand Down
5 changes: 5 additions & 0 deletions spec/spec_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ Code.require_file "support/factories.exs", __DIR__

Application.put_env(:ecto, TestRepo, adapter: Cassandra.Ecto)
Application.put_env(:ecto, TestUpsertRepo, adapter: Cassandra.Ecto, upsert: true)
Application.put_env(:ecto, TestNowRepo, adapter: Cassandra.Ecto, binary_id: :now)

defmodule TestRepo, do:
use Ecto.Repo, otp_app: :ecto

defmodule TestUpsertRepo, do:
use Ecto.Repo, otp_app: :ecto

defmodule TestNowRepo, do:
use Ecto.Repo, otp_app: :ecto

ESpec.configure fn(config) ->
config.before fn
%{context_tag: :db} ->
Expand All @@ -28,3 +32,4 @@ end

{:ok, _pid} = TestRepo.start_link
{:ok, _pid} = TestUpsertRepo.start_link
{:ok, _pid} = TestNowRepo.start_link

0 comments on commit 99c839d

Please sign in to comment.