Current section
20 Versions
Jump to
Current section
20 Versions
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…