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.
@@ -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