activerecord 3.2.22.5 → 5.2.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +657 -621
- data/MIT-LICENSE +2 -2
- data/README.rdoc +41 -46
- data/examples/performance.rb +55 -42
- data/examples/simple.rb +6 -5
- data/lib/active_record/aggregations.rb +264 -236
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -42
- data/lib/active_record/associations/association.rb +127 -75
- data/lib/active_record/associations/association_scope.rb +126 -92
- data/lib/active_record/associations/belongs_to_association.rb +78 -27
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
- data/lib/active_record/associations/builder/association.rb +117 -32
- data/lib/active_record/associations/builder/belongs_to.rb +135 -60
- data/lib/active_record/associations/builder/collection_association.rb +61 -54
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
- data/lib/active_record/associations/builder/has_many.rb +10 -64
- data/lib/active_record/associations/builder/has_one.rb +19 -51
- data/lib/active_record/associations/builder/singular_association.rb +28 -18
- data/lib/active_record/associations/collection_association.rb +226 -293
- data/lib/active_record/associations/collection_proxy.rb +1067 -69
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +83 -47
- data/lib/active_record/associations/has_many_through_association.rb +98 -65
- data/lib/active_record/associations/has_one_association.rb +57 -20
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
- data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
- data/lib/active_record/associations/join_dependency.rb +212 -164
- data/lib/active_record/associations/preloader/association.rb +95 -89
- data/lib/active_record/associations/preloader/through_association.rb +84 -44
- data/lib/active_record/associations/preloader.rb +123 -111
- data/lib/active_record/associations/singular_association.rb +33 -24
- data/lib/active_record/associations/through_association.rb +60 -26
- data/lib/active_record/associations.rb +1759 -1506
- data/lib/active_record/attribute_assignment.rb +60 -193
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
- data/lib/active_record/attribute_methods/dirty.rb +113 -74
- data/lib/active_record/attribute_methods/primary_key.rb +106 -77
- data/lib/active_record/attribute_methods/query.rb +8 -5
- data/lib/active_record/attribute_methods/read.rb +63 -114
- data/lib/active_record/attribute_methods/serialization.rb +60 -90
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
- data/lib/active_record/attribute_methods/write.rb +43 -45
- data/lib/active_record/attribute_methods.rb +366 -149
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +312 -225
- data/lib/active_record/base.rb +114 -505
- data/lib/active_record/callbacks.rb +145 -67
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +32 -23
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
- data/lib/active_record/connection_adapters/column.rb +50 -255
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
- data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +200 -105
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +107 -69
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +245 -60
- data/lib/active_record/explain.rb +35 -71
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +18 -9
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +418 -275
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +209 -100
- data/lib/active_record/integration.rb +116 -21
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +107 -94
- data/lib/active_record/locking/pessimistic.rb +20 -8
- data/lib/active_record/log_subscriber.rb +99 -34
- data/lib/active_record/migration/command_recorder.rb +199 -64
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/migration.rb +893 -296
- data/lib/active_record/model_schema.rb +328 -175
- data/lib/active_record/nested_attributes.rb +338 -242
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +557 -170
- data/lib/active_record/query_cache.rb +14 -43
- data/lib/active_record/querying.rb +36 -24
- data/lib/active_record/railtie.rb +147 -52
- data/lib/active_record/railties/console_sandbox.rb +5 -4
- data/lib/active_record/railties/controller_runtime.rb +13 -6
- data/lib/active_record/railties/databases.rake +206 -488
- data/lib/active_record/readonly_attributes.rb +4 -6
- data/lib/active_record/reflection.rb +734 -228
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +249 -52
- data/lib/active_record/relation/calculations.rb +330 -284
- data/lib/active_record/relation/delegation.rb +135 -37
- data/lib/active_record/relation/finder_methods.rb +450 -287
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder.rb +132 -43
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1037 -221
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +48 -151
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +451 -359
- data/lib/active_record/result.rb +129 -20
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +164 -136
- data/lib/active_record/schema.rb +31 -19
- data/lib/active_record/schema_dumper.rb +154 -107
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping/default.rb +108 -98
- data/lib/active_record/scoping/named.rb +125 -112
- data/lib/active_record/scoping.rb +77 -123
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +10 -6
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +175 -16
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +80 -41
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +240 -119
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +35 -18
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +133 -75
- data/lib/active_record/validations.rb +53 -43
- data/lib/active_record/version.rb +7 -7
- data/lib/active_record.rb +89 -57
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/migration.rb +28 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
- data/lib/rails/generators/active_record.rb +10 -16
- metadata +141 -62
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,42 +1,60 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require
|
3
|
+
require "active_record/connection_adapters/abstract_mysql_adapter"
|
4
|
+
require "active_record/connection_adapters/mysql/database_statements"
|
5
|
+
|
6
|
+
gem "mysql2", ">= 0.4.4", "< 0.6.0"
|
7
|
+
require "mysql2"
|
5
8
|
|
6
9
|
module ActiveRecord
|
7
|
-
|
10
|
+
module ConnectionHandling # :nodoc:
|
8
11
|
# Establishes a connection to the database that's used by all Active Record objects.
|
9
|
-
def
|
10
|
-
config
|
12
|
+
def mysql2_connection(config)
|
13
|
+
config = config.symbolize_keys
|
14
|
+
config[:flags] ||= 0
|
11
15
|
|
12
|
-
if
|
13
|
-
config[:flags]
|
16
|
+
if config[:flags].kind_of? Array
|
17
|
+
config[:flags].push "FOUND_ROWS".freeze
|
18
|
+
else
|
19
|
+
config[:flags] |= Mysql2::Client::FOUND_ROWS
|
14
20
|
end
|
15
21
|
|
16
|
-
client = Mysql2::Client.new(config
|
17
|
-
|
18
|
-
|
22
|
+
client = Mysql2::Client.new(config)
|
23
|
+
ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
|
24
|
+
rescue Mysql2::Error => error
|
25
|
+
if error.message.include?("Unknown database")
|
26
|
+
raise ActiveRecord::NoDatabaseError
|
27
|
+
else
|
28
|
+
raise
|
29
|
+
end
|
19
30
|
end
|
20
31
|
end
|
21
32
|
|
22
33
|
module ConnectionAdapters
|
23
34
|
class Mysql2Adapter < AbstractMysqlAdapter
|
35
|
+
ADAPTER_NAME = "Mysql2".freeze
|
24
36
|
|
25
|
-
|
26
|
-
def adapter
|
27
|
-
Mysql2Adapter
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
ADAPTER_NAME = 'Mysql2'
|
37
|
+
include MySQL::DatabaseStatements
|
32
38
|
|
33
39
|
def initialize(connection, logger, connection_options, config)
|
34
40
|
super
|
35
|
-
@
|
41
|
+
@prepared_statements = false unless config.key?(:prepared_statements)
|
36
42
|
configure_connection
|
37
43
|
end
|
38
44
|
|
39
|
-
def
|
45
|
+
def supports_json?
|
46
|
+
!mariadb? && version >= "5.7.8"
|
47
|
+
end
|
48
|
+
|
49
|
+
def supports_comments?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def supports_comments_in_create?
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def supports_savepoints?
|
40
58
|
true
|
41
59
|
end
|
42
60
|
|
@@ -44,7 +62,7 @@ module ActiveRecord
|
|
44
62
|
|
45
63
|
def each_hash(result) # :nodoc:
|
46
64
|
if block_given?
|
47
|
-
result.each(:
|
65
|
+
result.each(as: :hash, symbolize_keys: true) do |row|
|
48
66
|
yield row
|
49
67
|
end
|
50
68
|
else
|
@@ -52,229 +70,60 @@ module ActiveRecord
|
|
52
70
|
end
|
53
71
|
end
|
54
72
|
|
55
|
-
def new_column(field, default, type, null, collation) # :nodoc:
|
56
|
-
Column.new(field, default, type, null, collation)
|
57
|
-
end
|
58
|
-
|
59
73
|
def error_number(exception)
|
60
74
|
exception.error_number if exception.respond_to?(:error_number)
|
61
75
|
end
|
62
76
|
|
77
|
+
#--
|
63
78
|
# QUOTING ==================================================
|
79
|
+
#++
|
64
80
|
|
65
81
|
def quote_string(string)
|
66
82
|
@connection.escape(string)
|
67
83
|
end
|
68
84
|
|
85
|
+
#--
|
69
86
|
# CONNECTION MANAGEMENT ====================================
|
87
|
+
#++
|
70
88
|
|
71
89
|
def active?
|
72
|
-
return false unless @connection
|
73
90
|
@connection.ping
|
74
91
|
end
|
75
92
|
|
76
93
|
def reconnect!
|
94
|
+
super
|
77
95
|
disconnect!
|
78
96
|
connect
|
79
97
|
end
|
98
|
+
alias :reset! :reconnect!
|
80
99
|
|
81
100
|
# Disconnects from the database if already connected.
|
82
101
|
# Otherwise, this method does nothing.
|
83
102
|
def disconnect!
|
84
|
-
|
85
|
-
|
86
|
-
@connection = nil
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def reset!
|
91
|
-
disconnect!
|
92
|
-
connect
|
103
|
+
super
|
104
|
+
@connection.close
|
93
105
|
end
|
94
106
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
|
99
|
-
start = Time.now
|
100
|
-
result = exec_query(sql, 'EXPLAIN', binds)
|
101
|
-
elapsed = Time.now - start
|
102
|
-
|
103
|
-
ExplainPrettyPrinter.new.pp(result, elapsed)
|
107
|
+
def discard! # :nodoc:
|
108
|
+
@connection.automatic_close = false
|
109
|
+
@connection = nil
|
104
110
|
end
|
105
111
|
|
106
|
-
|
107
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
108
|
-
# MySQL shell:
|
109
|
-
#
|
110
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
111
|
-
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|
112
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
113
|
-
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
|
114
|
-
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
|
115
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
116
|
-
# 2 rows in set (0.00 sec)
|
117
|
-
#
|
118
|
-
# This is an exercise in Ruby hyperrealism :).
|
119
|
-
def pp(result, elapsed)
|
120
|
-
widths = compute_column_widths(result)
|
121
|
-
separator = build_separator(widths)
|
122
|
-
|
123
|
-
pp = []
|
124
|
-
|
125
|
-
pp << separator
|
126
|
-
pp << build_cells(result.columns, widths)
|
127
|
-
pp << separator
|
128
|
-
|
129
|
-
result.rows.each do |row|
|
130
|
-
pp << build_cells(row, widths)
|
131
|
-
end
|
132
|
-
|
133
|
-
pp << separator
|
134
|
-
pp << build_footer(result.rows.length, elapsed)
|
135
|
-
|
136
|
-
pp.join("\n") + "\n"
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
def compute_column_widths(result)
|
142
|
-
[].tap do |widths|
|
143
|
-
result.columns.each_with_index do |column, i|
|
144
|
-
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
|
145
|
-
widths << cells_in_column.map(&:length).max
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def build_separator(widths)
|
151
|
-
padding = 1
|
152
|
-
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
|
153
|
-
end
|
112
|
+
private
|
154
113
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
item = 'NULL' if item.nil?
|
159
|
-
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
|
160
|
-
cells << item.to_s.send(justifier, widths[i])
|
161
|
-
end
|
162
|
-
'| ' + cells.join(' | ') + ' |'
|
114
|
+
def connect
|
115
|
+
@connection = Mysql2::Client.new(@config)
|
116
|
+
configure_connection
|
163
117
|
end
|
164
118
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
119
|
+
def configure_connection
|
120
|
+
@connection.query_options.merge!(as: :array)
|
121
|
+
super
|
168
122
|
end
|
169
|
-
end
|
170
123
|
|
171
|
-
|
172
|
-
|
173
|
-
# The overrides below perform much better than the originals in AbstractAdapter
|
174
|
-
# because we're able to take advantage of mysql2's lazy-loading capabilities
|
175
|
-
#
|
176
|
-
# # Returns a record hash with the column names as keys and column values
|
177
|
-
# # as values.
|
178
|
-
# def select_one(sql, name = nil)
|
179
|
-
# result = execute(sql, name)
|
180
|
-
# result.each(:as => :hash) do |r|
|
181
|
-
# return r
|
182
|
-
# end
|
183
|
-
# end
|
184
|
-
#
|
185
|
-
# # Returns a single value from a record
|
186
|
-
# def select_value(sql, name = nil)
|
187
|
-
# result = execute(sql, name)
|
188
|
-
# if first = result.first
|
189
|
-
# first.first
|
190
|
-
# end
|
191
|
-
# end
|
192
|
-
#
|
193
|
-
# # Returns an array of the values of the first column in a select:
|
194
|
-
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
195
|
-
# def select_values(sql, name = nil)
|
196
|
-
# execute(sql, name).map { |row| row.first }
|
197
|
-
# end
|
198
|
-
|
199
|
-
# Returns an array of arrays containing the field values.
|
200
|
-
# Order is the same as that returned by +columns+.
|
201
|
-
def select_rows(sql, name = nil)
|
202
|
-
execute(sql, name).to_a
|
203
|
-
end
|
204
|
-
|
205
|
-
# Executes the SQL statement in the context of this connection.
|
206
|
-
def execute(sql, name = nil)
|
207
|
-
if @connection
|
208
|
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
209
|
-
# made since we established the connection
|
210
|
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
124
|
+
def full_version
|
125
|
+
@full_version ||= @connection.server_info[:version]
|
211
126
|
end
|
212
|
-
|
213
|
-
super
|
214
|
-
end
|
215
|
-
|
216
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
217
|
-
result = execute(sql, name)
|
218
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
219
|
-
end
|
220
|
-
|
221
|
-
alias exec_without_stmt exec_query
|
222
|
-
|
223
|
-
# Returns an array of record hashes with the column names as keys and
|
224
|
-
# column values as values.
|
225
|
-
def select(sql, name = nil, binds = [])
|
226
|
-
exec_query(sql, name).to_a
|
227
|
-
end
|
228
|
-
|
229
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
230
|
-
super
|
231
|
-
id_value || @connection.last_id
|
232
|
-
end
|
233
|
-
alias :create :insert_sql
|
234
|
-
|
235
|
-
def exec_insert(sql, name, binds)
|
236
|
-
execute to_sql(sql, binds), name
|
237
|
-
end
|
238
|
-
|
239
|
-
def exec_delete(sql, name, binds)
|
240
|
-
execute to_sql(sql, binds), name
|
241
|
-
@connection.affected_rows
|
242
|
-
end
|
243
|
-
alias :exec_update :exec_delete
|
244
|
-
|
245
|
-
def last_inserted_id(result)
|
246
|
-
@connection.last_id
|
247
|
-
end
|
248
|
-
|
249
|
-
private
|
250
|
-
|
251
|
-
def connect
|
252
|
-
@connection = Mysql2::Client.new(@config)
|
253
|
-
configure_connection
|
254
|
-
end
|
255
|
-
|
256
|
-
def configure_connection
|
257
|
-
@connection.query_options.merge!(:as => :array)
|
258
|
-
|
259
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
260
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
261
|
-
variable_assignments = ['SQL_AUTO_IS_NULL=0']
|
262
|
-
encoding = @config[:encoding]
|
263
|
-
|
264
|
-
# make sure we set the encoding
|
265
|
-
variable_assignments << "NAMES '#{encoding}'" if encoding
|
266
|
-
|
267
|
-
# increase timeout so mysql server doesn't disconnect us
|
268
|
-
wait_timeout = @config[:wait_timeout]
|
269
|
-
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
|
270
|
-
variable_assignments << "@@wait_timeout = #{wait_timeout}"
|
271
|
-
|
272
|
-
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
|
273
|
-
end
|
274
|
-
|
275
|
-
def version
|
276
|
-
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
277
|
-
end
|
278
127
|
end
|
279
128
|
end
|
280
129
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
# PostgreSQL-specific extensions to column definitions in a table.
|
6
|
+
class PostgreSQLColumn < Column #:nodoc:
|
7
|
+
delegate :array, :oid, :fmod, to: :sql_type_metadata
|
8
|
+
alias :array? :array
|
9
|
+
|
10
|
+
def initialize(*, max_identifier_length: 63, **)
|
11
|
+
super
|
12
|
+
@max_identifier_length = max_identifier_length
|
13
|
+
end
|
14
|
+
|
15
|
+
def serial?
|
16
|
+
return unless default_function
|
17
|
+
|
18
|
+
if %r{\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z} =~ default_function
|
19
|
+
sequence_name_from_parts(table_name, name, suffix) == sequence_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
attr_reader :max_identifier_length
|
25
|
+
|
26
|
+
private
|
27
|
+
def sequence_name_from_parts(table_name, column_name, suffix)
|
28
|
+
over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length
|
29
|
+
|
30
|
+
if over_length > 0
|
31
|
+
column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
|
32
|
+
over_length -= column_name.length - column_name_length
|
33
|
+
column_name = column_name[0, column_name_length - [over_length, 0].min]
|
34
|
+
end
|
35
|
+
|
36
|
+
if over_length > 0
|
37
|
+
table_name = table_name[0, table_name.length - over_length]
|
38
|
+
end
|
39
|
+
|
40
|
+
"#{table_name}_#{column_name}_#{suffix}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
module DatabaseStatements
|
7
|
+
def explain(arel, binds = [])
|
8
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
9
|
+
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
|
10
|
+
end
|
11
|
+
|
12
|
+
# The internal PostgreSQL identifier of the money data type.
|
13
|
+
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
14
|
+
# The internal PostgreSQL identifier of the BYTEA data type.
|
15
|
+
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
|
16
|
+
|
17
|
+
# create a 2D array representing the result set
|
18
|
+
def result_as_array(res) #:nodoc:
|
19
|
+
# check if we have any binary column and if they need escaping
|
20
|
+
ftypes = Array.new(res.nfields) do |i|
|
21
|
+
[i, res.ftype(i)]
|
22
|
+
end
|
23
|
+
|
24
|
+
rows = res.values
|
25
|
+
return rows unless ftypes.any? { |_, x|
|
26
|
+
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
27
|
+
}
|
28
|
+
|
29
|
+
typehash = ftypes.group_by { |_, type| type }
|
30
|
+
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
31
|
+
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
32
|
+
|
33
|
+
rows.each do |row|
|
34
|
+
# unescape string passed BYTEA field (OID == 17)
|
35
|
+
binaries.each do |index, _|
|
36
|
+
row[index] = unescape_bytea(row[index])
|
37
|
+
end
|
38
|
+
|
39
|
+
# If this is a money type column and there are any currency symbols,
|
40
|
+
# then strip them off. Indeed it would be prettier to do this in
|
41
|
+
# PostgreSQLColumn.string_to_decimal but would break form input
|
42
|
+
# fields that call value_before_type_cast.
|
43
|
+
monies.each do |index, _|
|
44
|
+
data = row[index]
|
45
|
+
# Because money output is formatted according to the locale, there are two
|
46
|
+
# cases to consider (note the decimal separators):
|
47
|
+
# (1) $12,345,678.12
|
48
|
+
# (2) $12.345.678,12
|
49
|
+
case data
|
50
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
51
|
+
data.gsub!(/[^-\d.]/, "")
|
52
|
+
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
53
|
+
data.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Queries the database and returns the results in an Array-like object
|
60
|
+
def query(sql, name = nil) #:nodoc:
|
61
|
+
log(sql, name) do
|
62
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
63
|
+
result_as_array @connection.async_exec(sql)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Executes an SQL statement, returning a PG::Result object on success
|
69
|
+
# or raising a PG::Error exception otherwise.
|
70
|
+
# Note: the PG::Result object is manually memory managed; if you don't
|
71
|
+
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
72
|
+
def execute(sql, name = nil)
|
73
|
+
log(sql, name) do
|
74
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
75
|
+
@connection.async_exec(sql)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
81
|
+
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
|
82
|
+
types = {}
|
83
|
+
fields = result.fields
|
84
|
+
fields.each_with_index do |fname, i|
|
85
|
+
ftype = result.ftype i
|
86
|
+
fmod = result.fmod i
|
87
|
+
types[fname] = get_oid_type(ftype, fmod, fname)
|
88
|
+
end
|
89
|
+
ActiveRecord::Result.new(fields, result.values, types)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def exec_delete(sql, name = nil, binds = [])
|
94
|
+
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
95
|
+
end
|
96
|
+
alias :exec_update :exec_delete
|
97
|
+
|
98
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
|
99
|
+
if pk.nil?
|
100
|
+
# Extract the table from the insert sql. Yuck.
|
101
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
102
|
+
pk = primary_key(table_ref) if table_ref
|
103
|
+
end
|
104
|
+
|
105
|
+
if pk = suppress_composite_primary_key(pk)
|
106
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
107
|
+
end
|
108
|
+
|
109
|
+
super
|
110
|
+
end
|
111
|
+
private :sql_for_insert
|
112
|
+
|
113
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
114
|
+
if use_insert_returning? || pk == false
|
115
|
+
super
|
116
|
+
else
|
117
|
+
result = exec_query(sql, name, binds)
|
118
|
+
unless sequence_name
|
119
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
120
|
+
if table_ref
|
121
|
+
pk = primary_key(table_ref) if pk.nil?
|
122
|
+
pk = suppress_composite_primary_key(pk)
|
123
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
124
|
+
end
|
125
|
+
return result unless sequence_name
|
126
|
+
end
|
127
|
+
last_insert_id_result(sequence_name)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Begins a transaction.
|
132
|
+
def begin_db_transaction
|
133
|
+
execute "BEGIN"
|
134
|
+
end
|
135
|
+
|
136
|
+
def begin_isolated_db_transaction(isolation)
|
137
|
+
begin_db_transaction
|
138
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# Commits a transaction.
|
142
|
+
def commit_db_transaction
|
143
|
+
execute "COMMIT"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Aborts a transaction.
|
147
|
+
def exec_rollback_db_transaction
|
148
|
+
execute "ROLLBACK"
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
# Returns the current ID of a table's sequence.
|
153
|
+
def last_insert_id_result(sequence_name)
|
154
|
+
exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
155
|
+
end
|
156
|
+
|
157
|
+
def suppress_composite_primary_key(pk)
|
158
|
+
pk unless pk.is_a?(Array)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
class ExplainPrettyPrinter # :nodoc:
|
7
|
+
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
|
8
|
+
# PostgreSQL shell:
|
9
|
+
#
|
10
|
+
# QUERY PLAN
|
11
|
+
# ------------------------------------------------------------------------------
|
12
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
13
|
+
# Join Filter: (posts.user_id = users.id)
|
14
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
15
|
+
# Index Cond: (id = 1)
|
16
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
17
|
+
# Filter: (posts.user_id = 1)
|
18
|
+
# (6 rows)
|
19
|
+
#
|
20
|
+
def pp(result)
|
21
|
+
header = result.columns.first
|
22
|
+
lines = result.rows.map(&:first)
|
23
|
+
|
24
|
+
# We add 2 because there's one char of padding at both sides, note
|
25
|
+
# the extra hyphens in the example above.
|
26
|
+
width = [header, *lines].map(&:length).max + 2
|
27
|
+
|
28
|
+
pp = []
|
29
|
+
|
30
|
+
pp << header.center(width).rstrip
|
31
|
+
pp << "-" * width
|
32
|
+
|
33
|
+
pp += lines.map { |line| " #{line}" }
|
34
|
+
|
35
|
+
nrows = result.rows.length
|
36
|
+
rows_label = nrows == 1 ? "row" : "rows"
|
37
|
+
pp << "(#{nrows} #{rows_label})"
|
38
|
+
|
39
|
+
pp.join("\n") + "\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module PostgreSQL
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Array < Type::Value # :nodoc:
|
8
|
+
include Type::Helpers::Mutable
|
9
|
+
|
10
|
+
Data = Struct.new(:encoder, :values) # :nodoc:
|
11
|
+
|
12
|
+
attr_reader :subtype, :delimiter
|
13
|
+
delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
|
14
|
+
|
15
|
+
def initialize(subtype, delimiter = ",")
|
16
|
+
@subtype = subtype
|
17
|
+
@delimiter = delimiter
|
18
|
+
|
19
|
+
@pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
|
20
|
+
@pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
|
21
|
+
end
|
22
|
+
|
23
|
+
def deserialize(value)
|
24
|
+
case value
|
25
|
+
when ::String
|
26
|
+
type_cast_array(@pg_decoder.decode(value), :deserialize)
|
27
|
+
when Data
|
28
|
+
type_cast_array(value.values, :deserialize)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def cast(value)
|
35
|
+
if value.is_a?(::String)
|
36
|
+
value = begin
|
37
|
+
@pg_decoder.decode(value)
|
38
|
+
rescue TypeError
|
39
|
+
# malformed array string is treated as [], will raise in PG 2.0 gem
|
40
|
+
# this keeps a consistent implementation
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
type_cast_array(value, :cast)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize(value)
|
48
|
+
if value.is_a?(::Array)
|
49
|
+
casted_values = type_cast_array(value, :serialize)
|
50
|
+
Data.new(@pg_encoder, casted_values)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
other.is_a?(Array) &&
|
58
|
+
subtype == other.subtype &&
|
59
|
+
delimiter == other.delimiter
|
60
|
+
end
|
61
|
+
|
62
|
+
def type_cast_for_schema(value)
|
63
|
+
return super unless value.is_a?(::Array)
|
64
|
+
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
|
65
|
+
end
|
66
|
+
|
67
|
+
def map(value, &block)
|
68
|
+
value.map(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def changed_in_place?(raw_old_value, new_value)
|
72
|
+
deserialize(raw_old_value) != new_value
|
73
|
+
end
|
74
|
+
|
75
|
+
def force_equality?(value)
|
76
|
+
value.is_a?(::Array)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def type_cast_array(value, method)
|
82
|
+
if value.is_a?(::Array)
|
83
|
+
value.map { |item| type_cast_array(item, method) }
|
84
|
+
else
|
85
|
+
@subtype.public_send(method, value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|