rooq 1.0.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/.tool-versions +1 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CLAUDE.md +54 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +116 -0
- data/LICENSE +661 -0
- data/README.md +98 -0
- data/Rakefile +130 -0
- data/USAGE.md +850 -0
- data/exe/rooq +7 -0
- data/lib/rooq/adapters/postgresql.rb +117 -0
- data/lib/rooq/adapters.rb +3 -0
- data/lib/rooq/cli.rb +230 -0
- data/lib/rooq/condition.rb +104 -0
- data/lib/rooq/configuration.rb +56 -0
- data/lib/rooq/connection.rb +131 -0
- data/lib/rooq/context.rb +141 -0
- data/lib/rooq/dialect/base.rb +27 -0
- data/lib/rooq/dialect/postgresql.rb +531 -0
- data/lib/rooq/dialect.rb +9 -0
- data/lib/rooq/dsl/delete_query.rb +37 -0
- data/lib/rooq/dsl/insert_query.rb +43 -0
- data/lib/rooq/dsl/select_query.rb +301 -0
- data/lib/rooq/dsl/update_query.rb +44 -0
- data/lib/rooq/dsl.rb +28 -0
- data/lib/rooq/executor.rb +65 -0
- data/lib/rooq/expression.rb +494 -0
- data/lib/rooq/field.rb +71 -0
- data/lib/rooq/generator/code_generator.rb +91 -0
- data/lib/rooq/generator/introspector.rb +265 -0
- data/lib/rooq/generator.rb +9 -0
- data/lib/rooq/parameter_converter.rb +98 -0
- data/lib/rooq/query_validator.rb +176 -0
- data/lib/rooq/result.rb +248 -0
- data/lib/rooq/schema_validator.rb +56 -0
- data/lib/rooq/table.rb +69 -0
- data/lib/rooq/version.rb +5 -0
- data/lib/rooq.rb +25 -0
- data/rooq.gemspec +35 -0
- data/sorbet/config +4 -0
- metadata +115 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Rooq
|
|
7
|
+
# Base class for all SQL expressions
|
|
8
|
+
class Expression
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(alias_name: Symbol).returns(AliasedExpression) }
|
|
12
|
+
def as(alias_name)
|
|
13
|
+
AliasedExpression.new(self, alias_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
sig { params(_dialect: T.untyped).returns(T.untyped) }
|
|
17
|
+
def to_sql(_dialect)
|
|
18
|
+
raise NotImplementedError, "Subclasses must implement #to_sql"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Comparison operators return Conditions
|
|
22
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
23
|
+
def eq(other)
|
|
24
|
+
Condition.new(self, :eq, other)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
28
|
+
def ne(other)
|
|
29
|
+
Condition.new(self, :ne, other)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
33
|
+
def gt(other)
|
|
34
|
+
Condition.new(self, :gt, other)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
38
|
+
def lt(other)
|
|
39
|
+
Condition.new(self, :lt, other)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
43
|
+
def gte(other)
|
|
44
|
+
Condition.new(self, :gte, other)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { params(other: T.untyped).returns(Condition) }
|
|
48
|
+
def lte(other)
|
|
49
|
+
Condition.new(self, :lte, other)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { params(values: T.any(T::Array[T.untyped], DSL::SelectQuery)).returns(Condition) }
|
|
53
|
+
def in(values)
|
|
54
|
+
Condition.new(self, :in, values)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
sig { params(pattern: String).returns(Condition) }
|
|
58
|
+
def like(pattern)
|
|
59
|
+
Condition.new(self, :like, pattern)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
sig { params(min: T.untyped, max: T.untyped).returns(Condition) }
|
|
63
|
+
def between(min, max)
|
|
64
|
+
Condition.new(self, :between, [min, max])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sig { returns(Condition) }
|
|
68
|
+
def is_null
|
|
69
|
+
Condition.new(self, :is_null, nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
sig { returns(Condition) }
|
|
73
|
+
def is_not_null
|
|
74
|
+
Condition.new(self, :is_not_null, nil)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Ordering
|
|
78
|
+
sig { returns(OrderSpecification) }
|
|
79
|
+
def asc
|
|
80
|
+
OrderSpecification.new(self, :asc)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
sig { returns(OrderSpecification) }
|
|
84
|
+
def desc
|
|
85
|
+
OrderSpecification.new(self, :desc)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Arithmetic operators
|
|
89
|
+
sig { params(other: T.any(Expression, Numeric)).returns(ArithmeticExpression) }
|
|
90
|
+
def +(other)
|
|
91
|
+
ArithmeticExpression.new(self, :+, other)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sig { params(other: T.any(Expression, Numeric)).returns(ArithmeticExpression) }
|
|
95
|
+
def -(other)
|
|
96
|
+
ArithmeticExpression.new(self, :-, other)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
sig { params(other: T.any(Expression, Numeric)).returns(ArithmeticExpression) }
|
|
100
|
+
def *(other)
|
|
101
|
+
ArithmeticExpression.new(self, :*, other)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
sig { params(other: T.any(Expression, Numeric)).returns(ArithmeticExpression) }
|
|
105
|
+
def /(other)
|
|
106
|
+
ArithmeticExpression.new(self, :/, other)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
sig { params(other: T.any(Expression, Numeric)).returns(ArithmeticExpression) }
|
|
110
|
+
def %(other)
|
|
111
|
+
ArithmeticExpression.new(self, :%, other)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class AliasedExpression < Expression
|
|
116
|
+
extend T::Sig
|
|
117
|
+
|
|
118
|
+
sig { returns(Expression) }
|
|
119
|
+
attr_reader :expression
|
|
120
|
+
|
|
121
|
+
sig { returns(Symbol) }
|
|
122
|
+
attr_reader :alias_name
|
|
123
|
+
|
|
124
|
+
sig { params(expression: Expression, alias_name: Symbol).void }
|
|
125
|
+
def initialize(expression, alias_name)
|
|
126
|
+
@expression = expression
|
|
127
|
+
@alias_name = alias_name
|
|
128
|
+
freeze
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class ArithmeticExpression < Expression
|
|
133
|
+
extend T::Sig
|
|
134
|
+
|
|
135
|
+
sig { returns(T.any(Expression, Numeric)) }
|
|
136
|
+
attr_reader :left
|
|
137
|
+
|
|
138
|
+
sig { returns(Symbol) }
|
|
139
|
+
attr_reader :operator
|
|
140
|
+
|
|
141
|
+
sig { returns(T.any(Expression, Numeric)) }
|
|
142
|
+
attr_reader :right
|
|
143
|
+
|
|
144
|
+
sig { params(left: T.any(Expression, Numeric), operator: Symbol, right: T.any(Expression, Numeric)).void }
|
|
145
|
+
def initialize(left, operator, right)
|
|
146
|
+
@left = left
|
|
147
|
+
@operator = operator
|
|
148
|
+
@right = right
|
|
149
|
+
freeze
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class Literal < Expression
|
|
154
|
+
extend T::Sig
|
|
155
|
+
|
|
156
|
+
sig { returns(T.untyped) }
|
|
157
|
+
attr_reader :value
|
|
158
|
+
|
|
159
|
+
sig { params(value: T.untyped).void }
|
|
160
|
+
def initialize(value)
|
|
161
|
+
@value = value
|
|
162
|
+
freeze
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Function call expression
|
|
167
|
+
class FunctionCall < Expression
|
|
168
|
+
extend T::Sig
|
|
169
|
+
|
|
170
|
+
sig { returns(Symbol) }
|
|
171
|
+
attr_reader :name
|
|
172
|
+
|
|
173
|
+
sig { returns(T::Array[T.untyped]) }
|
|
174
|
+
attr_reader :arguments
|
|
175
|
+
|
|
176
|
+
sig { returns(T::Boolean) }
|
|
177
|
+
attr_reader :distinct
|
|
178
|
+
|
|
179
|
+
sig { params(name: Symbol, arguments: T.untyped, distinct: T::Boolean).void }
|
|
180
|
+
def initialize(name, *arguments, distinct: false)
|
|
181
|
+
@name = name
|
|
182
|
+
@arguments = T.let(arguments.flatten.freeze, T::Array[T.untyped])
|
|
183
|
+
@distinct = distinct
|
|
184
|
+
freeze
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Aggregate functions
|
|
189
|
+
module Aggregates
|
|
190
|
+
extend T::Sig
|
|
191
|
+
|
|
192
|
+
class << self
|
|
193
|
+
extend T::Sig
|
|
194
|
+
|
|
195
|
+
sig { params(expression: T.nilable(Expression), distinct: T::Boolean).returns(FunctionCall) }
|
|
196
|
+
def count(expression = nil, distinct: false)
|
|
197
|
+
expression ||= Literal.new(:*)
|
|
198
|
+
FunctionCall.new(:count, expression, distinct: distinct)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
sig { params(expression: Expression, distinct: T::Boolean).returns(FunctionCall) }
|
|
202
|
+
def sum(expression, distinct: false)
|
|
203
|
+
FunctionCall.new(:sum, expression, distinct: distinct)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
sig { params(expression: Expression, distinct: T::Boolean).returns(FunctionCall) }
|
|
207
|
+
def avg(expression, distinct: false)
|
|
208
|
+
FunctionCall.new(:avg, expression, distinct: distinct)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
sig { params(expression: Expression).returns(FunctionCall) }
|
|
212
|
+
def min(expression)
|
|
213
|
+
FunctionCall.new(:min, expression)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
sig { params(expression: Expression).returns(FunctionCall) }
|
|
217
|
+
def max(expression)
|
|
218
|
+
FunctionCall.new(:max, expression)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
sig { params(expression: Expression, distinct: T::Boolean).returns(FunctionCall) }
|
|
222
|
+
def array_agg(expression, distinct: false)
|
|
223
|
+
FunctionCall.new(:array_agg, expression, distinct: distinct)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
sig { params(expression: Expression, delimiter: String, distinct: T::Boolean).returns(FunctionCall) }
|
|
227
|
+
def string_agg(expression, delimiter, distinct: false)
|
|
228
|
+
FunctionCall.new(:string_agg, expression, delimiter, distinct: distinct)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Window function expression
|
|
234
|
+
class WindowFunction < Expression
|
|
235
|
+
extend T::Sig
|
|
236
|
+
|
|
237
|
+
FrameBound = T.type_alias { T.any(Symbol, T::Array[T.any(Symbol, Integer)]) }
|
|
238
|
+
|
|
239
|
+
sig { returns(FunctionCall) }
|
|
240
|
+
attr_reader :function
|
|
241
|
+
|
|
242
|
+
sig { returns(T.nilable(WindowFrame)) }
|
|
243
|
+
attr_reader :frame
|
|
244
|
+
|
|
245
|
+
sig do
|
|
246
|
+
params(
|
|
247
|
+
function: FunctionCall,
|
|
248
|
+
partition_fields: T::Array[Expression],
|
|
249
|
+
order_specs: T::Array[OrderSpecification],
|
|
250
|
+
frame: T.nilable(WindowFrame)
|
|
251
|
+
).void
|
|
252
|
+
end
|
|
253
|
+
def initialize(function, partition_fields: [], order_specs: [], frame: nil)
|
|
254
|
+
@function = function
|
|
255
|
+
@partition_fields = T.let(Array(partition_fields).freeze, T::Array[Expression])
|
|
256
|
+
@order_specs = T.let(Array(order_specs).freeze, T::Array[OrderSpecification])
|
|
257
|
+
@frame = frame
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
sig { params(fields: Expression).returns(T.any(T::Array[Expression], WindowFunction)) }
|
|
261
|
+
def partition_by(*fields)
|
|
262
|
+
return @partition_fields if fields.empty?
|
|
263
|
+
WindowFunction.new(@function, partition_fields: @partition_fields + fields.flatten, order_specs: @order_specs, frame: @frame)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
sig { params(specs: OrderSpecification).returns(T.any(T::Array[OrderSpecification], WindowFunction)) }
|
|
267
|
+
def order_by(*specs)
|
|
268
|
+
return @order_specs if specs.empty?
|
|
269
|
+
WindowFunction.new(@function, partition_fields: @partition_fields, order_specs: @order_specs + specs.flatten, frame: @frame)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
sig { params(start_bound: FrameBound, end_bound: T.nilable(FrameBound)).returns(WindowFunction) }
|
|
273
|
+
def rows(start_bound, end_bound = nil)
|
|
274
|
+
new_frame = WindowFrame.new(WindowFrame::ROWS, start_bound, end_bound)
|
|
275
|
+
WindowFunction.new(@function, partition_fields: @partition_fields, order_specs: @order_specs, frame: new_frame)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
sig { params(start_bound: FrameBound, end_bound: FrameBound).returns(WindowFunction) }
|
|
279
|
+
def rows_between(start_bound, end_bound)
|
|
280
|
+
new_frame = WindowFrame.new(WindowFrame::ROWS, start_bound, end_bound)
|
|
281
|
+
WindowFunction.new(@function, partition_fields: @partition_fields, order_specs: @order_specs, frame: new_frame)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
sig { params(start_bound: FrameBound, end_bound: FrameBound).returns(WindowFunction) }
|
|
285
|
+
def range_between(start_bound, end_bound)
|
|
286
|
+
new_frame = WindowFrame.new(WindowFrame::RANGE, start_bound, end_bound)
|
|
287
|
+
WindowFunction.new(@function, partition_fields: @partition_fields, order_specs: @order_specs, frame: new_frame)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
sig do
|
|
291
|
+
params(
|
|
292
|
+
partition_by: T::Array[Expression],
|
|
293
|
+
order_by: T::Array[OrderSpecification],
|
|
294
|
+
frame: T.nilable(WindowFrame)
|
|
295
|
+
).returns(WindowFunction)
|
|
296
|
+
end
|
|
297
|
+
def over(partition_by: [], order_by: [], frame: nil)
|
|
298
|
+
WindowFunction.new(@function, partition_fields: partition_by, order_specs: order_by, frame: frame)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Window frame specification
|
|
303
|
+
class WindowFrame
|
|
304
|
+
extend T::Sig
|
|
305
|
+
|
|
306
|
+
FrameBound = T.type_alias { T.any(Symbol, T::Array[T.any(Symbol, Integer)]) }
|
|
307
|
+
|
|
308
|
+
sig { returns(Symbol) }
|
|
309
|
+
attr_reader :type
|
|
310
|
+
|
|
311
|
+
sig { returns(FrameBound) }
|
|
312
|
+
attr_reader :start_bound
|
|
313
|
+
|
|
314
|
+
sig { returns(T.nilable(FrameBound)) }
|
|
315
|
+
attr_reader :end_bound
|
|
316
|
+
|
|
317
|
+
ROWS = T.let(:rows, Symbol)
|
|
318
|
+
RANGE = T.let(:range, Symbol)
|
|
319
|
+
GROUPS = T.let(:groups, Symbol)
|
|
320
|
+
|
|
321
|
+
UNBOUNDED_PRECEDING = T.let(:unbounded_preceding, Symbol)
|
|
322
|
+
CURRENT_ROW = T.let(:current_row, Symbol)
|
|
323
|
+
UNBOUNDED_FOLLOWING = T.let(:unbounded_following, Symbol)
|
|
324
|
+
|
|
325
|
+
sig { params(type: Symbol, start_bound: FrameBound, end_bound: T.nilable(FrameBound)).void }
|
|
326
|
+
def initialize(type, start_bound, end_bound = nil)
|
|
327
|
+
@type = type
|
|
328
|
+
@start_bound = start_bound
|
|
329
|
+
@end_bound = end_bound
|
|
330
|
+
freeze
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
class << self
|
|
334
|
+
extend T::Sig
|
|
335
|
+
|
|
336
|
+
sig { params(start_bound: FrameBound, end_bound: T.nilable(FrameBound)).returns(WindowFrame) }
|
|
337
|
+
def rows(start_bound, end_bound = nil)
|
|
338
|
+
WindowFrame.new(ROWS, start_bound, end_bound)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
sig { params(start_bound: FrameBound, end_bound: T.nilable(FrameBound)).returns(WindowFrame) }
|
|
342
|
+
def range(start_bound, end_bound = nil)
|
|
343
|
+
WindowFrame.new(RANGE, start_bound, end_bound)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
sig { params(n: Integer).returns(T::Array[T.any(Symbol, Integer)]) }
|
|
347
|
+
def preceding(n)
|
|
348
|
+
[:preceding, n]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
sig { params(n: Integer).returns(T::Array[T.any(Symbol, Integer)]) }
|
|
352
|
+
def following(n)
|
|
353
|
+
[:following, n]
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Window functions module
|
|
359
|
+
module WindowFunctions
|
|
360
|
+
extend T::Sig
|
|
361
|
+
|
|
362
|
+
class << self
|
|
363
|
+
extend T::Sig
|
|
364
|
+
|
|
365
|
+
sig { returns(WindowFunction) }
|
|
366
|
+
def row_number
|
|
367
|
+
WindowFunction.new(FunctionCall.new(:row_number))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
sig { returns(WindowFunction) }
|
|
371
|
+
def rank
|
|
372
|
+
WindowFunction.new(FunctionCall.new(:rank))
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
sig { returns(WindowFunction) }
|
|
376
|
+
def dense_rank
|
|
377
|
+
WindowFunction.new(FunctionCall.new(:dense_rank))
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
sig { params(n: Integer).returns(WindowFunction) }
|
|
381
|
+
def ntile(n)
|
|
382
|
+
WindowFunction.new(FunctionCall.new(:ntile, Literal.new(n)))
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
sig { params(expression: Expression, offset: Integer, default: T.nilable(T.untyped)).returns(WindowFunction) }
|
|
386
|
+
def lag(expression, offset = 1, default = nil)
|
|
387
|
+
args = [expression, Literal.new(offset)]
|
|
388
|
+
args << default if default
|
|
389
|
+
WindowFunction.new(FunctionCall.new(:lag, *args))
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
sig { params(expression: Expression, offset: Integer, default: T.nilable(T.untyped)).returns(WindowFunction) }
|
|
393
|
+
def lead(expression, offset = 1, default = nil)
|
|
394
|
+
args = [expression, Literal.new(offset)]
|
|
395
|
+
args << default if default
|
|
396
|
+
WindowFunction.new(FunctionCall.new(:lead, *args))
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
sig { params(expression: Expression).returns(WindowFunction) }
|
|
400
|
+
def first_value(expression)
|
|
401
|
+
WindowFunction.new(FunctionCall.new(:first_value, expression))
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
sig { params(expression: Expression).returns(WindowFunction) }
|
|
405
|
+
def last_value(expression)
|
|
406
|
+
WindowFunction.new(FunctionCall.new(:last_value, expression))
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
sig { params(expression: Expression, n: Integer).returns(WindowFunction) }
|
|
410
|
+
def nth_value(expression, n)
|
|
411
|
+
WindowFunction.new(FunctionCall.new(:nth_value, expression, Literal.new(n)))
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# CASE WHEN expression
|
|
417
|
+
class CaseExpression < Expression
|
|
418
|
+
extend T::Sig
|
|
419
|
+
|
|
420
|
+
CasePair = T.type_alias { [Condition, Expression] }
|
|
421
|
+
|
|
422
|
+
sig { returns(T::Array[CasePair]) }
|
|
423
|
+
attr_reader :cases
|
|
424
|
+
|
|
425
|
+
sig { returns(T.nilable(Expression)) }
|
|
426
|
+
attr_reader :else_result
|
|
427
|
+
|
|
428
|
+
sig { void }
|
|
429
|
+
def initialize
|
|
430
|
+
@cases = T.let([], T::Array[CasePair])
|
|
431
|
+
@else_result = T.let(nil, T.nilable(Expression))
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
sig { params(condition: Condition, result: Expression).returns(CaseExpression) }
|
|
435
|
+
def when(condition, result)
|
|
436
|
+
new_case = CaseExpression.new
|
|
437
|
+
new_case.instance_variable_set(:@cases, @cases + [[condition, result]])
|
|
438
|
+
new_case.instance_variable_set(:@else_result, @else_result)
|
|
439
|
+
new_case
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
sig { params(result: Expression).returns(CaseExpression) }
|
|
443
|
+
def else(result)
|
|
444
|
+
new_case = CaseExpression.new
|
|
445
|
+
new_case.instance_variable_set(:@cases, @cases.dup)
|
|
446
|
+
new_case.instance_variable_set(:@else_result, result)
|
|
447
|
+
new_case.freeze
|
|
448
|
+
new_case
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
extend T::Sig
|
|
453
|
+
|
|
454
|
+
# Helper to create CASE expressions
|
|
455
|
+
sig { returns(CaseExpression) }
|
|
456
|
+
def self.case_when
|
|
457
|
+
CaseExpression.new
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# COALESCE function
|
|
461
|
+
sig { params(expressions: Expression).returns(FunctionCall) }
|
|
462
|
+
def self.coalesce(*expressions)
|
|
463
|
+
FunctionCall.new(:coalesce, *expressions)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# NULLIF function
|
|
467
|
+
sig { params(expr1: Expression, expr2: Expression).returns(FunctionCall) }
|
|
468
|
+
def self.nullif(expr1, expr2)
|
|
469
|
+
FunctionCall.new(:nullif, expr1, expr2)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# CAST expression
|
|
473
|
+
class CastExpression < Expression
|
|
474
|
+
extend T::Sig
|
|
475
|
+
|
|
476
|
+
sig { returns(Expression) }
|
|
477
|
+
attr_reader :expression
|
|
478
|
+
|
|
479
|
+
sig { returns(T.any(String, Symbol)) }
|
|
480
|
+
attr_reader :target_type
|
|
481
|
+
|
|
482
|
+
sig { params(expression: Expression, target_type: T.any(String, Symbol)).void }
|
|
483
|
+
def initialize(expression, target_type)
|
|
484
|
+
@expression = expression
|
|
485
|
+
@target_type = target_type
|
|
486
|
+
freeze
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
sig { params(expression: Expression, as: T.any(String, Symbol)).returns(CastExpression) }
|
|
491
|
+
def self.cast(expression, as:)
|
|
492
|
+
CastExpression.new(expression, as)
|
|
493
|
+
end
|
|
494
|
+
end
|
data/lib/rooq/field.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Rooq
|
|
7
|
+
class OrderSpecification
|
|
8
|
+
extend T::Sig
|
|
9
|
+
|
|
10
|
+
NullsPosition = T.type_alias { T.nilable(Symbol) }
|
|
11
|
+
|
|
12
|
+
sig { returns(Expression) }
|
|
13
|
+
attr_reader :expression
|
|
14
|
+
|
|
15
|
+
sig { returns(Symbol) }
|
|
16
|
+
attr_reader :direction
|
|
17
|
+
|
|
18
|
+
sig { returns(NullsPosition) }
|
|
19
|
+
attr_reader :nulls
|
|
20
|
+
|
|
21
|
+
sig { params(expression: Expression, direction: Symbol, nulls: NullsPosition).void }
|
|
22
|
+
def initialize(expression, direction, nulls: nil)
|
|
23
|
+
@expression = expression
|
|
24
|
+
@direction = direction
|
|
25
|
+
@nulls = nulls
|
|
26
|
+
freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sig { returns(OrderSpecification) }
|
|
30
|
+
def nulls_first
|
|
31
|
+
OrderSpecification.new(@expression, @direction, nulls: :first)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
sig { returns(OrderSpecification) }
|
|
35
|
+
def nulls_last
|
|
36
|
+
OrderSpecification.new(@expression, @direction, nulls: :last)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# For backwards compatibility
|
|
40
|
+
sig { returns(Expression) }
|
|
41
|
+
def field
|
|
42
|
+
@expression
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Field < Expression
|
|
47
|
+
extend T::Sig
|
|
48
|
+
|
|
49
|
+
sig { returns(Symbol) }
|
|
50
|
+
attr_reader :name
|
|
51
|
+
|
|
52
|
+
sig { returns(Symbol) }
|
|
53
|
+
attr_reader :table_name
|
|
54
|
+
|
|
55
|
+
sig { returns(Symbol) }
|
|
56
|
+
attr_reader :type
|
|
57
|
+
|
|
58
|
+
sig { params(name: Symbol, table_name: Symbol, type: Symbol).void }
|
|
59
|
+
def initialize(name, table_name, type)
|
|
60
|
+
@name = name
|
|
61
|
+
@table_name = table_name
|
|
62
|
+
@type = type
|
|
63
|
+
freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sig { returns(String) }
|
|
67
|
+
def qualified_name
|
|
68
|
+
"#{table_name}.#{name}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Rooq
|
|
7
|
+
module Generator
|
|
8
|
+
class CodeGenerator
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(schema_info: T::Array[T.untyped], typed: T::Boolean, namespace: String).void }
|
|
12
|
+
def initialize(schema_info, typed: true, namespace: "Schema")
|
|
13
|
+
@schema_info = schema_info
|
|
14
|
+
@typed = typed
|
|
15
|
+
@namespace = namespace
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
sig { returns(String) }
|
|
19
|
+
def generate
|
|
20
|
+
tables_code = @schema_info.map { |table_info| generate_table(table_info) }
|
|
21
|
+
|
|
22
|
+
if @typed
|
|
23
|
+
generate_typed(tables_code)
|
|
24
|
+
else
|
|
25
|
+
generate_untyped(tables_code)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
sig { params(tables_code: T::Array[String]).returns(String) }
|
|
32
|
+
def generate_typed(tables_code)
|
|
33
|
+
<<~RUBY
|
|
34
|
+
# typed: strict
|
|
35
|
+
# frozen_string_literal: true
|
|
36
|
+
|
|
37
|
+
# Auto-generated by Rooq - DO NOT EDIT
|
|
38
|
+
# Generated at: #{Time.now.utc.iso8601}
|
|
39
|
+
|
|
40
|
+
require "rooq"
|
|
41
|
+
require "sorbet-runtime"
|
|
42
|
+
|
|
43
|
+
module #{@namespace}
|
|
44
|
+
extend T::Sig
|
|
45
|
+
|
|
46
|
+
#{indent(tables_code.join("\n\n"), 2)}
|
|
47
|
+
end
|
|
48
|
+
RUBY
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sig { params(tables_code: T::Array[String]).returns(String) }
|
|
52
|
+
def generate_untyped(tables_code)
|
|
53
|
+
untyped_tables = tables_code.map { |code| code.gsub(/T\.let\((.*), Rooq::Table\)/, '\1') }
|
|
54
|
+
|
|
55
|
+
<<~RUBY
|
|
56
|
+
# frozen_string_literal: true
|
|
57
|
+
|
|
58
|
+
# Auto-generated by Rooq - DO NOT EDIT
|
|
59
|
+
# Generated at: #{Time.now.utc.iso8601}
|
|
60
|
+
|
|
61
|
+
require "rooq"
|
|
62
|
+
|
|
63
|
+
module #{@namespace}
|
|
64
|
+
#{indent(untyped_tables.join("\n\n"), 2)}
|
|
65
|
+
end
|
|
66
|
+
RUBY
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sig { params(table_info: T.untyped).returns(String) }
|
|
70
|
+
def generate_table(table_info)
|
|
71
|
+
const_name = table_info.name.to_s.upcase
|
|
72
|
+
fields_code = table_info.columns.map do |column|
|
|
73
|
+
" t.field :#{column.name}, :#{column.type}"
|
|
74
|
+
end.join("\n")
|
|
75
|
+
|
|
76
|
+
<<~RUBY.chomp
|
|
77
|
+
#{const_name} = T.let(Rooq::Table.new(:#{table_info.name}) do |t|
|
|
78
|
+
#{fields_code}
|
|
79
|
+
end, Rooq::Table)
|
|
80
|
+
RUBY
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
sig { params(text: String, spaces: Integer).returns(String) }
|
|
86
|
+
def indent(text, spaces)
|
|
87
|
+
text.lines.map { |line| "#{' ' * spaces}#{line}" }.join
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|