jade-sql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,268 @@
1
+ module Sql.Mutation exposing (
2
+ Mutation,
3
+ delete,
4
+ delete_all,
5
+ insert,
6
+ insert_all,
7
+ returning,
8
+ to_sql,
9
+ update,
10
+ update_all,
11
+ )
12
+
13
+ import Sql exposing (
14
+ Assignment,
15
+ Expr(..),
16
+ Renderable,
17
+ Selector(..),
18
+ Table,
19
+ TableRef(..),
20
+ pk_values,
21
+ set_,
22
+ to_assigns,
23
+ )
24
+ import Sql.Query exposing (Q)
25
+ import Decode exposing (Value)
26
+
27
+
28
+ type MutationKind
29
+ = InsertK
30
+ | UpdateK
31
+ | DeleteK
32
+
33
+
34
+ struct Mutation(ret, c) = {
35
+ kind: MutationKind,
36
+ table: TableRef,
37
+ cols: c,
38
+ rows: List(List(Assignment)),
39
+ wheres: List(Expr(Bool)),
40
+ returning_selector: Selector(ret)
41
+ }
42
+
43
+
44
+ def insert(value: a, t: Table(c, m)) -> Mutation(Int, c)
45
+ Mutation(
46
+ InsertK,
47
+ TableRef(t.name, t.alias_),
48
+ t.alias_ |> t.cols,
49
+ [to_assigns(value)],
50
+ [],
51
+ Selector([], []),
52
+ )
53
+ end
54
+
55
+
56
+ def insert_all(values: List(a), t: Table(c, m)) -> Mutation(Int, c)
57
+ Mutation(
58
+ InsertK,
59
+ TableRef(t.name, t.alias_),
60
+ t.alias_ |> t.cols,
61
+ List.map(values, (v) -> { to_assigns(v) }),
62
+ [],
63
+ Selector([], []),
64
+ )
65
+ end
66
+
67
+
68
+ def update(value: a, t: Table(c, m)) -> Mutation(Int, c)
69
+ pk_pred = pk_predicate(t.pk_columns, pk_values(value))
70
+
71
+ Mutation(
72
+ UpdateK,
73
+ TableRef(t.name, t.alias_),
74
+ t.alias_ |> t.cols,
75
+ [to_assigns(value)],
76
+ [pk_pred],
77
+ Selector([], []),
78
+ )
79
+ end
80
+
81
+
82
+ def delete(value: a, t: Table(c, m)) -> Mutation(Int, c)
83
+ pk_pred = pk_predicate(t.pk_columns, pk_values(value))
84
+
85
+ Mutation(
86
+ DeleteK,
87
+ TableRef(t.name, t.alias_),
88
+ t.alias_ |> t.cols,
89
+ [],
90
+ [pk_pred],
91
+ Selector([], []),
92
+ )
93
+ end
94
+
95
+
96
+ def update_all(
97
+ t: Table(c, m),
98
+ pred_fn: c -> Expr(Bool),
99
+ build: c -> List(Assignment),
100
+ ) -> Mutation(Int, c)
101
+ cols_ = t.alias_ |> t.cols
102
+ pred = cols_ |> pred_fn
103
+
104
+ Mutation(
105
+ UpdateK,
106
+ TableRef(t.name, t.alias_),
107
+ cols_,
108
+ [cols_ |> build],
109
+ [pred],
110
+ Selector([], []),
111
+ )
112
+ end
113
+
114
+
115
+ def delete_all(t: Table(c, m), pred_fn: c -> Expr(Bool)) -> Mutation(Int, c)
116
+ cols_ = t.alias_ |> t.cols
117
+ pred = cols_ |> pred_fn
118
+
119
+ Mutation(
120
+ DeleteK,
121
+ TableRef(t.name, t.alias_),
122
+ cols_,
123
+ [],
124
+ [pred],
125
+ Selector([], []),
126
+ )
127
+ end
128
+
129
+
130
+ def returning(m: Mutation(a, c), build: c -> Q(Selector(b))) -> Mutation(b, c)
131
+ Mutation(m.kind, m.table, m.cols, m.rows, m.wheres, build(m.cols).result)
132
+ end
133
+
134
+
135
+ def pk_predicate(cols: List(String), values: List(Value)) -> Expr(Bool)
136
+ sql = cols
137
+ |> List.map((col) -> { col ++ " = ?" })
138
+ |> String.join(" AND ")
139
+
140
+ Expr(sql, values)
141
+ end
142
+
143
+
144
+ def render_row(row: List(Assignment)) -> String
145
+ cols_part = row
146
+ |> List.map((a) -> { a.value_sql })
147
+ |> String.join(", ")
148
+
149
+ "(" ++ cols_part ++ ")"
150
+ end
151
+
152
+
153
+ def returning_clause(s: Selector(ret)) -> String
154
+ List.empty?(s.columns_sql) ? "" : " RETURNING " ++ String.join(s.columns_sql, ", ")
155
+ end
156
+
157
+
158
+ def insert_cols_str(rows: List(List(Assignment))) -> String
159
+ case rows
160
+ in [] then ""
161
+ in [first | _]
162
+ "(" ++ String.join(List.map(first, (a) -> { a.col }), ", ") ++ ")"
163
+ end
164
+ end
165
+
166
+
167
+ def insert_params(rows: List(List(Assignment))) -> List(Value)
168
+ List.fold(
169
+ rows,
170
+ [],
171
+ (acc, row) -> { acc ++ List.fold(row, [], (acc2, a) -> { acc2 ++ a.params }) },
172
+ )
173
+ end
174
+
175
+
176
+ def set_sqls_for(rows: List(List(Assignment))) -> List(String)
177
+ case rows
178
+ in [first | _] then List.map(first, (a) -> { a.col ++ " = " ++ a.value_sql })
179
+ in [] then []
180
+ end
181
+ end
182
+
183
+
184
+ def set_params_for(rows: List(List(Assignment))) -> List(Value)
185
+ case rows
186
+ in [first | _] then List.fold(first, [], (acc, a) -> { acc ++ a.params })
187
+ in [] then []
188
+ end
189
+ end
190
+
191
+
192
+ def where_sql(wheres: List(Expr(Bool))) -> String
193
+ List.empty?(wheres)
194
+ ? ""
195
+ : " WHERE " ++ String.join(List.map(wheres, (w) -> { w.sql }), " AND ")
196
+ end
197
+
198
+
199
+ def where_params(wheres: List(Expr(Bool))) -> List(Value)
200
+ List.fold(wheres, [], (acc, w) -> { acc ++ w.params })
201
+ end
202
+
203
+
204
+ def strip_table_alias(sql: String, alias_: String) -> String
205
+ String.replace(sql, alias_ ++ ".", "")
206
+ end
207
+
208
+
209
+ def render_insert(m: Mutation(ret, c)) -> (String, List(Value))
210
+ cols_str = insert_cols_str(m.rows)
211
+ values_str = m.rows
212
+ |> List.map(render_row)
213
+ |> String.join(", ")
214
+ returning_ = strip_table_alias(returning_clause(m.returning_selector), m.table.alias_)
215
+ sql = "INSERT INTO "
216
+ ++ m.table.name
217
+ ++ " "
218
+ ++ cols_str
219
+ ++ " VALUES "
220
+ ++ values_str
221
+ ++ returning_
222
+
223
+ Tuple.Tuple2(sql, insert_params(m.rows) ++ m.returning_selector.params)
224
+ end
225
+
226
+
227
+ def render_update(m: Mutation(ret, c)) -> (String, List(Value))
228
+ set_clause = "SET " ++ String.join(set_sqls_for(m.rows), ", ")
229
+ where = strip_table_alias(where_sql(m.wheres), m.table.alias_)
230
+ returning_ = strip_table_alias(returning_clause(m.returning_selector), m.table.alias_)
231
+ sql = "UPDATE "
232
+ ++ m.table.name
233
+ ++ " "
234
+ ++ set_clause
235
+ ++ where
236
+ ++ returning_
237
+
238
+ Tuple.Tuple2(
239
+ sql,
240
+ set_params_for(m.rows) ++ where_params(m.wheres) ++ m.returning_selector.params,
241
+ )
242
+ end
243
+
244
+
245
+ def render_delete(m: Mutation(ret, c)) -> (String, List(Value))
246
+ where = strip_table_alias(where_sql(m.wheres), m.table.alias_)
247
+ returning_ = strip_table_alias(returning_clause(m.returning_selector), m.table.alias_)
248
+ sql = "DELETE FROM "
249
+ ++ m.table.name
250
+ ++ where
251
+ ++ returning_
252
+
253
+ Tuple.Tuple2(sql, where_params(m.wheres) ++ m.returning_selector.params)
254
+ end
255
+
256
+
257
+ def to_sql(m: Mutation(ret, c)) -> (String, List(Value))
258
+ case m.kind
259
+ in InsertK then render_insert(m)
260
+ in UpdateK then render_update(m)
261
+ in DeleteK then render_delete(m)
262
+ end
263
+ end
264
+
265
+
266
+ implements Renderable(Mutation(ret, c)) with
267
+ render: to_sql
268
+ end
@@ -0,0 +1,313 @@
1
+ module Sql.Query exposing (
2
+ Q,
3
+ field,
4
+ from,
5
+ group,
6
+ join,
7
+ left_join,
8
+ limit,
9
+ offset,
10
+ order,
11
+ order_desc,
12
+ select,
13
+ to_sql,
14
+ where,
15
+ )
16
+
17
+ import Sql exposing (Expr, Renderable, Selector(..), Table, TableRef(..))
18
+ import Decode exposing (Value)
19
+
20
+
21
+ type JoinKind
22
+ = InnerJ
23
+ | LeftJ
24
+
25
+
26
+ struct Join = {
27
+ kind: JoinKind,
28
+ name: String,
29
+ alias_: String,
30
+ on: Expr(Bool)
31
+ }
32
+
33
+
34
+ type OrderDir
35
+ = Asc
36
+ | Desc
37
+
38
+
39
+ struct OrderTerm = {
40
+ dir: OrderDir,
41
+ sql: String,
42
+ params: List(Value)
43
+ }
44
+
45
+
46
+ struct GroupTerm = {
47
+ sql: String,
48
+ params: List(Value)
49
+ }
50
+
51
+
52
+ struct Q(a) = {
53
+ tables: List(TableRef),
54
+ joins: List(Join),
55
+ wheres: List(Expr(Bool)),
56
+ groups: List(GroupTerm),
57
+ orders: List(OrderTerm),
58
+ limit_: Maybe(Int),
59
+ offset_: Maybe(Int),
60
+ result: a
61
+ }
62
+
63
+
64
+ implements Chainable(Q(a)) with
65
+ and_then: q_and_then
66
+ end
67
+
68
+
69
+ def q_and_then(q: Q(a), fn: a -> Q(b)) -> Q(b)
70
+ next_ = q.result |> fn
71
+
72
+ Q(
73
+ q.tables ++ next_.tables,
74
+ q.joins ++ next_.joins,
75
+ q.wheres ++ next_.wheres,
76
+ q.groups ++ next_.groups,
77
+ q.orders ++ next_.orders,
78
+ merge_paging(q.limit_, next_.limit_),
79
+ merge_paging(q.offset_, next_.offset_),
80
+ next_.result,
81
+ )
82
+ end
83
+
84
+
85
+ def merge_paging(prev: Maybe(Int), next_: Maybe(Int)) -> Maybe(Int)
86
+ case next_
87
+ in Just(_) then next_
88
+ in Nothing then prev
89
+ end
90
+ end
91
+
92
+
93
+ def from(t: Table(c, m)) -> Q(c)
94
+ cols_ = t.alias_ |> t.cols
95
+
96
+ Q([TableRef(t.name, t.alias_)], [], [], [], [], Nothing, Nothing, cols_)
97
+ end
98
+
99
+
100
+ def join(t: Table(c, m), on_: c -> Expr(Bool)) -> Q(c)
101
+ cols_ = t.alias_ |> t.cols
102
+ pred = cols_ |> on_
103
+
104
+ Q(
105
+ [],
106
+ [Join(InnerJ, t.name, t.alias_, pred)],
107
+ [],
108
+ [],
109
+ [],
110
+ Nothing,
111
+ Nothing,
112
+ cols_,
113
+ )
114
+ end
115
+
116
+
117
+ def left_join(t: Table(c, m), on_: c -> Expr(Bool)) -> Q(m)
118
+ cols_strict = t.alias_ |> t.cols
119
+ cols_maybe = t.alias_ |> t.maybe_cols
120
+ pred = cols_strict |> on_
121
+
122
+ Q(
123
+ [],
124
+ [Join(LeftJ, t.name, t.alias_, pred)],
125
+ [],
126
+ [],
127
+ [],
128
+ Nothing,
129
+ Nothing,
130
+ cols_maybe,
131
+ )
132
+ end
133
+
134
+
135
+ def where(q: Q(a), predicate: Expr(Bool)) -> Q(a)
136
+ Q(
137
+ q.tables,
138
+ q.joins,
139
+ q.wheres ++ [predicate],
140
+ q.groups,
141
+ q.orders,
142
+ q.limit_,
143
+ q.offset_,
144
+ q.result,
145
+ )
146
+ end
147
+
148
+
149
+ def group(q: Q(a), e: Expr(b)) -> Q(a)
150
+ Q(
151
+ q.tables,
152
+ q.joins,
153
+ q.wheres,
154
+ q.groups ++ [GroupTerm(e.sql, e.params)],
155
+ q.orders,
156
+ q.limit_,
157
+ q.offset_,
158
+ q.result,
159
+ )
160
+ end
161
+
162
+
163
+ def order(q: Q(a), e: Expr(b)) -> Q(a)
164
+ Q(
165
+ q.tables,
166
+ q.joins,
167
+ q.wheres,
168
+ q.groups,
169
+ q.orders ++ [OrderTerm(Asc, e.sql, e.params)],
170
+ q.limit_,
171
+ q.offset_,
172
+ q.result,
173
+ )
174
+ end
175
+
176
+
177
+ def order_desc(q: Q(a), e: Expr(b)) -> Q(a)
178
+ Q(
179
+ q.tables,
180
+ q.joins,
181
+ q.wheres,
182
+ q.groups,
183
+ q.orders ++ [OrderTerm(Desc, e.sql, e.params)],
184
+ q.limit_,
185
+ q.offset_,
186
+ q.result,
187
+ )
188
+ end
189
+
190
+
191
+ def limit(q: Q(a), n: Int) -> Q(a)
192
+ Q(
193
+ q.tables,
194
+ q.joins,
195
+ q.wheres,
196
+ q.groups,
197
+ q.orders,
198
+ Just(n),
199
+ q.offset_,
200
+ q.result,
201
+ )
202
+ end
203
+
204
+
205
+ def offset(q: Q(a), n: Int) -> Q(a)
206
+ Q(
207
+ q.tables,
208
+ q.joins,
209
+ q.wheres,
210
+ q.groups,
211
+ q.orders,
212
+ q.limit_,
213
+ Just(n),
214
+ q.result,
215
+ )
216
+ end
217
+
218
+
219
+ def select(make: a -> b) -> Q(Selector(a -> b))
220
+ Q([], [], [], [], [], Nothing, Nothing, Selector([], []))
221
+ end
222
+
223
+
224
+ def field(qs: Q(Selector(a -> b)), e: Expr(a)) -> Q(Selector(b))
225
+ Q(
226
+ qs.tables,
227
+ qs.joins,
228
+ qs.wheres,
229
+ qs.groups,
230
+ qs.orders,
231
+ qs.limit_,
232
+ qs.offset_,
233
+ Selector(qs.result.columns_sql ++ [e.sql], qs.result.params ++ e.params),
234
+ )
235
+ end
236
+
237
+
238
+ def join_keyword(k: JoinKind) -> String
239
+ case k
240
+ in InnerJ then "INNER JOIN"
241
+ in LeftJ then "LEFT JOIN"
242
+ end
243
+ end
244
+
245
+
246
+ def render_join(j: Join) -> String
247
+ join_keyword(j.kind) ++ " " ++ j.name ++ " " ++ j.alias_ ++ " ON " ++ j.on.sql
248
+ end
249
+
250
+
251
+ def render_order_term(t: OrderTerm) -> String
252
+ case t.dir
253
+ in Asc then t.sql
254
+ in Desc then t.sql ++ " DESC"
255
+ end
256
+ end
257
+
258
+
259
+ def render_from(tables: List(TableRef)) -> String
260
+ case tables
261
+ in [] then ""
262
+ in [first | _] then " FROM " ++ first.name ++ " " ++ first.alias_
263
+ end
264
+ end
265
+
266
+
267
+ def render_int_clause(keyword: String, m: Maybe(Int)) -> String
268
+ case m
269
+ in Just(n) then keyword ++ String.from_int(n)
270
+ in Nothing then ""
271
+ end
272
+ end
273
+
274
+
275
+ implements Renderable(Q(Selector(a))) with
276
+ render: to_sql
277
+ end
278
+
279
+
280
+ def to_sql(q: Q(Selector(a))) -> (String, List(Value))
281
+ select_clause = "SELECT " ++ String.join(q.result.columns_sql, ", ")
282
+ from_clause = render_from(q.tables)
283
+ joins_clause = q.joins
284
+ |> List.map((j) -> { " " ++ render_join(j) })
285
+ |> String.concat
286
+ where_clause = List.empty?(q.wheres)
287
+ ? ""
288
+ : " WHERE " ++ String.join(List.map(q.wheres, (w) -> { w.sql }), " AND ")
289
+ group_clause = List.empty?(q.groups)
290
+ ? ""
291
+ : " GROUP BY " ++ String.join(List.map(q.groups, (g) -> { g.sql }), ", ")
292
+ order_clause = List.empty?(q.orders)
293
+ ? ""
294
+ : " ORDER BY " ++ String.join(List.map(q.orders, render_order_term), ", ")
295
+ limit_clause = render_int_clause(" LIMIT ", q.limit_)
296
+ offset_clause = render_int_clause(" OFFSET ", q.offset_)
297
+ join_ps = List.fold(q.joins, [], (acc, j) -> { acc ++ j.on.params })
298
+ where_ps = List.fold(q.wheres, [], (acc, w) -> { acc ++ w.params })
299
+ group_ps = List.fold(q.groups, [], (acc, g) -> { acc ++ g.params })
300
+ order_ps = List.fold(q.orders, [], (acc, o) -> { acc ++ o.params })
301
+
302
+ Tuple.Tuple2(
303
+ select_clause
304
+ ++ from_clause
305
+ ++ joins_clause
306
+ ++ where_clause
307
+ ++ group_clause
308
+ ++ order_clause
309
+ ++ limit_clause
310
+ ++ offset_clause,
311
+ q.result.params ++ join_ps ++ where_ps ++ group_ps ++ order_ps,
312
+ )
313
+ end
@@ -0,0 +1,126 @@
1
+ module Sql.Uuid exposing (
2
+ Uuid,
3
+ from_b64,
4
+ parse,
5
+ to_b64,
6
+ to_string,
7
+ v4,
8
+ v7,
9
+ )
10
+
11
+ import Decode exposing (Decodable, Decoder, Value)
12
+ import Encode exposing (Encodable)
13
+ import Bytes exposing (Bytes)
14
+
15
+
16
+ type Uuid = Uuid(String)
17
+
18
+
19
+ uses JadeSql::Uuid::Runtime with
20
+ generate_v4 : Task({ value: String }, Never),
21
+ generate_v7 : Task({ value: String }, Never)
22
+ end
23
+
24
+
25
+ def v4 -> Task(Uuid, Never)
26
+ raw <- generate_v4()
27
+
28
+ Task.succeed(Uuid(raw.value))
29
+ end
30
+
31
+
32
+ def v7 -> Task(Uuid, Never)
33
+ raw <- generate_v7()
34
+
35
+ Task.succeed(Uuid(raw.value))
36
+ end
37
+
38
+
39
+ def to_string(u: Uuid) -> String
40
+ Uuid(s) = u
41
+
42
+ s
43
+ end
44
+
45
+
46
+ def parse(s: String) -> Maybe(Uuid)
47
+ valid_form(s) ? Just(Uuid(String.to_lower(s))) : Nothing
48
+ end
49
+
50
+
51
+ def valid_form(s: String) -> Bool
52
+ case String.split(s, "-")
53
+ in [a, b, c, d, e] then valid_widths(a, b, c, d, e)
54
+ else False
55
+ end
56
+ end
57
+
58
+
59
+ def valid_widths(a: String, b: String, c: String, d: String, e: String) -> Bool
60
+ String.length(a) == 8 && String.length(b) == 4 && String.length(c) == 4 && String.length(d) == 4 && String.length(e) == 12
61
+ end
62
+
63
+
64
+ def uuid_eq(a: Uuid, b: Uuid) -> Bool
65
+ Uuid(sa) = a
66
+ Uuid(sb) = b
67
+
68
+ sa == sb
69
+ end
70
+
71
+
72
+ implements Eq(Uuid) with
73
+ (==): uuid_eq
74
+ end
75
+
76
+
77
+ def parse_decoder(s: String) -> Decoder(Uuid)
78
+ case parse(s)
79
+ in Just(u) then Decode.succeed(u)
80
+ in Nothing then Decode.fail("invalid uuid: " ++ s)
81
+ end
82
+ end
83
+
84
+
85
+ implements Decodable(Uuid) with
86
+ decoder: -> { Decode.string |> Decode.and_then(parse_decoder) }
87
+ end
88
+
89
+
90
+ implements Encodable(Uuid) with
91
+ encoder: (u) -> { Encode.string(to_string(u)) }
92
+ end
93
+
94
+
95
+ def to_b64(u: Uuid) -> String
96
+ Uuid(s) = u
97
+
98
+ s
99
+ |> String.replace("-", "")
100
+ |> Bytes.from_hex
101
+ |> Maybe.map(Bytes.to_base64_url)
102
+ |> Maybe.with_default("")
103
+ end
104
+
105
+
106
+ def from_b64(s: String) -> Maybe(Uuid)
107
+ Bytes.from_base64_url(s) |> Maybe.and_then(uuid_from_bytes)
108
+ end
109
+
110
+
111
+ def uuid_from_bytes(b: Bytes) -> Maybe(Uuid)
112
+ Bytes.width(b) == 16 ? parse(canonical_form(Bytes.to_hex(b))) : Nothing
113
+ end
114
+
115
+
116
+ def canonical_form(hex32: String) -> String
117
+ String.slice(hex32, 0, 8)
118
+ ++ "-"
119
+ ++ String.slice(hex32, 8, 12)
120
+ ++ "-"
121
+ ++ String.slice(hex32, 12, 16)
122
+ ++ "-"
123
+ ++ String.slice(hex32, 16, 20)
124
+ ++ "-"
125
+ ++ String.slice(hex32, 20, 32)
126
+ end