activerecord 7.0.8.7 → 7.1.5.1
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/CHANGELOG.md +1795 -1424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +20 -4
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +19 -13
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +319 -217
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +145 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +104 -5
- data/lib/active_record/migration/compatibility.rb +145 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -6
- data/lib/active_record/railties/databases.rake +144 -150
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +181 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +371 -68
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +152 -108
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +122 -17
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arel # :nodoc: all
|
4
|
+
module Nodes
|
5
|
+
class Cte < Arel::Nodes::Binary
|
6
|
+
alias :name :left
|
7
|
+
alias :relation :right
|
8
|
+
attr_reader :materialized
|
9
|
+
|
10
|
+
def initialize(name, relation, materialized: nil)
|
11
|
+
super(name, relation)
|
12
|
+
@materialized = materialized
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
[name, relation, materialized].hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def eql?(other)
|
20
|
+
self.class == other.class &&
|
21
|
+
self.name == other.name &&
|
22
|
+
self.relation == other.relation &&
|
23
|
+
self.materialized == other.materialized
|
24
|
+
end
|
25
|
+
alias :== :eql?
|
26
|
+
|
27
|
+
def to_cte
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_table
|
32
|
+
Arel::Table.new(name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arel # :nodoc: all
|
4
|
+
module Nodes
|
5
|
+
class Fragments < Arel::Nodes::Node
|
6
|
+
attr_reader :values
|
7
|
+
|
8
|
+
def initialize(values = [])
|
9
|
+
super()
|
10
|
+
@values = values
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_copy(other)
|
14
|
+
super
|
15
|
+
@values = @values.clone
|
16
|
+
end
|
17
|
+
|
18
|
+
def hash
|
19
|
+
[@values].hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def +(other)
|
23
|
+
raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
|
24
|
+
|
25
|
+
self.class.new([*@values, other])
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self.class == other.class &&
|
30
|
+
self.values == other.values
|
31
|
+
end
|
32
|
+
alias :== :eql?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -36,14 +36,6 @@ module Arel # :nodoc: all
|
|
36
36
|
attribute.quoted_array(values)
|
37
37
|
end
|
38
38
|
|
39
|
-
def table_name
|
40
|
-
attribute.relation.table_alias || attribute.relation.name
|
41
|
-
end
|
42
|
-
|
43
|
-
def column_name
|
44
|
-
attribute.name
|
45
|
-
end
|
46
|
-
|
47
39
|
def casted_values
|
48
40
|
type = attribute.type_caster
|
49
41
|
|
@@ -56,7 +48,7 @@ module Arel # :nodoc: all
|
|
56
48
|
end
|
57
49
|
|
58
50
|
def proc_for_binds
|
59
|
-
-> value { ActiveModel::Attribute.with_cast_value(attribute.name, value,
|
51
|
+
-> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, ActiveModel::Type.default_value) }
|
60
52
|
end
|
61
53
|
|
62
54
|
def fetch_attribute(&block)
|
data/lib/arel/nodes/node.rb
CHANGED
@@ -2,8 +2,117 @@
|
|
2
2
|
|
3
3
|
module Arel # :nodoc: all
|
4
4
|
module Nodes
|
5
|
-
|
6
|
-
#
|
5
|
+
# = Using +Arel::Nodes::Node+
|
6
|
+
#
|
7
|
+
# Active Record uses Arel to compose SQL statements. Instead of building SQL strings directly, it's building an
|
8
|
+
# abstract syntax tree (AST) of the statement using various types of Arel::Nodes::Node. Each node represents a
|
9
|
+
# fragment of a SQL statement.
|
10
|
+
#
|
11
|
+
# The intermediate representation allows Arel to compile the statement into the database's specific SQL dialect
|
12
|
+
# only before sending it without having to care about the nuances of each database when building the statement.
|
13
|
+
# It also allows easier composition of statements without having to resort to (brittle and unsafe) string manipulation.
|
14
|
+
#
|
15
|
+
# == Building constraints
|
16
|
+
#
|
17
|
+
# One of the most common use cases of Arel is generating constraints for +SELECT+ statements. To help with that,
|
18
|
+
# most nodes include a couple of useful factory methods to create subtree structures for common constraints. For
|
19
|
+
# a full list of those, please refer to Arel::Predications.
|
20
|
+
#
|
21
|
+
# The following example creates an equality constraint where the value of the name column on the users table
|
22
|
+
# matches the value DHH.
|
23
|
+
#
|
24
|
+
# users = Arel::Table.new(:users)
|
25
|
+
# constraint = users[:name].eq("DHH")
|
26
|
+
#
|
27
|
+
# # => Arel::Nodes::Equality.new(
|
28
|
+
# # Arel::Attributes::Attribute.new(users, "name"),
|
29
|
+
# # Arel::Nodes::Casted.new(
|
30
|
+
# # "DHH",
|
31
|
+
# # Arel::Attributes::Attribute.new(users, "name")
|
32
|
+
# # )
|
33
|
+
# # )
|
34
|
+
#
|
35
|
+
# The resulting SQL fragment will look like this:
|
36
|
+
#
|
37
|
+
# "users"."name" = 'DHH'
|
38
|
+
#
|
39
|
+
# The constraint fragments can be used with regular ActiveRecord::Relation objects instead of a Hash. The
|
40
|
+
# following two examples show two ways of creating the same query.
|
41
|
+
#
|
42
|
+
# User.where(name: 'DHH')
|
43
|
+
#
|
44
|
+
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
|
45
|
+
#
|
46
|
+
# users = User.arel_table
|
47
|
+
#
|
48
|
+
# User.where(users[:name].eq('DHH'))
|
49
|
+
#
|
50
|
+
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
|
51
|
+
#
|
52
|
+
# == Functions
|
53
|
+
#
|
54
|
+
# Arel comes with built-in support for SQL functions like +COUNT+, +SUM+, +MIN+, +MAX+, and +AVG+. The
|
55
|
+
# Arel::Expressions module includes factory methods for the default functions.
|
56
|
+
#
|
57
|
+
# employees = Employee.arel_table
|
58
|
+
#
|
59
|
+
# Employee.select(employees[:department_id], employees[:salary].average).group(employees[:department_id])
|
60
|
+
#
|
61
|
+
# # SELECT "employees"."department_id", AVG("employees"."salary")
|
62
|
+
# # FROM "employees" GROUP BY "employees"."department_id"
|
63
|
+
#
|
64
|
+
# It’s also possible to use custom functions by using the Arel::Nodes::NamedFunction node type. It accepts a
|
65
|
+
# function name and an array of parameters.
|
66
|
+
#
|
67
|
+
# Arel::Nodes::NamedFunction.new('date_trunc', [Arel::Nodes.build_quoted('day'), User.arel_table[:created_at]])
|
68
|
+
#
|
69
|
+
# # date_trunc('day', "users"."created_at")
|
70
|
+
#
|
71
|
+
# == Quoting & bind params
|
72
|
+
#
|
73
|
+
# Values that you pass to Arel nodes need to be quoted or wrapped in bind params. This ensures they are properly
|
74
|
+
# converted into the correct format without introducing a possible SQL injection vulnerability. Most factory
|
75
|
+
# methods (like +eq+, +gt+, +lteq+, …) quote passed values automatically. When not using a factory method, it’s
|
76
|
+
# possible to convert a value and wrap it in an Arel::Nodes::Quoted node (if necessary) by calling +Arel::Nodes.
|
77
|
+
# build_quoted+.
|
78
|
+
#
|
79
|
+
# Arel::Nodes.build_quoted("foo") # 'foo'
|
80
|
+
# Arel::Nodes.build_quoted(12.3) # 12.3
|
81
|
+
#
|
82
|
+
# Instead of quoting values and embedding them directly in the SQL statement, it’s also possible to create bind
|
83
|
+
# params. This keeps the actual values outside of the statement and allows using the prepared statement feature
|
84
|
+
# of some databases.
|
85
|
+
#
|
86
|
+
# attribute = ActiveRecord::Relation::QueryAttribute.new(:name, "DHH", ActiveRecord::Type::String.new)
|
87
|
+
# Arel::Nodes::BindParam.new(attribute)
|
88
|
+
#
|
89
|
+
# When ActiveRecord runs the query, bind params are replaced by placeholders (like +$1+) and the values are passed
|
90
|
+
# separately.
|
91
|
+
#
|
92
|
+
# == SQL Literals
|
93
|
+
#
|
94
|
+
# For cases where there is no way to represent a particular SQL fragment using Arel nodes, you can use an SQL
|
95
|
+
# literal. SQL literals are strings that Arel will treat “as is”.
|
96
|
+
#
|
97
|
+
# Arel.sql('LOWER("users"."name")').eq('dhh')
|
98
|
+
#
|
99
|
+
# # LOWER("users"."name") = 'dhh'
|
100
|
+
#
|
101
|
+
# Please keep in mind that passing data as raw SQL literals might introduce a possible SQL injection. However,
|
102
|
+
# `Arel.sql` supports binding parameters which will ensure proper quoting. This can be useful when you need to
|
103
|
+
# control the exact SQL you run, but you still have potentially user-supplied values.
|
104
|
+
#
|
105
|
+
# Arel.sql('LOWER("users"."name") = ?', 'dhh')
|
106
|
+
#
|
107
|
+
# # LOWER("users"."name") = 'dhh'
|
108
|
+
#
|
109
|
+
# You can also combine SQL literals.
|
110
|
+
#
|
111
|
+
# sql = Arel.sql('SELECT * FROM "users" WHERE ')
|
112
|
+
# sql += Arel.sql('LOWER("users"."name") = :name', name: 'dhh')
|
113
|
+
# sql += Arel.sql('AND "users"."age" > :age', age: 35)
|
114
|
+
#
|
115
|
+
# # SELECT * FROM "users" WHERE LOWER("users"."name") = 'dhh' AND "users"."age" > '35'
|
7
116
|
class Node
|
8
117
|
include Arel::FactoryMethods
|
9
118
|
|
data/lib/arel/nodes.rb
CHANGED
@@ -8,6 +8,7 @@ require "arel/nodes/select_core"
|
|
8
8
|
require "arel/nodes/insert_statement"
|
9
9
|
require "arel/nodes/update_statement"
|
10
10
|
require "arel/nodes/bind_param"
|
11
|
+
require "arel/nodes/fragments"
|
11
12
|
|
12
13
|
# terminal
|
13
14
|
|
@@ -38,6 +39,7 @@ require "arel/nodes/unary_operation"
|
|
38
39
|
require "arel/nodes/over"
|
39
40
|
require "arel/nodes/matches"
|
40
41
|
require "arel/nodes/regexp"
|
42
|
+
require "arel/nodes/cte"
|
41
43
|
|
42
44
|
# nary
|
43
45
|
require "arel/nodes/and"
|
@@ -63,9 +65,11 @@ require "arel/nodes/inner_join"
|
|
63
65
|
require "arel/nodes/outer_join"
|
64
66
|
require "arel/nodes/right_outer_join"
|
65
67
|
require "arel/nodes/string_join"
|
68
|
+
require "arel/nodes/leading_join"
|
66
69
|
|
67
70
|
require "arel/nodes/comment"
|
68
71
|
|
69
72
|
require "arel/nodes/sql_literal"
|
73
|
+
require "arel/nodes/bound_sql_literal"
|
70
74
|
|
71
75
|
require "arel/nodes/casted"
|
data/lib/arel/predications.rb
CHANGED
data/lib/arel/table.rb
CHANGED
@@ -8,13 +8,17 @@ module Arel # :nodoc: all
|
|
8
8
|
@engine = nil
|
9
9
|
class << self; attr_accessor :engine; end
|
10
10
|
|
11
|
-
attr_accessor :name
|
12
|
-
|
13
|
-
# TableAlias and Table both have a #table_name which is the name of the underlying table
|
14
|
-
alias :table_name :name
|
11
|
+
attr_accessor :name
|
12
|
+
attr_reader :table_alias
|
15
13
|
|
16
14
|
def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster)
|
17
|
-
@name =
|
15
|
+
@name =
|
16
|
+
case name
|
17
|
+
when Symbol then name.to_s
|
18
|
+
else
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
18
22
|
@klass = klass
|
19
23
|
@type_caster = type_caster
|
20
24
|
|
data/lib/arel/tree_manager.rb
CHANGED
data/lib/arel/visitors/mysql.rb
CHANGED
@@ -5,8 +5,9 @@ module Arel # :nodoc: all
|
|
5
5
|
class MySQL < Arel::Visitors::ToSql
|
6
6
|
private
|
7
7
|
def visit_Arel_Nodes_Bin(o, collector)
|
8
|
-
collector << "
|
8
|
+
collector << "CAST("
|
9
9
|
visit o.expr, collector
|
10
|
+
collector << " AS BINARY)"
|
10
11
|
end
|
11
12
|
|
12
13
|
def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
|
@@ -63,6 +64,12 @@ module Arel # :nodoc: all
|
|
63
64
|
visit o.expr, collector
|
64
65
|
end
|
65
66
|
|
67
|
+
def visit_Arel_Nodes_Cte(o, collector)
|
68
|
+
collector << quote_table_name(o.name)
|
69
|
+
collector << " AS "
|
70
|
+
visit o.relation, collector
|
71
|
+
end
|
72
|
+
|
66
73
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
67
74
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
68
75
|
# these, we must use a subquery.
|
data/lib/arel/visitors/to_sql.rb
CHANGED
@@ -333,7 +333,7 @@ module Arel # :nodoc: all
|
|
333
333
|
def visit_Arel_Nodes_HomogeneousIn(o, collector)
|
334
334
|
collector.preparable = false
|
335
335
|
|
336
|
-
|
336
|
+
visit o.left, collector
|
337
337
|
|
338
338
|
if o.type == :in
|
339
339
|
collector << " IN ("
|
@@ -350,7 +350,6 @@ module Arel # :nodoc: all
|
|
350
350
|
end
|
351
351
|
|
352
352
|
collector << ")"
|
353
|
-
collector
|
354
353
|
end
|
355
354
|
|
356
355
|
def visit_Arel_SelectManager(o, collector)
|
@@ -569,11 +568,17 @@ module Arel # :nodoc: all
|
|
569
568
|
end
|
570
569
|
|
571
570
|
def visit_Arel_Table(o, collector)
|
572
|
-
if o.
|
573
|
-
|
571
|
+
if Arel::Nodes::Node === o.name
|
572
|
+
visit o.name, collector
|
574
573
|
else
|
575
574
|
collector << quote_table_name(o.name)
|
576
575
|
end
|
576
|
+
|
577
|
+
if o.table_alias
|
578
|
+
collector << " " << quote_table_name(o.table_alias)
|
579
|
+
end
|
580
|
+
|
581
|
+
collector
|
577
582
|
end
|
578
583
|
|
579
584
|
def visit_Arel_Nodes_In(o, collector)
|
@@ -729,6 +734,20 @@ module Arel # :nodoc: all
|
|
729
734
|
collector << quote_column_name(o.name)
|
730
735
|
end
|
731
736
|
|
737
|
+
def visit_Arel_Nodes_Cte(o, collector)
|
738
|
+
collector << quote_table_name(o.name)
|
739
|
+
collector << " AS "
|
740
|
+
|
741
|
+
case o.materialized
|
742
|
+
when true
|
743
|
+
collector << "MATERIALIZED "
|
744
|
+
when false
|
745
|
+
collector << "NOT MATERIALIZED "
|
746
|
+
end
|
747
|
+
|
748
|
+
visit o.relation, collector
|
749
|
+
end
|
750
|
+
|
732
751
|
def visit_Arel_Attributes_Attribute(o, collector)
|
733
752
|
join_name = o.relation.table_alias || o.relation.name
|
734
753
|
collector << quote_table_name(join_name) << "." << quote_column_name(o.name)
|
@@ -752,6 +771,59 @@ module Arel # :nodoc: all
|
|
752
771
|
collector << o.to_s
|
753
772
|
end
|
754
773
|
|
774
|
+
def visit_Arel_Nodes_BoundSqlLiteral(o, collector)
|
775
|
+
bind_index = 0
|
776
|
+
|
777
|
+
new_bind = lambda do |value|
|
778
|
+
if Arel.arel_node?(value)
|
779
|
+
visit value, collector
|
780
|
+
elsif value.is_a?(Array)
|
781
|
+
if value.empty?
|
782
|
+
collector << @connection.quote(nil)
|
783
|
+
else
|
784
|
+
if value.none? { |v| Arel.arel_node?(v) }
|
785
|
+
collector.add_binds(value.map { |v| @connection.cast_bound_value(v) }, &bind_block)
|
786
|
+
else
|
787
|
+
value.each_with_index do |v, i|
|
788
|
+
collector << ", " unless i == 0
|
789
|
+
if Arel.arel_node?(v)
|
790
|
+
visit v, collector
|
791
|
+
else
|
792
|
+
collector.add_bind(@connection.cast_bound_value(v), &bind_block)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
else
|
798
|
+
collector.add_bind(@connection.cast_bound_value(value), &bind_block)
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
if o.positional_binds
|
803
|
+
o.sql_with_placeholders.scan(/\?|([^?]+)/) do
|
804
|
+
if $1
|
805
|
+
collector << $1
|
806
|
+
else
|
807
|
+
value = o.positional_binds[bind_index]
|
808
|
+
bind_index += 1
|
809
|
+
|
810
|
+
new_bind.call(value)
|
811
|
+
end
|
812
|
+
end
|
813
|
+
else
|
814
|
+
o.sql_with_placeholders.scan(/:(?<!::)([a-zA-Z]\w*)|([^:]+|.)/) do
|
815
|
+
if $2
|
816
|
+
collector << $2
|
817
|
+
else
|
818
|
+
value = o.named_binds[$1.to_sym]
|
819
|
+
new_bind.call(value)
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
collector
|
825
|
+
end
|
826
|
+
|
755
827
|
def visit_Integer(o, collector)
|
756
828
|
collector << o.to_s
|
757
829
|
end
|
@@ -791,6 +863,10 @@ module Arel # :nodoc: all
|
|
791
863
|
end
|
792
864
|
alias :visit_Set :visit_Array
|
793
865
|
|
866
|
+
def visit_Arel_Nodes_Fragments(o, collector)
|
867
|
+
inject_join o.values, collector, " "
|
868
|
+
end
|
869
|
+
|
794
870
|
def quote(value)
|
795
871
|
return value if Arel::Nodes::SqlLiteral === value
|
796
872
|
@connection.quote value
|
@@ -854,7 +930,8 @@ module Arel # :nodoc: all
|
|
854
930
|
stmt.limit = nil
|
855
931
|
stmt.offset = nil
|
856
932
|
stmt.orders = []
|
857
|
-
|
933
|
+
columns = Arel::Nodes::Grouping.new(o.key)
|
934
|
+
stmt.wheres = [Nodes::In.new(columns, [build_subselect(o.key, o)])]
|
858
935
|
stmt.relation = o.relation.left if has_join_sources?(o)
|
859
936
|
stmt.groups = o.groups unless o.groups.empty?
|
860
937
|
stmt.havings = o.havings unless o.havings.empty?
|
@@ -933,19 +1010,7 @@ module Arel # :nodoc: all
|
|
933
1010
|
def collect_ctes(children, collector)
|
934
1011
|
children.each_with_index do |child, i|
|
935
1012
|
collector << ", " unless i == 0
|
936
|
-
|
937
|
-
case child
|
938
|
-
when Arel::Nodes::As
|
939
|
-
name = child.left.name
|
940
|
-
relation = child.right
|
941
|
-
when Arel::Nodes::TableAlias
|
942
|
-
name = child.name
|
943
|
-
relation = child.relation
|
944
|
-
end
|
945
|
-
|
946
|
-
collector << quote_table_name(name)
|
947
|
-
collector << " AS "
|
948
|
-
visit relation, collector
|
1013
|
+
visit child.to_cte, collector
|
949
1014
|
end
|
950
1015
|
|
951
1016
|
collector
|
@@ -16,8 +16,8 @@ module Arel # :nodoc: all
|
|
16
16
|
|
17
17
|
def self.dispatch_cache
|
18
18
|
@dispatch_cache ||= Hash.new do |hash, klass|
|
19
|
-
hash[klass] = "visit_#{(klass.name ||
|
20
|
-
end
|
19
|
+
hash[klass] = :"visit_#{(klass.name || "").gsub("::", "_")}"
|
20
|
+
end.compare_by_identity
|
21
21
|
end
|
22
22
|
|
23
23
|
def get_dispatch_cache
|
data/lib/arel.rb
CHANGED
@@ -35,8 +35,22 @@ module Arel
|
|
35
35
|
# Great caution should be taken to avoid SQL injection vulnerabilities.
|
36
36
|
# This method should not be used with unsafe values such as request
|
37
37
|
# parameters or model attributes.
|
38
|
-
|
39
|
-
|
38
|
+
#
|
39
|
+
# Take a look at the {security guide}[https://guides.rubyonrails.org/security.html#sql-injection]
|
40
|
+
# for more information.
|
41
|
+
#
|
42
|
+
# To construct a more complex query fragment, including the possible
|
43
|
+
# use of user-provided values, the +sql_string+ may contain <tt>?</tt> and
|
44
|
+
# +:key+ placeholders, corresponding to the additional arguments. Note
|
45
|
+
# that this behavior only applies when bind value parameters are
|
46
|
+
# supplied in the call; without them, the placeholder tokens have no
|
47
|
+
# special meaning, and will be passed through to the query as-is.
|
48
|
+
def self.sql(sql_string, *positional_binds, **named_binds)
|
49
|
+
if positional_binds.empty? && named_binds.empty?
|
50
|
+
Arel::Nodes::SqlLiteral.new sql_string
|
51
|
+
else
|
52
|
+
Arel::Nodes::BoundSqlLiteral.new sql_string, positional_binds, named_binds
|
53
|
+
end
|
40
54
|
end
|
41
55
|
|
42
56
|
def self.star # :nodoc:
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Description:
|
2
|
+
Generates an `ApplicationRecord` base class for other models to inherit from.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
`bin/rails generate application_record`
|
6
|
+
|
7
|
+
This generates the base class. A test is not generated because no
|
8
|
+
behaviour is included in `ApplicationRecord` by default.
|
@@ -41,11 +41,13 @@ module ActiveRecord
|
|
41
41
|
|
42
42
|
def configured_migrate_path
|
43
43
|
return unless database = options[:database]
|
44
|
+
|
44
45
|
config = ActiveRecord::Base.configurations.configs_for(
|
45
46
|
env_name: Rails.env,
|
46
47
|
name: database
|
47
48
|
)
|
48
|
-
|
49
|
+
|
50
|
+
Array(config&.migrations_paths).first
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|