activerecord-sqlserver-adapter 3.2.18 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -28
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -9
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +3 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +74 -80
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +24 -15
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +24 -19
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +28 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +118 -77
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +10 -13
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +8 -11
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -5
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +4 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +121 -247
- data/lib/active_record/connection_adapters/sqlserver_column.rb +116 -0
- data/lib/active_record/sqlserver_base.rb +28 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/arel/arel_sqlserver.rb +5 -0
- data/lib/arel/nodes_sqlserver.rb +14 -0
- data/lib/arel/select_manager_sqlserver.rb +62 -0
- data/lib/arel/visitors/sqlserver.rb +251 -188
- metadata +32 -10
- data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +0 -97
@@ -1,35 +1,31 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
|
3
2
|
class LostConnection < WrappedDatabaseException
|
4
3
|
end
|
5
|
-
|
4
|
+
|
6
5
|
class DeadlockVictim < WrappedDatabaseException
|
7
6
|
end
|
8
|
-
|
7
|
+
|
9
8
|
module ConnectionAdapters
|
10
9
|
module Sqlserver
|
11
10
|
module Errors
|
12
|
-
|
13
11
|
LOST_CONNECTION_EXCEPTIONS = {
|
14
|
-
:
|
15
|
-
:
|
12
|
+
dblib: ['TinyTds::Error'],
|
13
|
+
odbc: ['ODBC::Error']
|
16
14
|
}.freeze
|
17
|
-
|
15
|
+
|
18
16
|
LOST_CONNECTION_MESSAGES = {
|
19
|
-
:
|
20
|
-
:
|
17
|
+
dblib: [/closed connection/, /dead or not enabled/, /server failed/i],
|
18
|
+
odbc: [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
|
21
19
|
}.freeze
|
22
|
-
|
23
|
-
|
20
|
+
|
24
21
|
def lost_connection_exceptions
|
25
22
|
exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
|
26
|
-
@lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
|
23
|
+
@lost_connection_exceptions ||= exceptions ? exceptions.map { |e| e.constantize rescue nil }.compact : []
|
27
24
|
end
|
28
|
-
|
25
|
+
|
29
26
|
def lost_connection_messages
|
30
27
|
LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
|
31
28
|
end
|
32
|
-
|
33
29
|
end
|
34
30
|
end
|
35
31
|
end
|
@@ -2,10 +2,9 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
module Quoting
|
5
|
-
|
6
5
|
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
7
6
|
QUOTED_STRING_PREFIX = 'N'
|
8
|
-
|
7
|
+
|
9
8
|
def quote(value, column = nil)
|
10
9
|
case value
|
11
10
|
when String, ActiveSupport::Multibyte::Chars
|
@@ -13,6 +12,8 @@ module ActiveRecord
|
|
13
12
|
value.to_i.to_s
|
14
13
|
elsif column && column.type == :binary
|
15
14
|
column.class.string_to_binary(value)
|
15
|
+
elsif column && [:uuid, :uniqueidentifier].include?(column.type)
|
16
|
+
"'#{quote_string(value)}'"
|
16
17
|
elsif value.is_utf8? || (column && column.type == :string)
|
17
18
|
"#{quoted_string_prefix}'#{quote_string(value)}'"
|
18
19
|
else
|
@@ -32,11 +33,11 @@ module ActiveRecord
|
|
32
33
|
super
|
33
34
|
end
|
34
35
|
end
|
35
|
-
|
36
|
+
|
36
37
|
def quoted_string_prefix
|
37
38
|
QUOTED_STRING_PREFIX
|
38
39
|
end
|
39
|
-
|
40
|
+
|
40
41
|
def quote_string(string)
|
41
42
|
string.to_s.gsub(/\'/, "''")
|
42
43
|
end
|
@@ -48,7 +49,20 @@ module ActiveRecord
|
|
48
49
|
def quote_table_name(name)
|
49
50
|
quote_column_name(name)
|
50
51
|
end
|
51
|
-
|
52
|
+
|
53
|
+
def quote_database_name(name)
|
54
|
+
schema_cache.quote_name(name, false)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Does not quote function default values for UUID columns
|
58
|
+
def quote_default_value(value, column)
|
59
|
+
if column.type == :uuid && value =~ /\(\)/
|
60
|
+
value
|
61
|
+
else
|
62
|
+
quote(value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
52
66
|
def substitute_at(column, index)
|
53
67
|
if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
|
54
68
|
nil
|
@@ -69,19 +83,15 @@ module ActiveRecord
|
|
69
83
|
if value.acts_like?(:time)
|
70
84
|
time_zone_qualified_value = quoted_value_acts_like_time_filter(value)
|
71
85
|
if value.is_a?(Date)
|
72
|
-
time_zone_qualified_value.
|
86
|
+
time_zone_qualified_value.iso8601(3).to(18)
|
73
87
|
else
|
74
|
-
# CHANGED [Ruby 1.8] Not needed when 1.8 is dropped.
|
75
|
-
if value.is_a?(ActiveSupport::TimeWithZone) && RUBY_VERSION < '1.9'
|
76
|
-
time_zone_qualified_value = time_zone_qualified_value.to_time
|
77
|
-
end
|
78
88
|
time_zone_qualified_value.iso8601(3).to(22)
|
79
89
|
end
|
80
90
|
else
|
81
91
|
quoted_date(value)
|
82
92
|
end
|
83
93
|
end
|
84
|
-
|
94
|
+
|
85
95
|
def quoted_full_iso8601(value)
|
86
96
|
if value.acts_like?(:time)
|
87
97
|
value.is_a?(Date) ? quoted_value_acts_like_time_filter(value).to_time.xmlschema.to(18) : quoted_value_acts_like_time_filter(value).iso8601(7).to(22)
|
@@ -92,21 +102,20 @@ module ActiveRecord
|
|
92
102
|
|
93
103
|
def quoted_date(value)
|
94
104
|
if value.acts_like?(:time) && value.respond_to?(:usec)
|
95
|
-
"#{super}.#{sprintf(
|
105
|
+
"#{super}.#{sprintf('%03d', value.usec / 1000)}"
|
96
106
|
elsif value.acts_like?(:date)
|
97
107
|
value.to_s(:_sqlserver_dateformat)
|
98
108
|
else
|
99
109
|
super
|
100
110
|
end
|
101
111
|
end
|
102
|
-
|
112
|
+
|
103
113
|
protected
|
104
|
-
|
114
|
+
|
105
115
|
def quoted_value_acts_like_time_filter(value)
|
106
116
|
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
107
117
|
value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
108
118
|
end
|
109
|
-
|
110
119
|
end
|
111
120
|
end
|
112
121
|
end
|
@@ -2,9 +2,8 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
|
5
|
-
|
6
5
|
attr_reader :view_information
|
7
|
-
|
6
|
+
|
8
7
|
def initialize(conn)
|
9
8
|
super
|
10
9
|
@table_names = nil
|
@@ -12,16 +11,16 @@ module ActiveRecord
|
|
12
11
|
@view_information = {}
|
13
12
|
@quoted_names = {}
|
14
13
|
end
|
15
|
-
|
14
|
+
|
16
15
|
# Superclass Overrides
|
17
|
-
|
16
|
+
|
18
17
|
def table_exists?(table_name)
|
19
18
|
return false if table_name.blank?
|
20
19
|
key = table_name_key(table_name)
|
21
20
|
return @tables[key] if @tables.key? key
|
22
21
|
@tables[key] = connection.table_exists?(table_name)
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
def clear!
|
26
25
|
super
|
27
26
|
@table_names = nil
|
@@ -29,7 +28,7 @@ module ActiveRecord
|
|
29
28
|
@view_information.clear
|
30
29
|
@quoted_names.clear
|
31
30
|
end
|
32
|
-
|
31
|
+
|
33
32
|
def clear_table_cache!(table_name)
|
34
33
|
key = table_name_key(table_name)
|
35
34
|
super(key)
|
@@ -45,41 +44,47 @@ module ActiveRecord
|
|
45
44
|
end
|
46
45
|
@view_information.delete key
|
47
46
|
end
|
48
|
-
|
47
|
+
|
49
48
|
# SQL Server Specific
|
50
|
-
|
49
|
+
|
51
50
|
def table_names
|
52
51
|
@table_names ||= connection.tables
|
53
52
|
end
|
54
|
-
|
53
|
+
|
55
54
|
def view_names
|
56
55
|
@view_names ||= connection.views
|
57
56
|
end
|
58
|
-
|
57
|
+
|
59
58
|
def view_exists?(table_name)
|
60
59
|
table_exists?(table_name)
|
61
60
|
end
|
62
|
-
|
61
|
+
|
63
62
|
def view_information(table_name)
|
64
63
|
key = table_name_key(table_name)
|
65
64
|
return @view_information[key] if @view_information.key? key
|
66
65
|
@view_information[key] = connection.send(:view_information, table_name)
|
67
66
|
end
|
68
|
-
|
69
|
-
def quote_name(name)
|
67
|
+
|
68
|
+
def quote_name(name, split_on_dots = true)
|
70
69
|
return @quoted_names[name] if @quoted_names.key? name
|
71
|
-
|
70
|
+
|
71
|
+
@quoted_names[name] = if split_on_dots
|
72
|
+
name.to_s.split('.').map { |n| quote_name_part(n) }.join('.')
|
73
|
+
else
|
74
|
+
quote_name_part(name.to_s)
|
75
|
+
end
|
72
76
|
end
|
73
|
-
|
74
|
-
|
77
|
+
|
75
78
|
private
|
76
|
-
|
79
|
+
|
80
|
+
def quote_name_part(part)
|
81
|
+
part =~ /^\[.*\]$/ ? part : "[#{part.to_s.gsub(']', ']]')}]"
|
82
|
+
end
|
83
|
+
|
77
84
|
def table_name_key(table_name)
|
78
85
|
Utils.unqualify_table_name(table_name)
|
79
86
|
end
|
80
|
-
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
84
90
|
end
|
85
|
-
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
5
|
+
private
|
6
|
+
|
7
|
+
def visit_ColumnDefinition(o)
|
8
|
+
sql = super
|
9
|
+
if o.primary_key? && o.type == :uuid
|
10
|
+
sql << ' PRIMARY KEY '
|
11
|
+
add_column_options!(sql, column_options(o))
|
12
|
+
end
|
13
|
+
sql
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_column_options!(sql, options)
|
17
|
+
column = options.fetch(:column) { return super }
|
18
|
+
if [:uniqueidentifier, :uuid].include?(column.type) && options[:default] =~ /\(\)/
|
19
|
+
sql << " DEFAULT #{options.delete(:default)}"
|
20
|
+
super
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -2,7 +2,6 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
module SchemaStatements
|
5
|
-
|
6
5
|
def native_database_types
|
7
6
|
@native_database_types ||= initialize_native_database_types.freeze
|
8
7
|
end
|
@@ -18,8 +17,8 @@ module ActiveRecord
|
|
18
17
|
end
|
19
18
|
|
20
19
|
def indexes(table_name, name = nil)
|
21
|
-
data = select("EXEC sp_helpindex #{quote(table_name)}",name) rescue []
|
22
|
-
data.
|
20
|
+
data = select("EXEC sp_helpindex #{quote(table_name)}", name) rescue []
|
21
|
+
data.reduce([]) do |indexes, index|
|
23
22
|
index = index.with_indifferent_access
|
24
23
|
if index[:index_description] =~ /primary key/
|
25
24
|
indexes
|
@@ -36,62 +35,77 @@ module ActiveRecord
|
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
39
|
-
def columns(table_name,
|
38
|
+
def columns(table_name, _name = nil)
|
40
39
|
return [] if table_name.blank?
|
41
|
-
column_definitions(table_name).
|
42
|
-
sqlserver_options = ci.except(:name
|
40
|
+
column_definitions(table_name).map do |ci|
|
41
|
+
sqlserver_options = ci.except(:name, :default_value, :type, :null).merge(database_year: database_year)
|
43
42
|
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
46
|
+
# like postgres, sqlserver requires the ORDER BY columns in the select list for distinct queries, and
|
47
|
+
# requires that the ORDER BY include the distinct column. Unfortunately, sqlserver does not support
|
48
|
+
# DISTINCT ON () like Posgres, or FIRST_VALUE() like Oracle (at least before SQL Server 2012). Because
|
49
|
+
# of these facts, we don't actually add any extra columns for distinct, but instead have to create
|
50
|
+
# a subquery with ROW_NUMBER() and DENSE_RANK() in our monkey-patches to Arel.
|
51
|
+
def columns_for_distinct(columns, _orders) #:nodoc:
|
52
|
+
columns
|
53
|
+
end
|
54
|
+
|
47
55
|
def rename_table(table_name, new_name)
|
48
56
|
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
57
|
+
rename_table_indexes(table_name, new_name)
|
49
58
|
end
|
50
59
|
|
51
|
-
def remove_column(table_name,
|
52
|
-
raise ArgumentError.new(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
remove_indexes(table_name, column_name)
|
58
|
-
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
59
|
-
end
|
60
|
+
def remove_column(table_name, column_name, _type = nil)
|
61
|
+
raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
|
62
|
+
remove_check_constraints(table_name, column_name)
|
63
|
+
remove_default_constraint(table_name, column_name)
|
64
|
+
remove_indexes(table_name, column_name)
|
65
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
60
66
|
end
|
61
67
|
|
62
68
|
def change_column(table_name, column_name, type, options = {})
|
63
69
|
sql_commands = []
|
64
70
|
indexes = []
|
65
|
-
column_object = schema_cache.columns
|
71
|
+
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
66
72
|
|
67
73
|
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
68
|
-
remove_default_constraint(table_name,column_name)
|
69
|
-
indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
|
74
|
+
remove_default_constraint(table_name, column_name)
|
75
|
+
indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
|
70
76
|
remove_indexes(table_name, column_name)
|
71
77
|
end
|
72
|
-
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{
|
78
|
+
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
|
73
79
|
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
74
|
-
sql_commands[-1] <<
|
80
|
+
sql_commands[-1] << ' NOT NULL' if !options[:null].nil? && options[:null] == false
|
75
81
|
if options_include_default?(options)
|
76
|
-
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{
|
82
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_value(options[:default], column_object)} FOR #{quote_column_name(column_name)}"
|
77
83
|
end
|
78
84
|
|
79
|
-
#Add any removed indexes back
|
85
|
+
# Add any removed indexes back
|
80
86
|
indexes.each do |index|
|
81
|
-
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.
|
87
|
+
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
|
82
88
|
end
|
83
89
|
sql_commands.each { |c| do_execute(c) }
|
84
90
|
end
|
85
91
|
|
86
92
|
def change_column_default(table_name, column_name, default)
|
87
93
|
remove_default_constraint(table_name, column_name)
|
88
|
-
|
94
|
+
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
95
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_value(default, column_object)} FOR #{quote_column_name(column_name)}"
|
96
|
+
schema_cache.clear_table_cache!(table_name)
|
89
97
|
end
|
90
98
|
|
91
99
|
def rename_column(table_name, column_name, new_column_name)
|
92
100
|
schema_cache.clear_table_cache!(table_name)
|
93
101
|
detect_column_for! table_name, column_name
|
94
102
|
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
103
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
104
|
+
schema_cache.clear_table_cache!(table_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def rename_index(table_name, old_name, new_name)
|
108
|
+
execute "EXEC sp_rename N'#{table_name}.#{old_name}', N'#{new_name}', N'INDEX'"
|
95
109
|
end
|
96
110
|
|
97
111
|
def remove_index!(table_name, index_name)
|
@@ -99,15 +113,15 @@ module ActiveRecord
|
|
99
113
|
end
|
100
114
|
|
101
115
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
102
|
-
type_limitable =
|
116
|
+
type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
|
103
117
|
limit = nil unless type_limitable
|
104
118
|
case type.to_s
|
105
119
|
when 'integer'
|
106
120
|
case limit
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
121
|
+
when 1..2 then 'smallint'
|
122
|
+
when 3..4, nil then 'integer'
|
123
|
+
when 5..8 then 'bigint'
|
124
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
111
125
|
end
|
112
126
|
else
|
113
127
|
super
|
@@ -120,7 +134,7 @@ module ActiveRecord
|
|
120
134
|
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
121
135
|
end
|
122
136
|
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
123
|
-
sql <<
|
137
|
+
sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
|
124
138
|
do_execute sql
|
125
139
|
end
|
126
140
|
|
@@ -130,33 +144,33 @@ module ActiveRecord
|
|
130
144
|
tables('VIEW')
|
131
145
|
end
|
132
146
|
|
133
|
-
|
134
147
|
protected
|
135
148
|
|
136
149
|
# === SQLServer Specific ======================================== #
|
137
150
|
|
138
151
|
def initialize_native_database_types
|
139
152
|
{
|
140
|
-
:
|
141
|
-
:
|
142
|
-
:
|
143
|
-
:
|
144
|
-
:
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
:
|
153
|
+
primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
|
154
|
+
string: { name: native_string_database_type, limit: 255 },
|
155
|
+
text: { name: native_text_database_type },
|
156
|
+
integer: { name: 'int', limit: 4 },
|
157
|
+
float: { name: 'float', limit: 8 },
|
158
|
+
decimal: { name: 'decimal' },
|
159
|
+
datetime: { name: 'datetime' },
|
160
|
+
timestamp: { name: 'datetime' },
|
161
|
+
time: { name: native_time_database_type },
|
162
|
+
date: { name: native_date_database_type },
|
163
|
+
binary: { name: native_binary_database_type },
|
164
|
+
boolean: { name: 'bit' },
|
165
|
+
uuid: { name: 'uniqueidentifier' },
|
152
166
|
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
167
|
+
char: { name: 'char' },
|
168
|
+
varchar_max: { name: 'varchar(max)' },
|
169
|
+
nchar: { name: 'nchar' },
|
170
|
+
nvarchar: { name: 'nvarchar', limit: 255 },
|
171
|
+
nvarchar_max: { name: 'nvarchar(max)' },
|
172
|
+
ntext: { name: 'ntext' },
|
173
|
+
ss_timestamp: { name: 'timestamp' }
|
160
174
|
}
|
161
175
|
end
|
162
176
|
|
@@ -208,29 +222,29 @@ module ActiveRecord
|
|
208
222
|
ON o.object_id = c.object_id
|
209
223
|
AND c.name = columns.COLUMN_NAME
|
210
224
|
WHERE columns.TABLE_NAME = @0
|
211
|
-
AND columns.TABLE_SCHEMA = #{table_schema.blank? ?
|
225
|
+
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? 'schema_name()' : '@1'}
|
212
226
|
ORDER BY columns.ordinal_position
|
213
|
-
}.gsub(/[ \t\r\n]+/,' ')
|
227
|
+
}.gsub(/[ \t\r\n]+/, ' ')
|
214
228
|
binds = [['table_name', table_name]]
|
215
|
-
binds << ['table_schema',table_schema] unless table_schema.blank?
|
229
|
+
binds << ['table_schema', table_schema] unless table_schema.blank?
|
216
230
|
results = do_exec_query(sql, 'SCHEMA', binds)
|
217
|
-
results.
|
231
|
+
results.map do |ci|
|
218
232
|
ci = ci.symbolize_keys
|
219
233
|
ci[:type] = case ci[:type]
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
234
|
+
when /^bit|image|text|ntext|datetime$/
|
235
|
+
ci[:type]
|
236
|
+
when /^numeric|decimal$/i
|
237
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
238
|
+
when /^float|real$/i
|
239
|
+
"#{ci[:type]}(#{ci[:numeric_precision]})"
|
240
|
+
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
241
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
242
|
+
else
|
243
|
+
ci[:type]
|
244
|
+
end
|
231
245
|
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
|
232
246
|
real_table_name = table_name_or_views_table_name(table_name)
|
233
|
-
real_column_name = views_real_column_name(table_name,ci[:name])
|
247
|
+
real_column_name = views_real_column_name(table_name, ci[:name])
|
234
248
|
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
|
235
249
|
ci[:default_value] = select_value col_default_sql, 'SCHEMA'
|
236
250
|
end
|
@@ -238,13 +252,14 @@ module ActiveRecord
|
|
238
252
|
when nil, '(null)', '(NULL)'
|
239
253
|
nil
|
240
254
|
when /\A\((\w+\(\))\)\Z/
|
241
|
-
ci[:default_function] =
|
255
|
+
ci[:default_function] = Regexp.last_match[1]
|
242
256
|
nil
|
243
257
|
else
|
244
258
|
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
245
259
|
match_data ? match_data[1] : nil
|
246
260
|
end
|
247
|
-
ci[:null] = ci[:is_nullable].to_i == 1
|
261
|
+
ci[:null] = ci[:is_nullable].to_i == 1
|
262
|
+
ci.delete(:is_nullable)
|
248
263
|
ci[:is_primary] = ci[:is_primary].to_i == 1
|
249
264
|
ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
|
250
265
|
ci
|
@@ -268,18 +283,18 @@ module ActiveRecord
|
|
268
283
|
end
|
269
284
|
|
270
285
|
def remove_indexes(table_name, column_name)
|
271
|
-
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
272
|
-
remove_index(table_name,
|
286
|
+
indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }.each do |index|
|
287
|
+
remove_index(table_name, name: index.name)
|
273
288
|
end
|
274
289
|
end
|
275
290
|
|
276
291
|
# === SQLServer Specific (Misc Helpers) ========================= #
|
277
292
|
|
278
293
|
def get_table_name(sql)
|
279
|
-
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO
|
280
|
-
|
294
|
+
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
295
|
+
Regexp.last_match[3] || Regexp.last_match[4]
|
281
296
|
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
282
|
-
|
297
|
+
Regexp.last_match[1]
|
283
298
|
else
|
284
299
|
nil
|
285
300
|
end
|
@@ -290,7 +305,7 @@ module ActiveRecord
|
|
290
305
|
end
|
291
306
|
|
292
307
|
def detect_column_for!(table_name, column_name)
|
293
|
-
unless column = schema_cache.columns
|
308
|
+
unless column = schema_cache.columns(table_name).find { |c| c.name == column_name.to_s }
|
294
309
|
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
295
310
|
end
|
296
311
|
column
|
@@ -329,7 +344,7 @@ module ActiveRecord
|
|
329
344
|
schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
330
345
|
end
|
331
346
|
|
332
|
-
def views_real_column_name(table_name,column_name)
|
347
|
+
def views_real_column_name(table_name, column_name)
|
333
348
|
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
|
334
349
|
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
335
350
|
match_data ? match_data[1] : column_name
|
@@ -351,6 +366,27 @@ module ActiveRecord
|
|
351
366
|
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
352
367
|
end
|
353
368
|
|
369
|
+
def strip_ident_from_update(sql)
|
370
|
+
# We can't update Identiy columns in sqlserver. So, strip out the id from the update.
|
371
|
+
# There has to be a better way to handle this, but this'll do for now.
|
372
|
+
table_name = get_table_name(sql)
|
373
|
+
id_column = identity_column(table_name)
|
374
|
+
|
375
|
+
if id_column
|
376
|
+
regex_col_name = Regexp.quote(quote_column_name(id_column.name))
|
377
|
+
if sql =~ /, #{regex_col_name} = @?[0-9]*/
|
378
|
+
sql = sql.gsub(/, #{regex_col_name} = @?[0-9]*/, '')
|
379
|
+
elsif sql =~ /\s#{regex_col_name} = @?[0-9]*,/
|
380
|
+
sql = sql.gsub(/\s#{regex_col_name} = @?[0-9]*,/, '')
|
381
|
+
end
|
382
|
+
end
|
383
|
+
sql
|
384
|
+
end
|
385
|
+
|
386
|
+
def update_sql?(sql)
|
387
|
+
!(sql =~ /^\s*(UPDATE|EXEC sp_executesql N'UPDATE)/i).nil?
|
388
|
+
end
|
389
|
+
|
354
390
|
def with_identity_insert_enabled(table_name)
|
355
391
|
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
356
392
|
set_identity_insert(table_name, true)
|
@@ -362,14 +398,19 @@ module ActiveRecord
|
|
362
398
|
def set_identity_insert(table_name, enable = true)
|
363
399
|
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
364
400
|
do_execute sql, 'SCHEMA'
|
365
|
-
rescue Exception
|
401
|
+
rescue Exception
|
366
402
|
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
367
403
|
end
|
368
404
|
|
369
405
|
def identity_column(table_name)
|
370
|
-
schema_cache.columns
|
406
|
+
schema_cache.columns(table_name).find(&:is_identity?)
|
371
407
|
end
|
372
408
|
|
409
|
+
private
|
410
|
+
|
411
|
+
def create_table_definition(name, temporary, options)
|
412
|
+
TableDefinition.new native_database_types, name, temporary, options
|
413
|
+
end
|
373
414
|
end
|
374
415
|
end
|
375
416
|
end
|