sic-activerecord-sqlserver-adapter 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 +7 -0
- data/CHANGELOG +603 -0
- data/MIT-LICENSE +20 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +109 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/select_manager_sqlserver.rb +64 -0
- data/lib/arel/visitors/sqlserver.rb +326 -0
- metadata +98 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
class LostConnection < WrappedDatabaseException
|
4
|
+
end
|
5
|
+
|
6
|
+
class DeadlockVictim < WrappedDatabaseException
|
7
|
+
end
|
8
|
+
|
9
|
+
module ConnectionAdapters
|
10
|
+
module Sqlserver
|
11
|
+
module Errors
|
12
|
+
|
13
|
+
LOST_CONNECTION_EXCEPTIONS = {
|
14
|
+
:dblib => ['TinyTds::Error'],
|
15
|
+
:odbc => ['ODBC::Error']
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
LOST_CONNECTION_MESSAGES = {
|
19
|
+
:dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
|
20
|
+
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
|
24
|
+
def lost_connection_exceptions
|
25
|
+
exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
|
26
|
+
@lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
|
27
|
+
end
|
28
|
+
|
29
|
+
def lost_connection_messages
|
30
|
+
LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Quoting
|
5
|
+
|
6
|
+
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
7
|
+
QUOTED_STRING_PREFIX = 'N'
|
8
|
+
|
9
|
+
def quote(value, column = nil)
|
10
|
+
case value
|
11
|
+
when String, ActiveSupport::Multibyte::Chars
|
12
|
+
if column && column.type == :integer && value.blank?
|
13
|
+
value.to_i.to_s
|
14
|
+
elsif column && column.type == :binary
|
15
|
+
column.class.string_to_binary(value)
|
16
|
+
elsif value.is_utf8? || (column && column.type == :string)
|
17
|
+
"#{quoted_string_prefix}'#{quote_string(value)}'"
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
when Date, Time
|
22
|
+
if column && column.sql_type == 'datetime'
|
23
|
+
"'#{quoted_datetime(value)}'"
|
24
|
+
elsif column && (column.sql_type == 'datetimeoffset' || column.sql_type == 'time')
|
25
|
+
"'#{quoted_full_iso8601(value)}'"
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
when nil
|
30
|
+
column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def quoted_string_prefix
|
37
|
+
QUOTED_STRING_PREFIX
|
38
|
+
end
|
39
|
+
|
40
|
+
def quote_string(string)
|
41
|
+
string.to_s.gsub(/\'/, "''")
|
42
|
+
end
|
43
|
+
|
44
|
+
def quote_column_name(name)
|
45
|
+
schema_cache.quote_name(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def quote_table_name(name)
|
49
|
+
quote_column_name(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def substitute_at(column, index)
|
53
|
+
if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
|
54
|
+
nil
|
55
|
+
else
|
56
|
+
Arel.sql "@#{index}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def quoted_true
|
61
|
+
QUOTED_TRUE
|
62
|
+
end
|
63
|
+
|
64
|
+
def quoted_false
|
65
|
+
QUOTED_FALSE
|
66
|
+
end
|
67
|
+
|
68
|
+
def quoted_datetime(value)
|
69
|
+
if value.acts_like?(:time)
|
70
|
+
time_zone_qualified_value = quoted_value_acts_like_time_filter(value)
|
71
|
+
if value.is_a?(Date)
|
72
|
+
time_zone_qualified_value.iso8601(3).to(18)
|
73
|
+
else
|
74
|
+
time_zone_qualified_value.iso8601(3).to(22)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
quoted_date(value)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def quoted_full_iso8601(value)
|
82
|
+
if value.acts_like?(:time)
|
83
|
+
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)
|
84
|
+
else
|
85
|
+
quoted_date(value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def quoted_date(value)
|
90
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
91
|
+
"#{super}.#{sprintf("%03d",value.usec/1000)}"
|
92
|
+
elsif value.acts_like?(:date)
|
93
|
+
value.to_s(:_sqlserver_dateformat)
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def quoted_value_acts_like_time_filter(value)
|
102
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
103
|
+
value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
|
5
|
+
|
6
|
+
attr_reader :view_information
|
7
|
+
|
8
|
+
def initialize(conn)
|
9
|
+
super
|
10
|
+
@table_names = nil
|
11
|
+
@view_names = nil
|
12
|
+
@view_information = {}
|
13
|
+
@quoted_names = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Superclass Overrides
|
17
|
+
|
18
|
+
def table_exists?(table_name)
|
19
|
+
return false if table_name.blank?
|
20
|
+
key = table_name_key(table_name)
|
21
|
+
return @tables[key] if @tables.key? key
|
22
|
+
@tables[key] = connection.table_exists?(table_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
super
|
27
|
+
@table_names = nil
|
28
|
+
@view_names = nil
|
29
|
+
@view_information.clear
|
30
|
+
@quoted_names.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_table_cache!(table_name)
|
34
|
+
key = table_name_key(table_name)
|
35
|
+
super(key)
|
36
|
+
super(table_name)
|
37
|
+
# SQL Server Specific
|
38
|
+
if @table_names
|
39
|
+
@table_names.delete key
|
40
|
+
@table_names.delete table_name
|
41
|
+
end
|
42
|
+
if @view_names
|
43
|
+
@view_names.delete key
|
44
|
+
@view_names.delete table_name
|
45
|
+
end
|
46
|
+
@view_information.delete key
|
47
|
+
end
|
48
|
+
|
49
|
+
# SQL Server Specific
|
50
|
+
|
51
|
+
def table_names
|
52
|
+
@table_names ||= connection.tables
|
53
|
+
end
|
54
|
+
|
55
|
+
def view_names
|
56
|
+
@view_names ||= connection.views
|
57
|
+
end
|
58
|
+
|
59
|
+
def view_exists?(table_name)
|
60
|
+
table_exists?(table_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def view_information(table_name)
|
64
|
+
key = table_name_key(table_name)
|
65
|
+
return @view_information[key] if @view_information.key? key
|
66
|
+
@view_information[key] = connection.send(:view_information, table_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def quote_name(name)
|
70
|
+
return @quoted_names[name] if @quoted_names.key? name
|
71
|
+
@quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def table_name_key(table_name)
|
78
|
+
Utils.unqualify_table_name(table_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,395 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module SchemaStatements
|
5
|
+
|
6
|
+
def native_database_types
|
7
|
+
@native_database_types ||= initialize_native_database_types.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def tables(table_type = 'BASE TABLE')
|
11
|
+
select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME", 'SCHEMA'
|
12
|
+
end
|
13
|
+
|
14
|
+
def table_exists?(table_name)
|
15
|
+
return false if table_name.blank?
|
16
|
+
unquoted_table_name = Utils.unqualify_table_name(table_name)
|
17
|
+
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def indexes(table_name, name = nil)
|
21
|
+
data = select("EXEC sp_helpindex #{quote(table_name)}",name) rescue []
|
22
|
+
data.inject([]) do |indexes,index|
|
23
|
+
index = index.with_indifferent_access
|
24
|
+
if index[:index_description] =~ /primary key/
|
25
|
+
indexes
|
26
|
+
else
|
27
|
+
name = index[:index_name]
|
28
|
+
unique = index[:index_description] =~ /unique/
|
29
|
+
columns = index[:index_keys].split(',').map do |column|
|
30
|
+
column.strip!
|
31
|
+
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
32
|
+
column
|
33
|
+
end
|
34
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def columns(table_name, name = nil)
|
40
|
+
return [] if table_name.blank?
|
41
|
+
column_definitions(table_name).collect do |ci|
|
42
|
+
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
43
|
+
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# like postgres, sqlserver requires the ORDER BY columns in the select list for distinct queries, and
|
48
|
+
# requires that the ORDER BY include the distinct column.
|
49
|
+
# this method is idental to the postgres method
|
50
|
+
def columns_for_distinct(columns, orders) #:nodoc:
|
51
|
+
order_columns = orders.map{ |s|
|
52
|
+
# Convert Arel node to string
|
53
|
+
s = s.to_sql unless s.is_a?(String)
|
54
|
+
# Remove any ASC/DESC modifiers
|
55
|
+
s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
|
56
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
57
|
+
|
58
|
+
[super, *order_columns].join(', ')
|
59
|
+
end
|
60
|
+
|
61
|
+
def rename_table(table_name, new_name)
|
62
|
+
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
63
|
+
rename_table_indexes(table_name, new_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove_column(table_name, column_name, type = nil)
|
67
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if (column_name.is_a? Array)
|
68
|
+
remove_check_constraints(table_name, column_name)
|
69
|
+
remove_default_constraint(table_name, column_name)
|
70
|
+
remove_indexes(table_name, column_name)
|
71
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def change_column(table_name, column_name, type, options = {})
|
75
|
+
sql_commands = []
|
76
|
+
indexes = []
|
77
|
+
column_object = schema_cache.columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
|
78
|
+
|
79
|
+
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
80
|
+
remove_default_constraint(table_name,column_name)
|
81
|
+
indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
|
82
|
+
remove_indexes(table_name, column_name)
|
83
|
+
end
|
84
|
+
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(options[:default])} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
|
85
|
+
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])}"
|
86
|
+
sql_commands[-1] << " NOT NULL" if !options[:null].nil? && options[:null] == false
|
87
|
+
if options_include_default?(options)
|
88
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
#Add any removed indexes back
|
92
|
+
indexes.each do |index|
|
93
|
+
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.collect {|c|quote_column_name(c)}.join(', ')})"
|
94
|
+
end
|
95
|
+
sql_commands.each { |c| do_execute(c) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def change_column_default(table_name, column_name, default)
|
99
|
+
remove_default_constraint(table_name, column_name)
|
100
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def rename_column(table_name, column_name, new_column_name)
|
104
|
+
schema_cache.clear_table_cache!(table_name)
|
105
|
+
detect_column_for! table_name, column_name
|
106
|
+
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
107
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
108
|
+
schema_cache.clear_table_cache!(table_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def rename_index(table_name, old_name, new_name)
|
112
|
+
execute "EXEC sp_rename N'#{table_name}.#{old_name}', N'#{new_name}', N'INDEX'"
|
113
|
+
end
|
114
|
+
|
115
|
+
def remove_index!(table_name, index_name)
|
116
|
+
do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
117
|
+
end
|
118
|
+
|
119
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
120
|
+
type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
|
121
|
+
limit = nil unless type_limitable
|
122
|
+
case type.to_s
|
123
|
+
when 'integer'
|
124
|
+
case limit
|
125
|
+
when 1..2 then 'smallint'
|
126
|
+
when 3..4, nil then 'integer'
|
127
|
+
when 5..8 then 'bigint'
|
128
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
129
|
+
end
|
130
|
+
else
|
131
|
+
super
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def change_column_null(table_name, column_name, allow_null, default = nil)
|
136
|
+
column = detect_column_for! table_name, column_name
|
137
|
+
if !allow_null.nil? && allow_null == false && !default.nil?
|
138
|
+
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
139
|
+
end
|
140
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
141
|
+
sql << " NOT NULL" if !allow_null.nil? && allow_null == false
|
142
|
+
do_execute sql
|
143
|
+
end
|
144
|
+
|
145
|
+
# === SQLServer Specific ======================================== #
|
146
|
+
|
147
|
+
def views
|
148
|
+
tables('VIEW')
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
protected
|
153
|
+
|
154
|
+
# === SQLServer Specific ======================================== #
|
155
|
+
|
156
|
+
def initialize_native_database_types
|
157
|
+
{
|
158
|
+
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
159
|
+
:string => { :name => native_string_database_type, :limit => 255 },
|
160
|
+
:text => { :name => native_text_database_type },
|
161
|
+
:integer => { :name => "int", :limit => 4 },
|
162
|
+
:float => { :name => "float", :limit => 8 },
|
163
|
+
:decimal => { :name => "decimal" },
|
164
|
+
:datetime => { :name => "datetime" },
|
165
|
+
:timestamp => { :name => "datetime" },
|
166
|
+
:time => { :name => native_time_database_type },
|
167
|
+
:date => { :name => native_date_database_type },
|
168
|
+
:binary => { :name => native_binary_database_type },
|
169
|
+
:boolean => { :name => "bit"},
|
170
|
+
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
171
|
+
:char => { :name => 'char' },
|
172
|
+
:varchar_max => { :name => 'varchar(max)' },
|
173
|
+
:nchar => { :name => "nchar" },
|
174
|
+
:nvarchar => { :name => "nvarchar", :limit => 255 },
|
175
|
+
:nvarchar_max => { :name => "nvarchar(max)" },
|
176
|
+
:ntext => { :name => "ntext" },
|
177
|
+
:ss_timestamp => { :name => 'timestamp' }
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def column_definitions(table_name)
|
182
|
+
db_name = Utils.unqualify_db_name(table_name)
|
183
|
+
db_name_with_period = "#{db_name}." if db_name
|
184
|
+
table_schema = Utils.unqualify_table_schema(table_name)
|
185
|
+
table_name = Utils.unqualify_table_name(table_name)
|
186
|
+
sql = %{
|
187
|
+
SELECT DISTINCT
|
188
|
+
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
|
189
|
+
#{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
|
190
|
+
columns.DATA_TYPE AS type,
|
191
|
+
columns.COLUMN_DEFAULT AS default_value,
|
192
|
+
columns.NUMERIC_SCALE AS numeric_scale,
|
193
|
+
columns.NUMERIC_PRECISION AS numeric_precision,
|
194
|
+
columns.ordinal_position,
|
195
|
+
CASE
|
196
|
+
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
197
|
+
ELSE COL_LENGTH('#{db_name_with_period}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
198
|
+
END AS [length],
|
199
|
+
CASE
|
200
|
+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
201
|
+
ELSE NULL
|
202
|
+
END AS [is_nullable],
|
203
|
+
CASE
|
204
|
+
WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
|
205
|
+
ELSE NULL
|
206
|
+
END AS [is_primary],
|
207
|
+
c.is_identity AS [is_identity]
|
208
|
+
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
209
|
+
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
210
|
+
ON TC.TABLE_NAME = columns.TABLE_NAME
|
211
|
+
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
212
|
+
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
213
|
+
ON KCU.COLUMN_NAME = columns.COLUMN_NAME
|
214
|
+
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
215
|
+
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
216
|
+
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
217
|
+
INNER JOIN #{db_name}.sys.schemas AS s
|
218
|
+
ON s.name = columns.TABLE_SCHEMA
|
219
|
+
AND s.schema_id = s.schema_id
|
220
|
+
INNER JOIN #{db_name}.sys.objects AS o
|
221
|
+
ON s.schema_id = o.schema_id
|
222
|
+
AND o.is_ms_shipped = 0
|
223
|
+
AND o.type IN ('U', 'V')
|
224
|
+
AND o.name = columns.TABLE_NAME
|
225
|
+
INNER JOIN #{db_name}.sys.columns AS c
|
226
|
+
ON o.object_id = c.object_id
|
227
|
+
AND c.name = columns.COLUMN_NAME
|
228
|
+
WHERE columns.TABLE_NAME = @0
|
229
|
+
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
|
230
|
+
ORDER BY columns.ordinal_position
|
231
|
+
}.gsub(/[ \t\r\n]+/,' ')
|
232
|
+
binds = [['table_name', table_name]]
|
233
|
+
binds << ['table_schema',table_schema] unless table_schema.blank?
|
234
|
+
results = do_exec_query(sql, 'SCHEMA', binds)
|
235
|
+
results.collect do |ci|
|
236
|
+
ci = ci.symbolize_keys
|
237
|
+
ci[:type] = case ci[:type]
|
238
|
+
when /^bit|image|text|ntext|datetime$/
|
239
|
+
ci[:type]
|
240
|
+
when /^numeric|decimal$/i
|
241
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
242
|
+
when /^float|real$/i
|
243
|
+
"#{ci[:type]}(#{ci[:numeric_precision]})"
|
244
|
+
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
245
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
246
|
+
else
|
247
|
+
ci[:type]
|
248
|
+
end
|
249
|
+
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
|
250
|
+
real_table_name = table_name_or_views_table_name(table_name)
|
251
|
+
real_column_name = views_real_column_name(table_name,ci[:name])
|
252
|
+
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}'"
|
253
|
+
ci[:default_value] = select_value col_default_sql, 'SCHEMA'
|
254
|
+
end
|
255
|
+
ci[:default_value] = case ci[:default_value]
|
256
|
+
when nil, '(null)', '(NULL)'
|
257
|
+
nil
|
258
|
+
when /\A\((\w+\(\))\)\Z/
|
259
|
+
ci[:default_function] = $1
|
260
|
+
nil
|
261
|
+
else
|
262
|
+
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
263
|
+
match_data ? match_data[1] : nil
|
264
|
+
end
|
265
|
+
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
266
|
+
ci[:is_primary] = ci[:is_primary].to_i == 1
|
267
|
+
ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
|
268
|
+
ci
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def remove_check_constraints(table_name, column_name)
|
273
|
+
constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", 'SCHEMA'
|
274
|
+
constraints.each do |constraint|
|
275
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def remove_default_constraint(table_name, column_name)
|
280
|
+
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
281
|
+
execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
|
282
|
+
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
283
|
+
end.each do |row|
|
284
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def remove_indexes(table_name, column_name)
|
289
|
+
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
290
|
+
remove_index(table_name, {:name => index.name})
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
295
|
+
|
296
|
+
def get_table_name(sql)
|
297
|
+
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
298
|
+
$3 || $4
|
299
|
+
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
300
|
+
$1
|
301
|
+
else
|
302
|
+
nil
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def default_constraint_name(table_name, column_name)
|
307
|
+
"DF_#{table_name}_#{column_name}"
|
308
|
+
end
|
309
|
+
|
310
|
+
def detect_column_for!(table_name, column_name)
|
311
|
+
unless column = schema_cache.columns(table_name).detect { |c| c.name == column_name.to_s }
|
312
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
313
|
+
end
|
314
|
+
column
|
315
|
+
end
|
316
|
+
|
317
|
+
def lowercase_schema_reflection_sql(node)
|
318
|
+
lowercase_schema_reflection ? "LOWER(#{node})" : node
|
319
|
+
end
|
320
|
+
|
321
|
+
# === SQLServer Specific (View Reflection) ====================== #
|
322
|
+
|
323
|
+
def view_table_name(table_name)
|
324
|
+
view_info = schema_cache.view_information(table_name)
|
325
|
+
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
326
|
+
end
|
327
|
+
|
328
|
+
def view_information(table_name)
|
329
|
+
table_name = Utils.unqualify_table_name(table_name)
|
330
|
+
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'", 'SCHEMA'
|
331
|
+
if view_info
|
332
|
+
view_info = view_info.with_indifferent_access
|
333
|
+
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
334
|
+
view_info[:VIEW_DEFINITION] = begin
|
335
|
+
select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
|
336
|
+
rescue
|
337
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
338
|
+
nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
view_info
|
343
|
+
end
|
344
|
+
|
345
|
+
def table_name_or_views_table_name(table_name)
|
346
|
+
unquoted_table_name = Utils.unqualify_table_name(table_name)
|
347
|
+
schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
348
|
+
end
|
349
|
+
|
350
|
+
def views_real_column_name(table_name,column_name)
|
351
|
+
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
|
352
|
+
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
353
|
+
match_data ? match_data[1] : column_name
|
354
|
+
end
|
355
|
+
|
356
|
+
# === SQLServer Specific (Identity Inserts) ===================== #
|
357
|
+
|
358
|
+
def query_requires_identity_insert?(sql)
|
359
|
+
|
360
|
+
if insert_sql?(sql)
|
361
|
+
table_name = get_table_name(sql)
|
362
|
+
id_column = identity_column(table_name)
|
363
|
+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
364
|
+
else
|
365
|
+
false
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def insert_sql?(sql)
|
370
|
+
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
371
|
+
end
|
372
|
+
|
373
|
+
def with_identity_insert_enabled(table_name)
|
374
|
+
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
375
|
+
set_identity_insert(table_name, true)
|
376
|
+
yield
|
377
|
+
ensure
|
378
|
+
set_identity_insert(table_name, false)
|
379
|
+
end
|
380
|
+
|
381
|
+
def set_identity_insert(table_name, enable = true)
|
382
|
+
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
383
|
+
do_execute sql, 'SCHEMA'
|
384
|
+
rescue Exception => e
|
385
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
386
|
+
end
|
387
|
+
|
388
|
+
def identity_column(table_name)
|
389
|
+
schema_cache.columns(table_name).detect(&:is_identity?)
|
390
|
+
end
|
391
|
+
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|