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,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
|