torque-postgresql 3.4.1 → 4.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 +4 -4
- data/lib/generators/torque/function_generator.rb +13 -0
- data/lib/generators/torque/templates/function.sql.erb +4 -0
- data/lib/generators/torque/templates/type.sql.erb +2 -0
- data/lib/generators/torque/templates/view.sql.erb +3 -0
- data/lib/generators/torque/type_generator.rb +13 -0
- data/lib/generators/torque/view_generator.rb +16 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +111 -94
- data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
- data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
- data/lib/torque/postgresql/adapter/oid.rb +1 -23
- data/lib/torque/postgresql/adapter/quoting.rb +13 -7
- data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
- data/lib/torque/postgresql/adapter/schema_definitions.rb +58 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +136 -34
- data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
- data/lib/torque/postgresql/adapter/schema_statements.rb +109 -49
- data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
- data/lib/torque/postgresql/arel/nodes.rb +16 -2
- data/lib/torque/postgresql/arel/operations.rb +7 -1
- data/lib/torque/postgresql/arel/visitors.rb +7 -9
- data/lib/torque/postgresql/associations/association_scope.rb +23 -31
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
- data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
- data/lib/torque/postgresql/attributes/builder/full_text_search.rb +109 -0
- data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
- data/lib/torque/postgresql/attributes/builder.rb +49 -11
- data/lib/torque/postgresql/attributes/enum.rb +7 -7
- data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
- data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
- data/lib/torque/postgresql/attributes/period.rb +2 -2
- data/lib/torque/postgresql/attributes.rb +0 -4
- data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
- data/lib/torque/postgresql/base.rb +5 -11
- data/lib/torque/postgresql/collector.rb +1 -1
- data/lib/torque/postgresql/config.rb +129 -5
- data/lib/torque/postgresql/function.rb +94 -0
- data/lib/torque/postgresql/inheritance.rb +52 -36
- data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
- data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
- data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
- data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
- data/lib/torque/postgresql/predicate_builder.rb +35 -0
- data/lib/torque/postgresql/railtie.rb +137 -30
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -2
- data/lib/torque/postgresql/relation/buckets.rb +124 -0
- data/lib/torque/postgresql/relation/distinct_on.rb +7 -2
- data/lib/torque/postgresql/relation/inheritance.rb +22 -15
- data/lib/torque/postgresql/relation/join_series.rb +112 -0
- data/lib/torque/postgresql/relation/merger.rb +17 -3
- data/lib/torque/postgresql/relation.rb +24 -38
- data/lib/torque/postgresql/schema_cache.rb +6 -12
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql/versioned_commands/command_migration.rb +146 -0
- data/lib/torque/postgresql/versioned_commands/generator.rb +57 -0
- data/lib/torque/postgresql/versioned_commands/migration_context.rb +83 -0
- data/lib/torque/postgresql/versioned_commands/migrator.rb +39 -0
- data/lib/torque/postgresql/versioned_commands/schema_table.rb +101 -0
- data/lib/torque/postgresql/versioned_commands.rb +161 -0
- data/lib/torque/postgresql.rb +2 -1
- data/spec/fixtures/migrations/20250101000001_create_users.rb +0 -0
- data/spec/fixtures/migrations/20250101000002_create_function_count_users_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000003_create_internal_users.rb +0 -0
- data/spec/fixtures/migrations/20250101000004_update_function_count_users_v2.sql +0 -0
- data/spec/fixtures/migrations/20250101000005_create_view_all_users_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000006_create_type_user_id_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000007_remove_function_count_users_v2.sql +0 -0
- data/spec/initialize.rb +67 -0
- data/spec/mocks/cache_query.rb +21 -21
- data/spec/mocks/create_table.rb +6 -26
- data/spec/schema.rb +17 -12
- data/spec/spec_helper.rb +11 -2
- data/spec/tests/arel_spec.rb +32 -7
- data/spec/tests/auxiliary_statement_spec.rb +3 -3
- data/spec/tests/belongs_to_many_spec.rb +72 -5
- data/spec/tests/enum_set_spec.rb +12 -11
- data/spec/tests/enum_spec.rb +4 -2
- data/spec/tests/full_text_seach_test.rb +280 -0
- data/spec/tests/function_spec.rb +42 -0
- data/spec/tests/has_many_spec.rb +21 -8
- data/spec/tests/interval_spec.rb +1 -7
- data/spec/tests/period_spec.rb +61 -61
- data/spec/tests/predicate_builder_spec.rb +132 -0
- data/spec/tests/relation_spec.rb +229 -0
- data/spec/tests/schema_spec.rb +6 -9
- data/spec/tests/table_inheritance_spec.rb +25 -26
- data/spec/tests/versioned_commands_spec.rb +513 -0
- metadata +64 -39
@@ -136,10 +136,10 @@ module Torque
|
|
136
136
|
|
137
137
|
col = table[name]
|
138
138
|
base.select_extra_values += [col.as(as)] unless as.nil?
|
139
|
-
parts = [col, source.
|
139
|
+
parts = [col, source.pg_cast(:varchar)]
|
140
140
|
|
141
|
-
columns << ::Arel.array([source]).
|
142
|
-
sub_columns <<
|
141
|
+
columns << ::Arel.array([source]).pg_cast(:varchar, true).as(name)
|
142
|
+
sub_columns << FN.array_append(*parts).as(name)
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
@@ -16,11 +16,13 @@ module Torque
|
|
16
16
|
class_attribute :schema, instance_writer: false
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
delegate :distinct_on, :with, :itself_only, :cast_records,
|
19
|
+
class_methods do
|
20
|
+
delegate :distinct_on, :with, :itself_only, :cast_records, :join_series,
|
21
|
+
:buckets, to: :all
|
21
22
|
|
22
23
|
# Make sure that table name is an instance of TableName class
|
23
24
|
def reset_table_name
|
25
|
+
return super unless PostgreSQL.config.schemas.enabled
|
24
26
|
self.table_name = TableName.new(self, super)
|
25
27
|
end
|
26
28
|
|
@@ -41,7 +43,7 @@ module Torque
|
|
41
43
|
next klass.table_name unless klass.physically_inheritances?
|
42
44
|
|
43
45
|
query = klass.unscoped.where(subclass.primary_key => id)
|
44
|
-
query.pluck(klass.arel_table['tableoid'].
|
46
|
+
query.pluck(klass.arel_table['tableoid'].pg_cast('regclass')).first
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
@@ -200,14 +202,6 @@ module Torque
|
|
200
202
|
::ActiveRecord::Reflection.add_reflection(self, name, reflection)
|
201
203
|
end
|
202
204
|
|
203
|
-
# Allow extra keyword arguments to be sent to +InsertAll+
|
204
|
-
unless Torque::PostgreSQL::AR720
|
205
|
-
def upsert_all(attributes, **xargs)
|
206
|
-
xargs = xargs.reverse_merge(on_duplicate: :update)
|
207
|
-
::ActiveRecord::InsertAll.new(self, attributes, **xargs).execute
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
205
|
protected
|
212
206
|
|
213
207
|
# Allow optional select attributes to be loaded manually when they are
|
@@ -4,7 +4,7 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
module Collector
|
6
6
|
|
7
|
-
# This
|
7
|
+
# This class helps to collect data in different ways. Used to configure
|
8
8
|
# auxiliary statements
|
9
9
|
def self.new(*args)
|
10
10
|
klass = Class.new
|
@@ -4,10 +4,6 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
include ActiveSupport::Configurable
|
6
6
|
|
7
|
-
# Stores a version check for compatibility purposes
|
8
|
-
AR710 = (ActiveRecord.gem_version >= Gem::Version.new('7.1.0'))
|
9
|
-
AR720 = (ActiveRecord.gem_version >= Gem::Version.new('7.2.0'))
|
10
|
-
|
11
7
|
# Use the same logger as the Active Record one
|
12
8
|
def self.logger
|
13
9
|
ActiveRecord::Base.logger
|
@@ -26,6 +22,12 @@ module Torque
|
|
26
22
|
# same configuration is set to true
|
27
23
|
config.eager_load = false
|
28
24
|
|
25
|
+
# Add support for joining any query/association with a generated series
|
26
|
+
config.join_series = true
|
27
|
+
|
28
|
+
# Add support for querying and calculating histogram buckets
|
29
|
+
config.buckets = true
|
30
|
+
|
29
31
|
# Set a list of irregular model name when associated with table names
|
30
32
|
config.irregular_models = {}
|
31
33
|
def config.irregular_models=(hash)
|
@@ -41,11 +43,19 @@ module Torque
|
|
41
43
|
# default. False means that no validation will be performed
|
42
44
|
assoc.belongs_to_many_required_by_default = false
|
43
45
|
|
46
|
+
# Although +belongs_to_many+ does not need a custom handler when joining
|
47
|
+
# the last chain scope, this can allow devs to pick which way they prefer:
|
48
|
+
# Rails default, or ANY with a single bind to improve prepared statements
|
49
|
+
# assoc.optimize_for_binds = false TODO: Add support
|
50
|
+
|
44
51
|
end
|
45
52
|
|
46
53
|
# Configure multiple schemas
|
47
54
|
config.nested(:schemas) do |schemas|
|
48
55
|
|
56
|
+
# Enables schemas handler by this gem, not Rails's own implementation
|
57
|
+
schemas.enabled = true
|
58
|
+
|
49
59
|
# Defines a list of LIKE-based schemas to not consider for a multiple
|
50
60
|
# schema database
|
51
61
|
schemas.blacklist = %w[information_schema pg_%]
|
@@ -59,6 +69,10 @@ module Torque
|
|
59
69
|
# Configure auxiliary statement features
|
60
70
|
config.nested(:auxiliary_statement) do |cte|
|
61
71
|
|
72
|
+
# Enables auxiliary statements handler by this gem, not Rails's own
|
73
|
+
# implementation
|
74
|
+
cte.enabled = true
|
75
|
+
|
62
76
|
# Define the key that is used on auxiliary statements to send extra
|
63
77
|
# arguments to format string or send on a proc
|
64
78
|
cte.send_arguments_key = :args
|
@@ -76,6 +90,9 @@ module Torque
|
|
76
90
|
# Configure ENUM features
|
77
91
|
config.nested(:enum) do |enum|
|
78
92
|
|
93
|
+
# Enables enum handler by this gem, not Rails's own implementation
|
94
|
+
enum.enabled = true
|
95
|
+
|
79
96
|
# The name of the method to be used on any ActiveRecord::Base to
|
80
97
|
# initialize model-based enum features
|
81
98
|
enum.base_method = :torque_enum
|
@@ -93,7 +110,7 @@ module Torque
|
|
93
110
|
enum.raise_conflicting = false
|
94
111
|
|
95
112
|
# Specify the namespace of each enum type of value
|
96
|
-
enum.namespace =
|
113
|
+
enum.namespace = nil
|
97
114
|
|
98
115
|
# Specify the scopes for I18n translations
|
99
116
|
enum.i18n_scopes = [
|
@@ -117,6 +134,9 @@ module Torque
|
|
117
134
|
# Configure geometry data types
|
118
135
|
config.nested(:geometry) do |geometry|
|
119
136
|
|
137
|
+
# Enables geometry handler by this gem, not Rails's own implementation
|
138
|
+
geometry.enabled = true
|
139
|
+
|
120
140
|
# Define the class that will be handling Point data types after decoding
|
121
141
|
# it. Any class provided here must respond to 'x', and 'y'
|
122
142
|
geometry.point_class = ActiveRecord::Point
|
@@ -162,6 +182,9 @@ module Torque
|
|
162
182
|
# Configure period features
|
163
183
|
config.nested(:period) do |period|
|
164
184
|
|
185
|
+
# Enables period handler by this gem
|
186
|
+
period.enabled = true
|
187
|
+
|
165
188
|
# The name of the method to be used on any ActiveRecord::Base to
|
166
189
|
# initialize model-based period features
|
167
190
|
period.base_method = :period_for
|
@@ -228,5 +251,106 @@ module Torque
|
|
228
251
|
}
|
229
252
|
|
230
253
|
end
|
254
|
+
|
255
|
+
# Configure period features
|
256
|
+
config.nested(:interval) do |interval|
|
257
|
+
|
258
|
+
# Enables interval handler by this gem, not Rails's own implementation
|
259
|
+
interval.enabled = true
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
# Configure arel additional features
|
264
|
+
config.nested(:arel) do |arel|
|
265
|
+
|
266
|
+
# When provided, the initializer will expose the Arel function helper on
|
267
|
+
# the given module
|
268
|
+
config.expose_function_helper_on = nil
|
269
|
+
|
270
|
+
# List of Arel INFIX operators that will be made available for using as
|
271
|
+
# methods on Arel::Nodes::Node and Arel::Attribute
|
272
|
+
arel.infix_operators = {
|
273
|
+
'contained_by' => '<@',
|
274
|
+
'has_key' => '?',
|
275
|
+
'has_all_keys' => '?&',
|
276
|
+
'has_any_keys' => '?|',
|
277
|
+
'strictly_left' => '<<',
|
278
|
+
'strictly_right' => '>>',
|
279
|
+
'doesnt_right_extend' => '&<',
|
280
|
+
'doesnt_left_extend' => '&>',
|
281
|
+
'adjacent_to' => '-|-',
|
282
|
+
}
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
# Configure full text search features
|
287
|
+
config.nested(:full_text_search) do |fts|
|
288
|
+
|
289
|
+
# Enables full text search handler by this gem
|
290
|
+
fts.enabled = true
|
291
|
+
|
292
|
+
# The name of the method to be used on any ActiveRecord::Base to
|
293
|
+
# initialize model-based full text search features
|
294
|
+
fts.base_method = :torque_search_for
|
295
|
+
|
296
|
+
# Defines the default language when generating search vector columns
|
297
|
+
fts.default_language = 'english'
|
298
|
+
|
299
|
+
# Defines the default mode to be used when generating full text search
|
300
|
+
# queries. It can be one of the following:
|
301
|
+
# - :default (to_tsquery)
|
302
|
+
# - :phrase (phraseto_tsquery)
|
303
|
+
# - :plain (plainto_tsquery)
|
304
|
+
# - :web (websearch_to_tsquery)
|
305
|
+
fts.default_mode = :phrase
|
306
|
+
|
307
|
+
# Defines the default index type to be used when creating search vector.
|
308
|
+
# It still requires that the column requests an index
|
309
|
+
fts.default_index_type = :gin
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
# Configure predicate builder additional features
|
314
|
+
config.nested(:predicate_builder) do |builder|
|
315
|
+
|
316
|
+
# List which handlers are enabled by default
|
317
|
+
builder.enabled = %i[regexp arel_attribute enumerator_lazy]
|
318
|
+
|
319
|
+
# When active, values provided to array attributes will be handled more
|
320
|
+
# friendly. It will use the +ANY+ operator on a equality check and
|
321
|
+
# overlaps when the given value is an array
|
322
|
+
builder.handle_array_attributes = false
|
323
|
+
|
324
|
+
# Make sure that the predicate builder will not spend more than 20ms
|
325
|
+
# trying to produce the underlying array
|
326
|
+
builder.lazy_timeout = 0.02
|
327
|
+
|
328
|
+
# Since lazy array is uncommon, it is better to limit the number of
|
329
|
+
# entries we try to pull so we don't cause a timeout or a long wait
|
330
|
+
# iteration
|
331
|
+
builder.lazy_limit = 2_000
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
# Configure versioned commands features
|
336
|
+
config.nested(:versioned_commands) do |vs|
|
337
|
+
|
338
|
+
# This is a feature that developers must explicitly opt-in. It is designed
|
339
|
+
# in a way that prevents a large impact on Rails' original migrations
|
340
|
+
# behavior. But, it is still a feature that everyone may not need, and
|
341
|
+
# some may complain about the additional schema table, which also uses
|
342
|
+
# inheritance
|
343
|
+
vs.enabled = false
|
344
|
+
|
345
|
+
# Define the list of commands that are going to be versioned by this
|
346
|
+
# method
|
347
|
+
vs.types = %i[function type view]
|
348
|
+
|
349
|
+
# The name of the table that will inherit from +schema_migrations+ and
|
350
|
+
# store the list of versioned commands that have been executed
|
351
|
+
vs.table_name = 'schema_versioned_commands'
|
352
|
+
|
353
|
+
end
|
354
|
+
|
231
355
|
end
|
232
356
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
# Simplified module for creating arel functions. This is used internally
|
6
|
+
# but can also be made available to other devs on their own projects
|
7
|
+
module Function
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# A facilitator to create a bind param that is fully compatible with
|
11
|
+
# Arel and ActiveRecord
|
12
|
+
def bind(*args)
|
13
|
+
attr = ::ActiveRecord::Relation::QueryAttribute.new(*args)
|
14
|
+
::Arel::Nodes::BindParam.new(attr)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Just a shortcut to create a bind param for a model attribute and a
|
18
|
+
# value for it
|
19
|
+
def bind_for(model, attribute, value)
|
20
|
+
bind(attribute, value, model.attribute_types[attribute])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Another shortcut, when we already have the arel attribute at hand
|
24
|
+
def bind_with(arel_attribute, value)
|
25
|
+
bind(arel_attribute.name, value, arel_attribute.type_caster)
|
26
|
+
end
|
27
|
+
|
28
|
+
# A facilitator to create a bind param with a specific type
|
29
|
+
def bind_type(value, type = nil, name: 'value', cast: nil)
|
30
|
+
type ||= ruby_type_to_model_type(value)
|
31
|
+
type = ActiveModel::Type.lookup(type) if type.is_a?(Symbol)
|
32
|
+
result = bind(name, value, type)
|
33
|
+
cast ? result.pg_cast(cast) : result
|
34
|
+
end
|
35
|
+
|
36
|
+
# A facilitator to create an infix operation
|
37
|
+
def infix(op, left, right)
|
38
|
+
::Arel::Nodes::InfixOperation.new(op, left, right)
|
39
|
+
end
|
40
|
+
|
41
|
+
# A facilitator to use several Infix operators to concatenate all the
|
42
|
+
# provided arguments. Arguments won't be sanitized, as other methods
|
43
|
+
# under this module
|
44
|
+
def concat(*args)
|
45
|
+
return args.first if args.one?
|
46
|
+
args.reduce { |left, right| infix(:"||", left, right) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# A simple helper to trick Rails into producing the right SQL for
|
50
|
+
# grouping operations
|
51
|
+
def group_by(arel, name)
|
52
|
+
Arel::Nodes::Ref.new(name.to_s, arel)
|
53
|
+
end
|
54
|
+
|
55
|
+
# As of now, this indicates that it supports any direct calls, since
|
56
|
+
# the idea is to simply map to an Arel function with the same name,
|
57
|
+
# without checking if it actually exists
|
58
|
+
def respond_to_missing?(*)
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# This method is used to catch any method calls that are not defined
|
63
|
+
# in this module. It will simply return an Arel function with the same
|
64
|
+
# name as the method called, passing all arguments to it, without
|
65
|
+
# any sanitization
|
66
|
+
def method_missing(name, *args, &block)
|
67
|
+
::Arel::Nodes::NamedFunction.new(name.to_s.upcase, args)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def ruby_type_to_model_type(value)
|
73
|
+
case value
|
74
|
+
when Integer then :integer
|
75
|
+
when Float then :float
|
76
|
+
when String then :string
|
77
|
+
when Time, ActiveSupport::TimeWithZone then :time
|
78
|
+
when TrueClass, FalseClass then :boolean
|
79
|
+
when DateTime then :datetime
|
80
|
+
when Date then :date
|
81
|
+
when BigDecimal then :decimal
|
82
|
+
when ActiveSupport::Duration
|
83
|
+
Adapter::OID::Interval.new
|
84
|
+
else
|
85
|
+
raise ArgumentError, "Cannot infer type from value: #{value.inspect}."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
FN = Function
|
93
|
+
end
|
94
|
+
end
|
@@ -20,15 +20,14 @@ module Torque
|
|
20
20
|
klass.find(self.id)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
class_methods do
|
24
24
|
delegate :_auto_cast_attribute, :_record_class_attribute, to: ActiveRecord::Relation
|
25
25
|
|
26
26
|
# Get a full list of all attributes from a model and all its dependents
|
27
27
|
def inheritance_merged_attributes
|
28
28
|
@inheritance_merged_attributes ||= begin
|
29
|
-
|
30
|
-
|
31
|
-
list.flatten.uniq.freeze
|
29
|
+
children = casted_dependents.values.flat_map(&:attribute_names)
|
30
|
+
attribute_names.to_set.merge(children).to_a.freeze
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
@@ -45,11 +44,11 @@ module Torque
|
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
48
|
-
result = types.
|
49
|
-
|
50
|
-
end
|
47
|
+
result = types.filter_map do |attribute, types|
|
48
|
+
attribute if types.each_with_object(types.shift).all?(&:==)
|
49
|
+
end
|
51
50
|
|
52
|
-
result.freeze
|
51
|
+
(attribute_names + result).freeze
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
@@ -111,22 +110,19 @@ module Torque
|
|
111
110
|
# For all main purposes, physical inherited classes should have
|
112
111
|
# base_class as their own
|
113
112
|
def base_class
|
114
|
-
|
115
|
-
self
|
113
|
+
physically_inherited? ? self : super
|
116
114
|
end
|
117
115
|
|
118
116
|
# Primary key is one exception when getting information about the class,
|
119
117
|
# it must returns the superclass PK
|
120
118
|
def primary_key
|
121
|
-
|
122
|
-
superclass.primary_key
|
119
|
+
physically_inherited? ? superclass.primary_key : super
|
123
120
|
end
|
124
121
|
|
125
122
|
# Add an additional check to return the name of the table even when the
|
126
123
|
# class is inherited, but only if it is a physical inheritance
|
127
124
|
def compute_table_name
|
128
|
-
|
129
|
-
decorated_table_name
|
125
|
+
physically_inherited? ? decorated_table_name : super
|
130
126
|
end
|
131
127
|
|
132
128
|
# Raises an error message saying that the giver record class was not
|
@@ -142,37 +138,57 @@ module Torque
|
|
142
138
|
|
143
139
|
private
|
144
140
|
|
145
|
-
|
141
|
+
# If the class is physically inherited, the klass needs to be properly
|
142
|
+
# changed before moving forward
|
143
|
+
def instantiate_instance_of(klass, attributes, types = {}, &block)
|
146
144
|
return super unless klass.physically_inheritances?
|
147
145
|
|
148
|
-
|
149
|
-
|
150
|
-
return super unless attributes.key?(record_class) &&
|
151
|
-
attributes.delete(auto_cast) && attributes[record_class] != table_name
|
152
|
-
|
153
|
-
klass = casted_dependents[attributes[record_class]]
|
154
|
-
raise_unable_to_cast(attributes[record_class]) if klass.nil?
|
155
|
-
filter_attributes_for_cast(attributes, klass)
|
146
|
+
real_class = torque_discriminate_class_for_record(klass, attributes)
|
147
|
+
return super if real_class.nil?
|
156
148
|
|
157
|
-
|
149
|
+
attributes, types = sanitize_attributes(real_class, attributes, types)
|
150
|
+
super(real_class, attributes, types, &block)
|
158
151
|
end
|
159
152
|
|
160
|
-
#
|
161
|
-
#
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
153
|
+
# Unwrap the attributes and column types from the given class when
|
154
|
+
# there are unmergeable attributes
|
155
|
+
def sanitize_attributes(real_class, attributes, types)
|
156
|
+
skip = (inheritance_merged_attributes - real_class.attribute_names).to_set
|
157
|
+
skip.merge(real_class.attribute_names - inheritance_mergeable_attributes)
|
158
|
+
return [attributes, types] if skip.empty?
|
159
|
+
|
160
|
+
dropped = 0
|
161
|
+
new_types = {}
|
162
|
+
|
163
|
+
row = attributes.instance_variable_get(:@row).dup
|
164
|
+
indexes = attributes.instance_variable_get(:@column_indexes).dup
|
165
|
+
indexes = indexes.each_with_object({}) do |(column, index), new_indexes|
|
166
|
+
attribute, prefix = column.split('__', 2).reverse
|
167
|
+
current_index = index - dropped
|
168
|
+
|
169
|
+
if prefix != table_name && skip.include?(attribute)
|
170
|
+
row.delete_at(current_index)
|
171
|
+
dropped += 1
|
172
|
+
else
|
173
|
+
new_types.merge!(types.slice(attribute))
|
174
|
+
new_types[current_index] = types[index]
|
175
|
+
new_indexes[attribute] = current_index
|
176
|
+
end
|
169
177
|
end
|
170
178
|
|
171
|
-
|
172
|
-
new_record.merge!(record.slice(*(record.keys - inheritance_merged_attributes)))
|
173
|
-
record.replace(new_record)
|
179
|
+
[ActiveRecord::Result::IndexedRow.new(indexes, row), new_types]
|
174
180
|
end
|
175
181
|
|
182
|
+
# Get the real class when handling physical inheritances and casting
|
183
|
+
# the record when existing properly is present
|
184
|
+
def torque_discriminate_class_for_record(klass, record)
|
185
|
+
return if record[_auto_cast_attribute.to_s] == false
|
186
|
+
|
187
|
+
embedded_type = record[_record_class_attribute.to_s]
|
188
|
+
return if embedded_type.blank? || embedded_type == table_name
|
189
|
+
|
190
|
+
casted_dependents[embedded_type] || raise_unable_to_cast(embedded_type)
|
191
|
+
end
|
176
192
|
end
|
177
193
|
end
|
178
194
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class ArelAttributeHandler
|
7
|
+
# Shortcut
|
8
|
+
def self.call(*args)
|
9
|
+
new.call(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*)
|
13
|
+
# There is no need to use or save the predicate builder here
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(attribute, value)
|
17
|
+
case
|
18
|
+
when array_typed?(attribute) && array_typed?(value) then attribute.overlaps(value)
|
19
|
+
when array_typed?(attribute) then value.eq(FN.any(attribute))
|
20
|
+
when array_typed?(value) then attribute.eq(FN.any(value))
|
21
|
+
else attribute.eq(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def array_typed?(attribute)
|
28
|
+
attribute.able_to_type_cast? && attribute.type_caster.is_a?(ARRAY_OID)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
module ArrayHandler
|
7
|
+
def call(attribute, value)
|
8
|
+
return super unless array_attribute?(attribute) &&
|
9
|
+
PostgreSQL.config.predicate_builder.handle_array_attributes
|
10
|
+
|
11
|
+
call_for_array(attribute, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_for_array(attribute, value)
|
15
|
+
if !value.is_a?(::Array)
|
16
|
+
call_with_value(attribute, value)
|
17
|
+
elsif value.any?
|
18
|
+
call_with_array(attribute, value)
|
19
|
+
else
|
20
|
+
call_with_empty(attribute)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def call_with_value(attribute, value)
|
27
|
+
FN.infix(:"=", FN.bind_with(attribute, value), FN.any(attribute))
|
28
|
+
end
|
29
|
+
|
30
|
+
def call_with_array(attribute, value)
|
31
|
+
attribute.overlaps(FN.bind_with(attribute, value))
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_with_empty(attribute)
|
35
|
+
FN.cardinality(attribute).eq(0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def array_attribute?(attribute)
|
39
|
+
attribute.type_caster.is_a?(ARRAY_OID)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
::ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ArrayHandler)
|
44
|
+
::ActiveRecord::PredicateBuilder::BasicObjectHandler.prepend(ArrayHandler)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class EnumeratorLazyHandler < ::ActiveRecord::PredicateBuilder::ArrayHandler
|
7
|
+
Timeout = Class.new(::Timeout::Error)
|
8
|
+
|
9
|
+
def call(attribute, value)
|
10
|
+
with_timeout do
|
11
|
+
super(attribute, limit.nil? ? value.force : value.first(limit))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def with_timeout
|
18
|
+
return yield if timeout.nil?
|
19
|
+
|
20
|
+
begin
|
21
|
+
::Timeout.timeout(timeout) { yield }
|
22
|
+
rescue ::Timeout::Error
|
23
|
+
raise Timeout, "Lazy predicate builder timed out after #{timeout} seconds"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def timeout
|
28
|
+
PostgreSQL.config.predicate_builder.lazy_timeout
|
29
|
+
end
|
30
|
+
|
31
|
+
def limit
|
32
|
+
PostgreSQL.config.predicate_builder.lazy_limit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class RegexpHandler
|
7
|
+
def initialize(predicate_builder)
|
8
|
+
@predicate_builder = predicate_builder
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(attribute, value)
|
12
|
+
operator = value.casefold? ? :"~*" : :"~"
|
13
|
+
FN.infix(operator, attribute, FN.bind_with(attribute, value.source))
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :predicate_builder
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'predicate_builder/array_handler'
|
4
|
+
|
5
|
+
require_relative 'predicate_builder/regexp_handler'
|
6
|
+
require_relative 'predicate_builder/arel_attribute_handler'
|
7
|
+
require_relative 'predicate_builder/enumerator_lazy_handler'
|
8
|
+
|
9
|
+
module Torque
|
10
|
+
module PostgreSQL
|
11
|
+
module PredicateBuilder
|
12
|
+
ARRAY_OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
|
17
|
+
handlers = Array.wrap(PostgreSQL.config.predicate_builder.enabled).inquiry
|
18
|
+
|
19
|
+
if handlers.regexp?
|
20
|
+
register_handler(Regexp, RegexpHandler.new(self))
|
21
|
+
end
|
22
|
+
|
23
|
+
if handlers.enumerator_lazy?
|
24
|
+
register_handler(Enumerator::Lazy, EnumeratorLazyHandler.new(self))
|
25
|
+
end
|
26
|
+
|
27
|
+
if handlers.arel_attribute?
|
28
|
+
register_handler(::Arel::Attributes::Attribute, ArelAttributeHandler.new(self))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
::ActiveRecord::PredicateBuilder.prepend(PredicateBuilder)
|
34
|
+
end
|
35
|
+
end
|