Skip to content

Commit 3ef95f5

Browse files
committed
v0.4.0 unsorted mode
1 parent 44e1532 commit 3ef95f5

14 files changed

Lines changed: 88 additions & 91 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic
77
Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [0.4.0] - 2023-02-27
10+
11+
### Changed
12+
- `fields` list was optional, but the behavior when none declared has changed.
13+
- Before, the `id_key` (default `:id`) was automatically used in the
14+
background. This meant that presorted indexes (ascending and descending)
15+
were auto-maintained and used as the default when calling `get_records` etc.
16+
- Now, no such presorted indexes will be maintained. When querying,
17+
`:ets.tab_to_list/2` will be used, with no particular order being promised.
18+
19+
## [0.3.3] - 2023-02-07
20+
21+
### Changed
22+
- Minor dialyzer fix.
23+
924
## [0.3.2] - 2023-02-03
1025

1126
### Added

lib/indexed.ex

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Indexed do
88
@moduledoc """
99
Tools for creating an index.
1010
"""
11-
alias Indexed.View
11+
alias Indexed.{Entity, View}
1212
alias __MODULE__
1313

1414
# Baseline opts. Others such as :named_table may be added.
@@ -21,7 +21,7 @@ defmodule Indexed do
2121
* `:index_ref` - ETS table reference for all indexed data.
2222
"""
2323
@type t :: %Indexed{
24-
entities: %{atom => Indexed.Entity.t()},
24+
entities: %{atom => Entity.t()},
2525
index_ref: table_ref
2626
}
2727

@@ -83,7 +83,7 @@ defmodule Indexed do
8383
@doc "Get a record by id from the index."
8484
@spec get(t, atom, id, any) :: any
8585
def get(index, entity_name, id, default \\ nil) do
86-
case :ets.lookup(Map.fetch!(index.entities, entity_name).ref, id) do
86+
case :ets.lookup(ref(index, entity_name), id) do
8787
[{_, val}] -> val
8888
[] -> default
8989
end
@@ -170,17 +170,17 @@ defmodule Indexed do
170170
sub-section of the data should be queried. Default is `nil` - no prefilter.
171171
"""
172172
@spec get_records(t, atom, prefilter, order_hint | nil) :: [record]
173-
def get_records(index, entity_name, prefilter, order_hint \\ nil) do
174-
default_order_hint = fn ->
175-
path = [Access.key(:entities), entity_name, Access.key(:fields)]
176-
index |> get_in(path) |> hd() |> elem(0)
173+
def get_records(index, entity_name, prefilter \\ nil, order_hint \\ nil) do
174+
if ord = order_hint || default_order_hint(index, entity_name) do
175+
index
176+
|> get_index(entity_name, prefilter, ord)
177+
|> Enum.map(&get(index, entity_name, &1))
178+
else
179+
index
180+
|> ref(entity_name)
181+
|> :ets.tab2list()
182+
|> Enum.map(&elem(&1, 1))
177183
end
178-
179-
order_hint = order_hint || default_order_hint.()
180-
181-
index
182-
|> get_index(entity_name, prefilter, order_hint)
183-
|> Enum.map(&get(index, entity_name, &1))
184184
end
185185

186186
@doc "Cache key for a given entity, field and direction."
@@ -243,12 +243,17 @@ defmodule Indexed do
243243

244244
@doc """
245245
Get the name of the first indexed field for an entity.
246+
If none, return `nil`, meaning there are no ordering indexes.
246247
Good order_hint default.
247248
"""
248249
@spec default_order_hint(t, atom) :: atom
249250
def default_order_hint(index, entity_name) do
250-
k = &Access.key(&1)
251-
index |> get_in([k.(:entities), entity_name, k.(:fields)]) |> hd() |> elem(0)
251+
path = [Access.key(:entities), entity_name, Access.key(:fields)]
252+
253+
case get_in(index, path) do
254+
[{name, _} | _] -> name
255+
_ -> nil
256+
end
252257
end
253258

254259
@doc "Delete all ETS tables associated with the given index."

lib/indexed/actions/create_view.ex

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ defmodule Indexed.Actions.CreateView do
44
"""
55
alias Indexed.Actions.Warm
66
alias Indexed.View
7-
require Logger
87

98
@typep id :: any
109

@@ -29,8 +28,6 @@ defmodule Indexed.Actions.CreateView do
2928
"""
3029
@spec run(Indexed.t(), atom, View.fingerprint(), keyword) :: {:ok, View.t()} | :error
3130
def run(index, entity_name, fingerprint, opts \\ []) do
32-
Logger.debug("Creating #{entity_name} view: #{fingerprint}")
33-
3431
entity = Map.fetch!(index.entities, entity_name)
3532
prefilter = opts[:prefilter]
3633

