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,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rooq
4
+ module DSL
5
+ class DeleteQuery
6
+ attr_reader :table, :conditions, :returning_fields
7
+
8
+ def initialize(table)
9
+ @table = table
10
+ @conditions = nil
11
+ @returning_fields = []
12
+ end
13
+
14
+ def where(condition)
15
+ dup_with(conditions: condition)
16
+ end
17
+
18
+ def returning(*fields)
19
+ dup_with(returning_fields: fields.flatten)
20
+ end
21
+
22
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
23
+ dialect.render_delete(self)
24
+ end
25
+
26
+ private
27
+
28
+ def dup_with(**changes)
29
+ new_query = self.class.allocate
30
+ new_query.instance_variable_set(:@table, changes.fetch(:table, @table))
31
+ new_query.instance_variable_set(:@conditions, changes.fetch(:conditions, @conditions))
32
+ new_query.instance_variable_set(:@returning_fields, changes.fetch(:returning_fields, @returning_fields))
33
+ new_query
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rooq
4
+ module DSL
5
+ class InsertQuery
6
+ attr_reader :table, :column_list, :insert_values, :returning_fields
7
+
8
+ def initialize(table)
9
+ @table = table
10
+ @column_list = []
11
+ @insert_values = []
12
+ @returning_fields = []
13
+ end
14
+
15
+ def columns(*cols)
16
+ dup_with(column_list: cols.flatten)
17
+ end
18
+
19
+ def values(*vals)
20
+ dup_with(insert_values: @insert_values + [vals.flatten])
21
+ end
22
+
23
+ def returning(*fields)
24
+ dup_with(returning_fields: fields.flatten)
25
+ end
26
+
27
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
28
+ dialect.render_insert(self)
29
+ end
30
+
31
+ private
32
+
33
+ def dup_with(**changes)
34
+ new_query = self.class.allocate
35
+ new_query.instance_variable_set(:@table, changes.fetch(:table, @table))
36
+ new_query.instance_variable_set(:@column_list, changes.fetch(:column_list, @column_list))
37
+ new_query.instance_variable_set(:@insert_values, changes.fetch(:insert_values, @insert_values))
38
+ new_query.instance_variable_set(:@returning_fields, changes.fetch(:returning_fields, @returning_fields))
39
+ new_query
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rooq
4
+ module DSL
5
+ class SelectQuery
6
+ attr_reader :selected_fields, :from_table, :conditions, :order_specs,
7
+ :limit_value, :offset_value, :joins, :distinct_flag,
8
+ :group_by_fields, :having_condition, :ctes, :for_update_flag,
9
+ :table_alias
10
+
11
+ def initialize(fields)
12
+ @selected_fields = fields.flatten.freeze
13
+ @from_table = nil
14
+ @table_alias = nil
15
+ @conditions = nil
16
+ @order_specs = []
17
+ @limit_value = nil
18
+ @offset_value = nil
19
+ @joins = []
20
+ @distinct_flag = false
21
+ @group_by_fields = []
22
+ @having_condition = nil
23
+ @ctes = []
24
+ @for_update_flag = false
25
+ end
26
+
27
+ def distinct
28
+ dup_with(distinct_flag: true)
29
+ end
30
+
31
+ def from(table, as: nil)
32
+ dup_with(from_table: table, table_alias: as)
33
+ end
34
+
35
+ def where(condition)
36
+ if @conditions
37
+ dup_with(conditions: @conditions.and(condition))
38
+ else
39
+ dup_with(conditions: condition)
40
+ end
41
+ end
42
+
43
+ def and_where(condition)
44
+ where(condition)
45
+ end
46
+
47
+ def or_where(condition)
48
+ if @conditions
49
+ dup_with(conditions: @conditions.or(condition))
50
+ else
51
+ dup_with(conditions: condition)
52
+ end
53
+ end
54
+
55
+ def group_by(*fields)
56
+ dup_with(group_by_fields: @group_by_fields + fields.flatten)
57
+ end
58
+
59
+ def having(condition)
60
+ if @having_condition
61
+ dup_with(having_condition: @having_condition.and(condition))
62
+ else
63
+ dup_with(having_condition: condition)
64
+ end
65
+ end
66
+
67
+ def order_by(*specs)
68
+ dup_with(order_specs: @order_specs + specs.flatten)
69
+ end
70
+
71
+ def limit(value)
72
+ dup_with(limit_value: value)
73
+ end
74
+
75
+ def offset(value)
76
+ dup_with(offset_value: value)
77
+ end
78
+
79
+ def for_update
80
+ dup_with(for_update_flag: true)
81
+ end
82
+
83
+ # JOIN methods
84
+ def inner_join(table, as: nil)
85
+ JoinBuilder.new(self, :inner, table, as)
86
+ end
87
+
88
+ def left_join(table, as: nil)
89
+ JoinBuilder.new(self, :left, table, as)
90
+ end
91
+
92
+ def right_join(table, as: nil)
93
+ JoinBuilder.new(self, :right, table, as)
94
+ end
95
+
96
+ def full_join(table, as: nil)
97
+ JoinBuilder.new(self, :full, table, as)
98
+ end
99
+
100
+ def cross_join(table, as: nil)
101
+ join = Join.new(:cross, table, nil, as)
102
+ dup_with(joins: @joins + [join])
103
+ end
104
+
105
+ def add_join(join)
106
+ dup_with(joins: @joins + [join])
107
+ end
108
+
109
+ # CTE support
110
+ def with(name, query, recursive: false)
111
+ cte = CTE.new(name, query, recursive: recursive)
112
+ dup_with(ctes: @ctes + [cte])
113
+ end
114
+
115
+ # Set operations - return a new combined query
116
+ def union(other, all: false)
117
+ SetOperation.new(:union, self, other, all: all)
118
+ end
119
+
120
+ def intersect(other, all: false)
121
+ SetOperation.new(:intersect, self, other, all: all)
122
+ end
123
+
124
+ def except(other, all: false)
125
+ SetOperation.new(:except, self, other, all: all)
126
+ end
127
+
128
+ # Convert to subquery for use in FROM or conditions
129
+ def as_subquery(alias_name)
130
+ Subquery.new(self, alias_name)
131
+ end
132
+
133
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
134
+ dialect.render_select(self)
135
+ end
136
+
137
+ private
138
+
139
+ def dup_with(**changes)
140
+ new_query = self.class.allocate
141
+ new_query.instance_variable_set(:@selected_fields, changes.fetch(:selected_fields, @selected_fields))
142
+ new_query.instance_variable_set(:@from_table, changes.fetch(:from_table, @from_table))
143
+ new_query.instance_variable_set(:@table_alias, changes.fetch(:table_alias, @table_alias))
144
+ new_query.instance_variable_set(:@conditions, changes.fetch(:conditions, @conditions))
145
+ new_query.instance_variable_set(:@order_specs, changes.fetch(:order_specs, @order_specs))
146
+ new_query.instance_variable_set(:@limit_value, changes.fetch(:limit_value, @limit_value))
147
+ new_query.instance_variable_set(:@offset_value, changes.fetch(:offset_value, @offset_value))
148
+ new_query.instance_variable_set(:@joins, changes.fetch(:joins, @joins))
149
+ new_query.instance_variable_set(:@distinct_flag, changes.fetch(:distinct_flag, @distinct_flag))
150
+ new_query.instance_variable_set(:@group_by_fields, changes.fetch(:group_by_fields, @group_by_fields))
151
+ new_query.instance_variable_set(:@having_condition, changes.fetch(:having_condition, @having_condition))
152
+ new_query.instance_variable_set(:@ctes, changes.fetch(:ctes, @ctes))
153
+ new_query.instance_variable_set(:@for_update_flag, changes.fetch(:for_update_flag, @for_update_flag))
154
+ new_query
155
+ end
156
+ end
157
+
158
+ class JoinBuilder
159
+ def initialize(query, type, table, table_alias)
160
+ @query = query
161
+ @type = type
162
+ @table = table
163
+ @table_alias = table_alias
164
+ end
165
+
166
+ def on(condition)
167
+ join = Join.new(@type, @table, condition, @table_alias)
168
+ @query.add_join(join)
169
+ end
170
+
171
+ def using(*columns)
172
+ join = Join.new(@type, @table, nil, @table_alias, using: columns.flatten)
173
+ @query.add_join(join)
174
+ end
175
+ end
176
+
177
+ class Join
178
+ attr_reader :type, :table, :condition, :table_alias, :using_columns
179
+
180
+ def initialize(type, table, condition, table_alias = nil, using: nil)
181
+ @type = type
182
+ @table = table
183
+ @condition = condition
184
+ @table_alias = table_alias
185
+ @using_columns = using&.freeze
186
+ freeze
187
+ end
188
+ end
189
+
190
+ class CTE
191
+ attr_reader :name, :query, :recursive
192
+
193
+ def initialize(name, query, recursive: false)
194
+ @name = name
195
+ @query = query
196
+ @recursive = recursive
197
+ freeze
198
+ end
199
+ end
200
+
201
+ class Subquery
202
+ attr_reader :query, :alias_name
203
+
204
+ def initialize(query, alias_name)
205
+ @query = query
206
+ @alias_name = alias_name
207
+ freeze
208
+ end
209
+
210
+ # Allow subquery to be used in conditions
211
+ def in(values)
212
+ Rooq::Condition.new(self, :in, values)
213
+ end
214
+ end
215
+
216
+ class SetOperation
217
+ attr_reader :operator, :left, :right, :all
218
+
219
+ def initialize(operator, left, right, all: false)
220
+ @operator = operator
221
+ @left = left
222
+ @right = right
223
+ @all = all
224
+ freeze
225
+ end
226
+
227
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
228
+ dialect.render_set_operation(self)
229
+ end
230
+
231
+ # Allow chaining
232
+ def union(other, all: false)
233
+ SetOperation.new(:union, self, other, all: all)
234
+ end
235
+
236
+ def intersect(other, all: false)
237
+ SetOperation.new(:intersect, self, other, all: all)
238
+ end
239
+
240
+ def except(other, all: false)
241
+ SetOperation.new(:except, self, other, all: all)
242
+ end
243
+
244
+ def order_by(*specs)
245
+ OrderedSetOperation.new(self, specs.flatten)
246
+ end
247
+ end
248
+
249
+ class OrderedSetOperation
250
+ attr_reader :set_operation, :order_specs, :limit_value, :offset_value
251
+
252
+ def initialize(set_operation, order_specs, limit_value: nil, offset_value: nil)
253
+ @set_operation = set_operation
254
+ @order_specs = order_specs.freeze
255
+ @limit_value = limit_value
256
+ @offset_value = offset_value
257
+ freeze
258
+ end
259
+
260
+ def limit(value)
261
+ OrderedSetOperation.new(@set_operation, @order_specs, limit_value: value, offset_value: @offset_value)
262
+ end
263
+
264
+ def offset(value)
265
+ OrderedSetOperation.new(@set_operation, @order_specs, limit_value: @limit_value, offset_value: value)
266
+ end
267
+
268
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
269
+ dialect.render_ordered_set_operation(self)
270
+ end
271
+ end
272
+
273
+ # Grouping sets for advanced GROUP BY
274
+ class GroupingSets
275
+ attr_reader :sets
276
+
277
+ def initialize(*sets)
278
+ @sets = sets.freeze
279
+ freeze
280
+ end
281
+ end
282
+
283
+ class Cube
284
+ attr_reader :fields
285
+
286
+ def initialize(*fields)
287
+ @fields = fields.flatten.freeze
288
+ freeze
289
+ end
290
+ end
291
+
292
+ class Rollup
293
+ attr_reader :fields
294
+
295
+ def initialize(*fields)
296
+ @fields = fields.flatten.freeze
297
+ freeze
298
+ end
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rooq
4
+ module DSL
5
+ class UpdateQuery
6
+ attr_reader :table, :set_values, :conditions, :returning_fields
7
+
8
+ def initialize(table)
9
+ @table = table
10
+ @set_values = {}
11
+ @conditions = nil
12
+ @returning_fields = []
13
+ end
14
+
15
+ def set(field, value)
16
+ new_set_values = @set_values.merge(field => value)
17
+ dup_with(set_values: new_set_values)
18
+ end
19
+
20
+ def where(condition)
21
+ dup_with(conditions: condition)
22
+ end
23
+
24
+ def returning(*fields)
25
+ dup_with(returning_fields: fields.flatten)
26
+ end
27
+
28
+ def to_sql(dialect = Rooq::Dialect::PostgreSQL.new)
29
+ dialect.render_update(self)
30
+ end
31
+
32
+ private
33
+
34
+ def dup_with(**changes)
35
+ new_query = self.class.allocate
36
+ new_query.instance_variable_set(:@table, changes.fetch(:table, @table))
37
+ new_query.instance_variable_set(:@set_values, changes.fetch(:set_values, @set_values))
38
+ new_query.instance_variable_set(:@conditions, changes.fetch(:conditions, @conditions))
39
+ new_query.instance_variable_set(:@returning_fields, changes.fetch(:returning_fields, @returning_fields))
40
+ new_query
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/rooq/dsl.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dsl/select_query"
4
+ require_relative "dsl/insert_query"
5
+ require_relative "dsl/update_query"
6
+ require_relative "dsl/delete_query"
7
+
8
+ module Rooq
9
+ module DSL
10
+ class << self
11
+ def select(*fields)
12
+ SelectQuery.new(fields)
13
+ end
14
+
15
+ def insert_into(table)
16
+ InsertQuery.new(table)
17
+ end
18
+
19
+ def update(table)
20
+ UpdateQuery.new(table)
21
+ end
22
+
23
+ def delete_from(table)
24
+ DeleteQuery.new(table)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rooq
4
+ class Executor
5
+ attr_reader :connection, :dialect, :hooks
6
+
7
+ def initialize(connection, dialect: Dialect::PostgreSQL.new)
8
+ @connection = connection
9
+ @dialect = dialect
10
+ @hooks = ExecutorHooks.new
11
+ end
12
+
13
+ def execute(query)
14
+ rendered = query.to_sql(@dialect)
15
+
16
+ hooks.before_execute&.call(rendered)
17
+
18
+ result = @connection.exec_params(rendered.sql, rendered.params)
19
+
20
+ hooks.after_execute&.call(rendered, result)
21
+
22
+ result
23
+ end
24
+
25
+ def fetch_one(query)
26
+ result = execute(query)
27
+ return nil if result.ntuples.zero?
28
+
29
+ result[0]
30
+ end
31
+
32
+ def fetch_all(query)
33
+ result = execute(query)
34
+ result.to_a
35
+ end
36
+
37
+ def on_before_execute(&block)
38
+ @hooks = @hooks.with_before_execute(block)
39
+ self
40
+ end
41
+
42
+ def on_after_execute(&block)
43
+ @hooks = @hooks.with_after_execute(block)
44
+ self
45
+ end
46
+ end
47
+
48
+ class ExecutorHooks
49
+ attr_reader :before_execute, :after_execute
50
+
51
+ def initialize(before_execute: nil, after_execute: nil)
52
+ @before_execute = before_execute
53
+ @after_execute = after_execute
54
+ freeze
55
+ end
56
+
57
+ def with_before_execute(block)
58
+ ExecutorHooks.new(before_execute: block, after_execute: @after_execute)
59
+ end
60
+
61
+ def with_after_execute(block)
62
+ ExecutorHooks.new(before_execute: @before_execute, after_execute: block)
63
+ end
64
+ end
65
+ end