activerecord-sqlserver-adapter 3.2.18 → 4.0.0
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.
- 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
|