activerecord 3.2.22.5 → 4.2.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- 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 +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- 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/array.rb +100 -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 +15 -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 +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -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 +19 -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 +109 -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/oid.rb +36 -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 +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- 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/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- 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 +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -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 +23 -0
- data/lib/active_record/type/integer.rb +59 -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 +62 -0
- data/lib/active_record/type/string.rb +40 -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 +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- 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/has_and_belongs_to_many.rb +0 -60
- 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/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/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- 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,83 +1,110 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
require 'arel/visitors/bind_visitor'
|
2
|
+
require 'active_support/core_ext/string/strip'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
5
|
module ConnectionAdapters
|
6
6
|
class AbstractMysqlAdapter < AbstractAdapter
|
7
|
+
include Savepoints
|
8
|
+
|
9
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
10
|
+
def visit_AddColumn(o)
|
11
|
+
add_column_position!(super, column_options(o))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def visit_DropForeignKey(name)
|
17
|
+
"DROP FOREIGN KEY #{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_TableDefinition(o)
|
21
|
+
name = o.name
|
22
|
+
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
|
23
|
+
|
24
|
+
statements = o.columns.map { |c| accept c }
|
25
|
+
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
|
26
|
+
|
27
|
+
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
28
|
+
create_sql << "#{o.options}"
|
29
|
+
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
30
|
+
create_sql
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_ChangeColumnDefinition(o)
|
34
|
+
column = o.column
|
35
|
+
options = o.options
|
36
|
+
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
37
|
+
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
38
|
+
add_column_options!(change_column_sql, options.merge(column: column))
|
39
|
+
add_column_position!(change_column_sql, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_column_position!(sql, options)
|
43
|
+
if options[:first]
|
44
|
+
sql << " FIRST"
|
45
|
+
elsif options[:after]
|
46
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
47
|
+
end
|
48
|
+
sql
|
49
|
+
end
|
50
|
+
|
51
|
+
def index_in_create(table_name, column_name, options)
|
52
|
+
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
|
53
|
+
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def schema_creation
|
58
|
+
SchemaCreation.new self
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare_column_options(column, types) # :nodoc:
|
62
|
+
spec = super
|
63
|
+
spec.delete(:limit) if :boolean === column.type
|
64
|
+
spec
|
65
|
+
end
|
66
|
+
|
7
67
|
class Column < ConnectionAdapters::Column # :nodoc:
|
8
|
-
attr_reader :collation
|
68
|
+
attr_reader :collation, :strict, :extra
|
9
69
|
|
10
|
-
def initialize(name, default, sql_type = nil, null = true, collation = nil)
|
11
|
-
|
70
|
+
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
71
|
+
@strict = strict
|
12
72
|
@collation = collation
|
73
|
+
@extra = extra
|
74
|
+
super(name, default, cast_type, sql_type, null)
|
75
|
+
assert_valid_default(default)
|
76
|
+
extract_default
|
13
77
|
end
|
14
78
|
|
15
|
-
def extract_default
|
16
|
-
if
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
21
|
-
end
|
22
|
-
elsif missing_default_forged_as_empty_string?(default)
|
23
|
-
nil
|
24
|
-
else
|
25
|
-
super
|
79
|
+
def extract_default
|
80
|
+
if blob_or_text_column?
|
81
|
+
@default = null || strict ? nil : ''
|
82
|
+
elsif missing_default_forged_as_empty_string?(@default)
|
83
|
+
@default = nil
|
26
84
|
end
|
27
85
|
end
|
28
86
|
|
29
87
|
def has_default?
|
30
|
-
return false if
|
88
|
+
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
31
89
|
super
|
32
90
|
end
|
33
91
|
|
34
|
-
|
35
|
-
|
36
|
-
raise NotImplementedError
|
92
|
+
def blob_or_text_column?
|
93
|
+
sql_type =~ /blob/i || type == :text
|
37
94
|
end
|
38
95
|
|
39
96
|
def case_sensitive?
|
40
97
|
collation && !collation.match(/_ci$/)
|
41
98
|
end
|
42
99
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
case field_type
|
49
|
-
when /enum/i, /set/i then :string
|
50
|
-
when /year/i then :integer
|
51
|
-
when /bit/i then :binary
|
52
|
-
else
|
53
|
-
super
|
54
|
-
end
|
100
|
+
def ==(other)
|
101
|
+
super &&
|
102
|
+
collation == other.collation &&
|
103
|
+
strict == other.strict &&
|
104
|
+
extra == other.extra
|
55
105
|
end
|
56
106
|
|
57
|
-
|
58
|
-
case sql_type
|
59
|
-
when /blob|text/i
|
60
|
-
case sql_type
|
61
|
-
when /tiny/i
|
62
|
-
255
|
63
|
-
when /medium/i
|
64
|
-
16777215
|
65
|
-
when /long/i
|
66
|
-
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
67
|
-
else
|
68
|
-
super # we could return 65535 here, but we leave it undecorated by default
|
69
|
-
end
|
70
|
-
when /^bigint/i; 8
|
71
|
-
when /^int/i; 4
|
72
|
-
when /^mediumint/i; 3
|
73
|
-
when /^smallint/i; 2
|
74
|
-
when /^tinyint/i; 1
|
75
|
-
when /^enum\((.+)\)/i
|
76
|
-
$1.split(',').map{|enum| enum.strip.length - 2}.max
|
77
|
-
else
|
78
|
-
super
|
79
|
-
end
|
80
|
-
end
|
107
|
+
private
|
81
108
|
|
82
109
|
# MySQL misreports NOT NULL column default when none is given.
|
83
110
|
# We can't detect this for columns which may have a legitimate ''
|
@@ -89,6 +116,16 @@ module ActiveRecord
|
|
89
116
|
def missing_default_forged_as_empty_string?(default)
|
90
117
|
type != :string && !null && default == ''
|
91
118
|
end
|
119
|
+
|
120
|
+
def assert_valid_default(default)
|
121
|
+
if blob_or_text_column? && default.present?
|
122
|
+
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def attributes_for_hash
|
127
|
+
super + [collation, strict, extra]
|
128
|
+
end
|
92
129
|
end
|
93
130
|
|
94
131
|
##
|
@@ -111,23 +148,21 @@ module ActiveRecord
|
|
111
148
|
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
112
149
|
|
113
150
|
NATIVE_DATABASE_TYPES = {
|
114
|
-
:primary_key => "int(11)
|
151
|
+
:primary_key => "int(11) auto_increment PRIMARY KEY",
|
115
152
|
:string => { :name => "varchar", :limit => 255 },
|
116
153
|
:text => { :name => "text" },
|
117
154
|
:integer => { :name => "int", :limit => 4 },
|
118
155
|
:float => { :name => "float" },
|
119
156
|
:decimal => { :name => "decimal" },
|
120
157
|
:datetime => { :name => "datetime" },
|
121
|
-
:timestamp => { :name => "datetime" },
|
122
158
|
:time => { :name => "time" },
|
123
159
|
:date => { :name => "date" },
|
124
160
|
:binary => { :name => "blob" },
|
125
161
|
:boolean => { :name => "tinyint", :limit => 1 }
|
126
162
|
}
|
127
163
|
|
128
|
-
|
129
|
-
|
130
|
-
end
|
164
|
+
INDEX_TYPES = [:fulltext, :spatial]
|
165
|
+
INDEX_USINGS = [:btree, :hash]
|
131
166
|
|
132
167
|
# FIXME: Make the first parameter more similar for the two adapters
|
133
168
|
def initialize(connection, logger, connection_options, config)
|
@@ -135,17 +170,15 @@ module ActiveRecord
|
|
135
170
|
@connection_options, @config = connection_options, config
|
136
171
|
@quoted_column_names, @quoted_table_names = {}, {}
|
137
172
|
|
138
|
-
|
139
|
-
|
173
|
+
@visitor = Arel::Visitors::MySQL.new self
|
174
|
+
|
175
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
176
|
+
@prepared_statements = true
|
140
177
|
else
|
141
|
-
@
|
178
|
+
@prepared_statements = false
|
142
179
|
end
|
143
180
|
end
|
144
181
|
|
145
|
-
def adapter_name #:nodoc:
|
146
|
-
self.class::ADAPTER_NAME
|
147
|
-
end
|
148
|
-
|
149
182
|
# Returns true, since this connection adapter supports migrations.
|
150
183
|
def supports_migrations?
|
151
184
|
true
|
@@ -155,11 +188,6 @@ module ActiveRecord
|
|
155
188
|
true
|
156
189
|
end
|
157
190
|
|
158
|
-
# Returns true, since this connection adapter supports savepoints.
|
159
|
-
def supports_savepoints?
|
160
|
-
true
|
161
|
-
end
|
162
|
-
|
163
191
|
def supports_bulk_alter? #:nodoc:
|
164
192
|
true
|
165
193
|
end
|
@@ -170,10 +198,38 @@ module ActiveRecord
|
|
170
198
|
true
|
171
199
|
end
|
172
200
|
|
201
|
+
# MySQL 4 technically support transaction isolation, but it is affected by a bug
|
202
|
+
# where the transaction level gets persisted for the whole session:
|
203
|
+
#
|
204
|
+
# http://bugs.mysql.com/bug.php?id=39170
|
205
|
+
def supports_transaction_isolation?
|
206
|
+
version >= '5.0.0'
|
207
|
+
end
|
208
|
+
|
209
|
+
def supports_indexes_in_create?
|
210
|
+
true
|
211
|
+
end
|
212
|
+
|
213
|
+
def supports_foreign_keys?
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
def supports_views?
|
218
|
+
version >= '5.0.0'
|
219
|
+
end
|
220
|
+
|
221
|
+
def supports_datetime_with_precision?
|
222
|
+
version >= '5.6.4'
|
223
|
+
end
|
224
|
+
|
173
225
|
def native_database_types
|
174
226
|
NATIVE_DATABASE_TYPES
|
175
227
|
end
|
176
228
|
|
229
|
+
def index_algorithms
|
230
|
+
{ default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
|
231
|
+
end
|
232
|
+
|
177
233
|
# HELPER METHODS ===========================================
|
178
234
|
|
179
235
|
# The two drivers have slightly different ways of yielding hashes of results, so
|
@@ -182,12 +238,11 @@ module ActiveRecord
|
|
182
238
|
raise NotImplementedError
|
183
239
|
end
|
184
240
|
|
185
|
-
|
186
|
-
|
187
|
-
Column.new(field, default, type, null, collation)
|
241
|
+
def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
|
242
|
+
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
|
188
243
|
end
|
189
244
|
|
190
|
-
# Must return the
|
245
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
191
246
|
# error number.
|
192
247
|
def error_number(exception) # :nodoc:
|
193
248
|
raise NotImplementedError
|
@@ -195,12 +250,9 @@ module ActiveRecord
|
|
195
250
|
|
196
251
|
# QUOTING ==================================================
|
197
252
|
|
198
|
-
def
|
199
|
-
if value.
|
200
|
-
|
201
|
-
"x'#{s}'"
|
202
|
-
elsif value.kind_of?(BigDecimal)
|
203
|
-
value.to_s("F")
|
253
|
+
def _quote(value) # :nodoc:
|
254
|
+
if value.is_a?(Type::Binary::Data)
|
255
|
+
"x'#{value.hex}'"
|
204
256
|
else
|
205
257
|
super
|
206
258
|
end
|
@@ -218,13 +270,29 @@ module ActiveRecord
|
|
218
270
|
QUOTED_TRUE
|
219
271
|
end
|
220
272
|
|
273
|
+
def unquoted_true
|
274
|
+
1
|
275
|
+
end
|
276
|
+
|
221
277
|
def quoted_false
|
222
278
|
QUOTED_FALSE
|
223
279
|
end
|
224
280
|
|
281
|
+
def unquoted_false
|
282
|
+
0
|
283
|
+
end
|
284
|
+
|
285
|
+
def quoted_date(value)
|
286
|
+
if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
|
287
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
288
|
+
else
|
289
|
+
super
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
225
293
|
# REFERENTIAL INTEGRITY ====================================
|
226
294
|
|
227
|
-
def disable_referential_integrity
|
295
|
+
def disable_referential_integrity #:nodoc:
|
228
296
|
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
229
297
|
|
230
298
|
begin
|
@@ -235,25 +303,22 @@ module ActiveRecord
|
|
235
303
|
end
|
236
304
|
end
|
237
305
|
|
306
|
+
#--
|
238
307
|
# DATABASE STATEMENTS ======================================
|
308
|
+
#++
|
309
|
+
|
310
|
+
def clear_cache!
|
311
|
+
super
|
312
|
+
reload_type_map
|
313
|
+
end
|
239
314
|
|
240
315
|
# Executes the SQL statement in the context of this connection.
|
241
316
|
def execute(sql, name = nil)
|
242
|
-
|
243
|
-
@connection.query(sql)
|
244
|
-
else
|
245
|
-
log(sql, name) { @connection.query(sql) }
|
246
|
-
end
|
247
|
-
rescue ActiveRecord::StatementInvalid => exception
|
248
|
-
if exception.message.split(":").first =~ /Packets out of order/
|
249
|
-
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
|
250
|
-
else
|
251
|
-
raise
|
252
|
-
end
|
317
|
+
log(sql, name) { @connection.query(sql) }
|
253
318
|
end
|
254
319
|
|
255
320
|
# MysqlAdapter has to free a result after using it, so we use this method to write
|
256
|
-
# stuff in
|
321
|
+
# stuff in an abstract way without concerning ourselves about whether it needs to be
|
257
322
|
# explicitly freed or not.
|
258
323
|
def execute_and_free(sql, name = nil) #:nodoc:
|
259
324
|
yield execute(sql, name)
|
@@ -266,85 +331,55 @@ module ActiveRecord
|
|
266
331
|
|
267
332
|
def begin_db_transaction
|
268
333
|
execute "BEGIN"
|
269
|
-
|
270
|
-
|
334
|
+
end
|
335
|
+
|
336
|
+
def begin_isolated_db_transaction(isolation)
|
337
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
338
|
+
begin_db_transaction
|
271
339
|
end
|
272
340
|
|
273
341
|
def commit_db_transaction #:nodoc:
|
274
342
|
execute "COMMIT"
|
275
|
-
rescue Exception
|
276
|
-
# Transactions aren't supported
|
277
343
|
end
|
278
344
|
|
279
|
-
def
|
345
|
+
def exec_rollback_db_transaction #:nodoc:
|
280
346
|
execute "ROLLBACK"
|
281
|
-
rescue Exception
|
282
|
-
# Transactions aren't supported
|
283
|
-
end
|
284
|
-
|
285
|
-
def create_savepoint
|
286
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
287
|
-
end
|
288
|
-
|
289
|
-
def rollback_to_savepoint
|
290
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
291
|
-
end
|
292
|
-
|
293
|
-
def release_savepoint
|
294
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
295
347
|
end
|
296
348
|
|
297
349
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
298
350
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
299
|
-
# these, we must use a subquery.
|
300
|
-
# temporary table for this automatically, so we have to give it some prompting
|
301
|
-
# in the form of a subsubquery. Ugh!
|
351
|
+
# these, we must use a subquery.
|
302
352
|
def join_to_update(update, select) #:nodoc:
|
303
353
|
if select.limit || select.offset || select.orders.any?
|
304
|
-
|
305
|
-
subsubselect.projections = [update.key]
|
306
|
-
|
307
|
-
subselect = Arel::SelectManager.new(select.engine)
|
308
|
-
subselect.project Arel.sql(update.key.name)
|
309
|
-
subselect.from subsubselect.as('__active_record_temp')
|
310
|
-
|
311
|
-
update.where update.key.in(subselect)
|
354
|
+
super
|
312
355
|
else
|
313
356
|
update.table select.source
|
314
357
|
update.wheres = select.constraints
|
315
358
|
end
|
316
359
|
end
|
317
360
|
|
318
|
-
|
319
|
-
|
320
|
-
def structure_dump #:nodoc:
|
321
|
-
if supports_views?
|
322
|
-
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
323
|
-
else
|
324
|
-
sql = "SHOW TABLES"
|
325
|
-
end
|
326
|
-
|
327
|
-
select_all(sql).map { |table|
|
328
|
-
table.delete('Table_type')
|
329
|
-
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
|
330
|
-
exec_query(sql).first['Create Table'] + ";\n\n"
|
331
|
-
}.join
|
361
|
+
def empty_insert_statement_value
|
362
|
+
"VALUES ()"
|
332
363
|
end
|
333
364
|
|
365
|
+
# SCHEMA STATEMENTS ========================================
|
366
|
+
|
334
367
|
# Drops the database specified on the +name+ attribute
|
335
368
|
# and creates it again using the provided +options+.
|
336
369
|
def recreate_database(name, options = {})
|
337
370
|
drop_database(name)
|
338
|
-
create_database(name, options)
|
371
|
+
sql = create_database(name, options)
|
372
|
+
reconnect!
|
373
|
+
sql
|
339
374
|
end
|
340
375
|
|
341
376
|
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
342
377
|
# Charset defaults to utf8.
|
343
378
|
#
|
344
379
|
# Example:
|
345
|
-
# create_database 'charset_test', :
|
380
|
+
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
|
346
381
|
# create_database 'matt_development'
|
347
|
-
# create_database 'matt_development', :
|
382
|
+
# create_database 'matt_development', charset: :big5
|
348
383
|
def create_database(name, options = {})
|
349
384
|
if options[:collation]
|
350
385
|
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
@@ -384,9 +419,14 @@ module ActiveRecord
|
|
384
419
|
result.collect { |field| field.first }
|
385
420
|
end
|
386
421
|
end
|
422
|
+
alias data_sources tables
|
423
|
+
|
424
|
+
def truncate(table_name, name = nil)
|
425
|
+
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
426
|
+
end
|
387
427
|
|
388
428
|
def table_exists?(name)
|
389
|
-
return false unless name
|
429
|
+
return false unless name.present?
|
390
430
|
return true if tables(nil, nil, name).any?
|
391
431
|
|
392
432
|
name = name.to_s
|
@@ -399,6 +439,7 @@ module ActiveRecord
|
|
399
439
|
|
400
440
|
tables(nil, schema, table).any?
|
401
441
|
end
|
442
|
+
alias data_source_exists? table_exists?
|
402
443
|
|
403
444
|
# Returns an array of indexes for the given table.
|
404
445
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -409,7 +450,11 @@ module ActiveRecord
|
|
409
450
|
if current_index != row[:Key_name]
|
410
451
|
next if row[:Key_name] == 'PRIMARY' # skip the primary key
|
411
452
|
current_index = row[:Key_name]
|
412
|
-
|
453
|
+
|
454
|
+
mysql_index_type = row[:Index_type].downcase.to_sym
|
455
|
+
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
456
|
+
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
457
|
+
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
413
458
|
end
|
414
459
|
|
415
460
|
indexes.last.columns << row[:Column_name]
|
@@ -421,11 +466,14 @@ module ActiveRecord
|
|
421
466
|
end
|
422
467
|
|
423
468
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
424
|
-
def columns(table_name
|
469
|
+
def columns(table_name)#:nodoc:
|
425
470
|
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
426
471
|
execute_and_free(sql, 'SCHEMA') do |result|
|
427
472
|
each_hash(result).map do |field|
|
428
|
-
|
473
|
+
field_name = set_field_encoding(field[:Field])
|
474
|
+
sql_type = field[:Type]
|
475
|
+
cast_type = lookup_cast_type(sql_type)
|
476
|
+
new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
|
429
477
|
end
|
430
478
|
end
|
431
479
|
end
|
@@ -435,7 +483,7 @@ module ActiveRecord
|
|
435
483
|
end
|
436
484
|
|
437
485
|
def bulk_change_table(table_name, operations) #:nodoc:
|
438
|
-
sqls = operations.
|
486
|
+
sqls = operations.flat_map do |command, args|
|
439
487
|
table, arguments = args.shift, args
|
440
488
|
method = :"#{command}_sql"
|
441
489
|
|
@@ -444,7 +492,7 @@ module ActiveRecord
|
|
444
492
|
else
|
445
493
|
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
446
494
|
end
|
447
|
-
end.
|
495
|
+
end.join(", ")
|
448
496
|
|
449
497
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
450
498
|
end
|
@@ -455,13 +503,24 @@ module ActiveRecord
|
|
455
503
|
# rename_table('octopuses', 'octopi')
|
456
504
|
def rename_table(table_name, new_name)
|
457
505
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
506
|
+
rename_table_indexes(table_name, new_name)
|
507
|
+
end
|
508
|
+
|
509
|
+
def drop_table(table_name, options = {})
|
510
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
458
511
|
end
|
459
512
|
|
460
|
-
def
|
461
|
-
|
513
|
+
def rename_index(table_name, old_name, new_name)
|
514
|
+
if supports_rename_index?
|
515
|
+
validate_index_length!(table_name, new_name)
|
516
|
+
|
517
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
518
|
+
else
|
519
|
+
super
|
520
|
+
end
|
462
521
|
end
|
463
522
|
|
464
|
-
def change_column_default(table_name, column_name, default)
|
523
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
465
524
|
column = column_for(table_name, column_name)
|
466
525
|
change_column table_name, column_name, column.sql_type, :default => default
|
467
526
|
end
|
@@ -482,11 +541,52 @@ module ActiveRecord
|
|
482
541
|
|
483
542
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
484
543
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
|
544
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
545
|
+
end
|
546
|
+
|
547
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
548
|
+
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
549
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
|
550
|
+
end
|
551
|
+
|
552
|
+
def foreign_keys(table_name)
|
553
|
+
fk_info = select_all <<-SQL.strip_heredoc
|
554
|
+
SELECT fk.referenced_table_name as 'to_table'
|
555
|
+
,fk.referenced_column_name as 'primary_key'
|
556
|
+
,fk.column_name as 'column'
|
557
|
+
,fk.constraint_name as 'name'
|
558
|
+
FROM information_schema.key_column_usage fk
|
559
|
+
WHERE fk.referenced_column_name is not null
|
560
|
+
AND fk.table_schema = '#{@config[:database]}'
|
561
|
+
AND fk.table_name = '#{table_name}'
|
562
|
+
SQL
|
563
|
+
|
564
|
+
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
565
|
+
|
566
|
+
fk_info.map do |row|
|
567
|
+
options = {
|
568
|
+
column: row['column'],
|
569
|
+
name: row['name'],
|
570
|
+
primary_key: row['primary_key']
|
571
|
+
}
|
572
|
+
|
573
|
+
options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
|
574
|
+
options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
|
575
|
+
|
576
|
+
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
577
|
+
end
|
485
578
|
end
|
486
579
|
|
487
580
|
# Maps logical Rails types to MySQL-specific data types.
|
488
581
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
489
582
|
case type.to_s
|
583
|
+
when 'binary'
|
584
|
+
case limit
|
585
|
+
when 0..0xfff; "varbinary(#{limit})"
|
586
|
+
when nil; "blob"
|
587
|
+
when 0x1000..0xffffffff; "blob(#{limit})"
|
588
|
+
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
589
|
+
end
|
490
590
|
when 'integer'
|
491
591
|
case limit
|
492
592
|
when 1; 'tinyint'
|
@@ -504,23 +604,24 @@ module ActiveRecord
|
|
504
604
|
when 0x1000000..0xffffffff; 'longtext'
|
505
605
|
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
506
606
|
end
|
607
|
+
when 'datetime'
|
608
|
+
return super unless precision
|
609
|
+
|
610
|
+
case precision
|
611
|
+
when 0..6; "datetime(#{precision})"
|
612
|
+
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
|
613
|
+
end
|
507
614
|
else
|
508
615
|
super
|
509
616
|
end
|
510
617
|
end
|
511
618
|
|
512
|
-
def add_column_position!(sql, options)
|
513
|
-
if options[:first]
|
514
|
-
sql << " FIRST"
|
515
|
-
elsif options[:after]
|
516
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
517
|
-
end
|
518
|
-
end
|
519
|
-
|
520
619
|
# SHOW VARIABLES LIKE 'name'
|
521
620
|
def show_variable(name)
|
522
|
-
variables = select_all("
|
621
|
+
variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
|
523
622
|
variables.first['Value'] unless variables.empty?
|
623
|
+
rescue ActiveRecord::StatementInvalid
|
624
|
+
nil
|
524
625
|
end
|
525
626
|
|
526
627
|
# Returns a table's primary key and belonging sequence.
|
@@ -528,7 +629,7 @@ module ActiveRecord
|
|
528
629
|
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
529
630
|
create_table = each_hash(result).first[:"Create Table"]
|
530
631
|
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
531
|
-
keys = $1.split(",").map { |key| key.
|
632
|
+
keys = $1.split(",").map { |key| key.delete('`"') }
|
532
633
|
keys.length == 1 ? [keys.first, nil] : nil
|
533
634
|
else
|
534
635
|
nil
|
@@ -542,10 +643,19 @@ module ActiveRecord
|
|
542
643
|
pk_and_sequence && pk_and_sequence.first
|
543
644
|
end
|
544
645
|
|
545
|
-
def case_sensitive_modifier(node)
|
646
|
+
def case_sensitive_modifier(node, table_attribute)
|
647
|
+
node = Arel::Nodes.build_quoted node, table_attribute
|
546
648
|
Arel::Nodes::Bin.new(node)
|
547
649
|
end
|
548
650
|
|
651
|
+
def case_sensitive_comparison(table, attribute, column, value)
|
652
|
+
if column.case_sensitive?
|
653
|
+
table[attribute].eq(value)
|
654
|
+
else
|
655
|
+
super
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
549
659
|
def case_insensitive_comparison(table, attribute, column, value)
|
550
660
|
if column.case_sensitive?
|
551
661
|
super
|
@@ -554,18 +664,101 @@ module ActiveRecord
|
|
554
664
|
end
|
555
665
|
end
|
556
666
|
|
557
|
-
|
558
|
-
|
667
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
668
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
669
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
670
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
|
671
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
672
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
673
|
+
# Convert Arel node to string
|
674
|
+
s = s.to_sql unless s.is_a?(String)
|
675
|
+
# Remove any ASC/DESC modifiers
|
676
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
677
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
678
|
+
|
679
|
+
[super, *order_columns].join(', ')
|
680
|
+
end
|
681
|
+
|
682
|
+
def strict_mode?
|
683
|
+
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
684
|
+
end
|
685
|
+
|
686
|
+
def valid_type?(type)
|
687
|
+
!native_database_types[type].nil?
|
559
688
|
end
|
560
689
|
|
561
690
|
protected
|
562
691
|
|
692
|
+
def initialize_type_map(m) # :nodoc:
|
693
|
+
super
|
694
|
+
|
695
|
+
register_class_with_limit m, %r(char)i, MysqlString
|
696
|
+
|
697
|
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
698
|
+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
699
|
+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
700
|
+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
|
701
|
+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
702
|
+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
703
|
+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
704
|
+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
705
|
+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
706
|
+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
707
|
+
|
708
|
+
register_integer_type m, %r(^bigint)i, limit: 8
|
709
|
+
register_integer_type m, %r(^int)i, limit: 4
|
710
|
+
register_integer_type m, %r(^mediumint)i, limit: 3
|
711
|
+
register_integer_type m, %r(^smallint)i, limit: 2
|
712
|
+
register_integer_type m, %r(^tinyint)i, limit: 1
|
713
|
+
|
714
|
+
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
|
715
|
+
m.alias_type %r(set)i, 'varchar'
|
716
|
+
m.alias_type %r(year)i, 'integer'
|
717
|
+
m.alias_type %r(bit)i, 'binary'
|
718
|
+
|
719
|
+
m.register_type(%r(datetime)i) do |sql_type|
|
720
|
+
precision = extract_precision(sql_type)
|
721
|
+
MysqlDateTime.new(precision: precision)
|
722
|
+
end
|
723
|
+
|
724
|
+
m.register_type(%r(enum)i) do |sql_type|
|
725
|
+
limit = sql_type[/^enum\((.+)\)/i, 1]
|
726
|
+
.split(',').map{|enum| enum.strip.length - 2}.max
|
727
|
+
MysqlString.new(limit: limit)
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def register_integer_type(mapping, key, options) # :nodoc:
|
732
|
+
mapping.register_type(key) do |sql_type|
|
733
|
+
if /unsigned/i =~ sql_type
|
734
|
+
Type::UnsignedInteger.new(options)
|
735
|
+
else
|
736
|
+
Type::Integer.new(options)
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
742
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
743
|
+
def subquery_for(key, select)
|
744
|
+
subsubselect = select.clone
|
745
|
+
subsubselect.projections = [key]
|
746
|
+
|
747
|
+
# Materialize subquery by adding distinct
|
748
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
749
|
+
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
750
|
+
|
751
|
+
subselect = Arel::SelectManager.new(select.engine)
|
752
|
+
subselect.project Arel.sql(key.name)
|
753
|
+
subselect.from subsubselect.as('__active_record_temp')
|
754
|
+
end
|
755
|
+
|
563
756
|
def add_index_length(option_strings, column_names, options = {})
|
564
757
|
if options.is_a?(Hash) && length = options[:length]
|
565
758
|
case length
|
566
759
|
when Hash
|
567
760
|
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
568
|
-
when
|
761
|
+
when Integer
|
569
762
|
column_names.each {|name| option_strings[name] += "(#{length})"}
|
570
763
|
end
|
571
764
|
end
|
@@ -597,10 +790,9 @@ module ActiveRecord
|
|
597
790
|
end
|
598
791
|
|
599
792
|
def add_column_sql(table_name, column_name, type, options = {})
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
add_column_sql
|
793
|
+
td = create_table_definition table_name, options[:temporary], options[:options]
|
794
|
+
cd = td.new_column_definition(column_name, type, options)
|
795
|
+
schema_creation.visit_AddColumn cd
|
604
796
|
end
|
605
797
|
|
606
798
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -614,32 +806,30 @@ module ActiveRecord
|
|
614
806
|
options[:null] = column.null
|
615
807
|
end
|
616
808
|
|
617
|
-
|
618
|
-
|
619
|
-
add_column_position!(change_column_sql, options)
|
620
|
-
change_column_sql
|
809
|
+
options[:name] = column.name
|
810
|
+
schema_creation.accept ChangeColumnDefinition.new column, type, options
|
621
811
|
end
|
622
812
|
|
623
813
|
def rename_column_sql(table_name, column_name, new_column_name)
|
624
|
-
|
814
|
+
column = column_for(table_name, column_name)
|
815
|
+
options = {
|
816
|
+
name: new_column_name,
|
817
|
+
default: column.default,
|
818
|
+
null: column.null,
|
819
|
+
auto_increment: column.extra == "auto_increment"
|
820
|
+
}
|
625
821
|
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
else
|
630
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
631
|
-
end
|
822
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
823
|
+
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
|
824
|
+
end
|
632
825
|
|
633
|
-
|
634
|
-
|
635
|
-
add_column_options!(rename_column_sql, options)
|
636
|
-
rename_column_sql
|
826
|
+
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
827
|
+
"DROP #{quote_column_name(column_name)}"
|
637
828
|
end
|
638
829
|
|
639
|
-
def
|
640
|
-
|
830
|
+
def remove_columns_sql(table_name, *column_names)
|
831
|
+
column_names.map {|column_name| remove_column_sql(table_name, column_name) }
|
641
832
|
end
|
642
|
-
alias :remove_columns_sql :remove_column
|
643
833
|
|
644
834
|
def add_index_sql(table_name, column_name, options = {})
|
645
835
|
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
@@ -651,25 +841,105 @@ module ActiveRecord
|
|
651
841
|
"DROP INDEX #{index_name}"
|
652
842
|
end
|
653
843
|
|
654
|
-
def add_timestamps_sql(table_name)
|
655
|
-
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
|
844
|
+
def add_timestamps_sql(table_name, options = {})
|
845
|
+
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
|
656
846
|
end
|
657
847
|
|
658
|
-
def remove_timestamps_sql(table_name)
|
848
|
+
def remove_timestamps_sql(table_name, options = {})
|
659
849
|
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
|
660
850
|
end
|
661
851
|
|
662
852
|
private
|
663
853
|
|
664
|
-
def
|
665
|
-
version[0]
|
854
|
+
def version
|
855
|
+
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
|
856
|
+
end
|
857
|
+
|
858
|
+
def mariadb?
|
859
|
+
full_version =~ /mariadb/i
|
860
|
+
end
|
861
|
+
|
862
|
+
def supports_rename_index?
|
863
|
+
mariadb? ? false : version >= '5.7.6'
|
864
|
+
end
|
865
|
+
|
866
|
+
def configure_connection
|
867
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
868
|
+
|
869
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
870
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
871
|
+
variables['sql_auto_is_null'] = 0
|
872
|
+
|
873
|
+
# Increase timeout so the server doesn't disconnect us.
|
874
|
+
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
875
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
876
|
+
variables["wait_timeout"] = wait_timeout
|
877
|
+
|
878
|
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
879
|
+
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
|
880
|
+
# If the user has provided another value for sql_mode, don't replace it.
|
881
|
+
unless variables.has_key?('sql_mode')
|
882
|
+
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
|
883
|
+
end
|
884
|
+
|
885
|
+
# NAMES does not have an equals sign, see
|
886
|
+
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
|
887
|
+
# (trailing comma because variable_assignments will always have content)
|
888
|
+
if @config[:encoding]
|
889
|
+
encoding = "NAMES #{@config[:encoding]}"
|
890
|
+
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
891
|
+
encoding << ", "
|
892
|
+
end
|
893
|
+
|
894
|
+
# Gather up all of the SET variables...
|
895
|
+
variable_assignments = variables.map do |k, v|
|
896
|
+
if v == ':default' || v == :default
|
897
|
+
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
898
|
+
elsif !v.nil?
|
899
|
+
"@@SESSION.#{k} = #{quote(v)}"
|
900
|
+
end
|
901
|
+
# or else nil; compact to clear nils out
|
902
|
+
end.compact.join(', ')
|
903
|
+
|
904
|
+
# ...and send them all in one query
|
905
|
+
@connection.query "SET #{encoding} #{variable_assignments}"
|
906
|
+
end
|
907
|
+
|
908
|
+
def extract_foreign_key_action(structure, name, action) # :nodoc:
|
909
|
+
if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
|
910
|
+
case $1
|
911
|
+
when 'CASCADE'; :cascade
|
912
|
+
when 'SET NULL'; :nullify
|
913
|
+
end
|
914
|
+
end
|
666
915
|
end
|
667
916
|
|
668
|
-
|
669
|
-
|
670
|
-
|
917
|
+
class MysqlDateTime < Type::DateTime # :nodoc:
|
918
|
+
private
|
919
|
+
|
920
|
+
def has_precision?
|
921
|
+
precision || 0
|
922
|
+
end
|
923
|
+
end
|
924
|
+
|
925
|
+
class MysqlString < Type::String # :nodoc:
|
926
|
+
def type_cast_for_database(value)
|
927
|
+
case value
|
928
|
+
when true then "1"
|
929
|
+
when false then "0"
|
930
|
+
else super
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
private
|
935
|
+
|
936
|
+
def cast_value(value)
|
937
|
+
case value
|
938
|
+
when true then "1"
|
939
|
+
when false then "0"
|
940
|
+
else super
|
941
|
+
end
|
671
942
|
end
|
672
|
-
column
|
673
943
|
end
|
674
944
|
end
|
675
945
|
end
|