activerecord-sqlserver-adapter_new 4.2.15
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/.gitignore +15 -0
- data/CHANGELOG.md +212 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Gemfile +61 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +20 -0
- data/README.md +201 -0
- data/RUNNING_UNIT_TESTS.md +121 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter_new.gemspec +20 -0
- data/appveyor.yml +39 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +40 -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 +34 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +386 -0
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +68 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +114 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +52 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +473 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +76 -0
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +57 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +40 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +76 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +136 -0
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +405 -0
- data/lib/active_record/connection_adapters/sqlserver_column.rb +53 -0
- data/lib/active_record/sqlserver_base.rb +20 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +214 -0
- data/lib/arel_sqlserver.rb +3 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/adapter_test_sqlserver.rb +444 -0
- data/test/cases/coerced_tests.rb +713 -0
- data/test/cases/column_test_sqlserver.rb +780 -0
- data/test/cases/connection_test_sqlserver.rb +142 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +44 -0
- data/test/cases/fetch_test_sqlserver.rb +57 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
- data/test/cases/helper_sqlserver.rb +54 -0
- data/test/cases/migration_test_sqlserver.rb +61 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
- data/test/cases/rake_test_sqlserver.rb +163 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +198 -0
- data/test/cases/schema_test_sqlserver.rb +54 -0
- data/test/cases/scratchpad_test_sqlserver.rb +9 -0
- data/test/cases/showplan_test_sqlserver.rb +65 -0
- data/test/cases/specific_schema_test_sqlserver.rb +167 -0
- data/test/cases/transaction_test_sqlserver.rb +66 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +48 -0
- data/test/config.yml +41 -0
- data/test/debug.rb +14 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/booking.rb +3 -0
- data/test/models/sqlserver/customers_view.rb +3 -0
- data/test/models/sqlserver/datatype.rb +3 -0
- data/test/models/sqlserver/datatype_migration.rb +3 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -0
- data/test/models/sqlserver/dot_table_name.rb +3 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +3 -0
- data/test/models/sqlserver/fk_has_pk.rb +3 -0
- data/test/models/sqlserver/natural_pk_data.rb +4 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
- data/test/models/sqlserver/no_pk_data.rb +3 -0
- data/test/models/sqlserver/object_default.rb +3 -0
- data/test/models/sqlserver/quoted_table.rb +7 -0
- data/test/models/sqlserver/quoted_view_1.rb +3 -0
- data/test/models/sqlserver/quoted_view_2.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
- data/test/models/sqlserver/string_defaults_view.rb +3 -0
- data/test/models/sqlserver/tinyint_pk.rb +3 -0
- data/test/models/sqlserver/upper.rb +3 -0
- data/test/models/sqlserver/uppered.rb +3 -0
- data/test/models/sqlserver/uuid.rb +3 -0
- data/test/schema/datatypes/2012.sql +55 -0
- data/test/schema/sqlserver_specific_schema.rb +207 -0
- data/test/support/coerceable_test_sqlserver.rb +45 -0
- data/test/support/connection_reflection.rb +37 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +32 -0
- metadata +253 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
module SQLServer
|
|
4
|
+
module DatabaseTasks
|
|
5
|
+
|
|
6
|
+
def create_database(database, options = {})
|
|
7
|
+
name = SQLServer::Utils.extract_identifiers(database)
|
|
8
|
+
db_options = create_database_options(options)
|
|
9
|
+
edition_options = create_database_edition_options(options)
|
|
10
|
+
do_execute "CREATE DATABASE #{name} #{db_options} #{edition_options}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def drop_database(database)
|
|
14
|
+
name = SQLServer::Utils.extract_identifiers(database)
|
|
15
|
+
do_execute "DROP DATABASE #{name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def current_database
|
|
19
|
+
select_value 'SELECT DB_NAME()'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def charset
|
|
23
|
+
select_value "SELECT DATABASEPROPERTYEX(DB_NAME(), 'SqlCharSetName')"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def collation
|
|
27
|
+
select_value "SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def create_database_options(options={})
|
|
34
|
+
keys = [:collate]
|
|
35
|
+
copts = @connection_options
|
|
36
|
+
options = {
|
|
37
|
+
collate: copts[:collation]
|
|
38
|
+
}.merge(options.symbolize_keys).select { |_, v|
|
|
39
|
+
v.present?
|
|
40
|
+
}.slice(*keys).map { |k,v|
|
|
41
|
+
"#{k.to_s.upcase} #{v}"
|
|
42
|
+
}.join(' ')
|
|
43
|
+
options
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_database_edition_options(options={})
|
|
47
|
+
keys = [:maxsize, :edition, :service_objective]
|
|
48
|
+
copts = @connection_options
|
|
49
|
+
edition_options = {
|
|
50
|
+
maxsize: copts[:azure_maxsize],
|
|
51
|
+
edition: copts[:azure_edition],
|
|
52
|
+
service_objective: copts[:azure_service_objective]
|
|
53
|
+
}.merge(options.symbolize_keys).select { |_, v|
|
|
54
|
+
v.present?
|
|
55
|
+
}.slice(*keys).map { |k,v|
|
|
56
|
+
"#{k.to_s.upcase} = #{v}"
|
|
57
|
+
}.join(', ')
|
|
58
|
+
edition_options = "( #{edition_options} )" if edition_options.present?
|
|
59
|
+
edition_options
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
module SQLServer
|
|
4
|
+
module Quoting
|
|
5
|
+
|
|
6
|
+
QUOTED_TRUE = '1'
|
|
7
|
+
QUOTED_FALSE = '0'
|
|
8
|
+
QUOTED_STRING_PREFIX = 'N'
|
|
9
|
+
|
|
10
|
+
def quote_string(s)
|
|
11
|
+
SQLServer::Utils.quote_string(s)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def quote_column_name(name)
|
|
15
|
+
SQLServer::Utils.extract_identifiers(name).quoted
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def quote_default_value(value, column)
|
|
19
|
+
if column.type == :uuid && value =~ /\(\)/
|
|
20
|
+
value
|
|
21
|
+
else
|
|
22
|
+
quote(value, column)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def quoted_true
|
|
27
|
+
QUOTED_TRUE
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def unquoted_true
|
|
31
|
+
1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def quoted_false
|
|
35
|
+
QUOTED_FALSE
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def unquoted_false
|
|
39
|
+
0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def quoted_date(value)
|
|
43
|
+
if value.acts_like?(:date)
|
|
44
|
+
Type::Date.new.type_cast_for_database(value)
|
|
45
|
+
else value.acts_like?(:time)
|
|
46
|
+
Type::DateTime.new.type_cast_for_database(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def _quote(value)
|
|
54
|
+
case value
|
|
55
|
+
when Type::Binary::Data
|
|
56
|
+
"0x#{value.hex}"
|
|
57
|
+
when ActiveRecord::Type::SQLServer::Char::Data
|
|
58
|
+
value.quoted
|
|
59
|
+
when String, ActiveSupport::Multibyte::Chars
|
|
60
|
+
"#{QUOTED_STRING_PREFIX}#{super}"
|
|
61
|
+
else
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
module SQLServer
|
|
4
|
+
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
|
|
5
|
+
|
|
6
|
+
def initialize(conn)
|
|
7
|
+
super
|
|
8
|
+
@views = {}
|
|
9
|
+
@view_information = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Superclass Overrides
|
|
13
|
+
|
|
14
|
+
def primary_keys(table_name)
|
|
15
|
+
name = key(table_name)
|
|
16
|
+
@primary_keys[name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def table_exists?(table_name)
|
|
20
|
+
name = key(table_name)
|
|
21
|
+
prepare_tables_and_views
|
|
22
|
+
return @tables[name] if @tables.key? name
|
|
23
|
+
table_exists = @tables[name] = connection.table_exists?(table_name)
|
|
24
|
+
table_exists || view_exists?(table_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def tables(name)
|
|
28
|
+
super(key(name))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def columns(table_name)
|
|
32
|
+
name = key(table_name)
|
|
33
|
+
@columns[name] ||= connection.columns(table_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def columns_hash(table_name)
|
|
37
|
+
name = key(table_name)
|
|
38
|
+
@columns_hash[name] ||= Hash[columns(table_name).map { |col|
|
|
39
|
+
[col.name, col]
|
|
40
|
+
}]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def clear!
|
|
44
|
+
super
|
|
45
|
+
@views.clear
|
|
46
|
+
@view_information.clear
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def size
|
|
50
|
+
super + [@views, @view_information].map{ |x| x.size }.inject(:+)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def clear_table_cache!(table_name)
|
|
54
|
+
name = key(table_name)
|
|
55
|
+
@columns.delete name
|
|
56
|
+
@columns_hash.delete name
|
|
57
|
+
@primary_keys.delete name
|
|
58
|
+
@tables.delete name
|
|
59
|
+
@views.delete name
|
|
60
|
+
@view_information.delete name
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def marshal_dump
|
|
64
|
+
super + [@views, @view_information]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def marshal_load(array)
|
|
68
|
+
@views, @view_information = array[-2..-1]
|
|
69
|
+
super(array[0..-3])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# SQL Server Specific
|
|
73
|
+
|
|
74
|
+
def view_exists?(table_name)
|
|
75
|
+
name = key(table_name)
|
|
76
|
+
prepare_tables_and_views
|
|
77
|
+
return @views[name] if @views.key? name
|
|
78
|
+
@views[name] = connection.views.include?(table_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def view_information(table_name)
|
|
82
|
+
name = key(table_name)
|
|
83
|
+
return @view_information[name] if @view_information.key? name
|
|
84
|
+
@view_information[name] = connection.send(:view_information, table_name)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def identifier(table_name)
|
|
91
|
+
SQLServer::Utils.extract_identifiers(table_name)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def key(table_name)
|
|
95
|
+
identifier(table_name).quoted
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def prepare_tables_and_views
|
|
99
|
+
prepare_views if @views.empty?
|
|
100
|
+
prepare_tables if @tables.empty?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def prepare_tables
|
|
104
|
+
connection.tables.each { |table| @tables[key(table)] = true }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def prepare_views
|
|
108
|
+
connection.views.each { |view| @views[key(view)] = true }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
module SQLServer
|
|
4
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def visit_ColumnDefinition(o)
|
|
9
|
+
sql = super
|
|
10
|
+
if o.primary_key? && o.type == :uuid
|
|
11
|
+
sql << ' PRIMARY KEY '
|
|
12
|
+
add_column_options!(sql, column_options(o))
|
|
13
|
+
end
|
|
14
|
+
sql
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def visit_TableDefinition(o)
|
|
18
|
+
if o.as
|
|
19
|
+
table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
|
|
20
|
+
projections, source = @conn.to_sql(o.as).match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
|
|
21
|
+
select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
|
|
22
|
+
else
|
|
23
|
+
o.instance_variable_set :@as, nil
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_column_options!(sql, options)
|
|
29
|
+
column = options.fetch(:column) { return super }
|
|
30
|
+
if (column.type == :uuid || column.type == :uniqueidentifier) && options[:default] =~ /\(\)/
|
|
31
|
+
sql << " DEFAULT #{options.delete(:default)}"
|
|
32
|
+
else
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def action_sql(action, dependency)
|
|
38
|
+
case dependency
|
|
39
|
+
when :restrict
|
|
40
|
+
raise ArgumentError, <<-MSG.strip_heredoc
|
|
41
|
+
'#{dependency}' is not supported for :on_update or :on_delete.
|
|
42
|
+
Supported values are: :nullify, :cascade
|
|
43
|
+
MSG
|
|
44
|
+
else
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,473 @@
|
|
|
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 data_sources
|
|
11
|
+
tables + views
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def tables(table_type = 'BASE TABLE')
|
|
15
|
+
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'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def table_exists?(table_name)
|
|
19
|
+
return false if table_name.blank?
|
|
20
|
+
unquoted_table_name = SQLServer::Utils.extract_identifiers(table_name).object
|
|
21
|
+
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_table(table_name, options = {})
|
|
25
|
+
res = super
|
|
26
|
+
schema_cache.clear_table_cache!(table_name)
|
|
27
|
+
res
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def indexes(table_name, name = nil)
|
|
31
|
+
data = select("EXEC sp_helpindex #{quote(table_name)}", name) rescue []
|
|
32
|
+
data.reduce([]) do |indexes, index|
|
|
33
|
+
index = index.with_indifferent_access
|
|
34
|
+
if index[:index_description] =~ /primary key/
|
|
35
|
+
indexes
|
|
36
|
+
else
|
|
37
|
+
name = index[:index_name]
|
|
38
|
+
unique = index[:index_description] =~ /unique/
|
|
39
|
+
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
|
|
40
|
+
columns = index[:index_keys].split(',').map do |column|
|
|
41
|
+
column.strip!
|
|
42
|
+
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
|
43
|
+
column
|
|
44
|
+
end
|
|
45
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns, nil, nil, where)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def columns(table_name, _name = nil)
|
|
51
|
+
return [] if table_name.blank?
|
|
52
|
+
column_definitions(table_name).map do |ci|
|
|
53
|
+
sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :default_function, :table_name, :collation
|
|
54
|
+
cast_type = lookup_cast_type(ci[:type])
|
|
55
|
+
new_column ci[:name], ci[:default_value], cast_type, ci[:type], ci[:null], sqlserver_options
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def new_column(name, default, cast_type, sql_type = nil, null = true, sqlserver_options={})
|
|
60
|
+
SQLServerColumn.new name, default, cast_type, sql_type, null, sqlserver_options
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def rename_table(table_name, new_name)
|
|
64
|
+
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
|
65
|
+
rename_table_indexes(table_name, new_name)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remove_column(table_name, column_name, type = nil, options = {})
|
|
69
|
+
raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
|
|
70
|
+
remove_check_constraints(table_name, column_name)
|
|
71
|
+
remove_default_constraint(table_name, column_name)
|
|
72
|
+
remove_indexes(table_name, column_name)
|
|
73
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def change_column(table_name, column_name, type, options = {})
|
|
77
|
+
sql_commands = []
|
|
78
|
+
indexes = []
|
|
79
|
+
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
|
80
|
+
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
|
81
|
+
remove_default_constraint(table_name, column_name)
|
|
82
|
+
indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
|
|
83
|
+
remove_indexes(table_name, column_name)
|
|
84
|
+
end
|
|
85
|
+
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?
|
|
86
|
+
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])}"
|
|
87
|
+
sql_commands[-1] << ' NOT NULL' if !options[:null].nil? && options[:null] == false
|
|
88
|
+
if options_include_default?(options)
|
|
89
|
+
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)}"
|
|
90
|
+
end
|
|
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.map { |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
|
+
schema_cache.clear_table_cache!(table_name)
|
|
100
|
+
remove_default_constraint(table_name, column_name)
|
|
101
|
+
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
|
102
|
+
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)}"
|
|
103
|
+
schema_cache.clear_table_cache!(table_name)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def rename_column(table_name, column_name, new_column_name)
|
|
107
|
+
schema_cache.clear_table_cache!(table_name)
|
|
108
|
+
detect_column_for! table_name, column_name
|
|
109
|
+
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
|
|
110
|
+
execute_procedure :sp_rename, identifier.quoted, new_column_name, 'COLUMN'
|
|
111
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
|
112
|
+
schema_cache.clear_table_cache!(table_name)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def rename_index(table_name, old_name, new_name)
|
|
116
|
+
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length
|
|
117
|
+
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
|
|
118
|
+
execute_procedure :sp_rename, identifier.quoted, new_name, 'INDEX'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def remove_index!(table_name, index_name)
|
|
122
|
+
do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def foreign_keys(table_name)
|
|
126
|
+
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
127
|
+
fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
|
|
128
|
+
fk_info.map do |row|
|
|
129
|
+
from_table = identifier.object
|
|
130
|
+
to_table = row['PKTABLE_NAME']
|
|
131
|
+
options = {
|
|
132
|
+
name: row['FK_NAME'],
|
|
133
|
+
column: row['FKCOLUMN_NAME'],
|
|
134
|
+
primary_key: row['PKCOLUMN_NAME'],
|
|
135
|
+
on_update: extract_foreign_key_action('update', row['FK_NAME']),
|
|
136
|
+
on_delete: extract_foreign_key_action('delete', row['FK_NAME'])
|
|
137
|
+
}
|
|
138
|
+
ForeignKeyDefinition.new from_table, to_table, options
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def extract_foreign_key_action(action, fk_name)
|
|
143
|
+
case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
|
|
144
|
+
when 'CASCADE' then :cascade
|
|
145
|
+
when 'SET_NULL' then :nullify
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
|
150
|
+
type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
|
|
151
|
+
limit = nil unless type_limitable
|
|
152
|
+
case type.to_s
|
|
153
|
+
when 'integer'
|
|
154
|
+
case limit
|
|
155
|
+
when 1..2 then 'smallint'
|
|
156
|
+
when 3..4, nil then 'integer'
|
|
157
|
+
when 5..8 then 'bigint'
|
|
158
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
super
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def columns_for_distinct(columns, orders)
|
|
166
|
+
order_columns = orders.reject(&:blank?).map{ |s|
|
|
167
|
+
s = s.to_sql unless s.is_a?(String)
|
|
168
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
|
169
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
|
|
170
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
|
171
|
+
[super, *order_columns].join(', ')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def change_column_null(table_name, column_name, allow_null, default = nil)
|
|
175
|
+
table_id = SQLServer::Utils.extract_identifiers(table_name)
|
|
176
|
+
column_id = SQLServer::Utils.extract_identifiers(column_name)
|
|
177
|
+
column = detect_column_for! table_name, column_name
|
|
178
|
+
if !allow_null.nil? && allow_null == false && !default.nil?
|
|
179
|
+
do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
|
|
180
|
+
end
|
|
181
|
+
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
|
182
|
+
sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
|
|
183
|
+
do_execute sql
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# === SQLServer Specific ======================================== #
|
|
187
|
+
|
|
188
|
+
def views
|
|
189
|
+
tables('VIEW')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
protected
|
|
194
|
+
|
|
195
|
+
# === SQLServer Specific ======================================== #
|
|
196
|
+
|
|
197
|
+
def initialize_native_database_types
|
|
198
|
+
{
|
|
199
|
+
primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
|
|
200
|
+
integer: { name: 'int', limit: 4 },
|
|
201
|
+
bigint: { name: 'bigint' },
|
|
202
|
+
boolean: { name: 'bit' },
|
|
203
|
+
decimal: { name: 'decimal' },
|
|
204
|
+
money: { name: 'money' },
|
|
205
|
+
smallmoney: { name: 'smallmoney' },
|
|
206
|
+
float: { name: 'float' },
|
|
207
|
+
real: { name: 'real' },
|
|
208
|
+
date: { name: 'date' },
|
|
209
|
+
datetime: { name: 'datetime' },
|
|
210
|
+
datetime2: { name: 'datetime2' },
|
|
211
|
+
datetimeoffset: { name: 'datetimeoffset' },
|
|
212
|
+
smalldatetime: { name: 'smalldatetime' },
|
|
213
|
+
timestamp: { name: 'datetime' },
|
|
214
|
+
time: { name: 'time' },
|
|
215
|
+
char: { name: 'char' },
|
|
216
|
+
varchar: { name: 'varchar', limit: 8000 },
|
|
217
|
+
varchar_max: { name: 'varchar(max)' },
|
|
218
|
+
text_basic: { name: 'text' },
|
|
219
|
+
nchar: { name: 'nchar' },
|
|
220
|
+
string: { name: 'nvarchar', limit: 4000 },
|
|
221
|
+
text: { name: 'nvarchar(max)' },
|
|
222
|
+
ntext: { name: 'ntext' },
|
|
223
|
+
binary_basic: { name: 'binary' },
|
|
224
|
+
varbinary: { name: 'varbinary', limit: 8000 },
|
|
225
|
+
binary: { name: 'varbinary(max)' },
|
|
226
|
+
uuid: { name: 'uniqueidentifier' },
|
|
227
|
+
ss_timestamp: { name: 'timestamp' }
|
|
228
|
+
}
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def column_definitions(table_name)
|
|
232
|
+
identifier = if database_prefix_remote_server?
|
|
233
|
+
SQLServer::Utils.extract_identifiers("#{database_prefix}#{table_name}")
|
|
234
|
+
else
|
|
235
|
+
SQLServer::Utils.extract_identifiers(table_name)
|
|
236
|
+
end
|
|
237
|
+
database = identifier.fully_qualified_database_quoted
|
|
238
|
+
view_exists = schema_cache.view_exists?(table_name)
|
|
239
|
+
view_tblnm = table_name_or_views_table_name(table_name) if view_exists
|
|
240
|
+
sql = %{
|
|
241
|
+
SELECT DISTINCT
|
|
242
|
+
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
|
|
243
|
+
#{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
|
|
244
|
+
columns.DATA_TYPE AS type,
|
|
245
|
+
columns.COLUMN_DEFAULT AS default_value,
|
|
246
|
+
columns.NUMERIC_SCALE AS numeric_scale,
|
|
247
|
+
columns.NUMERIC_PRECISION AS numeric_precision,
|
|
248
|
+
columns.DATETIME_PRECISION AS datetime_precision,
|
|
249
|
+
columns.COLLATION_NAME AS collation,
|
|
250
|
+
columns.ordinal_position,
|
|
251
|
+
CASE
|
|
252
|
+
WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
|
253
|
+
ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
|
254
|
+
END AS [length],
|
|
255
|
+
CASE
|
|
256
|
+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
|
257
|
+
ELSE NULL
|
|
258
|
+
END AS [is_nullable],
|
|
259
|
+
CASE
|
|
260
|
+
WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
|
|
261
|
+
ELSE NULL
|
|
262
|
+
END AS [is_primary],
|
|
263
|
+
c.is_identity AS [is_identity]
|
|
264
|
+
FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
|
|
265
|
+
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
|
266
|
+
ON TC.TABLE_NAME = columns.TABLE_NAME
|
|
267
|
+
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
268
|
+
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
|
269
|
+
ON KCU.COLUMN_NAME = columns.COLUMN_NAME
|
|
270
|
+
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
|
271
|
+
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
|
272
|
+
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
|
273
|
+
INNER JOIN #{database}.sys.schemas AS s
|
|
274
|
+
ON s.name = columns.TABLE_SCHEMA
|
|
275
|
+
AND s.schema_id = s.schema_id
|
|
276
|
+
INNER JOIN #{database}.sys.objects AS o
|
|
277
|
+
ON s.schema_id = o.schema_id
|
|
278
|
+
AND o.is_ms_shipped = 0
|
|
279
|
+
AND o.type IN ('U', 'V')
|
|
280
|
+
AND o.name = columns.TABLE_NAME
|
|
281
|
+
INNER JOIN #{database}.sys.columns AS c
|
|
282
|
+
ON o.object_id = c.object_id
|
|
283
|
+
AND c.name = columns.COLUMN_NAME
|
|
284
|
+
WHERE columns.TABLE_NAME = @0
|
|
285
|
+
AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : '@1'}
|
|
286
|
+
ORDER BY columns.ordinal_position
|
|
287
|
+
}.gsub(/[ \t\r\n]+/, ' ')
|
|
288
|
+
binds = [[info_schema_table_name_column, identifier.object]]
|
|
289
|
+
binds << [info_schema_table_schema_column, identifier.schema] unless identifier.schema.blank?
|
|
290
|
+
results = sp_executesql(sql, 'SCHEMA', binds)
|
|
291
|
+
results.map do |ci|
|
|
292
|
+
ci = ci.symbolize_keys
|
|
293
|
+
ci[:_type] = ci[:type]
|
|
294
|
+
ci[:table_name] = view_tblnm || table_name
|
|
295
|
+
ci[:type] = case ci[:type]
|
|
296
|
+
when /^bit|image|text|ntext|datetime$/
|
|
297
|
+
ci[:type]
|
|
298
|
+
when /^datetime2|datetimeoffset$/i
|
|
299
|
+
"#{ci[:type]}(#{ci[:datetime_precision]})"
|
|
300
|
+
when /^time$/i
|
|
301
|
+
"#{ci[:type]}(#{ci[:datetime_precision]})"
|
|
302
|
+
when /^numeric|decimal$/i
|
|
303
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
|
304
|
+
when /^float|real$/i
|
|
305
|
+
"#{ci[:type]}"
|
|
306
|
+
when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
|
|
307
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
|
308
|
+
else
|
|
309
|
+
ci[:type]
|
|
310
|
+
end
|
|
311
|
+
ci[:default_value],
|
|
312
|
+
ci[:default_function] = begin
|
|
313
|
+
default = ci[:default_value]
|
|
314
|
+
if default.nil? && view_exists
|
|
315
|
+
default = select_value "
|
|
316
|
+
SELECT c.COLUMN_DEFAULT
|
|
317
|
+
FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
|
|
318
|
+
WHERE c.TABLE_NAME = '#{view_tblnm}'
|
|
319
|
+
AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'".squish, 'SCHEMA'
|
|
320
|
+
end
|
|
321
|
+
case default
|
|
322
|
+
when nil
|
|
323
|
+
[nil, nil]
|
|
324
|
+
when /\A\((\w+\(\))\)\Z/
|
|
325
|
+
default_function = Regexp.last_match[1]
|
|
326
|
+
[nil, default_function]
|
|
327
|
+
when /\A\(N'(.*)'\)\Z/m
|
|
328
|
+
string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
|
|
329
|
+
[string_literal, nil]
|
|
330
|
+
when /CREATE DEFAULT/mi
|
|
331
|
+
[nil, nil]
|
|
332
|
+
else
|
|
333
|
+
type = case ci[:type]
|
|
334
|
+
when /smallint|int|bigint/ then ci[:_type]
|
|
335
|
+
else ci[:type]
|
|
336
|
+
end
|
|
337
|
+
value = default.match(/\A\((.*)\)\Z/m)[1]
|
|
338
|
+
value = select_value "SELECT CAST(#{value} AS #{type}) AS value", 'SCHEMA'
|
|
339
|
+
[value, nil]
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
ci[:null] = ci[:is_nullable].to_i == 1
|
|
343
|
+
ci.delete(:is_nullable)
|
|
344
|
+
ci[:is_primary] = ci[:is_primary].to_i == 1
|
|
345
|
+
ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
|
|
346
|
+
ci
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def info_schema_table_name_column
|
|
351
|
+
@info_schema_table_name_column ||= new_column 'table_name', nil, lookup_cast_type('nvarchar(128)'), 'nvarchar(128)', true
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def info_schema_table_schema_column
|
|
355
|
+
@info_schema_table_schema_column ||= new_column 'table_schema', nil, lookup_cast_type('nvarchar(128)'), 'nvarchar(128)', true
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def remove_check_constraints(table_name, column_name)
|
|
359
|
+
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'
|
|
360
|
+
constraints.each do |constraint|
|
|
361
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def remove_default_constraint(table_name, column_name)
|
|
366
|
+
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
|
367
|
+
execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
|
|
368
|
+
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
|
369
|
+
end.each do |row|
|
|
370
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def remove_indexes(table_name, column_name)
|
|
375
|
+
indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }.each do |index|
|
|
376
|
+
remove_index(table_name, name: index.name)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
|
381
|
+
|
|
382
|
+
def get_table_name(sql)
|
|
383
|
+
tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
|
384
|
+
Regexp.last_match[3] || Regexp.last_match[4]
|
|
385
|
+
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
|
386
|
+
Regexp.last_match[1]
|
|
387
|
+
else
|
|
388
|
+
nil
|
|
389
|
+
end
|
|
390
|
+
SQLServer::Utils.extract_identifiers(tn).object
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def default_constraint_name(table_name, column_name)
|
|
394
|
+
"DF_#{table_name}_#{column_name}"
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def detect_column_for!(table_name, column_name)
|
|
398
|
+
unless column = schema_cache.columns(table_name).find { |c| c.name == column_name.to_s }
|
|
399
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
|
400
|
+
end
|
|
401
|
+
column
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def lowercase_schema_reflection_sql(node)
|
|
405
|
+
lowercase_schema_reflection ? "LOWER(#{node})" : node
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# === SQLServer Specific (View Reflection) ====================== #
|
|
409
|
+
|
|
410
|
+
def view_table_name(table_name)
|
|
411
|
+
view_info = schema_cache.view_information(table_name)
|
|
412
|
+
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def view_information(table_name)
|
|
416
|
+
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
417
|
+
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{identifier.object}'", 'SCHEMA'
|
|
418
|
+
if view_info
|
|
419
|
+
view_info = view_info.with_indifferent_access
|
|
420
|
+
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
|
421
|
+
view_info[:VIEW_DEFINITION] = begin
|
|
422
|
+
select_values("EXEC sp_helptext #{identifier.object_quoted}", 'SCHEMA').join
|
|
423
|
+
rescue
|
|
424
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
|
425
|
+
nil
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
view_info
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def table_name_or_views_table_name(table_name)
|
|
433
|
+
schema_cache.view_exists?(table_name) ? view_table_name(table_name) : table_name
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def views_real_column_name(table_name, column_name)
|
|
437
|
+
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
|
|
438
|
+
return column_name unless view_definition
|
|
439
|
+
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
|
440
|
+
match_data ? match_data[1] : column_name
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# === SQLServer Specific (Identity Inserts) ===================== #
|
|
444
|
+
|
|
445
|
+
def query_requires_identity_insert?(sql)
|
|
446
|
+
if insert_sql?(sql)
|
|
447
|
+
table_name = get_table_name(sql)
|
|
448
|
+
id_column = identity_column(table_name)
|
|
449
|
+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
|
450
|
+
else
|
|
451
|
+
false
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def insert_sql?(sql)
|
|
456
|
+
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def identity_column(table_name)
|
|
460
|
+
schema_cache.columns(table_name).find(&:is_identity?)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
private
|
|
465
|
+
|
|
466
|
+
def create_table_definition(name, temporary, options, as = nil)
|
|
467
|
+
SQLServer::TableDefinition.new native_database_types, name, temporary, options, as
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|