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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +68 -0
- data/docs/building.md +491 -0
- data/docs/running.md +103 -0
- data/exe/jade-sql +67 -0
- data/lib/jade-sql/bin/generate_schema.rb +219 -0
- data/lib/jade-sql/runtime.rb +193 -0
- data/lib/jade-sql/sql/loader.jd +30 -0
- data/lib/jade-sql/sql/mutation.jd +268 -0
- data/lib/jade-sql/sql/query.jd +313 -0
- data/lib/jade-sql/sql/uuid.jd +126 -0
- data/lib/jade-sql/sql.jd +421 -0
- data/lib/jade-sql/tasks.rake +19 -0
- data/lib/jade-sql/uuid_runtime.rb +16 -0
- data/lib/jade-sql/version.rb +3 -0
- data/lib/jade-sql.rb +42 -0
- metadata +75 -0
data/lib/jade-sql/sql.jd
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
module Sql exposing (
|
|
2
|
+
Assignment(..),
|
|
3
|
+
Expr(..),
|
|
4
|
+
Identified,
|
|
5
|
+
Renderable,
|
|
6
|
+
Selector(..),
|
|
7
|
+
SqlError(..),
|
|
8
|
+
SqlMapper,
|
|
9
|
+
Table,
|
|
10
|
+
TableRef(..),
|
|
11
|
+
aliased,
|
|
12
|
+
and,
|
|
13
|
+
array_append,
|
|
14
|
+
array_concat,
|
|
15
|
+
array_contained_by,
|
|
16
|
+
array_contains,
|
|
17
|
+
array_has,
|
|
18
|
+
array_length,
|
|
19
|
+
array_overlaps,
|
|
20
|
+
array_remove,
|
|
21
|
+
assign,
|
|
22
|
+
cast,
|
|
23
|
+
coalesce,
|
|
24
|
+
column,
|
|
25
|
+
columns,
|
|
26
|
+
count,
|
|
27
|
+
count_all,
|
|
28
|
+
eq,
|
|
29
|
+
execute,
|
|
30
|
+
execute_raw,
|
|
31
|
+
fetch_many,
|
|
32
|
+
fetch_many_raw,
|
|
33
|
+
fetch_one,
|
|
34
|
+
fetch_one_raw,
|
|
35
|
+
in_,
|
|
36
|
+
is_not_null,
|
|
37
|
+
is_null,
|
|
38
|
+
jsonb_contains,
|
|
39
|
+
jsonb_path_exists,
|
|
40
|
+
maybe_columns,
|
|
41
|
+
neg,
|
|
42
|
+
nullable,
|
|
43
|
+
pk_values,
|
|
44
|
+
render,
|
|
45
|
+
set_,
|
|
46
|
+
sum,
|
|
47
|
+
table,
|
|
48
|
+
to_assigns,
|
|
49
|
+
to_expr,
|
|
50
|
+
transaction,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
import Encode exposing (Encodable, encode)
|
|
54
|
+
import Decode exposing (Decodable, Decoder, Value)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
struct Expr(a) = {
|
|
58
|
+
sql: String,
|
|
59
|
+
params: List(Value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
struct Selector(a) = {
|
|
64
|
+
columns_sql: List(String),
|
|
65
|
+
params: List(Value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
struct Table(c, m) = {
|
|
70
|
+
name: String,
|
|
71
|
+
alias_: String,
|
|
72
|
+
cols: String -> c,
|
|
73
|
+
maybe_cols: String -> m,
|
|
74
|
+
pk_columns: List(String)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
struct TableRef = {
|
|
79
|
+
name: String,
|
|
80
|
+
alias_: String
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
struct Assignment = {
|
|
85
|
+
col: String,
|
|
86
|
+
value_sql: String,
|
|
87
|
+
params: List(Value)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def set_(col: Expr(a), value: Expr(a)) -> Assignment
|
|
92
|
+
Assignment(strip_alias(col.sql), value.sql, value.params)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def assign(col: String, value: a) -> Assignment
|
|
97
|
+
Assignment(col, "?", [encode(value)])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def strip_alias(sql: String) -> String
|
|
102
|
+
parts = String.split(sql, ".")
|
|
103
|
+
|
|
104
|
+
case parts
|
|
105
|
+
in [] then sql
|
|
106
|
+
in [name] then name
|
|
107
|
+
in [_ | rest] then String.join(rest, ".")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
interface SqlMapper(a) with
|
|
113
|
+
to_assigns : a -> List(Assignment)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
implements SqlMapper(List(Assignment)) with
|
|
118
|
+
to_assigns: (a) -> { a }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
interface Identified(a) with
|
|
123
|
+
pk_values : a -> List(Value)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
interface Renderable(r) with
|
|
128
|
+
render : r -> (String, List(Value))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def column(alias_: String, name: String) -> Expr(a)
|
|
133
|
+
Expr(alias_ ++ "." ++ name, [])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def to_expr(value: a) -> Expr(a)
|
|
138
|
+
Expr("?", [encode(value)])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def eq(left: Expr(a), right: Expr(a)) -> Expr(Bool)
|
|
143
|
+
Expr(left.sql ++ " = " ++ right.sql, left.params ++ right.params)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def is_null(e: Expr(a)) -> Expr(Bool)
|
|
148
|
+
Expr(e.sql ++ " IS NULL", e.params)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_not_null(e: Expr(a)) -> Expr(Bool)
|
|
153
|
+
Expr(e.sql ++ " IS NOT NULL", e.params)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def nullable(e: Expr(a)) -> Expr(Maybe(a))
|
|
158
|
+
Expr(e.sql, e.params)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cast(e: Expr(a)) -> Expr(b)
|
|
163
|
+
Expr(e.sql, e.params)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def and(left: Expr(Bool), right: Expr(Bool)) -> Expr(Bool)
|
|
168
|
+
Expr(left.sql ++ " AND " ++ right.sql, left.params ++ right.params)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# SUM may return NULL on an empty group, so the result is Expr(Maybe(Int)).
|
|
173
|
+
# Wrap with `coalesce(sum(x), to_expr(0))` for a strict total.
|
|
174
|
+
def sum(e: Expr(Int)) -> Expr(Maybe(Int))
|
|
175
|
+
Expr("SUM(" ++ e.sql ++ ")", e.params)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def count(e: Expr(a)) -> Expr(Int)
|
|
180
|
+
Expr("COUNT(" ++ e.sql ++ ")", e.params)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def count_all -> Expr(Int)
|
|
185
|
+
Expr("COUNT(*)", [])
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def coalesce(maybe_e: Expr(Maybe(a)), default: Expr(a)) -> Expr(a)
|
|
190
|
+
Expr(
|
|
191
|
+
"COALESCE(" ++ maybe_e.sql ++ ", " ++ default.sql ++ ")",
|
|
192
|
+
maybe_e.params ++ default.params,
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def neg(e: Expr(Int)) -> Expr(Int)
|
|
198
|
+
Expr("-(" ++ e.sql ++ ")", e.params)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# PG array predicates.
|
|
203
|
+
# All bind the array as a single param (`$1`) instead of expanding to
|
|
204
|
+
# `ARRAY[$1,...,$N]`. The pg driver maps Ruby Array → PG array; PG
|
|
205
|
+
# infers the element type from the column on the other side, so empty
|
|
206
|
+
# arrays bind correctly and no `ARRAY[]::t[]` cast is needed.
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def encode_list(values: List(a)) -> Value
|
|
210
|
+
Encode.list((x) -> { encode(x) }, values)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def array_overlaps(col: Expr(List(a)), values: List(a)) -> Expr(Bool)
|
|
215
|
+
Expr(col.sql ++ " && ?", col.params ++ [encode_list(values)])
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def array_has(col: Expr(List(a)), value: a) -> Expr(Bool)
|
|
220
|
+
Expr("? = ANY(" ++ col.sql ++ ")", [encode(value)] ++ col.params)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def array_contains(col: Expr(List(a)), values: List(a)) -> Expr(Bool)
|
|
225
|
+
Expr(col.sql ++ " @> ?", col.params ++ [encode_list(values)])
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def array_contained_by(col: Expr(List(a)), values: List(a)) -> Expr(Bool)
|
|
230
|
+
Expr(col.sql ++ " <@ ?", col.params ++ [encode_list(values)])
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def array_length(col: Expr(List(a))) -> Expr(Int)
|
|
235
|
+
Expr("cardinality(" ++ col.sql ++ ")", col.params)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def array_append(col: Expr(List(a)), value: a) -> Expr(List(a))
|
|
240
|
+
Expr(
|
|
241
|
+
"array_append(" ++ col.sql ++ ", ?)",
|
|
242
|
+
col.params ++ [encode(value)],
|
|
243
|
+
)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def array_remove(col: Expr(List(a)), value: a) -> Expr(List(a))
|
|
248
|
+
Expr(
|
|
249
|
+
"array_remove(" ++ col.sql ++ ", ?)",
|
|
250
|
+
col.params ++ [encode(value)],
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def array_concat(left: Expr(List(a)), right: Expr(List(a))) -> Expr(List(a))
|
|
256
|
+
Expr(left.sql ++ " || " ++ right.sql, left.params ++ right.params)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# jsonb predicates. `value` is encoded via the caller's `Encodable(a)`
|
|
261
|
+
# instance, so user code can pass a record directly:
|
|
262
|
+
#
|
|
263
|
+
# jsonb_contains(column("rules", "match"), { kind: "income" })
|
|
264
|
+
#
|
|
265
|
+
# pg binds Ruby Hash/Array as jsonb when paired with a jsonb column.
|
|
266
|
+
# The `@?` jsonpath operator needs an explicit `::jsonpath` cast since
|
|
267
|
+
# the param is bound as text.
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def jsonb_contains(col: Expr(Value), value: a) -> Expr(Bool)
|
|
271
|
+
Expr(col.sql ++ " @> ?", col.params ++ [encode(value)])
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def jsonb_path_exists(col: Expr(Value), path: String) -> Expr(Bool)
|
|
276
|
+
Expr(col.sql ++ " @? ?::jsonpath", col.params ++ [encode(path)])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def in_(col: Expr(a), values: List(a)) -> Expr(Bool)
|
|
281
|
+
case values
|
|
282
|
+
in [] then Expr("FALSE", [])
|
|
283
|
+
else in_nonempty(col, List.map(values, encode))
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def in_nonempty(col: Expr(a), encoded: List(Value)) -> Expr(Bool)
|
|
289
|
+
placeholders = encoded
|
|
290
|
+
|> List.map((_) -> { "?" })
|
|
291
|
+
|> String.join(", ")
|
|
292
|
+
|
|
293
|
+
Expr(col.sql ++ " IN (" ++ placeholders ++ ")", col.params ++ encoded)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def table(
|
|
298
|
+
name: String,
|
|
299
|
+
alias_: String,
|
|
300
|
+
cols: String -> c,
|
|
301
|
+
maybe_cols: String -> m,
|
|
302
|
+
pk_columns: List(String),
|
|
303
|
+
) -> Table(c, m)
|
|
304
|
+
Table(name, alias_, cols, maybe_cols, pk_columns)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def columns(t: Table(c, m), alias_: String) -> c
|
|
309
|
+
alias_ |> t.cols
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def maybe_columns(t: Table(c, m), alias_: String) -> m
|
|
314
|
+
alias_ |> t.maybe_cols
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def aliased(t: Table(c, m), alias_: String) -> Table(c, m)
|
|
319
|
+
Table(t.name, alias_, t.cols, t.maybe_cols, t.pk_columns)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
type SqlError
|
|
324
|
+
= DbError(String)
|
|
325
|
+
| NotFound
|
|
326
|
+
| NotUnique
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
implements Encodable(SqlError) with
|
|
330
|
+
encoder: encode_sql_error
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def encode_sql_error(e: SqlError) -> Value
|
|
335
|
+
case e
|
|
336
|
+
in DbError(msg) then Encode.variant("DbError", [Encode.string(msg)])
|
|
337
|
+
in NotFound then Encode.variant("NotFound", [])
|
|
338
|
+
in NotUnique then Encode.variant("NotUnique", [])
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
implements Decodable(SqlError) with
|
|
344
|
+
decoder: sql_error_decoder
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def sql_error_decoder -> Decoder(SqlError)
|
|
349
|
+
Decode.type_
|
|
350
|
+
|> Decode.variant("DbError", db_error_decoder)
|
|
351
|
+
|> Decode.variant("NotFound", Decode.succeed(NotFound))
|
|
352
|
+
|> Decode.variant("NotUnique", Decode.succeed(NotUnique))
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def db_error_decoder -> Decoder(SqlError)
|
|
357
|
+
Decode.index(1, Decode.string) |> Decode.map(DbError)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
uses JadeSql::Runtime with
|
|
362
|
+
port_execute_count : (String, List(Value)) -> Task(Int, SqlError),
|
|
363
|
+
port_execute_one : (String, List(Value)) -> Task(a, SqlError),
|
|
364
|
+
port_execute_many : (String, List(Value)) -> Task(List(a), SqlError),
|
|
365
|
+
port_begin : Task(Bool, SqlError),
|
|
366
|
+
port_commit : Task(Bool, SqlError),
|
|
367
|
+
port_rollback : Task(Bool, SqlError)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def execute_raw(p: (String, List(Value))) -> Task(Int, SqlError)
|
|
372
|
+
port_execute_count(p)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def fetch_one_raw(p: (String, List(Value))) -> Task(a, SqlError)
|
|
377
|
+
port_execute_one(p)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def fetch_many_raw(p: (String, List(Value))) -> Task(List(a), SqlError)
|
|
382
|
+
port_execute_many(p)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def execute(r: r) -> Task(Int, SqlError)
|
|
387
|
+
render(r) |> execute_raw
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def fetch_one(r: r) -> Task(a, SqlError)
|
|
392
|
+
render(r) |> fetch_one_raw
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def fetch_many(r: r) -> Task(List(a), SqlError)
|
|
397
|
+
render(r) |> fetch_many_raw
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Runs `task` inside a single DB transaction on the shared connection:
|
|
402
|
+
# every execute/fetch the task performs participates in it. Commits on
|
|
403
|
+
# Ok, rolls back and re-raises the error on Err. Does not nest — wrapping
|
|
404
|
+
# a transaction in a transaction is unsupported for now (no savepoints).
|
|
405
|
+
def transaction(task: Task(a, SqlError)) -> Task(a, SqlError)
|
|
406
|
+
port_begin()
|
|
407
|
+
|> Task.and_then((_) -> { commit_on_ok(task) })
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def commit_on_ok(task: Task(a, SqlError)) -> Task(a, SqlError)
|
|
412
|
+
task
|
|
413
|
+
|> Task.and_then((value) -> { Task.map(port_commit(), (_) -> { value }) })
|
|
414
|
+
|> Task.on_error(rollback_then_fail)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def rollback_then_fail(err: SqlError) -> Task(a, SqlError)
|
|
419
|
+
port_rollback()
|
|
420
|
+
|> Task.and_then((_) -> { Task.fail(err) })
|
|
421
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'jade-sql/bin/generate_schema'
|
|
2
|
+
|
|
3
|
+
namespace :jade do
|
|
4
|
+
desc "Generate schema.jd from db/structure.sql (INPUT, OUTPUT, TABLES, MODULE)"
|
|
5
|
+
task :schema do
|
|
6
|
+
input = ENV['INPUT'] || 'db/structure.sql'
|
|
7
|
+
output = ENV['OUTPUT'] || 'app/jade/schema.jd'
|
|
8
|
+
tables = ENV['TABLES']&.split(',')&.map(&:strip)&.reject(&:empty?)
|
|
9
|
+
module_name = ENV['MODULE'] || 'Schema'
|
|
10
|
+
|
|
11
|
+
FileUtils.mkdir_p(File.dirname(output))
|
|
12
|
+
File.write(
|
|
13
|
+
output,
|
|
14
|
+
JadeSql::SchemaGenerator.generate(File.read(input), tables: tables, module_name: module_name),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
puts "wrote #{output}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
require 'jade/port'
|
|
3
|
+
|
|
4
|
+
# UUID v4/v7 generation ports for Sql.Uuid. Stdlib-only, no AR — safe
|
|
5
|
+
# to require eagerly from jade-sql.rb (unlike runtime.rb which pulls
|
|
6
|
+
# in ActiveRecord and is opt-in).
|
|
7
|
+
module JadeSql
|
|
8
|
+
module Uuid
|
|
9
|
+
module Runtime
|
|
10
|
+
extend Jade::Port
|
|
11
|
+
|
|
12
|
+
task(:generate_v4) { |t| t.ok({ "value" => ::SecureRandom.uuid }) }
|
|
13
|
+
task(:generate_v7) { |t| t.ok({ "value" => ::SecureRandom.uuid_v7 }) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/jade-sql.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'jade'
|
|
2
|
+
|
|
3
|
+
require_relative 'jade-sql/version'
|
|
4
|
+
|
|
5
|
+
Jade.extension(__FILE__)
|
|
6
|
+
|
|
7
|
+
require_relative 'jade-sql/uuid_runtime'
|
|
8
|
+
|
|
9
|
+
# Encoded `Sql.SqlError` values (the `[tag, ...args]` shape produced
|
|
10
|
+
# by Jade's variant encoder). Used by `runtime.rb` to emit errors back
|
|
11
|
+
# across the port boundary, and by anyone stubbing the ports in tests.
|
|
12
|
+
module JadeSql
|
|
13
|
+
module SqlErrors
|
|
14
|
+
NOT_FOUND = ["NotFound"].freeze
|
|
15
|
+
NOT_UNIQUE = ["NotUnique"].freeze
|
|
16
|
+
|
|
17
|
+
def self.db_error(msg) = ["DbError", msg]
|
|
18
|
+
def self.not_found = NOT_FOUND
|
|
19
|
+
def self.not_unique = NOT_UNIQUE
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Sql
|
|
24
|
+
module Errors
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
class DbError < Error; end
|
|
27
|
+
class NotFound < Error; end
|
|
28
|
+
class NotUnique < Error; end
|
|
29
|
+
|
|
30
|
+
BY_TAG = {
|
|
31
|
+
"DbError" => DbError,
|
|
32
|
+
"NotFound" => NotFound,
|
|
33
|
+
"NotUnique" => NotUnique,
|
|
34
|
+
}.freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.raise_typed!(encoded)
|
|
38
|
+
type, message = encoded
|
|
39
|
+
klass = Errors::BY_TAG.fetch(type, Errors::Error)
|
|
40
|
+
raise klass, message
|
|
41
|
+
end
|
|
42
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jade-sql
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- agustin
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-06-14 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: jade-lang
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
description: Query and mutation builders, schema generation from db/structure.sql,
|
|
27
|
+
and an ActiveRecord-backed runtime for the Jade language. Renders typed queries
|
|
28
|
+
to (String, List(Value)) and decodes rows into Jade structs.
|
|
29
|
+
email:
|
|
30
|
+
- agustincornu@fastmail.com
|
|
31
|
+
executables:
|
|
32
|
+
- jade-sql
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.md
|
|
38
|
+
- docs/building.md
|
|
39
|
+
- docs/running.md
|
|
40
|
+
- exe/jade-sql
|
|
41
|
+
- lib/jade-sql.rb
|
|
42
|
+
- lib/jade-sql/bin/generate_schema.rb
|
|
43
|
+
- lib/jade-sql/runtime.rb
|
|
44
|
+
- lib/jade-sql/sql.jd
|
|
45
|
+
- lib/jade-sql/sql/loader.jd
|
|
46
|
+
- lib/jade-sql/sql/mutation.jd
|
|
47
|
+
- lib/jade-sql/sql/query.jd
|
|
48
|
+
- lib/jade-sql/sql/uuid.jd
|
|
49
|
+
- lib/jade-sql/tasks.rake
|
|
50
|
+
- lib/jade-sql/uuid_runtime.rb
|
|
51
|
+
- lib/jade-sql/version.rb
|
|
52
|
+
homepage: https://github.com/agustinrhcp/jade-sql
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
source_code_uri: https://github.com/agustinrhcp/jade-sql
|
|
57
|
+
rubygems_mfa_required: 'true'
|
|
58
|
+
rdoc_options: []
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '3.4'
|
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
requirements: []
|
|
72
|
+
rubygems_version: 3.6.2
|
|
73
|
+
specification_version: 4
|
|
74
|
+
summary: Type-safe SQL extension for Jade
|
|
75
|
+
test_files: []
|