Skip to content

Commit

Permalink
Feat: introduce live_query method.
Browse files Browse the repository at this point in the history
Problem: 
- currently it's very cumbersome to handle live query callbacks

Solution: 
- Add a functions that combines: 
  1. sending a live query to Surreal
  2. registering the live query in the socket state
  3. registering the callback function on live query updates
  • Loading branch information
mindreframer committed Nov 10, 2023
1 parent 3e16169 commit 66ebe3b
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 7 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ Code foundation was taken from https://github.com/joojscript/surrealdb_ex. Since

## Todo

- [x] handle live query updates properly
- [x] debug modus with verbose logging
- [x] integration tests
- [ ] handle disconnects gracefully
- [ ] handle live query updates properly
- [ ] debug modus with verbose logging
- [ ] integration tests
- [ ] benchmarks
10 changes: 10 additions & 0 deletions lib/surrealix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ defmodule Surrealix do
defdelegate start_link(opts \\ []), to: Socket
defdelegate stop(pid), to: Socket

@doc """
Convenience method, that combines sending an query (live_query) and registering a callback
Params:
sql: string
vars: map with variables to interpolate into SQL
callback: fn (event, data, config)
"""
defdelegate live_query(pid, sql, vars \\ %{}, callback), to: Socket

@doc """
ping
This method pings the SurrealDB instance
Expand Down
30 changes: 26 additions & 4 deletions lib/surrealix/socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ defmodule Surrealix.Socket do
exit(:normal)
end

def handle_cast({:register_lq, sql, query_id}, state) do
state = SocketState.add_lq(state, sql, query_id)
{:ok, state}
end

def handle_cast({method, args, id, task}, state) do
Logger.debug("[surrealix] [handle_cast] #{inspect(state)}")

payload = build_cast_payload(method, args, id)
state = SocketState.add_task(state, id, task)
frame = {:text, payload}
Expand All @@ -61,7 +65,7 @@ defmodule Surrealix.Socket do
task = SocketState.get_task(state, id)

if is_nil(task) do
# there is no registered task for this ID, it must be a live query update!
# No registered task for this ID, must be a live query update
lq_id = get_in(json, ["result", "id"])
Surrealix.Dispatch.execute([:live_query, lq_id], json)
else
Expand All @@ -70,8 +74,7 @@ defmodule Surrealix.Socket do
end
end

state = SocketState.delete_task(state, id)
{:ok, state}
{:ok, SocketState.delete_task(state, id)}
end

defp exec_method(pid, {method, args}, opts \\ []) do
Expand Down Expand Up @@ -142,6 +145,25 @@ defmodule Surrealix.Socket do
|> Jason.encode!()
end

@doc """
Convenience method that combines sending a (live-)query and registering a callback.
Params:
sql: string
vars: map with variables to interpolate into SQL
callback: fn (event, data, config)
"""
@spec live_query(pid(), String.t(), map(), (any, any, list() -> any)) :: :ok
def live_query(pid, sql, vars \\ %{}, callback) do
# TODO: check if SQL somewhat resembles a live query
with {:ok, res} <- query(pid, sql, vars),
%{"result" => [%{"result" => lq_id}]} <- res do
event = [:live_query, lq_id]
:ok = Surrealix.Dispatch.attach("#{lq_id}_main", event, callback)
WebSockex.cast(pid, {:register_lq, sql, lq_id})
end
end

### API METHODS : START ###
@doc """
ping
Expand Down
52 changes: 52 additions & 0 deletions lib/surrealix_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,56 @@ defmodule Surrealix.Test do
assert res == [%{"id" => "user:marcus", "name" => "Marcus Aurelius - 3", "age" => 44}]
end
end

describe "live_query" do
setup [:setup_surrealix]

test "callbacks are properly executed", %{pid: pid} do
testpid = self()

Surrealix.live_query(pid, "LIVE SELECT * FROM user;", fn _event, data, _config ->
send(testpid, {:lq, data})
end)

Surrealix.insert(pid, "user", %{id: "marcus", name: "Marcus Aurelius"})
assert_receive {:lq, data}

%{
"result" => %{
"action" => "CREATE",
"id" => _lq_id,
"result" => %{"id" => "user:marcus", "name" => "Marcus Aurelius"}
}
} = data

res = Surrealix.query(pid, "select * from user:marcus") |> extract_res(0)
assert res == [%{"id" => "user:marcus", "name" => "Marcus Aurelius"}]

Surrealix.merge(pid, "user:marcus", %{age: 44})
assert_receive {:lq, data2}

%{
"result" => %{
"action" => "UPDATE",
"result" => %{"age" => 44, "id" => "user:marcus", "name" => "Marcus Aurelius"}
}
} = data2

res = Surrealix.query(pid, "select * from user:marcus") |> extract_res(0)
assert res == [%{"id" => "user:marcus", "name" => "Marcus Aurelius", "age" => 44}]

Surrealix.delete(pid, "user:marcus")
assert_receive {:lq, data3}

%{
"result" => %{
"action" => "DELETE",
"result" => "user:marcus"
}
} = data3

res = Surrealix.query(pid, "select * from user:marcus") |> extract_res(0)
assert res == []
end
end
end

0 comments on commit 66ebe3b

Please sign in to comment.