activerecord 4.2.0
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 +7 -0
- data/CHANGELOG.md +1372 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +218 -0
- data/examples/performance.rb +184 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +173 -0
- data/lib/active_record/aggregations.rb +266 -0
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations.rb +1724 -0
- data/lib/active_record/associations/alias_tracker.rb +87 -0
- data/lib/active_record/associations/association.rb +253 -0
- data/lib/active_record/associations/association_scope.rb +194 -0
- data/lib/active_record/associations/belongs_to_association.rb +111 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +149 -0
- data/lib/active_record/associations/builder/belongs_to.rb +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +91 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +23 -0
- data/lib/active_record/associations/builder/singular_association.rb +38 -0
- data/lib/active_record/associations/collection_association.rb +634 -0
- data/lib/active_record/associations/collection_proxy.rb +1027 -0
- data/lib/active_record/associations/has_many_association.rb +184 -0
- data/lib/active_record/associations/has_many_through_association.rb +238 -0
- data/lib/active_record/associations/has_one_association.rb +105 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +282 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +203 -0
- data/lib/active_record/associations/preloader/association.rb +162 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +96 -0
- data/lib/active_record/associations/singular_association.rb +86 -0
- data/lib/active_record/associations/through_association.rb +96 -0
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +439 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
- data/lib/active_record/attribute_methods/dirty.rb +181 -0
- data/lib/active_record/attribute_methods/primary_key.rb +128 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +103 -0
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +83 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +439 -0
- data/lib/active_record/base.rb +317 -0
- data/lib/active_record/callbacks.rb +313 -0
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
- data/lib/active_record/connection_adapters/column.rb +82 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +566 -0
- data/lib/active_record/counter_cache.rb +175 -0
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +198 -0
- data/lib/active_record/errors.rb +252 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +1007 -0
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +204 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +75 -0
- data/lib/active_record/migration.rb +1051 -0
- data/lib/active_record/migration/command_recorder.rb +197 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +340 -0
- data/lib/active_record/nested_attributes.rb +548 -0
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +532 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +162 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +391 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +881 -0
- data/lib/active_record/relation.rb +681 -0
- data/lib/active_record/relation/batches.rb +138 -0
- data/lib/active_record/relation/calculations.rb +403 -0
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +528 -0
- data/lib/active_record/relation/merger.rb +170 -0
- data/lib/active_record/relation/predicate_builder.rb +126 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +1176 -0
- data/lib/active_record/relation/spawn_methods.rb +75 -0
- data/lib/active_record/result.rb +131 -0
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +64 -0
- data/lib/active_record/schema_dumper.rb +251 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/scoping/default.rb +134 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +193 -0
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +296 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +121 -0
- data/lib/active_record/transactions.rb +417 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/validations.rb +90 -0
- data/lib/active_record/validations/associated.rb +51 -0
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +229 -0
- data/lib/active_record/version.rb +8 -0
- data/lib/rails/generators/active_record.rb +17 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- metadata +309 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# :stopdoc:
|
5
|
+
module ConnectionAdapters
|
6
|
+
# An abstract definition of a column in a table.
|
7
|
+
class Column
|
8
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
|
9
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
|
10
|
+
|
11
|
+
module Format
|
12
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
13
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
|
17
|
+
|
18
|
+
delegate :type, :precision, :scale, :limit, :klass, :accessor,
|
19
|
+
:number?, :binary?, :changed?,
|
20
|
+
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
|
21
|
+
:type_cast_for_schema,
|
22
|
+
to: :cast_type
|
23
|
+
|
24
|
+
# Instantiates a new column in the table.
|
25
|
+
#
|
26
|
+
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
|
27
|
+
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
|
28
|
+
# +cast_type+ is the object used for type casting and type information.
|
29
|
+
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
|
30
|
+
# <tt>company_name varchar(60)</tt>.
|
31
|
+
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
|
32
|
+
# +null+ determines if this column allows +NULL+ values.
|
33
|
+
def initialize(name, default, cast_type, sql_type = nil, null = true)
|
34
|
+
@name = name
|
35
|
+
@cast_type = cast_type
|
36
|
+
@sql_type = sql_type
|
37
|
+
@null = null
|
38
|
+
@default = default
|
39
|
+
@default_function = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_default?
|
43
|
+
!default.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the human name of the column name.
|
47
|
+
#
|
48
|
+
# ===== Examples
|
49
|
+
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
|
50
|
+
def human_name
|
51
|
+
Base.human_attribute_name(@name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def with_type(type)
|
55
|
+
dup.tap do |clone|
|
56
|
+
clone.instance_variable_set('@cast_type', type)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
other.name == name &&
|
62
|
+
other.default == default &&
|
63
|
+
other.cast_type == cast_type &&
|
64
|
+
other.sql_type == sql_type &&
|
65
|
+
other.null == null &&
|
66
|
+
other.default_function == default_function
|
67
|
+
end
|
68
|
+
alias :eql? :==
|
69
|
+
|
70
|
+
def hash
|
71
|
+
attributes_for_hash.hash
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def attributes_for_hash
|
77
|
+
[self.class, name, default, cast_type, sql_type, null, default_function]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
# :startdoc:
|
82
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'active_support/core_ext/string/filters'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
class ConnectionSpecification #:nodoc:
|
7
|
+
attr_reader :config, :adapter_method
|
8
|
+
|
9
|
+
def initialize(config, adapter_method)
|
10
|
+
@config, @adapter_method = config, adapter_method
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_dup(original)
|
14
|
+
@config = original.config.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
# Expands a connection string into a hash.
|
18
|
+
class ConnectionUrlResolver # :nodoc:
|
19
|
+
|
20
|
+
# == Example
|
21
|
+
#
|
22
|
+
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
|
23
|
+
# ConnectionUrlResolver.new(url).to_hash
|
24
|
+
# # => {
|
25
|
+
# "adapter" => "postgresql",
|
26
|
+
# "host" => "localhost",
|
27
|
+
# "port" => 9000,
|
28
|
+
# "database" => "foo_test",
|
29
|
+
# "username" => "foo",
|
30
|
+
# "password" => "bar",
|
31
|
+
# "pool" => "5",
|
32
|
+
# "timeout" => "3000"
|
33
|
+
# }
|
34
|
+
def initialize(url)
|
35
|
+
raise "Database URL cannot be empty" if url.blank?
|
36
|
+
@uri = uri_parser.parse(url)
|
37
|
+
@adapter = @uri.scheme.tr('-', '_')
|
38
|
+
@adapter = "postgresql" if @adapter == "postgres"
|
39
|
+
|
40
|
+
if @uri.opaque
|
41
|
+
@uri.opaque, @query = @uri.opaque.split('?', 2)
|
42
|
+
else
|
43
|
+
@query = @uri.query
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Converts the given URL to a full connection hash.
|
48
|
+
def to_hash
|
49
|
+
config = raw_config.reject { |_,value| value.blank? }
|
50
|
+
config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
|
51
|
+
config
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def uri
|
57
|
+
@uri
|
58
|
+
end
|
59
|
+
|
60
|
+
def uri_parser
|
61
|
+
@uri_parser ||= URI::Parser.new
|
62
|
+
end
|
63
|
+
|
64
|
+
# Converts the query parameters of the URI into a hash.
|
65
|
+
#
|
66
|
+
# "localhost?pool=5&reaping_frequency=2"
|
67
|
+
# # => { "pool" => "5", "reaping_frequency" => "2" }
|
68
|
+
#
|
69
|
+
# returns empty hash if no query present.
|
70
|
+
#
|
71
|
+
# "localhost"
|
72
|
+
# # => {}
|
73
|
+
def query_hash
|
74
|
+
Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
|
75
|
+
end
|
76
|
+
|
77
|
+
def raw_config
|
78
|
+
if uri.opaque
|
79
|
+
query_hash.merge({
|
80
|
+
"adapter" => @adapter,
|
81
|
+
"database" => uri.opaque })
|
82
|
+
else
|
83
|
+
query_hash.merge({
|
84
|
+
"adapter" => @adapter,
|
85
|
+
"username" => uri.user,
|
86
|
+
"password" => uri.password,
|
87
|
+
"port" => uri.port,
|
88
|
+
"database" => database_from_path,
|
89
|
+
"host" => uri.hostname })
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns name of the database.
|
94
|
+
def database_from_path
|
95
|
+
if @adapter == 'sqlite3'
|
96
|
+
# 'sqlite3:/foo' is absolute, because that makes sense. The
|
97
|
+
# corresponding relative version, 'sqlite3:foo', is handled
|
98
|
+
# elsewhere, as an "opaque".
|
99
|
+
|
100
|
+
uri.path
|
101
|
+
else
|
102
|
+
# Only SQLite uses a filename as the "database" name; for
|
103
|
+
# anything else, a leading slash would be silly.
|
104
|
+
|
105
|
+
uri.path.sub(%r{^/}, "")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Builds a ConnectionSpecification from user input.
|
112
|
+
class Resolver # :nodoc:
|
113
|
+
attr_reader :configurations
|
114
|
+
|
115
|
+
# Accepts a hash two layers deep, keys on the first layer represent
|
116
|
+
# environments such as "production". Keys must be strings.
|
117
|
+
def initialize(configurations)
|
118
|
+
@configurations = configurations
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a hash with database connection information.
|
122
|
+
#
|
123
|
+
# == Examples
|
124
|
+
#
|
125
|
+
# Full hash Configuration.
|
126
|
+
#
|
127
|
+
# configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
|
128
|
+
# Resolver.new(configurations).resolve(:production)
|
129
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
|
130
|
+
#
|
131
|
+
# Initialized with URL configuration strings.
|
132
|
+
#
|
133
|
+
# configurations = { "production" => "postgresql://localhost/foo" }
|
134
|
+
# Resolver.new(configurations).resolve(:production)
|
135
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
136
|
+
#
|
137
|
+
def resolve(config)
|
138
|
+
if config
|
139
|
+
resolve_connection config
|
140
|
+
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
|
141
|
+
resolve_symbol_connection env.to_sym
|
142
|
+
else
|
143
|
+
raise AdapterNotSpecified
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Expands each key in @configurations hash into fully resolved hash
|
148
|
+
def resolve_all
|
149
|
+
config = configurations.dup
|
150
|
+
config.each do |key, value|
|
151
|
+
config[key] = resolve(value) if value
|
152
|
+
end
|
153
|
+
config
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns an instance of ConnectionSpecification for a given adapter.
|
157
|
+
# Accepts a hash one layer deep that contains all connection information.
|
158
|
+
#
|
159
|
+
# == Example
|
160
|
+
#
|
161
|
+
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
|
162
|
+
# spec = Resolver.new(config).spec(:production)
|
163
|
+
# spec.adapter_method
|
164
|
+
# # => "sqlite3_connection"
|
165
|
+
# spec.config
|
166
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
|
167
|
+
#
|
168
|
+
def spec(config)
|
169
|
+
spec = resolve(config).symbolize_keys
|
170
|
+
|
171
|
+
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
|
172
|
+
|
173
|
+
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
174
|
+
begin
|
175
|
+
require path_to_adapter
|
176
|
+
rescue Gem::LoadError => e
|
177
|
+
raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
|
178
|
+
rescue LoadError => e
|
179
|
+
raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
|
180
|
+
end
|
181
|
+
|
182
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
183
|
+
ConnectionSpecification.new(spec, adapter_method)
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# Returns fully resolved connection, accepts hash, string or symbol.
|
189
|
+
# Always returns a hash.
|
190
|
+
#
|
191
|
+
# == Examples
|
192
|
+
#
|
193
|
+
# Symbol representing current environment.
|
194
|
+
#
|
195
|
+
# Resolver.new("production" => {}).resolve_connection(:production)
|
196
|
+
# # => {}
|
197
|
+
#
|
198
|
+
# One layer deep hash of connection values.
|
199
|
+
#
|
200
|
+
# Resolver.new({}).resolve_connection("adapter" => "sqlite3")
|
201
|
+
# # => { "adapter" => "sqlite3" }
|
202
|
+
#
|
203
|
+
# Connection URL.
|
204
|
+
#
|
205
|
+
# Resolver.new({}).resolve_connection("postgresql://localhost/foo")
|
206
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
207
|
+
#
|
208
|
+
def resolve_connection(spec)
|
209
|
+
case spec
|
210
|
+
when Symbol
|
211
|
+
resolve_symbol_connection spec
|
212
|
+
when String
|
213
|
+
resolve_string_connection spec
|
214
|
+
when Hash
|
215
|
+
resolve_hash_connection spec
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def resolve_string_connection(spec)
|
220
|
+
# Rails has historically accepted a string to mean either
|
221
|
+
# an environment key or a URL spec, so we have deprecated
|
222
|
+
# this ambiguous behaviour and in the future this function
|
223
|
+
# can be removed in favor of resolve_url_connection.
|
224
|
+
if configurations.key?(spec) || spec !~ /:/
|
225
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
226
|
+
Passing a string to ActiveRecord::Base.establish_connection for a
|
227
|
+
configuration lookup is deprecated, please pass a symbol
|
228
|
+
(#{spec.to_sym.inspect}) instead.
|
229
|
+
MSG
|
230
|
+
|
231
|
+
resolve_symbol_connection(spec)
|
232
|
+
else
|
233
|
+
resolve_url_connection(spec)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Takes the environment such as +:production+ or +:development+.
|
238
|
+
# This requires that the @configurations was initialized with a key that
|
239
|
+
# matches.
|
240
|
+
#
|
241
|
+
# Resolver.new("production" => {}).resolve_symbol_connection(:production)
|
242
|
+
# # => {}
|
243
|
+
#
|
244
|
+
def resolve_symbol_connection(spec)
|
245
|
+
if config = configurations[spec.to_s]
|
246
|
+
resolve_connection(config)
|
247
|
+
else
|
248
|
+
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Accepts a hash. Expands the "url" key that contains a
|
253
|
+
# URL database connection to a full connection
|
254
|
+
# hash and merges with the rest of the hash.
|
255
|
+
# Connection details inside of the "url" key win any merge conflicts
|
256
|
+
def resolve_hash_connection(spec)
|
257
|
+
if spec["url"] && spec["url"] !~ /^jdbc:/
|
258
|
+
connection_hash = resolve_url_connection(spec.delete("url"))
|
259
|
+
spec.merge!(connection_hash)
|
260
|
+
end
|
261
|
+
spec
|
262
|
+
end
|
263
|
+
|
264
|
+
# Takes a connection URL.
|
265
|
+
#
|
266
|
+
# Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
|
267
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
268
|
+
#
|
269
|
+
def resolve_url_connection(url)
|
270
|
+
ConnectionUrlResolver.new(url).to_hash
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
|
3
|
+
gem 'mysql2', '~> 0.3.13'
|
4
|
+
require 'mysql2'
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionHandling # :nodoc:
|
8
|
+
# Establishes a connection to the database that's used by all Active Record objects.
|
9
|
+
def mysql2_connection(config)
|
10
|
+
config = config.symbolize_keys
|
11
|
+
|
12
|
+
config[:username] = 'root' if config[:username].nil?
|
13
|
+
|
14
|
+
if Mysql2::Client.const_defined? :FOUND_ROWS
|
15
|
+
config[:flags] = Mysql2::Client::FOUND_ROWS
|
16
|
+
end
|
17
|
+
|
18
|
+
client = Mysql2::Client.new(config)
|
19
|
+
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
20
|
+
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
|
21
|
+
rescue Mysql2::Error => error
|
22
|
+
if error.message.include?("Unknown database")
|
23
|
+
raise ActiveRecord::NoDatabaseError.new(error.message, error)
|
24
|
+
else
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ConnectionAdapters
|
31
|
+
class Mysql2Adapter < AbstractMysqlAdapter
|
32
|
+
ADAPTER_NAME = 'Mysql2'.freeze
|
33
|
+
|
34
|
+
def initialize(connection, logger, connection_options, config)
|
35
|
+
super
|
36
|
+
@prepared_statements = false
|
37
|
+
configure_connection
|
38
|
+
end
|
39
|
+
|
40
|
+
MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
|
41
|
+
def initialize_schema_migrations_table
|
42
|
+
if @config[:encoding] == 'utf8mb4'
|
43
|
+
ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
|
44
|
+
else
|
45
|
+
ActiveRecord::SchemaMigration.create_table
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def supports_explain?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# HELPER METHODS ===========================================
|
54
|
+
|
55
|
+
def each_hash(result) # :nodoc:
|
56
|
+
if block_given?
|
57
|
+
result.each(:as => :hash, :symbolize_keys => true) do |row|
|
58
|
+
yield row
|
59
|
+
end
|
60
|
+
else
|
61
|
+
to_enum(:each_hash, result)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def error_number(exception)
|
66
|
+
exception.error_number if exception.respond_to?(:error_number)
|
67
|
+
end
|
68
|
+
|
69
|
+
#--
|
70
|
+
# QUOTING ==================================================
|
71
|
+
#++
|
72
|
+
|
73
|
+
def quote_string(string)
|
74
|
+
@connection.escape(string)
|
75
|
+
end
|
76
|
+
|
77
|
+
def quoted_date(value)
|
78
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
79
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#--
|
86
|
+
# CONNECTION MANAGEMENT ====================================
|
87
|
+
#++
|
88
|
+
|
89
|
+
def active?
|
90
|
+
return false unless @connection
|
91
|
+
@connection.ping
|
92
|
+
end
|
93
|
+
|
94
|
+
def reconnect!
|
95
|
+
super
|
96
|
+
disconnect!
|
97
|
+
connect
|
98
|
+
end
|
99
|
+
alias :reset! :reconnect!
|
100
|
+
|
101
|
+
# Disconnects from the database if already connected.
|
102
|
+
# Otherwise, this method does nothing.
|
103
|
+
def disconnect!
|
104
|
+
super
|
105
|
+
unless @connection.nil?
|
106
|
+
@connection.close
|
107
|
+
@connection = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#--
|
112
|
+
# DATABASE STATEMENTS ======================================
|
113
|
+
#++
|
114
|
+
|
115
|
+
def explain(arel, binds = [])
|
116
|
+
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
|
117
|
+
start = Time.now
|
118
|
+
result = exec_query(sql, 'EXPLAIN', binds)
|
119
|
+
elapsed = Time.now - start
|
120
|
+
|
121
|
+
ExplainPrettyPrinter.new.pp(result, elapsed)
|
122
|
+
end
|
123
|
+
|
124
|
+
class ExplainPrettyPrinter # :nodoc:
|
125
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
126
|
+
# MySQL shell:
|
127
|
+
#
|
128
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
129
|
+
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|
130
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
131
|
+
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
|
132
|
+
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
|
133
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
134
|
+
# 2 rows in set (0.00 sec)
|
135
|
+
#
|
136
|
+
# This is an exercise in Ruby hyperrealism :).
|
137
|
+
def pp(result, elapsed)
|
138
|
+
widths = compute_column_widths(result)
|
139
|
+
separator = build_separator(widths)
|
140
|
+
|
141
|
+
pp = []
|
142
|
+
|
143
|
+
pp << separator
|
144
|
+
pp << build_cells(result.columns, widths)
|
145
|
+
pp << separator
|
146
|
+
|
147
|
+
result.rows.each do |row|
|
148
|
+
pp << build_cells(row, widths)
|
149
|
+
end
|
150
|
+
|
151
|
+
pp << separator
|
152
|
+
pp << build_footer(result.rows.length, elapsed)
|
153
|
+
|
154
|
+
pp.join("\n") + "\n"
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def compute_column_widths(result)
|
160
|
+
[].tap do |widths|
|
161
|
+
result.columns.each_with_index do |column, i|
|
162
|
+
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
|
163
|
+
widths << cells_in_column.map(&:length).max
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def build_separator(widths)
|
169
|
+
padding = 1
|
170
|
+
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
|
171
|
+
end
|
172
|
+
|
173
|
+
def build_cells(items, widths)
|
174
|
+
cells = []
|
175
|
+
items.each_with_index do |item, i|
|
176
|
+
item = 'NULL' if item.nil?
|
177
|
+
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
|
178
|
+
cells << item.to_s.send(justifier, widths[i])
|
179
|
+
end
|
180
|
+
'| ' + cells.join(' | ') + ' |'
|
181
|
+
end
|
182
|
+
|
183
|
+
def build_footer(nrows, elapsed)
|
184
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
185
|
+
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# FIXME: re-enable the following once a "better" query_cache solution is in core
|
190
|
+
#
|
191
|
+
# The overrides below perform much better than the originals in AbstractAdapter
|
192
|
+
# because we're able to take advantage of mysql2's lazy-loading capabilities
|
193
|
+
#
|
194
|
+
# # Returns a record hash with the column names as keys and column values
|
195
|
+
# # as values.
|
196
|
+
# def select_one(sql, name = nil)
|
197
|
+
# result = execute(sql, name)
|
198
|
+
# result.each(as: :hash) do |r|
|
199
|
+
# return r
|
200
|
+
# end
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# # Returns a single value from a record
|
204
|
+
# def select_value(sql, name = nil)
|
205
|
+
# result = execute(sql, name)
|
206
|
+
# if first = result.first
|
207
|
+
# first.first
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# # Returns an array of the values of the first column in a select:
|
212
|
+
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
213
|
+
# def select_values(sql, name = nil)
|
214
|
+
# execute(sql, name).map { |row| row.first }
|
215
|
+
# end
|
216
|
+
|
217
|
+
# Returns an array of arrays containing the field values.
|
218
|
+
# Order is the same as that returned by +columns+.
|
219
|
+
def select_rows(sql, name = nil, binds = [])
|
220
|
+
execute(sql, name).to_a
|
221
|
+
end
|
222
|
+
|
223
|
+
# Executes the SQL statement in the context of this connection.
|
224
|
+
def execute(sql, name = nil)
|
225
|
+
if @connection
|
226
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
227
|
+
# made since we established the connection
|
228
|
+
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
229
|
+
end
|
230
|
+
|
231
|
+
super
|
232
|
+
end
|
233
|
+
|
234
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
235
|
+
result = execute(sql, name)
|
236
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
237
|
+
end
|
238
|
+
|
239
|
+
alias exec_without_stmt exec_query
|
240
|
+
|
241
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
242
|
+
super
|
243
|
+
id_value || @connection.last_id
|
244
|
+
end
|
245
|
+
alias :create :insert_sql
|
246
|
+
|
247
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
248
|
+
execute to_sql(sql, binds), name
|
249
|
+
end
|
250
|
+
|
251
|
+
def exec_delete(sql, name, binds)
|
252
|
+
execute to_sql(sql, binds), name
|
253
|
+
@connection.affected_rows
|
254
|
+
end
|
255
|
+
alias :exec_update :exec_delete
|
256
|
+
|
257
|
+
def last_inserted_id(result)
|
258
|
+
@connection.last_id
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
263
|
+
def connect
|
264
|
+
@connection = Mysql2::Client.new(@config)
|
265
|
+
configure_connection
|
266
|
+
end
|
267
|
+
|
268
|
+
def configure_connection
|
269
|
+
@connection.query_options.merge!(:as => :array)
|
270
|
+
super
|
271
|
+
end
|
272
|
+
|
273
|
+
def full_version
|
274
|
+
@full_version ||= @connection.info[:version]
|
275
|
+
end
|
276
|
+
|
277
|
+
def set_field_encoding field_name
|
278
|
+
field_name
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|