Skip to content

Commit 009474d

Browse files
authored
feat: add an option to deparse a query from AST (#5)
1 parent ffcb734 commit 009474d

File tree

4 files changed

+79
-3
lines changed

4 files changed

+79
-3
lines changed

c_src/libpg_query_ex.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,46 @@ static ERL_NIF_TERM parse_query(ErlNifEnv *env, int argc,
7676
}
7777
}
7878

79-
static ErlNifFunc funcs[] = {{"parse_query", 1, parse_query}};
79+
static ERL_NIF_TERM deparse_query(ErlNifEnv *env, int argc,
80+
const ERL_NIF_TERM argv[]) {
81+
ErlNifBinary proto;
82+
ERL_NIF_TERM term;
83+
84+
if (argc == 1 && enif_inspect_binary(env, argv[0], &proto)) {
85+
PgQueryProtobuf parse_tree;
86+
parse_tree.data = (char *)proto.data;
87+
parse_tree.len = proto.size;
88+
89+
PgQueryDeparseResult result = pg_query_deparse_protobuf(parse_tree);
90+
91+
if (result.error) {
92+
ERL_NIF_TERM error_map = enif_make_new_map(env);
93+
94+
if (!enif_make_map_put(
95+
env,
96+
error_map,
97+
enif_make_atom(env, "message"),
98+
make_binary(env, result.error->message),
99+
&error_map
100+
)) {
101+
return enif_raise_exception(env, make_binary(env, "failed to update map"));
102+
}
103+
104+
term = enif_make_tuple2(env, enif_make_atom(env, "error"), error_map);
105+
} else {
106+
term = result_tuple(env, "ok", result.query, strlen(result.query));
107+
}
108+
pg_query_free_deparse_result(result);
109+
110+
return term;
111+
} else {
112+
return enif_make_badarg(env);
113+
}
114+
}
115+
116+
static ErlNifFunc funcs[] = {
117+
{"parse_query", 1, parse_query},
118+
{"deparse_query", 1, deparse_query}
119+
};
80120

81121
ERL_NIF_INIT(Elixir.PgQuery.Parser, funcs, NULL, NULL, NULL, NULL)

lib/pg_query.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,16 @@ defmodule PgQuery do
1313
"""
1414
@spec parse(String.t()) :: {:ok, %PgQuery.ParseResult{}} | {:error, error()}
1515
defdelegate parse(stmt), to: PgQuery.Parser
16+
17+
@doc """
18+
Deparses the Protobuf AST `parse_result` into a query string.
19+
"""
20+
@spec protobuf_to_query(%PgQuery.ParseResult{}) :: {:ok, String.t()} | {:error, error()}
21+
defdelegate protobuf_to_query(parse_result), to: PgQuery.Parser
22+
23+
@doc """
24+
Deparses the Protobuf AST `parse_result` into a query string.
25+
"""
26+
@spec protobuf_to_query!(%PgQuery.ParseResult{}) :: String.t() | no_return()
27+
defdelegate protobuf_to_query!(parse_result), to: PgQuery.Parser
1628
end

lib/pg_query/parser.ex

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,27 @@ defmodule PgQuery.Parser do
2727
{:ok, proto} ->
2828
Protox.decode!(proto, PgQuery.ParseResult)
2929

30-
{:error, reason} ->
31-
raise Error, message: reason
30+
{:error, %{message: message}} ->
31+
raise Error, message: message
32+
end
33+
end
34+
35+
def protobuf_to_query(%PgQuery.ParseResult{} = parse_result) do
36+
with {:ok, encoded} <- Protox.encode(parse_result) do
37+
deparse_query(IO.iodata_to_binary(encoded))
38+
end
39+
end
40+
41+
def protobuf_to_query!(%PgQuery.ParseResult{} = parse_result) do
42+
case protobuf_to_query(parse_result) do
43+
{:ok, query} ->
44+
query
45+
46+
{:error, %{message: message}} ->
47+
raise Error, message: message
3248
end
3349
end
3450

3551
def parse_query(_query), do: :erlang.nif_error(:nif_not_loaded)
52+
def deparse_query(_encoded_proto), do: :erlang.nif_error(:nif_not_loaded)
3653
end

test/pg_query_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,11 @@ defmodule PgQueryTest do
3232

3333
assert binary_part(query, cursorpos, 4) == "snot"
3434
end
35+
36+
test "parses and deparses a query" do
37+
query = "CREATE TABLE a (id int8 PRIMARY KEY)"
38+
assert {:ok, ast} = PgQuery.parse(query)
39+
assert {:ok, query2} = PgQuery.protobuf_to_query(ast)
40+
assert query == query2
41+
end
3542
end

0 commit comments

Comments
 (0)