README.md

# erlquery

The erlquery parser and codegen library. Designed to be composed with other tools like rebar3 plugins.

erlquery parses .erlq files and generates corresponding erlang modules from them. The syntax is meant to look
like Erlang, but it allows writing queries without Erlang string literals. This is not only more aesthetically pleasing,
but it helps reduce annoying bugs when writing queries in erlang string literals. Since it is merely text it is database
agnostic out of the box.

Check out [rebar3_erlquery](https://git.sr.ht/~fancycade/rebar3_erlquery) for usage instructions.

## Build

    rebar3 compile
        
## Test

    rebar3 eunit

## Usage

Include in deps:

    erlquery

erlquery supports three modes depending on the special query attribute.

1. No query - This makes the methods simply return the query string.
2. Query with Arity 2 - Generated methods will have an arity of 0 and 1. 0 for no args, 1 for the args list.
3. Query with Arity 3 - Generated methods will have an arity of 1 and 2. 1 for the connection param, and 2 for connection + args list.

The examples should make this more clear what the differences are.

### No Query

Seemingly trivial, this use case makes it easy for erlquery to compose with other tools and libraries.

Make a file like this and name it example.erlq.

    -module(example).

    select_todos ->
        SELECT * FROM todos.

This file is parsed into a config like this:

    {ok, Config} = erlquery:parse("example.erlq").

Then we take that config and generate the forms for an erlang module:

    {ok, Forms} = erlquery:codegen(Config).

We can take these forms and plug them into erlang's native compile and code loading modules.

    {ok, example, Bin} = compile:forms(Forms),
    {module, example} = code:load_binary(example, "example.beam", Bin).

This will load the example module. Since there is no query attribute there will be two methods available.

    example:select_todos/0
    example:select_todos/1

Calling example:select_todos() will return <<"SELECT * FROM todos">>.

Calling example:select_todos with an args list or something has no side effect. It merely returns the query text.
This is allowed to make the api not as strict when integrating with external tools.

### Query with Arity 2

Make an erlq module like this:

    -module(example).

    -query(pgo:query/2).

    select_todos ->
        SELECT * FROM todos.

Notice the query attribute where we specify pgo:query/2. This means all queries will execute with that function.
pgo:query has an arity of 2 because the first argument is the query, and the second is for the args list.

The main difference from no query is that the select_todos methods will actually call pgo:query with the query and the args provided.

The generated erlang code will look like this:

    -module(example).

    -export([select_todos/1]).

    select_todos() ->
        select_todos([]).

    select_todos(Args) ->
        pgo:query(<<"SELECT * FROM todos">>, Args).

Once the example module is loaded into the environment and it can be used like this:

    example:select_todos().
    example:select_todos([]).

The args list is optional since some queries don't need the args list.

### Query Arity 3

This is similar to query arity 2 but it provides another argument for a connection.

The generated methods are a bit different.

The generated modules look like this:

    select_todos/1
    select_todos/2

They are used like this:

    {ok, Conn} = db_connection(),
    Result = select_todos(Conn),
    Result2 = select_todos(Conn, []).

The connection to the database is always the first argument, and the args list is an optional second argument.

Passing the database connection is a common pattern, and is especially useful when working with connection pools.

# License

Apache 2.0