Skip to content

Commit

Permalink
add_script_query_type
Browse files Browse the repository at this point in the history
  • Loading branch information
dkuku committed Nov 13, 2023
1 parent 02b671c commit 7b28bf5
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 22 deletions.
60 changes: 54 additions & 6 deletions lib/ayesql/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ defmodule AyeSQL.Compiler do
"""
@type fragments :: [fragment() | param()]

@typedoc """
Query type.
"""
@type type :: nil | atom()

@typedoc """
Query name.
"""
Expand All @@ -32,7 +37,7 @@ defmodule AyeSQL.Compiler do
@typedoc """
Query.
"""
@type query :: {name(), docs(), fragments()}
@type query :: {name(), type(), docs(), fragments()}

@typedoc """
Queries.
Expand Down Expand Up @@ -140,15 +145,18 @@ defmodule AyeSQL.Compiler do
@spec create_queries(queries(), [Macro.t()]) :: Macro.t() | [Macro.t()]
defp create_queries(queries, acc \\ [])

defp create_queries([{nil, nil, fragments}], _acc) do
defp create_queries([{nil, _any, nil, fragments}], _acc) do
create_single_query(fragments)
end

defp create_queries([], acc) do
Enum.reverse(acc)
end

defp create_queries([{_name, _docs, _fragments} = query | queries], acc) do
defp create_queries(
[{_name, _type, _docs, _fragments} = query | queries],
acc
) do
acc = [create_query!(query), create_query(query) | acc]

create_queries(queries, acc)
Expand Down Expand Up @@ -181,7 +189,47 @@ defmodule AyeSQL.Compiler do
end

@spec create_query(query()) :: Macro.t()
defp create_query({name, docs, fragments}) do
defp create_query({name, :script, docs, fragments}) do
fragments = Macro.escape(fragments)

quote do
@doc AyeSQL.Compiler.gen_docs(unquote(docs), unquote(fragments))
@spec unquote(name)(AyeSQL.Core.parameters()) ::
{:ok, AyeSQL.Query.t() | term()}
| {:error, AyeSQL.Error.t() | term()}
@spec unquote(name)(AyeSQL.Core.parameters(), AyeSQL.Core.options()) ::
{:ok, AyeSQL.Query.t() | term()}
| {:error, AyeSQL.Error.t() | term()}
def unquote(name)(params, options \\ [])

def unquote(name)(params, options) do
options = Keyword.merge(__MODULE__.__db_options__(), options)

{index, options} = Keyword.pop(options, :index, 1)
{run?, options} = Keyword.pop(options, :run, true)

content = AyeSQL.AST.expand(__MODULE__, unquote(fragments))
context = AyeSQL.AST.Context.new(index: index)

with {:ok, %{statement: statement} = query} <-
AyeSQL.Core.evaluate(content, params, context) do
if run? do
statement
|> String.split(";", trim: true)
|> Enum.map(&String.trim(&1))
|> Enum.each(&__MODULE__.run!(%{query | statement: &1}, options))

{:ok, []}
else
{:ok, query}
end
end
end
end
end

@spec create_query(query()) :: Macro.t()
defp create_query({name, :normal, docs, fragments}) do
fragments = Macro.escape(fragments)

quote do
Expand Down Expand Up @@ -215,7 +263,7 @@ defmodule AyeSQL.Compiler do
end

@spec create_query!(query()) :: Macro.t()
defp create_query!({name, docs, _}) do
defp create_query!({name, _type, docs, _}) do
name! = String.to_atom("#{name}!")

quote do
Expand Down Expand Up @@ -297,7 +345,7 @@ defmodule AyeSQL.Compiler do
no_return()
defp raise_error(
contents,
{line, _, ['syntax error before: ', info]},
{line, _, [~c"syntax error before: ", info]},
options
) do
error_context = options[:error_context] || 2
Expand Down
5 changes: 5 additions & 0 deletions src/ayesql_lexer.xrl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ NewLine = (\n|\r)+

FunName = {NewLine}*(\-\-)\sname\:\s[^\n\r]+
FunDocs = {NewLine}*(\-\-)\sdocs\:\s[^\n\r]+
FunType = {NewLine}*(\-\-)\stype\:\s[^\n\r]+
Comment = (\-\-)[^\n\r]+

Atom = [a-z_][0-9a-zA-Z_]*
Expand All @@ -26,6 +27,7 @@ Rules.

{FunName} : new_comment(TokenLine, TokenLen, TokenChars).
{FunDocs} : new_comment(TokenLine, TokenLen, TokenChars).
{FunType} : new_comment(TokenLine, TokenLen, TokenChars).

