Skip to content

Commit 5e61538

Browse files
committed
Add Fakegres for benchmarking
1 parent 86c6116 commit 5e61538

File tree

10 files changed

+977
-35
lines changed

10 files changed

+977
-35
lines changed

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ cd ..
4141

4242
These commands should pass before committing. Warnings or noise in test is not acceptable.
4343

44+
## `mix test`
45+
46+
- For fast feedback, run `mix test --exclude unboxed`. This skips synchronous tests. Then run `mix test` at the end to validate your work.
47+
- New tests that you create MUST NOT generate extra logs, even if the tests pass.
48+
4449
## Using jj workspaces for isolated work
4550

4651
When working on a feature or fix, you can create an isolated workspace using jj:

lib/sequin/circular_buffer.ex

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# From https://github.com/elixir-toniq/circular_buffer/blob/main/lib/circular_buffer.ex
2+
3+
# SPDX-FileCopyrightText: 2019 Chris Keathley
4+
# SPDX-FileCopyrightText: 2020 Frank Hunleth
5+
# SPDX-FileCopyrightText: 2022 Milton Mazzarri
6+
#
7+
# SPDX-License-Identifier: MIT
8+
#
9+
defmodule Sequin.CircularBuffer do
10+
@moduledoc """
11+
Circular Buffer
12+
13+
When creating a circular buffer you must specify the max size:
14+
15+
```
16+
cb = CircularBuffer.new(10)
17+
```
18+
19+
CircularBuffers are implemented as Okasaki queues like Erlang's `:queue`
20+
module, but with additional optimizations thanks to the reduced set
21+
of operations.
22+
23+
CircularBuffer implements both the
24+
[`Enumerable`](https://hexdocs.pm/elixir/Enumerable.html) and
25+
[`Collectable`](https://hexdocs.pm/elixir/Collectable.html) protocols, so code
26+
like the following works:
27+
28+
iex> cb = Enum.into([1, 2, 3, 4], CircularBuffer.new(3))
29+
#CircularBuffer<[2, 3, 4]>
30+
iex> Enum.map(cb, fn x -> x * 2 end)
31+
[4, 6, 8]
32+
"""
33+
34+
alias __MODULE__, as: CB
35+
36+
defstruct [:a, :b, :max_size, :count]
37+
@typedoc "A circular buffer"
38+
@opaque t() :: %__MODULE__{
39+
a: list(),
40+
b: list(),
41+
max_size: pos_integer(),
42+
count: non_neg_integer()
43+
}
44+
45+
@doc """
46+
Creates a new circular buffer with a given size.
47+
"""
48+
@spec new(pos_integer()) :: t()
49+
def new(size) when is_integer(size) and size > 0 do
50+
%CB{a: [], b: [], max_size: size, count: 0}
51+
end
52+
53+
@doc """
54+
Inserts a new item into the next location of the circular buffer
55+
"""
56+
@spec insert(t(), any()) :: t()
57+
def insert(%CB{b: b} = cb, item) when b != [] do
58+
%{cb | a: [item | cb.a], b: tl(b)}
59+
end
60+
61+
def insert(%CB{count: count, max_size: max_size} = cb, item) when count < max_size do
62+
%{cb | a: [item | cb.a], count: cb.count + 1}
63+
end
64+
65+
def insert(%CB{b: []} = cb, item) do
66+
new_b = cb.a |> Enum.reverse() |> tl()
67+
%{cb | a: [item], b: new_b}
68+
end
69+
70+
@doc """
71+
Converts a circular buffer to a list. The list is ordered from oldest to newest
72+
elements based on their insertion order.
73+
"""
74+
@spec to_list(t()) :: list()
75+
def to_list(%CB{} = cb) do
76+
cb.b ++ Enum.reverse(cb.a)
77+
end
78+
79+
@doc """
80+
Returns the newest element in the buffer
81+
82+
## Examples
83+
84+
iex> cb = CircularBuffer.new(3)
85+
iex> CircularBuffer.newest(cb)
86+
nil
87+
iex> cb = Enum.reduce(1..4, cb, fn n, cb -> CircularBuffer.insert(cb, n) end)
88+
iex> CircularBuffer.newest(cb)
89+
4
90+
91+
"""
92+
@spec newest(t()) :: any()
93+
def newest(%CB{a: [newest | _rest]}), do: newest
94+
def newest(%CB{b: []}), do: nil
95+
96+
@doc """
97+
Returns the oldest element in the buffer
98+
"""
99+
@spec oldest(t()) :: any()
100+
def oldest(%CB{b: [oldest | _rest]}), do: oldest
101+
def oldest(%CB{a: a}), do: List.last(a)
102+
103+
@doc """
104+
Checks the buffer to see if its empty
105+
106+
Returns `true` if the given circular buffer is empty, otherwise `false`.
107+
108+
## Examples
109+
110+
iex> cb = CircularBuffer.new(1)
111+
iex> CircularBuffer.empty?(cb)
112+
true
113+
iex> cb |> CircularBuffer.insert(1) |> CircularBuffer.empty?()
114+
false
115+
116+
"""
117+
@spec empty?(t()) :: boolean()
118+
def empty?(%CB{} = cb) do
119+
cb.count == 0
120+
end
121+
122+
defimpl Enumerable do
123+
def count(cb) do
124+
{:ok, cb.count}
125+
end
126+
127+
def member?(cb, element) do
128+
{:ok, Enum.member?(cb.a, element) or Enum.member?(cb.b, element)}
129+
end
130+
131+
def reduce(cb, acc, fun) do
132+
Enumerable.List.reduce(CB.to_list(cb), acc, fun)
133+
end
134+
135+
def slice(_cb) do
136+
{:error, __MODULE__}
137+
end
138+
end
139+
140+
defimpl Collectable do
141+
def into(original) do
142+
collector_fn = fn
143+
cb, {:cont, elem} -> CB.insert(cb, elem)
144+
cb, :done -> cb
145+
_cb, :halt -> :ok
146+
end
147+
148+
{original, collector_fn}
149+
end
150+
end
151+
152+
defimpl Inspect do
153+
import Inspect.Algebra
154+
155+
def inspect(cb, opts) do
156+
concat(["#CircularBuffer<", to_doc(CB.to_list(cb), opts), ">"])
157+
end
158+
end
159+
end

lib/sequin/postgres/backend.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Sequin.Postgres.Backend do
77
- FakegresBackend: Fake backend for benchmarking
88
"""
99

10-
@type state :: term()
10+
@type state :: Postgrex.Protocol.state() | Sequin.Postgres.FakegresBackend.State.t()
1111
@type copies :: [binary()]
1212

1313
@callback connect(opts :: keyword()) :: {:ok, state()} | {:error, term()}

0 commit comments

Comments
 (0)