Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
248f137
start update
camshaft Mar 30, 2017
3d81a9d
continue update
bbassett Mar 31, 2017
f18a6d0
more update
camshaft Mar 31, 2017
64a185e
start insert, fix warnings
bbassett Apr 2, 2017
2ba248c
start refactor
camshaft Jul 27, 2017
da5c9ed
add travis file
camshaft Jul 27, 2017
dba3cd2
fix travis port
camshaft Jul 27, 2017
17bb9b1
cache build directories
camshaft Jul 27, 2017
7d2f001
fix tests
camshaft Jul 27, 2017
9000a83
escape columns and tables
camshaft Jul 27, 2017
91a7b87
add primary key tests
camshaft Jul 27, 2017
30a0f87
implement foreign keys
camshaft Jul 27, 2017
c7113db
escape column types
camshaft Jul 27, 2017
9405c66
don't execute list of statements in transaction
camshaft Jul 27, 2017
9f5b5a5
add drop tests
bbassett Jul 27, 2017
62c1c27
copy/pasta shame
bbassett Jul 27, 2017
aa4f3ff
implement drop table
camshaft Jul 27, 2017
66262c2
manipulation tests
bbassett Jul 27, 2017
0297076
partially implement insertion
camshaft Jul 27, 2017
954cf97
finish insertions
camshaft Jul 27, 2017
113f3c1
query tests
bbassett Jul 27, 2017
21dc357
improve between call
camshaft Jul 27, 2017
d1c7c28
fix unary operators
camshaft Jul 27, 2017
c2912fe
expr tests
bbassett Jul 27, 2017
fe9dffd
fix a bunch of query tests
camshaft Jul 27, 2017
789ae45
add coveralls
camshaft Jul 28, 2017
43b4337
fix some warnings
camshaft Jul 28, 2017
4fb5edd
alter table tests 1st pass
bbassett Jul 28, 2017
c39c1c9
implement join
camshaft Jul 28, 2017
42bfe92
cleanup
camshaft Jul 28, 2017
0d2c4db
more cleanup
camshaft Jul 28, 2017
d07a827
udate row tests
bbassett Jul 31, 2017
0296f02
fix struct
bbassett Nov 2, 2017
ffb69db
fix update and insert
bbassett Nov 11, 2017
34a432d
fix update after breaking it
bbassett Nov 11, 2017
b312cec
fix comments, add delete
bbassett Nov 13, 2017
fbbd02c
fix some tests, comment out tests for functionality that isn't yet im…
bbassett Nov 14, 2017
4464006
tag pending tests
bbassett Nov 14, 2017
0bbea16
alter table
bbassett Nov 16, 2017
f1f8c6b
fix primary key issues
bbassett Dec 12, 2017
355d6d2
fix joins
bbassett Dec 16, 2017
f30b524
fix warnings, remove tool-versions, support manipulation.update
bbassett Apr 6, 2018
c0c4d12
fix joins
bbassett Apr 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/deps
erl_crash.dump
*.ez
/*.csv
28 changes: 28 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
language: elixir

elixir:
- 1.5.0

otp_release:
- 20.0

env:
global:
- DATABASE_URL=postgres://postgres:@localhost:5432/sonata
- MIX_ENV=test

services:
- postgresql

addons:
postgresql: "9.6"

before_script:
- psql -c 'create database sonata;' -U postgres

script: mix coveralls.travis

cache:
directories:
- _build
- deps
21 changes: 21 additions & 0 deletions EXTRACT_INFO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Extract all of the operators

```sql
SELECT DISTINCT
p.oprname as "name"
FROM pg_catalog.pg_operator p
```

# Extract all of the functions

```sql
SELECT DISTINCT
p.proname as "name",
p.pronargs as "arity",
d.description
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_description d on p.oid = d.objoid
WHERE pg_catalog.pg_function_is_visible(p.oid)
AND p.prorettype != 'pg_catalog.trigger'::pg_catalog.regtype
ORDER BY 1
```
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# sonata
# sonata [![Build Status](https://travis-ci.org/exstruct/sonata.svg?branch=master)](https://travis-ci.org/exstruct/sonata)

sql functions for elixir

Expand All @@ -8,12 +8,16 @@ If [available in Hex](https://hex.pm/docs/publish), the package can be installed

1. Add sonata to your list of dependencies in `mix.exs`:

def deps do
[{:sonata, "~> 0.1.0"}]
end
```elixir
def deps do
[{:sonata, "~> 0.1.0"}]
end
```

2. Ensure sonata is started before your application:

def application do
[applications: [:sonata]]
end
```elixir
def application do
[applications: [:sonata]]
end
```
11 changes: 11 additions & 0 deletions _snapshots/Test.Sonata.AlterTable.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%{"test add column": :alter_table, "test alter column data type": :alter_table,
"test drop column that exists": :alter_table,
"test fail to drop column that doesn't exist": %Postgrex.Error{connection_id: nil,
message: nil,
postgres: %{code: :undefined_column, file: "tablecmds.c", line: "5725",
message: "column \"third_column\" of relation \"my_first_table\" does not exist",
pg_code: "42703", routine: "ATExecDropColumn", severity: "ERROR",
unknown: "ERROR"}}, "test rename column": :alter_table,
"test rename table": :alter_table,
"test success drop column that doesn't exist with `IF EXISTS`": :alter_table,
"test success drop column that exists with `IF EXISTS`": :alter_table}
110 changes: 110 additions & 0 deletions _snapshots/Test.Sonata.CreateTable.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
%{"test default product no": [%{"column_name" => "product_no",
"data_type" => "integer"}],
"test default products": [%{"column_name" => "product_no",
"data_type" => "integer"},
%{"column_name" => "name", "data_type" => "text"},
%{"column_name" => "price", "data_type" => "numeric"}],
"test foreign key": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :foreign_key_violation, constraint: "t1_a_fkey",
detail: "Key (a, b)=(1, 1) is not present in table \"other_table\".",
file: "ri_triggers.c", line: "3324",
message: "insert or update on table \"t1\" violates foreign key constraint \"t1_a_fkey\"",
pg_code: "23503", routine: "ri_ReportViolation", schema: "public",
severity: "ERROR", table: "t1", unknown: "ERROR"}},
"test my_first_table": [%{"column_name" => "first_column",
"data_type" => "text"},
%{"column_name" => "second_column", "data_type" => "integer"}],
"test not null": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :not_null_violation, column: "product_no",
detail: "Failing row contains (null, null, null).", file: "execMain.c",
line: "1734",
message: "null value in column \"product_no\" violates not-null constraint",
pg_code: "23502", routine: "ExecConstraints", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test primary key": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :unique_violation, constraint: "products_pkey",
detail: "Key (product_no)=(1) already exists.", file: "nbtinsert.c",
line: "433",
message: "duplicate key value violates unique constraint \"products_pkey\"",
pg_code: "23505", routine: "_bt_check_unique", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test primary key multiple": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :unique_violation, constraint: "products_pkey",
detail: "Key (a, b)=(1, 2) already exists.", file: "nbtinsert.c",
line: "433",
message: "duplicate key value violates unique constraint \"products_pkey\"",
pg_code: "23505", routine: "_bt_check_unique", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test products": [%{"column_name" => "product_no", "data_type" => "integer"},
%{"column_name" => "name", "data_type" => "text"},
%{"column_name" => "price", "data_type" => "numeric"}],
"test products CHECK operator": %Postgrex.Error{connection_id: nil,
message: nil,
postgres: %{code: :check_violation, constraint: "products_price_check",
detail: "Failing row contains (1, My product, -1).", file: "execMain.c",
line: "1760",
message: "new row for relation \"products\" violates check constraint \"products_price_check\"",
pg_code: "23514", routine: "ExecConstraints", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test products CHECK price > 0": %Postgrex.Error{connection_id: nil,
message: nil,
postgres: %{code: :check_violation, constraint: "products_price_check",
detail: "Failing row contains (1, My product, -1).", file: "execMain.c",
line: "1760",
message: "new row for relation \"products\" violates check constraint \"products_price_check\"",
pg_code: "23514", routine: "ExecConstraints", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test products SERIAL": [%{"column_name" => "product_no",
"data_type" => "integer"}],
"test products named CHECK": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :check_violation, constraint: "positive_price",
detail: "Failing row contains (1, My product, -1).", file: "execMain.c",
line: "1760",
message: "new row for relation \"products\" violates check constraint \"positive_price\"",
pg_code: "23514", routine: "ExecConstraints", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test references": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :foreign_key_violation,
constraint: "orders_product_no_fkey",
detail: "Key (product_no)=(1) is not present in table \"products\".",
file: "ri_triggers.c", line: "3324",
message: "insert or update on table \"orders\" violates foreign key constraint \"orders_product_no_fkey\"",
pg_code: "23503", routine: "ri_ReportViolation", schema: "public",
severity: "ERROR", table: "orders", unknown: "ERROR"}},
"test references explicit": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :foreign_key_violation,
constraint: "orders_product_no_fkey",
detail: "Key (product_no)=(1) is not present in table \"products\".",
file: "ri_triggers.c", line: "3324",
message: "insert or update on table \"orders\" violates foreign key constraint \"orders_product_no_fkey\"",
pg_code: "23503", routine: "ri_ReportViolation", schema: "public",
severity: "ERROR", table: "orders", unknown: "ERROR"}},
"test references pk": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :foreign_key_violation,
constraint: "orders_product_no_fkey",
detail: "Key (product_no)=(1) is not present in table \"products\".",
file: "ri_triggers.c", line: "3324",
message: "insert or update on table \"orders\" violates foreign key constraint \"orders_product_no_fkey\"",
pg_code: "23503", routine: "ri_ReportViolation", schema: "public",
severity: "ERROR", table: "orders", unknown: "ERROR"}},
"test table-wide checks": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :check_violation, constraint: "products_check",
detail: "Failing row contains (1, My product, 100, 400).",
file: "execMain.c", line: "1760",
message: "new row for relation \"products\" violates check constraint \"products_check\"",
pg_code: "23514", routine: "ExecConstraints", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test unique": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :unique_violation, constraint: "products_product_no_key",
detail: "Key (product_no)=(1) already exists.", file: "nbtinsert.c",
line: "433",
message: "duplicate key value violates unique constraint \"products_product_no_key\"",
pg_code: "23505", routine: "_bt_check_unique", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}},
"test unique multiple": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :unique_violation, constraint: "products_a_b_key",
detail: "Key (a, b)=(1, 2) already exists.", file: "nbtinsert.c",
line: "433",
message: "duplicate key value violates unique constraint \"products_a_b_key\"",
pg_code: "23505", routine: "_bt_check_unique", schema: "public",
severity: "ERROR", table: "products", unknown: "ERROR"}}}
12 changes: 12 additions & 0 deletions _snapshots/Test.Sonata.DropTable.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
%{"test should drop table": %Postgrex.Error{connection_id: nil, message: nil,
postgres: %{code: :undefined_table, file: "parse_relation.c", line: "1160",
message: "relation \"my_first_table\" does not exist", pg_code: "42P01",
position: "15", routine: "parserOpenTable", severity: "ERROR",
unknown: "ERROR"}},
"test should fail to drop table": %Postgrex.Error{connection_id: nil,
message: nil,
postgres: %{code: :undefined_table, file: "tablecmds.c", line: "760",
message: "table \"doesnt_exist\" does not exist", pg_code: "42P01",
routine: "DropErrorMsgNonExistent", severity: "ERROR", unknown: "ERROR"}},
"test should successfully drop table, whether it exists or not, pt.1": :drop_table,
"test should successfully drop table, whether it exists or not, pt.2": :drop_table}
26 changes: 26 additions & 0 deletions _snapshots/Test.Sonata.Expr.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
%{"test BETWEEN": [%{"first_column" => "foo", "second_column" => 2},
%{"first_column" => "bar", "second_column" => 3},
%{"first_column" => "baz", "second_column" => 4}],
"test ILIKE": [%{"first_column" => "foo", "second_column" => 1}],
"test IS_DISTINCT_FROM": [%{"first_column" => "bar",
"second_column" => "foo"}],
"test IS_FALSE": [%{"first_column" => "bar", "second_column" => false}],
"test IS_NOT_DISTINCT_FROM": [%{"first_column" => "foo",
"second_column" => "foo"},
%{"first_column" => "bar", "second_column" => "bar"}],
"test IS_NOT_FALSE": [%{"first_column" => "foo", "second_column" => true},
%{"first_column" => "baz", "second_column" => nil}],
"test IS_NOT_NULL": [%{"first_column" => "foo", "second_column" => 1}],
"test IS_NOT_TRUE": [%{"first_column" => "bar", "second_column" => false},
%{"first_column" => "baz", "second_column" => nil}],
"test IS_NULL": [%{"first_column" => "bar", "second_column" => nil}],
"test IS_TRUE": [%{"first_column" => "foo", "second_column" => true}],
"test LIKE": [%{"first_column" => "foo", "second_column" => 1}],
"test NOT_ILIKE": [%{"first_column" => "foobar", "second_column" => 1},
%{"first_column" => "FOOBAR", "second_column" => 2},
%{"first_column" => "BAZ", "second_column" => 3}],
"test NOT_LIKE": [%{"first_column" => "foobar", "second_column" => 2},
%{"first_column" => "FOOBAR", "second_column" => 3}],
"test NOT_SIMILAR_TO": [%{"first_column" => "foobar", "second_column" => 1},
%{"first_column" => "FOOBAR", "second_column" => 2},
%{"first_column" => "BAZ", "second_column" => 3}], "test SIMILAR_TO": []}
5 changes: 5 additions & 0 deletions _snapshots/Test.Sonata.Insertion.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
%{"test one row, multiple values again (different interface)": :update,
"test should update multiple cols in a row": :update,
"test should update multiple rows, multiple values": :update,
"test should update multiple rows, one col each": :update,
"test should update one value in row": :update}
9 changes: 9 additions & 0 deletions _snapshots/Test.Sonata.Manipulation.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
%{"test should do nothing on conflict": [%{"id" => 1, "value" => 123}],
"test should insert row": [%{"first_column" => nil, "second_column" => 1}],
"test should insert row without specifying columns": [%{"first_column" => "foo",
"second_column" => 345}],
"test should insert value other than default": [%{"first_column" => "foo",
"second_column" => 456}],
"test should insert with defaults": [%{"first_column" => "foo",
"second_column" => 123}], "test should return value after insert": :insert,
"test should update on conflict": []}
18 changes: 18 additions & 0 deletions _snapshots/Test.Sonata.Query.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
%{"test should find all results": [%{"first_column" => "foo",
"second_column" => 1}, %{"first_column" => "bar", "second_column" => 2},
%{"first_column" => "baz", "second_column" => 3}],
"test should find foo row": [%{"first_column" => "foo",
"second_column" => 1}],
"test should find rows with value < 3 row": [%{"first_column" => "foo",
"second_column" => 1}, %{"first_column" => "bar", "second_column" => 2}],
"test should return count based on a where clause": [%{"label" => 2}],
"test should return count from group by clause": [%{"count" => 2,
"second_column" => 1}, %{"count" => 1, "second_column" => 2}],
"test should return count of all rows": [%{"count" => 3}],
"test should return joined tables": [%{"first_column" => "foo", "id" => 1,
"label" => "label 1", "second_column" => 1}],
"test should return limited result": [%{"first_column" => "foo",
"second_column" => 1}],
"test should reverse order": [%{"first_column" => "baz",
"second_column" => 3}, %{"first_column" => "bar", "second_column" => 2},
%{"first_column" => "foo", "second_column" => 1}]}
32 changes: 32 additions & 0 deletions bin/gen_functions.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
functions = "functions.csv"
|> File.stream!()
|> CSV.decode!(headers: true)

str = quote do
defmodule Sonata.Function do
unquote_splicing(Enum.flat_map(functions, fn
(%{
"name" => name
}) when name in ["like"] ->
[]
(%{
"name" => name,
"arity" => arity,
"description" => description,
}) ->
arity = String.to_integer(arity)
fun = String.to_atom(name)
args = Macro.generate_arguments(arity, nil)
quote do
@doc unquote(description)
def unquote(fun)(unquote_splicing(args)) do
%Sonata.Expr.Call{name: unquote(name), arguments: unquote(args)}
end
end
|> elem(2)
end))
end
end
|> Macro.to_string()

File.write!("lib/sonata/function.ex", str)
20 changes: 20 additions & 0 deletions bin/gen_operators.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
operators = "operators.csv"
|> File.stream!()
|> CSV.decode!(headers: true)
|> Stream.map(fn(%{
"name" => name,
}) ->
fun = String.to_atom(name)
"""
def unquote(#{inspect(fun)})(lhs, rhs) do
%Sonata.Expr.Operator{operator: #{inspect(name)}, lhs: lhs, rhs: rhs}
end
"""
end)

str = """
defmodule Sonata.Operator do
#{Enum.join(operators, "\n")}end
"""

File.write!("lib/sonata/operator.ex", str)
9 changes: 9 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use Mix.Config

if Mix.env === :test do
config :sonata, Test.Sonata.Repo,
adapter: Ecto.Adapters.Postgres,
pool: Ecto.Adapters.SQL.Sandbox,
url: {:system, "DATABASE_URL"},
loggers: []
end
6 changes: 6 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"skip_files": [
"lib/sonata/function.ex",
"lib/sonata/operator.ex"
]
}
30 changes: 30 additions & 0 deletions lib/sonata.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
defmodule Sonata do
defmacro __using__(_) do
quote do
import Sonata.{
AlterTable,
Builder,
Combination,
CreateTable,
DropTable,
Definition.Column,
Expr,
Function,
Manipulation,
Query,
Transaction,
}
end
end

def to_sql(statement, opts \\ %{}) do
opts = Enum.into(opts, %{})
{sql, params, _idx} = Sonata.Postgres.to_sql(statement, opts, 1)
on_row = Sonata.Postgres.on_row(statement, opts)
{sql, Enum.to_list(params), on_row}
end

defmacro sonata(struct) do
quote do
use Sonata
unquote(struct)
end
end
end
Loading