{NamedParam} : new_param(TokenLine, TokenLen, TokenChars).
({Comment}?|({String}|{Fragment})+) : new_fragment(TokenLine, TokenLen, TokenChars).
Expand Down Expand Up @@ -64,6 +66,9 @@ new_fragment(TokenLine, TokenLen, TokenChars) ->
new_comment(TokenLine, TokenLen, "-- name: " ++ Value = TokenChars) ->
Identifier = string:trim(Value),
new_token("name", Identifier, TokenChars, TokenLine, TokenLen);
new_comment(TokenLine, TokenLen, "-- type: " ++ Value = TokenChars) ->
Identifier = string:trim(Value),
new_token("type", Identifier, TokenChars, TokenLine, TokenLen);
new_comment(TokenLine, TokenLen, "-- docs: " ++ Value = TokenChars) ->
Documentation = string:trim(Value),
new_token("docs", Documentation, TokenChars, TokenLine, TokenLen);
Expand Down
47 changes: 31 additions & 16 deletions src/ayesql_parser.yrl
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
Nonterminals queries named_queries named_query fragments.
Terminals '$name' '$docs' '$fragment' '$named_param'.
Terminals '$name' '$type' '$docs' '$fragment' '$named_param'.
Rootsymbol queries.


queries -> fragments : [ {nil, nil, join_fragments('$1', [])} ].
queries -> fragments : [ {nil, normal, nil, join_fragments('$1', [])} ].
queries -> named_queries : '$1'.

named_queries -> named_query : [ '$1' ].
named_queries -> named_query named_queries : [ '$1' | '$2' ].

named_query -> '$name' fragments : {extract_value('$1'), nil, join_fragments('$2', [])}.
named_query -> '$name' '$docs' fragments : {extract_value('$1'), extract_value('$2'), join_fragments('$3', [])}.
named_query -> '$name' fragments
: {extract_name('$1'), normal, nil, join_fragments('$2', [])}.
named_query -> '$name' '$docs' fragments
: {extract_name('$1'), normal, extract_docs('$2'), join_fragments('$3', [])}.
named_query -> '$name' '$type' fragments
: {extract_name('$1'), extract_type('$2'), nil, join_fragments('$3', [])}.
named_query -> '$name' '$type' '$docs' fragments
: {extract_name('$1'), extract_type('$2'), extract_docs('$3'), join_fragments('$4', [])}.

fragments -> '$fragment' : [ extract_value('$1') ].
fragments -> '$named_param' : [ extract_value('$1') ].
fragments -> '$fragment' fragments : [ extract_value('$1') | '$2' ].
fragments -> '$named_param' fragments : [ extract_value('$1') | '$2' ].
fragments -> '$fragment' : [ extract_fragment('$1') ].
fragments -> '$fragment' fragments : [ extract_fragment('$1') | '$2' ].
fragments -> '$named_param' : [ extract_named('$1') ].
fragments -> '$named_param' fragments : [ extract_named('$1') | '$2' ].

Erlang code.

extract_value({'$name', _, {Value, _, _}}) ->
binary_to_atom(Value);
extract_value({'$docs', _, {<<>>, _, _}}) ->
extract_type({'$type', _, {<<>>, _, _}}) ->
normal;
extract_type({'$type', _, {Value, _, _}}) ->
binary_to_atom(Value).

extract_name({'$name', _, {Value, _, _}}) ->
Value1 = [X || <<X>> <= Value, not lists:member(X, "#")],
list_to_atom(Value1).

extract_docs({'$docs', _, {<<>>, _, _}}) ->
nil;
extract_value({'$docs', _, {Value, _, _}}) ->
Value;
extract_value({'$fragment', _, {Value, _, _}}) ->
Value;
extract_value({'$named_param', _, {Value, _, _}}) ->
extract_docs({'$docs', _, {Value, _, _}}) ->
Value.

extract_fragment({'$fragment', _, {Value, _, _}}) ->
Value.

extract_named({'$named_param', _, {Value, _, _}}) ->
binary_to_atom(Value).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down
10 changes: 10 additions & 0 deletions test/ayesql_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ defmodule AyeSQLTest do
end
end

describe "when query is a script" do
import AyeSQL, only: [defqueries: 3]

defqueries(Script, "support/script.sql", runner: TestRunner)

test "can expand query with empty params list" do
assert Script.set_schema([]) == {:ok, []}
end
end

describe "when query does not need parameters" do
import AyeSQL, only: [defqueries: 3]

Expand Down
5 changes: 5 additions & 0 deletions test/support/script.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Runs multiple sql statements
-- name: set_schema
-- type: script
CREATE SCHEMA schema1;
CREATE SCHEMA schema2;

0 comments on commit 7b28bf5

Please sign in to comment.