@@ -113,8 +110,6 @@ defmodule Indexed.Actions.CreateView do
113110
map_key = Indexed.uniques_map_key(entity_name, fingerprint, field_name)
114111
list_key = Indexed.uniques_list_key(entity_name, fingerprint, field_name)
115112

116-
Logger.debug(" * Saving #{field_name} uniques with #{map_size(counts_map)} values.")
117-
118113
:ets.insert(index.index_ref, {map_key, counts_map})
119114
:ets.insert(index.index_ref, {list_key, list})
120115
end
@@ -141,8 +136,6 @@ defmodule Indexed.Actions.CreateView do
141136
asc_key = Indexed.index_key(entity_name, fingerprint, {:asc, field_name})
142137
desc_key = Indexed.index_key(entity_name, fingerprint, {:desc, field_name})
143138

144-
Logger.debug(" * Saving #{field_name} index with #{length(sorted_ids)} ids.")
145-
146139
:ets.insert(index.index_ref, {asc_key, sorted_ids})
147140
:ets.insert(index.index_ref, {desc_key, Enum.reverse(sorted_ids)})
148141
end

lib/indexed/actions/put.ex

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ defmodule Indexed.Actions.Put do
33
import Indexed.Helpers, only: [add_to_lookup: 4, id: 1]
44
alias Indexed.{Entity, UniquesBundle, View}
55
alias __MODULE__
6-
require Logger
76

87
defstruct [:current_view, :entity_name, :index, :previous, :pubsub, :record]
98

