Current section

20 Versions

Jump to

Compare versions

7 files changed
+70 additions
-34 deletions
  @@ -1,5 +1,16 @@
1 1 # Changelog for v0.x
2 2
3 + ## v0.3.1 (2024-10-24)
4 +
5 + ### Bug fixes
6 +
7 + * Fixed consistency issue with index updates. Previously, the old index key was still queryable.
8 + * Fixed write amplification issue when updating struct's non-indexed fields.
9 +
10 + ### New Documentation
11 +
12 + * Added `fdb_api_counting_text.exs` which tests and documents the `:erlfdb` operations that our Layer is expected to make.
13 +
3 14 ## v0.3 (2024-10-20)
4 15
5 16 ### \*\* Major breaking changes \*\*
  @@ -12,7 +23,7 @@ to discuss the upgrade path.
12 23 ### Enhancements
13 24
14 25 * [Watches](Ecto.Adapters.FoundationDB.html#module-watches): Support for FDB Watches, which is like a database-driven PubSub on an Ecto struct.
15 - * [Directory Tenants](EctoFoundationDB.Tenant): A new default backend for Multitenancy that is production-ready. Managed tenants have been moved to "Experimental" status.
26 + * [Directory Tenants](EctoFoundationDB.Tenant.html): A new default backend for Multitenancy that is production-ready. Managed tenants have been moved to "Experimental" status.
16 27 * `@schema_context usetenant: true` is no longer required.
17 28 * The `:open_db` option now defines a 1-arity function that accepts the Repo module.
  @@ -2,7 +2,7 @@
2 2 [{<<"GitHub">>,
3 3 <<"https://github.com/foundationdb-beam/ecto_foundationdb">>}]}.
4 4 {<<"name">>,<<"ecto_foundationdb">>}.
5 - {<<"version">>,<<"0.3.0">>}.
5 + {<<"version">>,<<"0.3.1">>}.
6 6 {<<"description">>,<<"FoundationDB adapter for Ecto">>}.
7 7 {<<"elixir">>,<<"~> 1.15">>}.
8 8 {<<"app">>,<<"ecto_foundationdb">>}.
  @@ -19,10 +19,17 @@ defmodule EctoFoundationDB.Indexer do
19 19 {integer(), {:erlfdb.key(), :erlfdb.key()}}
20 20 @callback set(Tenant.t(), :erlfdb.transaction(), Index.t(), Ecto.Schema.t(), tuple()) :: :ok
21 21 @callback clear(Tenant.t(), :erlfdb.transaction(), Index.t(), Ecto.Schema.t(), tuple()) :: :ok
22 - @callback update(Tenant.t(), :erlfdb.transaction(), Index.t(), Ecto.Schema.t(), tuple()) :: :ok
22 + @callback update(
23 + Tenant.t(),
24 + :erlfdb.transaction(),
25 + Index.t(),
26 + Ecto.Schema.t(),
27 + tuple(),
28 + Keyword.t()
29 + ) :: :ok
23 30 @callback range(Index.t(), QueryPlan.t(), Keyword.t()) :: tuple()
24 31 @callback unpack(Index.t(), QueryPlan.t(), tuple()) :: tuple()
25 - @optional_callbacks update: 5, unpack: 3
32 + @optional_callbacks update: 6, unpack: 3
26 33
27 34 def create_range(tenant, idx),
28 35 do: idx[:indexer].create_range(tenant, idx)
  @@ -30,29 +37,29 @@ defmodule EctoFoundationDB.Indexer do
30 37 def create(tenant, tx, idx, schema, range, limit),
31 38 do: idx[:indexer].create(tenant, tx, idx, schema, range, limit)
32 39
33 - def set(tenant, tx, idxs, partial_idxs, schema, kv) do
34 - idxs = idxs ++ filter_partials(partial_idxs, kv, [])
40 + def set(tenant, tx, idxs, partial_idxs, schema, kv = {k, _}) do
41 + idxs = idxs ++ filter_partials(partial_idxs, k, [])
35 42
36 43 for idx <- idxs,
37 44 do: idx[:indexer].set(tenant, tx, idx, schema, kv)
38 45 end
39 46
40 - def clear(tenant, tx, idxs, partial_idxs, schema, kv) do
41 - idxs = idxs ++ filter_partials(partial_idxs, kv, [])
47 + def clear(tenant, tx, idxs, partial_idxs, schema, kv = {k, _}) do
48 + idxs = idxs ++ filter_partials(partial_idxs, k, [])
42 49
43 50 for idx <- idxs,
44 51 do: idx[:indexer].clear(tenant, tx, idx, schema, kv)
45 52 end
46 53
47 - def update(tenant, tx, idxs, partial_idxs, schema, kv) do
48 - idxs = idxs ++ filter_partials(partial_idxs, kv, [])
54 + def update(tenant, tx, idxs, partial_idxs, schema, kv = {k, _}, updates) do
55 + idxs = idxs ++ filter_partials(partial_idxs, k, [])
49 56
50 57 for idx <- idxs do
51 58 apply(
52 59 idx[:indexer],
53 60 :update,
54 - [tenant, tx, idx, schema, kv],
55 - &_update/5
61 + [tenant, tx, idx, schema, kv, updates],
62 + &_update/6
56 63 )
57 64 end
58 65 end
  @@ -73,8 +80,27 @@ defmodule EctoFoundationDB.Indexer do
