From 3a0471b9757661302df4d6902578e85dea3d18db Mon Sep 17 00:00:00 2001 From: Misha Conway Date: Sun, 4 Jun 2023 17:49:59 -0700 Subject: [PATCH] Adds support for generate truncate table queries. --- lib/cassandrax/connection.ex | 2 ++ lib/cassandrax/keyspace.ex | 13 +++++++++ lib/cassandrax/keyspace/schema.ex | 34 +++++++++++++++++++++++ lib/cassandrax/schema.ex | 1 + test/cassandrax/connection_test.exs | 15 +++++++++++ test/cassandrax/keyspace_test.exs | 42 +++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+) diff --git a/lib/cassandrax/connection.ex b/lib/cassandrax/connection.ex index f252ca0..6813f55 100644 --- a/lib/cassandrax/connection.ex +++ b/lib/cassandrax/connection.ex @@ -118,6 +118,8 @@ defmodule Cassandrax.Connection do ["DELETE FROM ", quote_table(keyspace, table), " WHERE " | filters] end + def truncate(keyspace, table), do: ["TRUNCATE TABLE ", quote_table(keyspace, table)] + defp assemble_filters(filters) do intersperse_map(filters, " AND ", fn field -> field = field |> Atom.to_string() |> quote_name() diff --git a/lib/cassandrax/keyspace.ex b/lib/cassandrax/keyspace.ex index 0e17659..f24f817 100644 --- a/lib/cassandrax/keyspace.ex +++ b/lib/cassandrax/keyspace.ex @@ -95,6 +95,9 @@ defmodule Cassandrax.Keyspace do def delete!(struct, opts \\ []), do: Cassandrax.Keyspace.Schema.delete!(__MODULE__, struct, opts) + def truncate(module_or_table) when is_atom(module_or_table), + do: Cassandrax.Keyspace.Schema.truncate(__MODULE__, module_or_table) + ## Queryable def all(queryable, opts \\ []), @@ -256,6 +259,16 @@ defmodule Cassandrax.Keyspace do opts :: Keyword.t() ) :: Cassandrax.Schema.t() + @doc """ + Truncates a schema. + + ## Example + ``` + MyKeyspace.truncate(User) + ``` + """ + @callback truncate(module() | atom()) :: :ok + @doc """ Fetches all entries from the data store that matches the given query. diff --git a/lib/cassandrax/keyspace/schema.ex b/lib/cassandrax/keyspace/schema.ex index a2cce3e..901649d 100644 --- a/lib/cassandrax/keyspace/schema.ex +++ b/lib/cassandrax/keyspace/schema.ex @@ -53,6 +53,22 @@ defmodule Cassandrax.Keyspace.Schema do end end + @doc """ + Implementation for `Cassandrax.Keyspace.truncate/2`. + """ + def truncate(keyspace, schema_module_or_table_name) do + conn = keyspace.__conn__ + keyspace_name = keyspace.__keyspace__ + + with {:ok, table} <- table_name_from_module_or_atom(schema_module_or_table_name), + statement = Cassandrax.Connection.truncate(keyspace_name, table), + {:ok, results} <- Cassandrax.cql(conn, statement) do + {:ok, results} + else + {:error, error} -> {:error, error} + end + end + @doc """ Implementation for `Cassandrax.Keyspace.insert/2`. """ @@ -258,4 +274,22 @@ defmodule Cassandrax.Keyspace.Schema do }} end end + + defp table_name_from_module_or_atom(module_or_atom) do + is_module = function_exported?(module_or_atom, :__info__, 1) + + table = + if is_module do + is_cassandrax_schema = + Keyword.has_key?(module_or_atom.__info__(:functions), :__cassandrax_source__) + + if is_cassandrax_schema do + {:ok, module_or_atom.__cassandrax_source__} + else + {:error, "module #{module_or_atom} does not use Cassandrax.Schema"} + end + else + {:ok, module_or_atom} + end + end end diff --git a/lib/cassandrax/schema.ex b/lib/cassandrax/schema.ex index ffba7c9..a14e2a1 100644 --- a/lib/cassandrax/schema.ex +++ b/lib/cassandrax/schema.ex @@ -68,6 +68,7 @@ defmodule Cassandrax.Schema do def __schema__(:queryable), do: %Cassandrax.Query{from: unquote(source), schema: __MODULE__} def __schema__(:pk), do: @primary_key + def __cassandrax_source__, do: unquote(source) # Set it to false to bypass Ecto primary_key verification @primary_key false diff --git a/test/cassandrax/connection_test.exs b/test/cassandrax/connection_test.exs index 37dedfe..29ead00 100644 --- a/test/cassandrax/connection_test.exs +++ b/test/cassandrax/connection_test.exs @@ -42,6 +42,7 @@ defmodule Cassandrax.ConnectionTest do end defp to_string({iodata, _values}), do: IO.iodata_to_binary(iodata) + defp to_string(iodata), do: IO.iodata_to_binary(iodata) defp all(queryable), do: Cassandrax.Connection.all(TestKeyspace, queryable) |> to_string() describe "all/2" do @@ -152,4 +153,18 @@ defmodule Cassandrax.ConnectionTest do assert all(queryable) =~ ~r/WHERE \("id" < \?\)/ end end + + defp truncate(queryable) do + changeset = Ecto.Changeset.change(queryable.__struct__) + schema = changeset.data.__struct__ + keyspace_name = TestKeyspace.__keyspace__() + table = schema.__schema__(:source) + Cassandrax.Connection.truncate(keyspace_name, table) |> to_string() + end + + describe "truncate" do + test "successfully generate a truncate clause when passed a schema" do + assert truncate(TestSchema) =~ ~r/TRUNCATE TABLE "test_keyspace"."my_table"/ + end + end end diff --git a/test/cassandrax/keyspace_test.exs b/test/cassandrax/keyspace_test.exs index 5504178..eaf3464 100644 --- a/test/cassandrax/keyspace_test.exs +++ b/test/cassandrax/keyspace_test.exs @@ -542,6 +542,48 @@ defmodule Cassandrax.KeyspaceTest do end end + describe "truncate" do + setup do + [ + first: fixture(TestData, id: "0", timestamp: "00:00", data: "0"), + second: + fixture(TestData, + id: "1", + timestamp: "01:00", + data: "1", + svalue: MapSet.new(["one", "another one"]) + ) + ] + end + + test "truncates all records on the table when passed module that uses Cassandrax.Schema" do + assert Enum.count(TestKeyspace.all(TestData)) == 2 + assert {:ok, %Xandra.Void{}} = TestKeyspace.truncate(TestData) + assert Enum.count(TestKeyspace.all(TestData)) == 0 + end + + test "truncates all records on the table when passed valid table name" do + assert Enum.count(TestKeyspace.all(TestData)) == 2 + assert {:ok, %Xandra.Void{}} = TestKeyspace.truncate(:test_data) + assert Enum.count(TestKeyspace.all(TestData)) == 0 + end + + test "returns error when passed invalid module" do + assert {:error, "module Elixir.String does not use Cassandrax.Schema"} = + TestKeyspace.truncate(String) + end + + test "returns error when passed invalid table name" do + assert {:error, + %Xandra.Error{ + __exception__: true, + message: "table test_data2 does not exist", + reason: :invalid, + warnings: [] + }} = TestKeyspace.truncate(:test_data2) + end + end + describe "cql" do setup do [