activerecord 4.2.11.3 → 5.0.0.1
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 +1281 -1204
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record/aggregations.rb +35 -24
- data/lib/active_record/association_relation.rb +3 -3
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +11 -9
- data/lib/active_record/associations/association_scope.rb +73 -102
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -10
- data/lib/active_record/associations/collection_association.rb +49 -41
- data/lib/active_record/associations/collection_proxy.rb +67 -27
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +20 -71
- data/lib/active_record/associations/has_many_through_association.rb +8 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
- data/lib/active_record/associations/join_dependency.rb +29 -19
- data/lib/active_record/associations/preloader/association.rb +46 -52
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +27 -14
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +11 -3
- data/lib/active_record/associations.rb +317 -209
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +68 -18
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_methods.rb +76 -47
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +6 -4
- data/lib/active_record/attribute_set.rb +30 -3
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +49 -16
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +89 -107
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +113 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +76 -40
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +43 -21
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration.rb +363 -133
- data/lib/active_record/model_schema.rb +129 -41
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +121 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +69 -46
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +282 -115
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +139 -34
- data/lib/active_record/relation/calculations.rb +79 -108
- data/lib/active_record/relation/delegation.rb +7 -20
- data/lib/active_record/relation/finder_methods.rb +163 -81
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +176 -116
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +95 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +62 -38
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping/default.rb +23 -9
- data/lib/active_record/scoping/named.rb +49 -28
- data/lib/active_record/scoping.rb +32 -15
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +16 -14
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +57 -43
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +138 -56
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +15 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +30 -29
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record.rb +8 -4
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +60 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module MySQL
|
4
|
+
module ColumnDumper
|
5
|
+
def column_spec_for_primary_key(column)
|
6
|
+
if column.bigint?
|
7
|
+
spec = { id: :bigint.inspect }
|
8
|
+
spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
|
9
|
+
else
|
10
|
+
spec = super
|
11
|
+
end
|
12
|
+
spec[:unsigned] = 'true' if column.unsigned?
|
13
|
+
spec
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare_column_options(column)
|
17
|
+
spec = super
|
18
|
+
spec[:unsigned] = 'true' if column.unsigned?
|
19
|
+
spec
|
20
|
+
end
|
21
|
+
|
22
|
+
def migration_keys
|
23
|
+
super + [:unsigned]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def default_primary_key?(column)
|
29
|
+
super && column.auto_increment?
|
30
|
+
end
|
31
|
+
|
32
|
+
def schema_type(column)
|
33
|
+
if column.sql_type == 'tinyblob'
|
34
|
+
:blob
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def schema_precision(column)
|
41
|
+
super unless /time/ === column.sql_type && column.precision == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def schema_collation(column)
|
45
|
+
if column.collation && table_name = column.table_name
|
46
|
+
@table_collation_cache ||= {}
|
47
|
+
@table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
|
48
|
+
column.collation.inspect if column.collation != @table_collation_cache[table_name]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module MySQL
|
4
|
+
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
|
5
|
+
attr_reader :extra, :strict
|
6
|
+
|
7
|
+
def initialize(type_metadata, extra: "", strict: false)
|
8
|
+
super(type_metadata)
|
9
|
+
@type_metadata = type_metadata
|
10
|
+
@extra = extra
|
11
|
+
@strict = strict
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
other.is_a?(MySQL::TypeMetadata) &&
|
16
|
+
attributes_for_hash == other.attributes_for_hash
|
17
|
+
end
|
18
|
+
alias eql? ==
|
19
|
+
|
20
|
+
def hash
|
21
|
+
attributes_for_hash.hash
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def attributes_for_hash
|
27
|
+
[self.class, @type_metadata, extra, strict]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql/database_statements'
|
2
3
|
|
3
|
-
gem 'mysql2', '>= 0.3.
|
4
|
+
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
4
5
|
require 'mysql2'
|
6
|
+
raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3'
|
5
7
|
|
6
8
|
module ActiveRecord
|
7
9
|
module ConnectionHandling # :nodoc:
|
@@ -10,17 +12,21 @@ module ActiveRecord
|
|
10
12
|
config = config.symbolize_keys
|
11
13
|
|
12
14
|
config[:username] = 'root' if config[:username].nil?
|
15
|
+
config[:flags] ||= 0
|
13
16
|
|
14
17
|
if Mysql2::Client.const_defined? :FOUND_ROWS
|
15
|
-
config[:flags]
|
18
|
+
if config[:flags].kind_of? Array
|
19
|
+
config[:flags].push "FOUND_ROWS".freeze
|
20
|
+
else
|
21
|
+
config[:flags] |= Mysql2::Client::FOUND_ROWS
|
22
|
+
end
|
16
23
|
end
|
17
24
|
|
18
25
|
client = Mysql2::Client.new(config)
|
19
|
-
|
20
|
-
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
|
26
|
+
ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
|
21
27
|
rescue Mysql2::Error => error
|
22
28
|
if error.message.include?("Unknown database")
|
23
|
-
raise ActiveRecord::NoDatabaseError
|
29
|
+
raise ActiveRecord::NoDatabaseError
|
24
30
|
else
|
25
31
|
raise
|
26
32
|
end
|
@@ -31,22 +37,27 @@ module ActiveRecord
|
|
31
37
|
class Mysql2Adapter < AbstractMysqlAdapter
|
32
38
|
ADAPTER_NAME = 'Mysql2'.freeze
|
33
39
|
|
40
|
+
include MySQL::DatabaseStatements
|
41
|
+
|
34
42
|
def initialize(connection, logger, connection_options, config)
|
35
43
|
super
|
36
|
-
@prepared_statements = false
|
44
|
+
@prepared_statements = false unless config.key?(:prepared_statements)
|
37
45
|
configure_connection
|
38
46
|
end
|
39
47
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
48
|
+
def supports_json?
|
49
|
+
!mariadb? && version >= '5.7.8'
|
50
|
+
end
|
51
|
+
|
52
|
+
def supports_comments?
|
53
|
+
true
|
47
54
|
end
|
48
55
|
|
49
|
-
def
|
56
|
+
def supports_comments_in_create?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def supports_savepoints?
|
50
61
|
true
|
51
62
|
end
|
52
63
|
|
@@ -79,6 +90,7 @@ module ActiveRecord
|
|
79
90
|
#++
|
80
91
|
|
81
92
|
def active?
|
93
|
+
return false unless @connection
|
82
94
|
@connection.ping
|
83
95
|
end
|
84
96
|
|
@@ -93,155 +105,10 @@ module ActiveRecord
|
|
93
105
|
# Otherwise, this method does nothing.
|
94
106
|
def disconnect!
|
95
107
|
super
|
96
|
-
@connection.
|
97
|
-
|
98
|
-
|
99
|
-
#--
|
100
|
-
# DATABASE STATEMENTS ======================================
|
101
|
-
#++
|
102
|
-
|
103
|
-
def explain(arel, binds = [])
|
104
|
-
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
|
105
|
-
start = Time.now
|
106
|
-
result = exec_query(sql, 'EXPLAIN', binds)
|
107
|
-
elapsed = Time.now - start
|
108
|
-
|
109
|
-
ExplainPrettyPrinter.new.pp(result, elapsed)
|
110
|
-
end
|
111
|
-
|
112
|
-
class ExplainPrettyPrinter # :nodoc:
|
113
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
114
|
-
# MySQL shell:
|
115
|
-
#
|
116
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
117
|
-
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|
118
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
119
|
-
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
|
120
|
-
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
|
121
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
122
|
-
# 2 rows in set (0.00 sec)
|
123
|
-
#
|
124
|
-
# This is an exercise in Ruby hyperrealism :).
|
125
|
-
def pp(result, elapsed)
|
126
|
-
widths = compute_column_widths(result)
|
127
|
-
separator = build_separator(widths)
|
128
|
-
|
129
|
-
pp = []
|
130
|
-
|
131
|
-
pp << separator
|
132
|
-
pp << build_cells(result.columns, widths)
|
133
|
-
pp << separator
|
134
|
-
|
135
|
-
result.rows.each do |row|
|
136
|
-
pp << build_cells(row, widths)
|
137
|
-
end
|
138
|
-
|
139
|
-
pp << separator
|
140
|
-
pp << build_footer(result.rows.length, elapsed)
|
141
|
-
|
142
|
-
pp.join("\n") + "\n"
|
143
|
-
end
|
144
|
-
|
145
|
-
private
|
146
|
-
|
147
|
-
def compute_column_widths(result)
|
148
|
-
[].tap do |widths|
|
149
|
-
result.columns.each_with_index do |column, i|
|
150
|
-
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
|
151
|
-
widths << cells_in_column.map(&:length).max
|
152
|
-
end
|
153
|
-
end
|
108
|
+
unless @connection.nil?
|
109
|
+
@connection.close
|
110
|
+
@connection = nil
|
154
111
|
end
|
155
|
-
|
156
|
-
def build_separator(widths)
|
157
|
-
padding = 1
|
158
|
-
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
|
159
|
-
end
|
160
|
-
|
161
|
-
def build_cells(items, widths)
|
162
|
-
cells = []
|
163
|
-
items.each_with_index do |item, i|
|
164
|
-
item = 'NULL' if item.nil?
|
165
|
-
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
|
166
|
-
cells << item.to_s.send(justifier, widths[i])
|
167
|
-
end
|
168
|
-
'| ' + cells.join(' | ') + ' |'
|
169
|
-
end
|
170
|
-
|
171
|
-
def build_footer(nrows, elapsed)
|
172
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
173
|
-
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# FIXME: re-enable the following once a "better" query_cache solution is in core
|
178
|
-
#
|
179
|
-
# The overrides below perform much better than the originals in AbstractAdapter
|
180
|
-
# because we're able to take advantage of mysql2's lazy-loading capabilities
|
181
|
-
#
|
182
|
-
# # Returns a record hash with the column names as keys and column values
|
183
|
-
# # as values.
|
184
|
-
# def select_one(sql, name = nil)
|
185
|
-
# result = execute(sql, name)
|
186
|
-
# result.each(as: :hash) do |r|
|
187
|
-
# return r
|
188
|
-
# end
|
189
|
-
# end
|
190
|
-
#
|
191
|
-
# # Returns a single value from a record
|
192
|
-
# def select_value(sql, name = nil)
|
193
|
-
# result = execute(sql, name)
|
194
|
-
# if first = result.first
|
195
|
-
# first.first
|
196
|
-
# end
|
197
|
-
# end
|
198
|
-
#
|
199
|
-
# # Returns an array of the values of the first column in a select:
|
200
|
-
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
201
|
-
# def select_values(sql, name = nil)
|
202
|
-
# execute(sql, name).map { |row| row.first }
|
203
|
-
# end
|
204
|
-
|
205
|
-
# Returns an array of arrays containing the field values.
|
206
|
-
# Order is the same as that returned by +columns+.
|
207
|
-
def select_rows(sql, name = nil, binds = [])
|
208
|
-
execute(sql, name).to_a
|
209
|
-
end
|
210
|
-
|
211
|
-
# Executes the SQL statement in the context of this connection.
|
212
|
-
def execute(sql, name = nil)
|
213
|
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
214
|
-
# made since we established the connection
|
215
|
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
216
|
-
|
217
|
-
super
|
218
|
-
end
|
219
|
-
|
220
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
221
|
-
result = execute(sql, name)
|
222
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
223
|
-
end
|
224
|
-
|
225
|
-
alias exec_without_stmt exec_query
|
226
|
-
|
227
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
228
|
-
super
|
229
|
-
id_value || @connection.last_id
|
230
|
-
end
|
231
|
-
alias :create :insert_sql
|
232
|
-
|
233
|
-
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
234
|
-
execute to_sql(sql, binds), name
|
235
|
-
end
|
236
|
-
|
237
|
-
def exec_delete(sql, name, binds)
|
238
|
-
execute to_sql(sql, binds), name
|
239
|
-
@connection.affected_rows
|
240
|
-
end
|
241
|
-
alias :exec_update :exec_delete
|
242
|
-
|
243
|
-
def last_inserted_id(result)
|
244
|
-
@connection.last_id
|
245
112
|
end
|
246
113
|
|
247
114
|
private
|
@@ -259,10 +126,6 @@ module ActiveRecord
|
|
259
126
|
def full_version
|
260
127
|
@full_version ||= @connection.server_info[:version]
|
261
128
|
end
|
262
|
-
|
263
|
-
def set_field_encoding field_name
|
264
|
-
field_name
|
265
|
-
end
|
266
129
|
end
|
267
130
|
end
|
268
131
|
end
|
@@ -2,18 +2,13 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
# PostgreSQL-specific extensions to column definitions in a table.
|
4
4
|
class PostgreSQLColumn < Column #:nodoc:
|
5
|
-
|
5
|
+
delegate :array, :oid, :fmod, to: :sql_type_metadata
|
6
|
+
alias :array? :array
|
6
7
|
|
7
|
-
def
|
8
|
-
|
9
|
-
@array = true
|
10
|
-
super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)
|
11
|
-
else
|
12
|
-
@array = false
|
13
|
-
super(name, default, cast_type, sql_type, null)
|
14
|
-
end
|
8
|
+
def serial?
|
9
|
+
return unless default_function
|
15
10
|
|
16
|
-
|
11
|
+
%r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
|
17
12
|
end
|
18
13
|
end
|
19
14
|
end
|
@@ -4,44 +4,7 @@ module ActiveRecord
|
|
4
4
|
module DatabaseStatements
|
5
5
|
def explain(arel, binds = [])
|
6
6
|
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
7
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
8
|
-
end
|
9
|
-
|
10
|
-
class ExplainPrettyPrinter # :nodoc:
|
11
|
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
12
|
-
# PostgreSQL shell:
|
13
|
-
#
|
14
|
-
# QUERY PLAN
|
15
|
-
# ------------------------------------------------------------------------------
|
16
|
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
17
|
-
# Join Filter: (posts.user_id = users.id)
|
18
|
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
19
|
-
# Index Cond: (id = 1)
|
20
|
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
21
|
-
# Filter: (posts.user_id = 1)
|
22
|
-
# (6 rows)
|
23
|
-
#
|
24
|
-
def pp(result)
|
25
|
-
header = result.columns.first
|
26
|
-
lines = result.rows.map(&:first)
|
27
|
-
|
28
|
-
# We add 2 because there's one char of padding at both sides, note
|
29
|
-
# the extra hyphens in the example above.
|
30
|
-
width = [header, *lines].map(&:length).max + 2
|
31
|
-
|
32
|
-
pp = []
|
33
|
-
|
34
|
-
pp << header.center(width).rstrip
|
35
|
-
pp << '-' * width
|
36
|
-
|
37
|
-
pp += lines.map {|line| " #{line}"}
|
38
|
-
|
39
|
-
nrows = result.rows.length
|
40
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
41
|
-
pp << "(#{nrows} #{rows_label})"
|
42
|
-
|
43
|
-
pp.join("\n") + "\n"
|
44
|
-
end
|
7
|
+
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
45
8
|
end
|
46
9
|
|
47
10
|
def select_value(arel, name = nil, binds = [])
|
@@ -52,8 +15,8 @@ module ActiveRecord
|
|
52
15
|
end
|
53
16
|
end
|
54
17
|
|
55
|
-
def select_values(arel, name = nil)
|
56
|
-
arel, binds = binds_from_relation arel,
|
18
|
+
def select_values(arel, name = nil, binds = [])
|
19
|
+
arel, binds = binds_from_relation arel, binds
|
57
20
|
sql = to_sql(arel, binds)
|
58
21
|
execute_and_clear(sql, name, binds) do |result|
|
59
22
|
if result.nfields > 0
|
@@ -72,28 +35,6 @@ module ActiveRecord
|
|
72
35
|
end
|
73
36
|
end
|
74
37
|
|
75
|
-
# Executes an INSERT query and returns the new record's ID
|
76
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
77
|
-
unless pk
|
78
|
-
# Extract the table from the insert sql. Yuck.
|
79
|
-
table_ref = extract_table_ref_from_insert_sql(sql)
|
80
|
-
pk = primary_key(table_ref) if table_ref
|
81
|
-
end
|
82
|
-
|
83
|
-
if pk && use_insert_returning?
|
84
|
-
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
85
|
-
elsif pk
|
86
|
-
super
|
87
|
-
last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
|
88
|
-
else
|
89
|
-
super
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def create
|
94
|
-
super.insert
|
95
|
-
end
|
96
|
-
|
97
38
|
# The internal PostgreSQL identifier of the money data type.
|
98
39
|
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
99
40
|
# The internal PostgreSQL identifier of the BYTEA data type.
|
@@ -150,14 +91,16 @@ module ActiveRecord
|
|
150
91
|
|
151
92
|
# Executes an SQL statement, returning a PGresult object on success
|
152
93
|
# or raising a PGError exception otherwise.
|
94
|
+
# Note: the PGresult object is manually memory managed; if you don't
|
95
|
+
# need it specifically, you many want consider the exec_query wrapper.
|
153
96
|
def execute(sql, name = nil)
|
154
97
|
log(sql, name) do
|
155
98
|
@connection.async_exec(sql)
|
156
99
|
end
|
157
100
|
end
|
158
101
|
|
159
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
160
|
-
execute_and_clear(sql, name, binds) do |result|
|
102
|
+
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
|
103
|
+
execute_and_clear(sql, name, binds, prepare: prepare) do |result|
|
161
104
|
types = {}
|
162
105
|
fields = result.fields
|
163
106
|
fields.each_with_index do |fname, i|
|
@@ -174,8 +117,8 @@ module ActiveRecord
|
|
174
117
|
end
|
175
118
|
alias :exec_update :exec_delete
|
176
119
|
|
177
|
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
178
|
-
|
120
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
|
121
|
+
if pk.nil?
|
179
122
|
# Extract the table from the insert sql. Yuck.
|
180
123
|
table_ref = extract_table_ref_from_insert_sql(sql)
|
181
124
|
pk = primary_key(table_ref) if table_ref
|
@@ -185,7 +128,7 @@ module ActiveRecord
|
|
185
128
|
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
186
129
|
end
|
187
130
|
|
188
|
-
|
131
|
+
super
|
189
132
|
end
|
190
133
|
|
191
134
|
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
@@ -202,11 +145,6 @@ module ActiveRecord
|
|
202
145
|
end
|
203
146
|
end
|
204
147
|
|
205
|
-
# Executes an UPDATE query and returns the number of affected tuples.
|
206
|
-
def update_sql(sql, name = nil)
|
207
|
-
super.cmd_tuples
|
208
|
-
end
|
209
|
-
|
210
148
|
# Begins a transaction.
|
211
149
|
def begin_db_transaction
|
212
150
|
execute "BEGIN"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module PostgreSQL
|
4
|
+
class ExplainPrettyPrinter # :nodoc:
|
5
|
+
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
|
6
|
+
# PostgreSQL shell:
|
7
|
+
#
|
8
|
+
# QUERY PLAN
|
9
|
+
# ------------------------------------------------------------------------------
|
10
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
11
|
+
# Join Filter: (posts.user_id = users.id)
|
12
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
13
|
+
# Index Cond: (id = 1)
|
14
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
15
|
+
# Filter: (posts.user_id = 1)
|
16
|
+
# (6 rows)
|
17
|
+
#
|
18
|
+
def pp(result)
|
19
|
+
header = result.columns.first
|
20
|
+
lines = result.rows.map(&:first)
|
21
|
+
|
22
|
+
# We add 2 because there's one char of padding at both sides, note
|
23
|
+
# the extra hyphens in the example above.
|
24
|
+
width = [header, *lines].map(&:length).max + 2
|
25
|
+
|
26
|
+
pp = []
|
27
|
+
|
28
|
+
pp << header.center(width).rstrip
|
29
|
+
pp << '-' * width
|
30
|
+
|
31
|
+
pp += lines.map {|line| " #{line}"}
|
32
|
+
|
33
|
+
nrows = result.rows.length
|
34
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
35
|
+
pp << "(#{nrows} #{rows_label})"
|
36
|
+
|
37
|
+
pp.join("\n") + "\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,51 +3,57 @@ module ActiveRecord
|
|
3
3
|
module PostgreSQL
|
4
4
|
module OID # :nodoc:
|
5
5
|
class Array < Type::Value # :nodoc:
|
6
|
-
include Type::Mutable
|
7
|
-
|
8
|
-
# Loads pg_array_parser if available. String parsing can be
|
9
|
-
# performed quicker by a native extension, which will not create
|
10
|
-
# a large amount of Ruby objects that will need to be garbage
|
11
|
-
# collected. pg_array_parser has a C and Java extension
|
12
|
-
begin
|
13
|
-
require 'pg_array_parser'
|
14
|
-
include PgArrayParser
|
15
|
-
rescue LoadError
|
16
|
-
require 'active_record/connection_adapters/postgresql/array_parser'
|
17
|
-
include PostgreSQL::ArrayParser
|
18
|
-
end
|
6
|
+
include Type::Helpers::Mutable
|
19
7
|
|
20
8
|
attr_reader :subtype, :delimiter
|
21
|
-
delegate :type, :limit, to: :subtype
|
9
|
+
delegate :type, :user_input_in_time_zone, :limit, to: :subtype
|
22
10
|
|
23
11
|
def initialize(subtype, delimiter = ',')
|
24
12
|
@subtype = subtype
|
25
13
|
@delimiter = delimiter
|
14
|
+
|
15
|
+
@pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
|
16
|
+
@pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
|
26
17
|
end
|
27
18
|
|
28
|
-
def
|
19
|
+
def deserialize(value)
|
29
20
|
if value.is_a?(::String)
|
30
|
-
type_cast_array(
|
21
|
+
type_cast_array(@pg_decoder.decode(value), :deserialize)
|
31
22
|
else
|
32
23
|
super
|
33
24
|
end
|
34
25
|
end
|
35
26
|
|
36
|
-
def
|
27
|
+
def cast(value)
|
37
28
|
if value.is_a?(::String)
|
38
|
-
value =
|
29
|
+
value = @pg_decoder.decode(value)
|
39
30
|
end
|
40
|
-
type_cast_array(value, :
|
31
|
+
type_cast_array(value, :cast)
|
41
32
|
end
|
42
33
|
|
43
|
-
def
|
34
|
+
def serialize(value)
|
44
35
|
if value.is_a?(::Array)
|
45
|
-
|
36
|
+
@pg_encoder.encode(type_cast_array(value, :serialize))
|
46
37
|
else
|
47
38
|
super
|
48
39
|
end
|
49
40
|
end
|
50
41
|
|
42
|
+
def ==(other)
|
43
|
+
other.is_a?(Array) &&
|
44
|
+
subtype == other.subtype &&
|
45
|
+
delimiter == other.delimiter
|
46
|
+
end
|
47
|
+
|
48
|
+
def type_cast_for_schema(value)
|
49
|
+
return super unless value.is_a?(::Array)
|
50
|
+
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
|
51
|
+
end
|
52
|
+
|
53
|
+
def map(value, &block)
|
54
|
+
value.map(&block)
|
55
|
+
end
|
56
|
+
|
51
57
|
private
|
52
58
|
|
53
59
|
def type_cast_array(value, method)
|
@@ -57,42 +63,6 @@ module ActiveRecord
|
|
57
63
|
@subtype.public_send(method, value)
|
58
64
|
end
|
59
65
|
end
|
60
|
-
|
61
|
-
def cast_value_for_database(value)
|
62
|
-
if value.is_a?(::Array)
|
63
|
-
casted_values = value.map { |item| cast_value_for_database(item) }
|
64
|
-
"{#{casted_values.join(delimiter)}}"
|
65
|
-
else
|
66
|
-
quote_and_escape(subtype.type_cast_for_database(value))
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays
|
71
|
-
|
72
|
-
def quote_and_escape(value)
|
73
|
-
case value
|
74
|
-
when ::String
|
75
|
-
if string_requires_quoting?(value)
|
76
|
-
value = value.gsub(/\\/, ARRAY_ESCAPE)
|
77
|
-
value.gsub!(/"/,"\\\"")
|
78
|
-
%("#{value}")
|
79
|
-
else
|
80
|
-
value
|
81
|
-
end
|
82
|
-
when nil then "NULL"
|
83
|
-
when ::Date, ::DateTime, ::Time then subtype.type_cast_for_schema(value)
|
84
|
-
else value
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
# See http://www.postgresql.org/docs/9.2/static/arrays.html#ARRAYS-IO
|
89
|
-
# for a list of all cases in which strings will be quoted.
|
90
|
-
def string_requires_quoting?(string)
|
91
|
-
string.empty? ||
|
92
|
-
string == "NULL" ||
|
93
|
-
string =~ /[\{\}"\\\s]/ ||
|
94
|
-
string.include?(delimiter)
|
95
|
-
end
|
96
66
|
end
|
97
67
|
end
|
98
68
|
end
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
:bit
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def cast(value)
|
11
11
|
if ::String === value
|
12
12
|
case value
|
13
13
|
when /^0x/i
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def serialize(value)
|
24
24
|
Data.new(super) if value
|
25
25
|
end
|
26
26
|
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
module PostgreSQL
|
4
4
|
module OID # :nodoc:
|
5
5
|
class Bytea < Type::Binary # :nodoc:
|
6
|
-
def
|
6
|
+
def deserialize(value)
|
7
7
|
return if value.nil?
|
8
8
|
return value.to_s if value.is_a?(Type::Binary::Data)
|
9
9
|
PGconn.unescape_bytea(super)
|