README.md

# Postgres Sigil

![CI build](https://github.com/ottatech/postgres-sigil/actions/workflows/config.yml/badge.svg)
[![Hex.pm Version](https://img.shields.io/hexpm/v/postgres_sigil.svg?style=flat)](https://hex.pm/packages/postgres_sigil)
[![Hexdocs.pm](https://img.shields.io/static/v1?style=flat&label=hexdocs&message=postgres_sigil&color=blueviolet)](https://hexdocs.pm/postgres_sigil)

A library to improve the ergonomics of working with [Postgrex](https://github.com/elixir-ecto/postgrex).
It can be thought of as a middle ground between [Ecto](https://github.com/elixir-ecto/ecto) and 
[ayesql](https://github.com/alexdesousa/ayesql) in that the goal is to write queries in plain SQL 
but within Elixir source files, not separately. The syntax is heavily inspired by the Scala library [doobie](https://tpolecat.github.io/doobie/).

## Writing queries

### Basic selects

Use the `~q` sigil to construct queries. Variables can be safely interpolated into the query
and will be replaced with `$1`, `$2` etc positional parameters before being sent to Postgres.

```elixir
~q"SELECT * FROM users WHERE id = #{id}" |> to_tuple()
# result: {"SELECT * FROM users WHERE id = $1", [1245]}
```

### Fragments

Queries can be interpolated into other queries which allows you to re-use fragments.

```elixir
recently_seen = ~q"last_seen >= NOW() - INTERVAL '1 day'"
~q"SELECT * FROM users WHERE #{recently_seen}" |> to_tuple()
# result: {"SELECT * FROM users WHERE last_seen >= NOW() - INTERVAL '1 day'", []}
```

### Inserts and updates

Interpolating a call to `values()` will result in the value being enclosed in brackets
and prefixed with `VALUES`.

Note you cannot directly insert maps because they do not have a defined order.

```elixir
user = %{name: "Tom", email: "tom@example.com"}
~q"INSERT INTO users (name, email) #{values(user.name, user.email)}" |> to_tuple()
# result: {"INSERT INTO users (name, email) VALUES ($1, $2)", ["Tom", "tom@example.com"]}
```

The main benefit this syntax offers is that if you pass a list to `values` it'll generate
the correct SQL for a batch insert operation:

```elixir
~q"INSERT INTO users (name, email, address1) #{values([
  {"A", "a@a.com", "123 fake street"},
  {"B", "b@b.com", "234 fake street"}
])}" |> to_tuple()

# result: {
#  "INSERT INTO users (name, email, address1) VALUES ($1, $2, $3), ($4, $5, $6)",
#  ["A", "a@a.com", "123 fake street", "B", "b@b.com", "234 fake street"]
#}
```

### Dynamic columns

Column names can be interpolated by wrapping the interpolation in `col()`

```elixir
~q"SELECT #{col("name")} FROM users" |> to_tuple()
# result: {"SELECT \"name\" FROM users", []}
```

### Unsafe interpolation

If you're really up to no good then you can wrap interpolations in `unsafe()` which
will result in the value being directly placed into the query with no escaping.
This should only be used if you're fully aware of the [security implications](https://owasp.org/www-community/attacks/SQL_Injection).

```elixir
~q"SELECT #{unsafe("name")} FROM users"
# result: {"SELECT name FROM users", []}
```

## Running queries

You can run the queries either with Ecto or directly with Postgrex.

```elixir
~q"SELECT * FROM users" |> PostgresSigil.Ecto.query!(MyApp.Repo) # ecto
~q"SELECT * FROM users" |> PostgresSigil.Postgrex.query!(:pid) # postgrex
```

## Explaining queries

Both the Ecto and Postgrex integrations provide `explain_to_file!` that will
run the query with `EXPLAIN ANALYZE` and write the result to a file named `explain.json`.

This can then be pasted into https://explain.dalibo.com/ for analysis.

## Handling results

`PostgresSigil.Results` defines a number of functions to make it easier to process the results that Postgrex returns.