109
@typedoc """
11-
* `:current_view` - View struct currently being updated.
12-
* `:entity_name` - Entity name being operated on.
13-
* `:index` - See `t:Indexed.t/0`.
14-
* `:previous` - The previous version of the record. `nil` if none.
15-
* `:pubsub` - If configured, a Phoenix.PubSub module to send view updates.
16-
* `:record` - The new record being added in the put operation.
10+
- `current_view` : View struct currently being updated.
11+
- `entity_name` : Entity name being operated on.
12+
- `index` : See `t:Indexed.t/0`.
13+
- `previous` : The previous version of the record. `nil` if none.
14+
- `pubsub` : If configured, a Phoenix.PubSub module to send view updates.
15+
- `record` : The new record being added in the put operation.
1716
"""
18-
@type t :: %__MODULE__{
17+
@type t :: %Put{
1918
current_view: View.t() | nil,
2019
entity_name: atom,
2120
index: Indexed.t(),
@@ -48,8 +47,6 @@ defmodule Indexed.Actions.Put do
4847
end
4948

5049
defp do_run(%{entity_name: name, index: index} = put, id) do
51-
Logger.debug("Putting into #{put.entity_name}: id #{id}")
52-
5350
%{fields: fields, lookups: lookups, prefilters: prefilters, ref: ref} =
5451
Map.fetch!(index.entities, name)
5552

@@ -63,8 +60,6 @@ defmodule Indexed.Actions.Put do
6360
update_all_uniques(put, pf_opts[:maintain_unique] || [], nil, false)
6461

6562
{pf_key, pf_opts} ->
66-
Logger.debug("--> Getting UB for #{name} prefilter nil, field: #{pf_key}")
67-
6863
handle_prefilter_value = fn pnb, value, new_value? ->
6964
prefilter = {pf_key, value}
7065
update_index_for_fields(put, prefilter, fields, new_value?)
@@ -199,8 +194,6 @@ defmodule Indexed.Actions.Put do
199194
%{previous: previous, record: record} = put
200195

201196
Enum.each(fields, fn {field_name, _} = field ->
202-
Logger.debug("--> Updating index for PF #{inspect(prefilter)}, field: #{field_name}")
203-
204197
this_under_pf = under_prefilter?(put, record, prefilter)
205198
prev_under_pf = previous && under_prefilter?(put, previous, prefilter)
206199
record_value = Map.get(record, field_name)

lib/indexed/actions/warm.ex

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ defmodule Indexed.Actions.Warm do
33
import Indexed.Helpers, only: [add_to_lookup: 4, id: 2]
44
alias Indexed.{Entity, UniquesBundle}
55
alias __MODULE__
6-
require Logger
76

87
defstruct [:data_tuple, :entity_name, :id_key, :index_ref]
98

@@ -55,9 +54,10 @@ defmodule Indexed.Actions.Warm do
5554
as keys and keyword lists of options are values. Allowed options are as
5655
follows:
5756
58-
* `:fields` - List of field name atoms to index by. At least one required.
57+
* `:fields` - List of field name atoms to index by.
5958
* If field is a DateTime, use sort: `{:my_field, sort: :datetime}`.
6059
* Ascending and descending will be indexed for each field.
60+
* If none, sorted results will not be available.
6161
* `:id_key` - Primary key to use in indexes and for accessing the records of
6262
this entity. See `t:Indexed.Entity.t/0`. Default: `:id`.
6363
* `:lookups` - See `t:Indexed.Entity.t/0`.
@@ -145,13 +145,11 @@ defmodule Indexed.Actions.Warm do
145145
index_ref: index.index_ref
146146
}
147147

148-
Logger.debug("Warming #{entity_name}...")
149-
150148
# Create and insert the caches for this entity: for each prefilter
151149
# configured, build & store indexes for each indexed field.
152-
# Internally, a `t:prefilter/0` refers to a `{:my_field, "possible
153-
# value"}` tuple or `nil` which we implicitly include, where no
154-
# prefilter is applied.
150+
# Internally, a `t:prefilter/0` refers to a `{:my_field, "some value"}`
151+
# tuple or `nil` which we implicitly include,
152+
# where no prefilter is applied.
155153
for prefilter_config <- entity.prefilters,
156154
do: warm_prefilter(warm, prefilter_config, entity.fields)
157155

@@ -190,11 +188,6 @@ defmodule Indexed.Actions.Warm do
190188
warm_sorted(warm, prefilter, field, data_tuple)
191189
end
192190

193-
Logger.debug("""
194-
* Putting index (PF #{pf_key || "nil"}) for \
195-
#{inspect(Enum.map(fields, &elem(&1, 0)))}\
196-
""")
197-
198191
if nil == pf_key do
199192
Enum.each(fields, &warm_sorted.(nil, &1, full_data))
200193

@@ -216,8 +209,6 @@ defmodule Indexed.Actions.Warm do
216209
bundle = UniquesBundle.new(counts_map, Enum.sort(Enum.uniq(list)))
217210
UniquesBundle.put(bundle, index_ref, entity_name, nil, pf_key, new?: true)
218211

219-
Logger.debug("--> Putting UB (for #{pf_key}) with #{map_size(counts_map)} elements.")
220-
221212
# For each value found for the prefilter, create a set of indexes.
222213
Enum.each(grouped, fn {pf_val, data} ->
223214
prefilter = {pf_key, pf_val}
@@ -276,21 +267,14 @@ defmodule Indexed.Actions.Warm do
276267
list = counts_map |> Map.keys() |> Enum.sort()
277268
bundle = UniquesBundle.new(counts_map, list)
278269

279-
Logger.debug("""
280-
--> Putting UB (PF #{inspect(prefilter)}, #{field_name}) \
281-
with #{map_size(counts_map)} elements."\
282-
""")
283-
284270
UniquesBundle.put(bundle, index_ref, entity_name, prefilter, field_name, new?: true)
285271
end)
286272
end
287273

288274
@doc "Normalize fields."
289-
@spec resolve_fields_opt([atom | Entity.field()], atom) :: [Entity.field()]
275+
@spec resolve_fields_opt([atom | Entity.field()] | nil, atom) :: [Entity.field()]
290276
def resolve_fields_opt(fields, _entity_name) do
291-
# match?([_ | _], fields) || raise "At least one field to index is required on #{entity_name}."
292-
293-
Enum.map(fields, fn
277+
Enum.map(List.wrap(fields), fn
294278
{_name, _opts} = f -> f
295279
name when is_atom(name) -> {name, []}
296280
end)

lib/indexed/entity.ex

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ defmodule Indexed.Entity do
44

55
@typedoc """
66
- `fields` : List of `t:field/0`s for which pairs of lists should be
7-
maintained with the ID sorted ascending and descending.
7+
maintained with the ID sorted ascending and descending. If none, then no
8+
such indexes will be maintained and queries will default to
9+
`order_hint: nil`, meaning that result ordering is not needed.
810
- `id_key` : Specifies how to find the id for a record. It can be an atom
911
field name to access, a function, or a tuple in the form `{module,
1012
function_name}`. In the latter two cases, the record will be passed in.
@@ -20,16 +22,16 @@ defmodule Indexed.Entity do
2022
unique values under the prefilter will be managed. These lists can be
2123
fetched via `Indexed.get_uniques_list/4` and
2224
`Indexed.get_uniques_map/4`.
23-
- `ref` : ETS table reference where records of this entity type are
24-
stored, keyed by id. This will be nil in the version compiled into a managed
25-
module.
25+
- `ref` : ETS table reference where records of
26+
this entity type are stored, keyed by id.
27+
(`nil` in the instances compiled into a managed module.)
2628
"""
2729
@type t :: %__MODULE__{
28-
fields: [field],
30+
fields: [field] | nil,
2931
id_key: any,
3032
lookups: [field_name],
3133
prefilters: [prefilter_config],
32-
ref: :ets.tid() | atom | nil
34+
ref: Indexed.table_ref() | nil
3335
}
3436

3537
@typedoc """

lib/indexed/managed.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ defmodule Indexed.Managed do
146146
"""
147147
@type t :: %Managed{
148148
children: children,
149-
fields: [atom | Entity.field()],
149+
fields: [atom | Entity.field()] | nil,
150150
id_key: id_key,
151151
lookups: [Entity.field_name()],
152152
query: (Ecto.Queryable.t() -> Ecto.Queryable.t()) | nil,

lib/indexed/managed/prepare.ex

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ defmodule Indexed.Managed.Prepare do
2222
manageds
2323
|> map_put.(:children, &do_rewrite_children/2)
2424
|> map_put.(:prefilters, &do_rewrite_prefilters/2)
25-
|> map_put.(:fields, &do_rewrite_fields/2)
2625
|> map_put.(:tracked, &do_rewrite_tracked/2)
2726
end
2827

@@ -84,16 +83,6 @@ defmodule Indexed.Managed.Prepare do
8483
else: finish.(required)
8584
end
8685

87-
# If :fields is empty, use the id key or the first field given by Ecto.
88-
@spec do_rewrite_fields(Managed.t(), [Managed.t()]) :: [atom | Entity.field()]
89-
defp do_rewrite_fields(%{fields: [], id_key: id_key}, _) when is_atom(id_key),
90-
do: [id_key]
91-
92-
defp do_rewrite_fields(%{fields: [], module: mod}, _),
93-
do: [hd(mod.__schema__(:fields))]
94-
95-
defp do_rewrite_fields(%{fields: fields}, _), do: fields
96-
9786
# Return true for tracked if another entity has a :one association to us.
9887
@spec do_rewrite_tracked(Managed.t(), [Managed.t()]) :: boolean
9988
defp do_rewrite_tracked(%{name: name}, manageds) do

lib/indexed/uniques_bundle.ex

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ defmodule Indexed.UniquesBundle do
99
of these values.
1010
"""
1111
alias __MODULE__
12-
require Logger
1312

1413
@typedoc """
1514
A 3-element tuple defines unique values under a prefilter:
@@ -66,11 +65,6 @@ defmodule Indexed.UniquesBundle do
6665
map = Indexed.get_uniques_map(index, entity_name, prefilter, field_name)
6766
list = Indexed.get_uniques_list(index, entity_name, prefilter, field_name)
6867

69-
Logger.debug(fn ->
70-
key = Indexed.uniques_map_key(entity_name, prefilter, field_name)
71-
"UB: Getting #{key}: #{inspect(map)}"
72-
end)
73-
7468
{map, list, [], false}
7569
end
7670

@@ -121,17 +115,13 @@ defmodule Indexed.UniquesBundle do
121115
field_pf? = is_tuple(prefilter)
122116

123117
if not new? and field_pf? and Enum.empty?(list) do
124-
Logger.debug("UB: Dropping #{counts_key}")
125-
126118
# This prefilter has ran out of records -- delete the ETS table.
127119
# Note that for views (binary prefilter) and the global prefilter (nil)
128120
# we allow the uniques to remain in the empty state. They go when the
129121
# view or entire index, respectively, is destroyed.
130122
:ets.delete(index_ref, list_key)
131123
:ets.delete(index_ref, counts_key)
132124
else
133-
Logger.debug("UB: Putting #{counts_key}: #{inspect(counts_map)}")
134-
135125
if new? or list_updated?,
136126
do: :ets.insert(index_ref, {list_key, list}),
137127
else: list

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Indexed.MixProject do
22
use Mix.Project
33

44
@source_url "https://github.com/djthread/indexed"
5-
@version "0.3.3"
5+
@version "0.3.4"
66

77
def project do
88
[

0 commit comments

Comments
 (0)