73 80 defp _unpack(_idx, _plan, {{_pkey, _pvalue}, {_skeybegin, _skeyend}, []}),
74 81 do: nil
75 82
76 - defp _update(tenant, tx, idx, schema, kv) do
83 + defp _update(tenant, tx, idx, schema, kv, updates) do
84 + if Keyword.get(idx[:options], :mapped?, true) do
85 + index_fields = idx[:fields]
86 + set_data = updates[:set]
87 +
88 + x = MapSet.intersection(MapSet.new(Keyword.keys(set_data)), MapSet.new(index_fields))
89 +
90 + if MapSet.size(x) == 0 do
91 + :ok
92 + else
93 + __update(tenant, tx, idx, schema, kv, updates)
94 + end
95 + else
96 + __update(tenant, tx, idx, schema, kv, updates)
97 + end
98 + end
99 +
100 + defp __update(tenant, tx, idx, schema, kv = {k, v}, updates) do
101 + set_data = updates[:set]
77 102 idx[:indexer].clear(tenant, tx, idx, schema, kv)
103 + kv = {k, Keyword.merge(v, set_data)}
78 104 idx[:indexer].set(tenant, tx, idx, schema, kv)
79 105 end
80 106
  @@ -86,19 +112,19 @@ defmodule EctoFoundationDB.Indexer do
86 112 end
87 113 end
88 114
89 - defp filter_partials([], _kv, acc) do
115 + defp filter_partials([], _k, acc) do
90 116 Enum.reverse(acc)
91 117 end
92 118
93 119 defp filter_partials(
94 120 [{partial_idx, {start_key, cursor_key, _end_key}} | partial_idxs],
95 - kv = {fdb_key, _fdb_value},
121 + fdb_key,
96 122 acc
97 123 ) do
98 124 if fdb_key >= start_key and fdb_key < cursor_key do
99 - filter_partials(partial_idxs, kv, [partial_idx | acc])
125 + filter_partials(partial_idxs, fdb_key, [partial_idx | acc])
100 126 else
101 - filter_partials(partial_idxs, kv, acc)
127 + filter_partials(partial_idxs, fdb_key, acc)
102 128 end
103 129 end
104 130 end
  @@ -128,7 +128,6 @@ defmodule EctoFoundationDB.Indexer.Default do
128 128 @impl true
129 129 def set(tenant, tx, idx, schema, kv) do
130 130 {index_key, index_object} = get_index_entry(tenant, idx, schema, kv)
131 -
132 131 :erlfdb.set(tx, index_key, index_object)
133 132 :ok
134 133 end
  @@ -289,17 +289,17 @@ defmodule EctoFoundationDB.Layer.IndexInventory do
289 289 defp tx_idxs_get_wait(tenant, tx, _adapter_opts, source, max_version_future, claim_future) do
290 290 {start_key, end_key} = idx_range(tenant, source)
291 291
292 + idxs_future = :erlfdb.get_range(tx, start_key, end_key, wait: false)
293 +
294 + [max_version, claim, idxs] =
295 + :erlfdb.wait_for_all_interleaving(tx, [max_version_future, claim_future, idxs_future])
296 +
292 297 idxs =
293 - tx
294 - |> :erlfdb.get_range(start_key, end_key, wait: true)
298 + idxs
295 299 |> Enum.map(fn {_, fdb_value} -> Pack.from_fdb_value(fdb_value) end)
296 300 # high priority first
297 301 |> Enum.sort(&(Keyword.get(&1, :priority, 0) > Keyword.get(&2, :priority, 0)))
298 302
299 - max_version = :erlfdb.wait(max_version_future)
300 -
301 - claim = :erlfdb.wait(claim_future)
302 -
303 303 case get_partial_idxs(claim, source) do
304 304 {claim_active?, []} ->
305 305 {MaxValue.decode(max_version), idxs, [], fn -> not claim_active? end}
Loading more files…