activerecord-sqlserver-adapter 5.2.0 → 6.0.1
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/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +6 -8
- data/CHANGELOG.md +46 -11
- data/{Dockerfile → Dockerfile.ci} +2 -2
- data/Gemfile +48 -41
- data/Guardfile +9 -8
- data/README.md +9 -37
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +14 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +25 -14
- data/appveyor.yml +24 -17
- data/docker-compose.ci.yml +7 -5
- data/guides/RELEASING.md +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +22 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +44 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +28 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +10 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +197 -165
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
- data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +132 -92
- data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
- data/lib/active_record/sqlserver_base.rb +9 -1
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +58 -24
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/adapter_test_sqlserver.rb +223 -180
- data/test/cases/change_column_null_test_sqlserver.rb +17 -15
- data/test/cases/coerced_tests.rb +654 -360
- data/test/cases/column_test_sqlserver.rb +635 -604
- data/test/cases/connection_test_sqlserver.rb +18 -21
- data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
- data/test/cases/fetch_test_sqlserver.rb +17 -23
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +20 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/migration_test_sqlserver.rb +30 -26
- data/test/cases/order_test_sqlserver.rb +53 -54
- data/test/cases/pessimistic_locking_test_sqlserver.rb +31 -37
- data/test/cases/rake_test_sqlserver.rb +44 -56
- data/test/cases/schema_dumper_test_sqlserver.rb +117 -112
- data/test/cases/schema_test_sqlserver.rb +20 -26
- data/test/cases/scratchpad_test_sqlserver.rb +4 -4
- data/test/cases/showplan_test_sqlserver.rb +32 -39
- data/test/cases/specific_schema_test_sqlserver.rb +75 -72
- data/test/cases/transaction_test_sqlserver.rb +27 -29
- data/test/cases/trigger_test_sqlserver.rb +18 -17
- data/test/cases/utils_test_sqlserver.rb +78 -78
- data/test/cases/uuid_test_sqlserver.rb +19 -20
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_null.rb +3 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
- data/test/models/sqlserver/booking.rb +3 -1
- data/test/models/sqlserver/customers_view.rb +3 -1
- data/test/models/sqlserver/datatype.rb +2 -0
- data/test/models/sqlserver/datatype_migration.rb +2 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -1
- data/test/models/sqlserver/edge_schema.rb +3 -3
- data/test/models/sqlserver/fk_has_fk.rb +3 -1
- data/test/models/sqlserver/fk_has_pk.rb +3 -1
- data/test/models/sqlserver/natural_pk_data.rb +4 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
- data/test/models/sqlserver/no_pk_data.rb +3 -1
- data/test/models/sqlserver/object_default.rb +3 -1
- data/test/models/sqlserver/quoted_table.rb +4 -2
- data/test/models/sqlserver/quoted_view_1.rb +3 -1
- data/test/models/sqlserver/quoted_view_2.rb +3 -1
- data/test/models/sqlserver/sst_memory.rb +3 -1
- data/test/models/sqlserver/string_default.rb +3 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
- data/test/models/sqlserver/string_defaults_view.rb +3 -1
- data/test/models/sqlserver/tinyint_pk.rb +3 -1
- data/test/models/sqlserver/trigger.rb +4 -2
- data/test/models/sqlserver/trigger_history.rb +3 -1
- data/test/models/sqlserver/upper.rb +3 -1
- data/test/models/sqlserver/uppered.rb +3 -1
- data/test/models/sqlserver/uuid.rb +3 -1
- data/test/schema/datatypes/2012.sql +1 -0
- data/test/schema/sqlserver_specific_schema.rb +31 -21
- data/test/support/coerceable_test_sqlserver.rb +15 -9
- data/test/support/connection_reflection.rb +3 -2
- data/test/support/core_ext/query_cache.rb +4 -1
- data/test/support/load_schema_sqlserver.rb +5 -5
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +13 -10
- data/test/support/sql_counter_sqlserver.rb +3 -4
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +23 -13
- data/BACKERS.md +0 -32
- data/circle.yml +0 -38
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module DatabaseTasks
|
|
5
|
-
|
|
6
7
|
def create_database(database, options = {})
|
|
7
8
|
name = SQLServer::Utils.extract_identifiers(database)
|
|
8
9
|
db_options = create_database_options(options)
|
|
@@ -12,11 +13,12 @@ module ActiveRecord
|
|
|
12
13
|
|
|
13
14
|
def drop_database(database)
|
|
14
15
|
name = SQLServer::Utils.extract_identifiers(database)
|
|
16
|
+
do_execute "ALTER DATABASE #{name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
|
15
17
|
do_execute "DROP DATABASE #{name}"
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def current_database
|
|
19
|
-
select_value
|
|
21
|
+
select_value "SELECT DB_NAME()"
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def charset
|
|
@@ -29,20 +31,20 @@ module ActiveRecord
|
|
|
29
31
|
|
|
30
32
|
private
|
|
31
33
|
|
|
32
|
-
def create_database_options(options={})
|
|
34
|
+
def create_database_options(options = {})
|
|
33
35
|
keys = [:collate]
|
|
34
36
|
copts = @connection_options
|
|
35
37
|
options = {
|
|
36
38
|
collate: copts[:collation]
|
|
37
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
38
40
|
v.present?
|
|
39
|
-
}.slice(*keys).map { |k,v|
|
|
41
|
+
}.slice(*keys).map { |k, v|
|
|
40
42
|
"#{k.to_s.upcase} #{v}"
|
|
41
|
-
}.join(
|
|
43
|
+
}.join(" ")
|
|
42
44
|
options
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
def create_database_edition_options(options={})
|
|
47
|
+
def create_database_edition_options(options = {})
|
|
46
48
|
keys = [:maxsize, :edition, :service_objective]
|
|
47
49
|
copts = @connection_options
|
|
48
50
|
edition_options = {
|
|
@@ -51,17 +53,13 @@ module ActiveRecord
|
|
|
51
53
|
service_objective: copts[:azure_service_objective]
|
|
52
54
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
53
55
|
v.present?
|
|
54
|
-
}.slice(*keys).map { |k,v|
|
|
56
|
+
}.slice(*keys).map { |k, v|
|
|
55
57
|
"#{k.to_s.upcase} = #{v}"
|
|
56
|
-
}.join(
|
|
58
|
+
}.join(", ")
|
|
57
59
|
edition_options = "( #{edition_options} )" if edition_options.present?
|
|
58
60
|
edition_options
|
|
59
61
|
end
|
|
60
|
-
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module Quoting
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
QUOTED_STRING_PREFIX = 'N'.freeze
|
|
7
|
+
QUOTED_TRUE = "1".freeze
|
|
8
|
+
QUOTED_FALSE = "0".freeze
|
|
9
|
+
QUOTED_STRING_PREFIX = "N".freeze
|
|
9
10
|
|
|
10
11
|
def fetch_type_metadata(sql_type, sqlserver_options = {})
|
|
11
12
|
cast_type = lookup_cast_type(sql_type)
|
|
@@ -61,13 +62,51 @@ module ActiveRecord
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def quoted_date(value)
|
|
64
|
-
if value.acts_like?(:
|
|
65
|
-
Type::Date.new.serialize(value)
|
|
66
|
-
else value.acts_like?(:time)
|
|
65
|
+
if value.acts_like?(:time)
|
|
67
66
|
Type::DateTime.new.serialize(value)
|
|
67
|
+
elsif value.acts_like?(:date)
|
|
68
|
+
Type::Date.new.serialize(value)
|
|
69
|
+
else
|
|
70
|
+
value
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
def column_name_matcher
|
|
75
|
+
COLUMN_NAME
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def column_name_with_order_matcher
|
|
79
|
+
COLUMN_NAME_WITH_ORDER
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
COLUMN_NAME = /
|
|
83
|
+
\A
|
|
84
|
+
(
|
|
85
|
+
(?:
|
|
86
|
+
# [table_name].[column_name] | function(one or no argument)
|
|
87
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
88
|
+
)
|
|
89
|
+
(?:\s+AS\s+(?:\w+|\[\w+\]))?
|
|
90
|
+
)
|
|
91
|
+
(?:\s*,\s*\g<1>)*
|
|
92
|
+
\z
|
|
93
|
+
/ix
|
|
94
|
+
|
|
95
|
+
COLUMN_NAME_WITH_ORDER = /
|
|
96
|
+
\A
|
|
97
|
+
(
|
|
98
|
+
(?:
|
|
99
|
+
# [table_name].[column_name] | function(one or no argument)
|
|
100
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
101
|
+
)
|
|
102
|
+
(?:\s+ASC|\s+DESC)?
|
|
103
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
104
|
+
)
|
|
105
|
+
(?:\s*,\s*\g<1>)*
|
|
106
|
+
\z
|
|
107
|
+
/ix
|
|
108
|
+
|
|
109
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
|
71
110
|
|
|
72
111
|
private
|
|
73
112
|
|
|
@@ -92,7 +131,6 @@ module ActiveRecord
|
|
|
92
131
|
super
|
|
93
132
|
end
|
|
94
133
|
end
|
|
95
|
-
|
|
96
134
|
end
|
|
97
135
|
end
|
|
98
136
|
end
|
|
@@ -1,20 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
class SchemaCreation < AbstractAdapter::SchemaCreation
|
|
5
|
-
|
|
6
7
|
private
|
|
7
8
|
|
|
8
9
|
def visit_TableDefinition(o)
|
|
10
|
+
if_not_exists = o.if_not_exists
|
|
11
|
+
|
|
9
12
|
if o.as
|
|
10
13
|
table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
|
|
11
14
|
query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
|
|
12
15
|
projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
|
|
13
|
-
|
|
16
|
+
sql = "SELECT #{projections} INTO #{table_name} FROM #{source}"
|
|
14
17
|
else
|
|
15
18
|
o.instance_variable_set :@as, nil
|
|
16
|
-
|
|
19
|
+
o.instance_variable_set :@if_not_exists, false
|
|
20
|
+
sql = super
|
|
17
21
|
end
|
|
22
|
+
|
|
23
|
+
if if_not_exists
|
|
24
|
+
o.instance_variable_set :@if_not_exists, true
|
|
25
|
+
table_name = o.temporary ? "##{o.name}" : o.name
|
|
26
|
+
sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='#{table_name}' and xtype='U') #{sql}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sql
|
|
18
30
|
end
|
|
19
31
|
|
|
20
32
|
def add_column_options!(sql, options)
|
|
@@ -34,7 +46,7 @@ module ActiveRecord
|
|
|
34
46
|
def action_sql(action, dependency)
|
|
35
47
|
case dependency
|
|
36
48
|
when :restrict
|
|
37
|
-
raise ArgumentError,
|
|
49
|
+
raise ArgumentError, <<~MSG.squish
|
|
38
50
|
'#{dependency}' is not supported for :on_update or :on_delete.
|
|
39
51
|
Supported values are: :nullify, :cascade
|
|
40
52
|
MSG
|
|
@@ -50,7 +62,6 @@ module ActiveRecord
|
|
|
50
62
|
def options_primary_key_with_nil_default?(options)
|
|
51
63
|
options[:primary_key] && options.include?(:default) && options[:default].nil?
|
|
52
64
|
end
|
|
53
|
-
|
|
54
65
|
end
|
|
55
66
|
end
|
|
56
67
|
end
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
class SchemaDumper < ConnectionAdapters::SchemaDumper
|
|
5
|
-
|
|
6
7
|
SQLSEVER_NO_LIMIT_TYPES = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
"text",
|
|
9
|
+
"ntext",
|
|
10
|
+
"varchar(max)",
|
|
11
|
+
"nvarchar(max)",
|
|
12
|
+
"varbinary(max)"
|
|
12
13
|
].freeze
|
|
13
14
|
|
|
14
15
|
private
|
|
@@ -19,18 +20,19 @@ module ActiveRecord
|
|
|
19
20
|
|
|
20
21
|
def schema_limit(column)
|
|
21
22
|
return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
|
|
23
|
+
|
|
22
24
|
super
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def schema_collation(column)
|
|
26
28
|
return unless column.collation
|
|
29
|
+
|
|
27
30
|
column.collation if column.collation != @connection.collation
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def default_primary_key?(column)
|
|
31
34
|
super && column.is_primary? && column.is_identity?
|
|
32
35
|
end
|
|
33
|
-
|
|
34
36
|
end
|
|
35
37
|
end
|
|
36
38
|
end
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module SchemaStatements
|
|
5
|
-
|
|
6
7
|
def native_database_types
|
|
7
8
|
@native_database_types ||= initialize_native_database_types.freeze
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
def create_table(table_name,
|
|
11
|
+
def create_table(table_name, **options)
|
|
11
12
|
res = super
|
|
12
13
|
clear_cache!
|
|
13
14
|
res
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
def drop_table(table_name, options
|
|
17
|
+
def drop_table(table_name, **options)
|
|
17
18
|
# Mimic CASCADE option as best we can.
|
|
18
19
|
if options[:force] == :cascade
|
|
19
20
|
execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
|
|
20
|
-
fktable = fkdata[
|
|
21
|
-
fkcolmn = fkdata[
|
|
22
|
-
pktable = fkdata[
|
|
23
|
-
pkcolmn = fkdata[
|
|
24
|
-
remove_foreign_key fktable, name: fkdata[
|
|
21
|
+
fktable = fkdata["FKTABLE_NAME"]
|
|
22
|
+
fkcolmn = fkdata["FKCOLUMN_NAME"]
|
|
23
|
+
pktable = fkdata["PKTABLE_NAME"]
|
|
24
|
+
pkcolmn = fkdata["PKCOLUMN_NAME"]
|
|
25
|
+
remove_foreign_key fktable, name: fkdata["FK_NAME"]
|
|
25
26
|
do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -47,11 +48,11 @@ module ActiveRecord
|
|
|
47
48
|
orders = {}
|
|
48
49
|
columns = []
|
|
49
50
|
|
|
50
|
-
index[:index_keys].split(
|
|
51
|
+
index[:index_keys].split(",").each do |column|
|
|
51
52
|
column.strip!
|
|
52
53
|
|
|
53
|
-
if column.ends_with?(
|
|
54
|
-
column.gsub!
|
|
54
|
+
if column.ends_with?("(-)")
|
|
55
|
+
column.gsub! "(-)", ""
|
|
55
56
|
orders[column] = :desc
|
|
56
57
|
end
|
|
57
58
|
|
|
@@ -65,15 +66,15 @@ module ActiveRecord
|
|
|
65
66
|
|
|
66
67
|
def columns(table_name)
|
|
67
68
|
return [] if table_name.blank?
|
|
69
|
+
|
|
68
70
|
column_definitions(table_name).map do |ci|
|
|
69
|
-
sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity
|
|
71
|
+
sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :table_name
|
|
70
72
|
sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
|
|
71
73
|
new_column(
|
|
72
74
|
ci[:name],
|
|
73
75
|
ci[:default_value],
|
|
74
76
|
sql_type_metadata,
|
|
75
77
|
ci[:null],
|
|
76
|
-
ci[:table_name],
|
|
77
78
|
ci[:default_function],
|
|
78
79
|
ci[:collation],
|
|
79
80
|
nil,
|
|
@@ -82,16 +83,16 @@ module ActiveRecord
|
|
|
82
83
|
end
|
|
83
84
|
end
|
|
84
85
|
|
|
85
|
-
def new_column(name, default, sql_type_metadata, null,
|
|
86
|
+
def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
|
|
86
87
|
SQLServerColumn.new(
|
|
87
88
|
name,
|
|
88
89
|
default,
|
|
89
90
|
sql_type_metadata,
|
|
90
|
-
null,
|
|
91
|
+
null,
|
|
91
92
|
default_function,
|
|
92
|
-
collation,
|
|
93
|
-
comment,
|
|
94
|
-
sqlserver_options
|
|
93
|
+
collation: collation,
|
|
94
|
+
comment: comment,
|
|
95
|
+
**sqlserver_options
|
|
95
96
|
)
|
|
96
97
|
end
|
|
97
98
|
|
|
@@ -104,7 +105,7 @@ module ActiveRecord
|
|
|
104
105
|
identifier = database_prefix_identifier(table_name)
|
|
105
106
|
database = identifier.fully_qualified_database_quoted
|
|
106
107
|
sql = %{
|
|
107
|
-
SELECT KCU.COLUMN_NAME AS [name]
|
|
108
|
+
SELECT #{lowercase_schema_reflection_sql('KCU.COLUMN_NAME')} AS [name]
|
|
108
109
|
FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
|
109
110
|
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
|
110
111
|
ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
|
@@ -116,12 +117,12 @@ module ActiveRecord
|
|
|
116
117
|
AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
|
|
117
118
|
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
118
119
|
ORDER BY KCU.ORDINAL_POSITION ASC
|
|
119
|
-
}.gsub(/[[:space:]]/,
|
|
120
|
+
}.gsub(/[[:space:]]/, " ")
|
|
120
121
|
binds = []
|
|
121
122
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
|
122
|
-
binds << Relation::QueryAttribute.new(
|
|
123
|
-
binds << Relation::QueryAttribute.new(
|
|
124
|
-
sp_executesql(sql,
|
|
123
|
+
binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
|
|
124
|
+
binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
|
|
125
|
+
sp_executesql(sql, "SCHEMA", binds).map { |r| r["name"] }
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
def rename_table(table_name, new_name)
|
|
@@ -130,7 +131,8 @@ module ActiveRecord
|
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
def remove_column(table_name, column_name, type = nil, options = {})
|
|
133
|
-
raise ArgumentError.new(
|
|
134
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array
|
|
135
|
+
|
|
134
136
|
remove_check_constraints(table_name, column_name)
|
|
135
137
|
remove_default_constraint(table_name, column_name)
|
|
136
138
|
remove_indexes(table_name, column_name)
|
|
@@ -141,18 +143,24 @@ module ActiveRecord
|
|
|
141
143
|
sql_commands = []
|
|
142
144
|
indexes = []
|
|
143
145
|
column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
|
144
|
-
|
|
146
|
+
without_constraints = options.key?(:default) || options.key?(:limit)
|
|
147
|
+
default = if !options.key?(:default) && column_object
|
|
148
|
+
column_object.default
|
|
149
|
+
else
|
|
150
|
+
options[:default]
|
|
151
|
+
end
|
|
152
|
+
if without_constraints || (column_object && column_object.type != type.to_sym)
|
|
145
153
|
remove_default_constraint(table_name, column_name)
|
|
146
154
|
indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
|
|
147
155
|
remove_indexes(table_name, column_name)
|
|
148
156
|
end
|
|
149
157
|
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{
|
|
158
|
+
alter_command = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
|
|
159
|
+
alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
|
|
160
|
+
sql_commands << alter_command
|
|
161
|
+
if without_constraints
|
|
162
|
+
default = quote_default_expression(default, column_object || column_for(table_name, column_name))
|
|
163
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{default} FOR #{quote_column_name(column_name)}"
|
|
156
164
|
end
|
|
157
165
|
# Add any removed indexes back
|
|
158
166
|
indexes.each do |index|
|
|
@@ -166,6 +174,7 @@ module ActiveRecord
|
|
|
166
174
|
clear_cache!
|
|
167
175
|
column = column_for(table_name, column_name)
|
|
168
176
|
return unless column
|
|
177
|
+
|
|
169
178
|
remove_default_constraint(table_name, column_name)
|
|
170
179
|
default = extract_new_default_value(default_or_changes)
|
|
171
180
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
|
|
@@ -175,15 +184,16 @@ module ActiveRecord
|
|
|
175
184
|
def rename_column(table_name, column_name, new_column_name)
|
|
176
185
|
clear_cache!
|
|
177
186
|
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{column_name}")
|
|
178
|
-
execute_procedure :sp_rename, identifier.quoted, new_column_name,
|
|
187
|
+
execute_procedure :sp_rename, identifier.quoted, new_column_name, "COLUMN"
|
|
179
188
|
rename_column_indexes(table_name, column_name, new_column_name)
|
|
180
189
|
clear_cache!
|
|
181
190
|
end
|
|
182
191
|
|
|
183
192
|
def rename_index(table_name, old_name, new_name)
|
|
184
193
|
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
|
|
194
|
+
|
|
185
195
|
identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
|
|
186
|
-
execute_procedure :sp_rename, identifier.quoted, new_name,
|
|
196
|
+
execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
|
|
187
197
|
end
|
|
188
198
|
|
|
189
199
|
def remove_index!(table_name, index_name)
|
|
@@ -195,13 +205,13 @@ module ActiveRecord
|
|
|
195
205
|
fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
|
|
196
206
|
fk_info.map do |row|
|
|
197
207
|
from_table = identifier.object
|
|
198
|
-
to_table = row[
|
|
208
|
+
to_table = row["PKTABLE_NAME"]
|
|
199
209
|
options = {
|
|
200
|
-
name: row[
|
|
201
|
-
column: row[
|
|
202
|
-
primary_key: row[
|
|
203
|
-
on_update: extract_foreign_key_action(
|
|
204
|
-
on_delete: extract_foreign_key_action(
|
|
210
|
+
name: row["FK_NAME"],
|
|
211
|
+
column: row["FKCOLUMN_NAME"],
|
|
212
|
+
primary_key: row["PKCOLUMN_NAME"],
|
|
213
|
+
on_update: extract_foreign_key_action("update", row["FK_NAME"]),
|
|
214
|
+
on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
|
|
205
215
|
}
|
|
206
216
|
ForeignKeyDefinition.new from_table, to_table, options
|
|
207
217
|
end
|
|
@@ -209,8 +219,8 @@ module ActiveRecord
|
|
|
209
219
|
|
|
210
220
|
def extract_foreign_key_action(action, fk_name)
|
|
211
221
|
case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
|
|
212
|
-
when
|
|
213
|
-
when
|
|
222
|
+
when "CASCADE" then :cascade
|
|
223
|
+
when "SET_NULL" then :nullify
|
|
214
224
|
end
|
|
215
225
|
end
|
|
216
226
|
|
|
@@ -218,21 +228,21 @@ module ActiveRecord
|
|
|
218
228
|
type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
|
|
219
229
|
limit = nil unless type_limitable
|
|
220
230
|
case type.to_s
|
|
221
|
-
when
|
|
231
|
+
when "integer"
|
|
222
232
|
case limit
|
|
223
|
-
when 1 then
|
|
224
|
-
when 2 then
|
|
225
|
-
when 3..4, nil then
|
|
226
|
-
when 5..8 then
|
|
233
|
+
when 1 then "tinyint"
|
|
234
|
+
when 2 then "smallint"
|
|
235
|
+
when 3..4, nil then "integer"
|
|
236
|
+
when 5..8 then "bigint"
|
|
227
237
|
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
|
228
238
|
end
|
|
229
|
-
when
|
|
239
|
+
when "datetime2"
|
|
230
240
|
column_type_sql = super
|
|
231
241
|
if precision
|
|
232
242
|
if (0..7) === precision
|
|
233
243
|
column_type_sql << "(#{precision})"
|
|
234
244
|
else
|
|
235
|
-
raise(ActiveRecordError, "The
|
|
245
|
+
raise(ActiveRecordError, "The datetime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
|
|
236
246
|
end
|
|
237
247
|
end
|
|
238
248
|
column_type_sql
|
|
@@ -242,11 +252,11 @@ module ActiveRecord
|
|
|
242
252
|
end
|
|
243
253
|
|
|
244
254
|
def columns_for_distinct(columns, orders)
|
|
245
|
-
order_columns = orders.reject(&:blank?).map{ |s|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
|
256
|
+
s = s.to_sql unless s.is_a?(String)
|
|
257
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
|
258
|
+
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
|
|
259
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
|
250
260
|
|
|
251
261
|
(order_columns << super).join(", ")
|
|
252
262
|
end
|
|
@@ -263,7 +273,7 @@ module ActiveRecord
|
|
|
263
273
|
do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
|
|
264
274
|
end
|
|
265
275
|
sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
|
|
266
|
-
sql
|
|
276
|
+
sql += " NOT NULL" if !allow_null.nil? && allow_null == false
|
|
267
277
|
do_execute sql
|
|
268
278
|
end
|
|
269
279
|
|
|
@@ -275,21 +285,21 @@ module ActiveRecord
|
|
|
275
285
|
|
|
276
286
|
def data_source_sql(name = nil, type: nil)
|
|
277
287
|
scope = quoted_scope name, type: type
|
|
278
|
-
table_name = lowercase_schema_reflection_sql
|
|
288
|
+
table_name = lowercase_schema_reflection_sql "TABLE_NAME"
|
|
279
289
|
sql = "SELECT #{table_name}"
|
|
280
|
-
sql
|
|
281
|
-
sql
|
|
282
|
-
sql
|
|
283
|
-
sql
|
|
284
|
-
sql
|
|
285
|
-
sql
|
|
290
|
+
sql += " FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
|
|
291
|
+
sql += " WHERE TABLE_CATALOG = DB_NAME()"
|
|
292
|
+
sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
|
|
293
|
+
sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
|
|
294
|
+
sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
|
|
295
|
+
sql += " ORDER BY #{table_name}"
|
|
286
296
|
sql
|
|
287
297
|
end
|
|
288
298
|
|
|
289
299
|
def quoted_scope(name = nil, type: nil)
|
|
290
300
|
identifier = SQLServer::Utils.extract_identifiers(name)
|
|
291
301
|
{}.tap do |scope|
|
|
292
|
-
scope[:schema] = identifier.schema ||
|
|
302
|
+
scope[:schema] = identifier.schema || "dbo"
|
|
293
303
|
scope[:name] = identifier.object if identifier.object
|
|
294
304
|
scope[:type] = type if type
|
|
295
305
|
end
|
|
@@ -299,37 +309,37 @@ module ActiveRecord
|
|
|
299
309
|
|
|
300
310
|
def initialize_native_database_types
|
|
301
311
|
{
|
|
302
|
-
primary_key:
|
|
303
|
-
primary_key_nonclustered:
|
|
304
|
-
integer: { name:
|
|
305
|
-
bigint: { name:
|
|
306
|
-
boolean: { name:
|
|
307
|
-
decimal: { name:
|
|
308
|
-
money: { name:
|
|
309
|
-
smallmoney: { name:
|
|
310
|
-
float: { name:
|
|
311
|
-
real: { name:
|
|
312
|
-
date: { name:
|
|
313
|
-
datetime: { name:
|
|
314
|
-
datetime2: { name:
|
|
315
|
-
datetimeoffset: { name:
|
|
316
|
-
smalldatetime: { name:
|
|
317
|
-
timestamp: { name:
|
|
318
|
-
time: { name:
|
|
319
|
-
char: { name:
|
|
320
|
-
varchar: { name:
|
|
321
|
-
varchar_max: { name:
|
|
322
|
-
text_basic: { name:
|
|
323
|
-
nchar: { name:
|
|
324
|
-
string: { name:
|
|
325
|
-
text: { name:
|
|
326
|
-
ntext: { name:
|
|
327
|
-
binary_basic: { name:
|
|
328
|
-
varbinary: { name:
|
|
329
|
-
binary: { name:
|
|
330
|
-
uuid: { name:
|
|
331
|
-
ss_timestamp: { name:
|
|
332
|
-
json: { name:
|
|
312
|
+
primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
|
313
|
+
primary_key_nonclustered: "int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
|
|
314
|
+
integer: { name: "int", limit: 4 },
|
|
315
|
+
bigint: { name: "bigint" },
|
|
316
|
+
boolean: { name: "bit" },
|
|
317
|
+
decimal: { name: "decimal" },
|
|
318
|
+
money: { name: "money" },
|
|
319
|
+
smallmoney: { name: "smallmoney" },
|
|
320
|
+
float: { name: "float" },
|
|
321
|
+
real: { name: "real" },
|
|
322
|
+
date: { name: "date" },
|
|
323
|
+
datetime: { name: "datetime" },
|
|
324
|
+
datetime2: { name: "datetime2" },
|
|
325
|
+
datetimeoffset: { name: "datetimeoffset" },
|
|
326
|
+
smalldatetime: { name: "smalldatetime" },
|
|
327
|
+
timestamp: { name: "datetime" },
|
|
328
|
+
time: { name: "time" },
|
|
329
|
+
char: { name: "char" },
|
|
330
|
+
varchar: { name: "varchar", limit: 8000 },
|
|
331
|
+
varchar_max: { name: "varchar(max)" },
|
|
332
|
+
text_basic: { name: "text" },
|
|
333
|
+
nchar: { name: "nchar" },
|
|
334
|
+
string: { name: "nvarchar", limit: 4000 },
|
|
335
|
+
text: { name: "nvarchar(max)" },
|
|
336
|
+
ntext: { name: "ntext" },
|
|
337
|
+
binary_basic: { name: "binary" },
|
|
338
|
+
varbinary: { name: "varbinary", limit: 8000 },
|
|
339
|
+
binary: { name: "varbinary(max)" },
|
|
340
|
+
uuid: { name: "uniqueidentifier" },
|
|
341
|
+
ss_timestamp: { name: "timestamp" },
|
|
342
|
+
json: { name: "nvarchar(max)" }
|
|
333
343
|
}
|
|
334
344
|
end
|
|
335
345
|
|
|
@@ -338,60 +348,14 @@ module ActiveRecord
|
|
|
338
348
|
database = identifier.fully_qualified_database_quoted
|
|
339
349
|
view_exists = view_exists?(table_name)
|
|
340
350
|
view_tblnm = view_table_name(table_name) if view_exists
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
#{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
|
|
345
|
-
columns.DATA_TYPE AS type,
|
|
346
|
-
columns.COLUMN_DEFAULT AS default_value,
|
|
347
|
-
columns.NUMERIC_SCALE AS numeric_scale,
|
|
348
|
-
columns.NUMERIC_PRECISION AS numeric_precision,
|
|
349
|
-
columns.DATETIME_PRECISION AS datetime_precision,
|
|
350
|
-
columns.COLLATION_NAME AS [collation],
|
|
351
|
-
columns.ordinal_position,
|
|
352
|
-
CASE
|
|
353
|
-
WHEN columns.DATA_TYPE IN ('nchar','nvarchar','char','varchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
|
354
|
-
ELSE COL_LENGTH('#{database}.'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
|
355
|
-
END AS [length],
|
|
356
|
-
CASE
|
|
357
|
-
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
|
358
|
-
ELSE NULL
|
|
359
|
-
END AS [is_nullable],
|
|
360
|
-
CASE
|
|
361
|
-
WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
|
|
362
|
-
ELSE NULL
|
|
363
|
-
END AS [is_primary],
|
|
364
|
-
c.is_identity AS [is_identity]
|
|
365
|
-
FROM #{database}.INFORMATION_SCHEMA.COLUMNS columns
|
|
366
|
-
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
|
367
|
-
ON TC.TABLE_NAME = columns.TABLE_NAME
|
|
368
|
-
AND TC.TABLE_SCHEMA = columns.TABLE_SCHEMA
|
|
369
|
-
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
|
370
|
-
LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
|
371
|
-
ON KCU.COLUMN_NAME = columns.COLUMN_NAME
|
|
372
|
-
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
|
373
|
-
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
|
374
|
-
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
|
375
|
-
INNER JOIN #{database}.sys.schemas AS s
|
|
376
|
-
ON s.name = columns.TABLE_SCHEMA
|
|
377
|
-
AND s.schema_id = s.schema_id
|
|
378
|
-
INNER JOIN #{database}.sys.objects AS o
|
|
379
|
-
ON s.schema_id = o.schema_id
|
|
380
|
-
AND o.is_ms_shipped = 0
|
|
381
|
-
AND o.type IN ('U', 'V')
|
|
382
|
-
AND o.name = columns.TABLE_NAME
|
|
383
|
-
INNER JOIN #{database}.sys.columns AS c
|
|
384
|
-
ON o.object_id = c.object_id
|
|
385
|
-
AND c.name = columns.COLUMN_NAME
|
|
386
|
-
WHERE columns.TABLE_NAME = #{prepared_statements ? '@0' : quote(identifier.object)}
|
|
387
|
-
AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? '@1' : quote(identifier.schema))}
|
|
388
|
-
ORDER BY columns.ordinal_position
|
|
389
|
-
}.gsub(/[ \t\r\n]+/, ' ').strip
|
|
351
|
+
|
|
352
|
+
sql = column_definitions_sql(database, identifier)
|
|
353
|
+
|
|
390
354
|
binds = []
|
|
391
355
|
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
|
|
392
|
-
binds << Relation::QueryAttribute.new(
|
|
393
|
-
binds << Relation::QueryAttribute.new(
|
|
394
|
-
results = sp_executesql(sql,
|
|
356
|
+
binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
|
|
357
|
+
binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
|
|
358
|
+
results = sp_executesql(sql, "SCHEMA", binds)
|
|
395
359
|
results.map do |ci|
|
|
396
360
|
ci = ci.symbolize_keys
|
|
397
361
|
ci[:_type] = ci[:type]
|
|
@@ -422,7 +386,7 @@ module ActiveRecord
|
|
|
422
386
|
WHERE
|
|
423
387
|
c.TABLE_NAME = '#{view_tblnm}'
|
|
424
388
|
AND c.COLUMN_NAME = '#{views_real_column_name(table_name, ci[:name])}'
|
|
425
|
-
}.squish,
|
|
389
|
+
}.squish, "SCHEMA"
|
|
426
390
|
end
|
|
427
391
|
case default
|
|
428
392
|
when nil
|
|
@@ -441,7 +405,7 @@ module ActiveRecord
|
|
|
441
405
|
else ci[:type]
|
|
442
406
|
end
|
|
443
407
|
value = default.match(/\A\((.*)\)\Z/m)[1]
|
|
444
|
-
value = select_value("SELECT CAST(#{value} AS #{type}) AS value",
|
|
408
|
+
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
|
|
445
409
|
[value, nil]
|
|
446
410
|
end
|
|
447
411
|
end
|
|
@@ -453,8 +417,76 @@ module ActiveRecord
|
|
|
453
417
|
end
|
|
454
418
|
end
|
|
455
419
|
|
|
420
|
+
def column_definitions_sql(database, identifier)
|
|
421
|
+
object_name = prepared_statements ? "@0" : quote(identifier.object)
|
|
422
|
+
schema_name = if identifier.schema.blank?
|
|
423
|
+
"schema_name()"
|
|
424
|
+
else
|
|
425
|
+
prepared_statements ? "@1" : quote(identifier.schema)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
%{
|
|
429
|
+
SELECT
|
|
430
|
+
#{lowercase_schema_reflection_sql('o.name')} AS [table_name],
|
|
431
|
+
#{lowercase_schema_reflection_sql('c.name')} AS [name],
|
|
432
|
+
t.name AS [type],
|
|
433
|
+
d.definition AS [default_value],
|
|
434
|
+
CASE
|
|
435
|
+
WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint')
|
|
436
|
+
THEN c.scale
|
|
437
|
+
END AS [numeric_scale],
|
|
438
|
+
CASE
|
|
439
|
+
WHEN t.name IN ('decimal', 'bigint', 'int', 'money', 'numeric', 'smallint', 'smallmoney', 'tinyint', 'real', 'float')
|
|
440
|
+
THEN c.precision
|
|
441
|
+
END AS [numeric_precision],
|
|
442
|
+
CASE
|
|
443
|
+
WHEN t.name IN ('date', 'datetime', 'datetime2', 'datetimeoffset', 'smalldatetime', 'time')
|
|
444
|
+
THEN c.scale
|
|
445
|
+
END AS [datetime_precision],
|
|
446
|
+
c.collation_name AS [collation],
|
|
447
|
+
ROW_NUMBER() OVER (ORDER BY c.column_id) AS [ordinal_position],
|
|
448
|
+
CASE
|
|
449
|
+
WHEN t.name IN ('nchar', 'nvarchar') AND c.max_length > 0
|
|
450
|
+
THEN c.max_length / 2
|
|
451
|
+
ELSE c.max_length
|
|
452
|
+
END AS [length],
|
|
453
|
+
CASE c.is_nullable
|
|
454
|
+
WHEN 1
|
|
455
|
+
THEN 1
|
|
456
|
+
END AS [is_nullable],
|
|
457
|
+
CASE
|
|
458
|
+
WHEN ic.object_id IS NOT NULL
|
|
459
|
+
THEN 1
|
|
460
|
+
END AS [is_primary],
|
|
461
|
+
c.is_identity AS [is_identity]
|
|
462
|
+
FROM #{database}.sys.columns c
|
|
463
|
+
INNER JOIN #{database}.sys.objects o
|
|
464
|
+
ON c.object_id = o.object_id
|
|
465
|
+
INNER JOIN #{database}.sys.schemas s
|
|
466
|
+
ON o.schema_id = s.schema_id
|
|
467
|
+
INNER JOIN #{database}.sys.types t
|
|
468
|
+
ON c.system_type_id = t.system_type_id
|
|
469
|
+
AND c.user_type_id = t.user_type_id
|
|
470
|
+
LEFT OUTER JOIN #{database}.sys.default_constraints d
|
|
471
|
+
ON c.object_id = d.parent_object_id
|
|
472
|
+
AND c.default_object_id = d.object_id
|
|
473
|
+
LEFT OUTER JOIN #{database}.sys.key_constraints k
|
|
474
|
+
ON c.object_id = k.parent_object_id
|
|
475
|
+
AND k.type = 'PK'
|
|
476
|
+
LEFT OUTER JOIN #{database}.sys.index_columns ic
|
|
477
|
+
ON k.parent_object_id = ic.object_id
|
|
478
|
+
AND k.unique_index_id = ic.index_id
|
|
479
|
+
AND c.column_id = ic.column_id
|
|
480
|
+
WHERE
|
|
481
|
+
o.name = #{object_name}
|
|
482
|
+
AND s.name = #{schema_name}
|
|
483
|
+
ORDER BY
|
|
484
|
+
c.column_id
|
|
485
|
+
}.gsub(/[ \t\r\n]+/, " ").strip
|
|
486
|
+
end
|
|
487
|
+
|
|
456
488
|
def remove_check_constraints(table_name, column_name)
|
|
457
|
-
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)}'",
|
|
489
|
+
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"
|
|
458
490
|
constraints.each do |constraint|
|
|
459
491
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
|
460
492
|
end
|
|
@@ -462,8 +494,8 @@ module ActiveRecord
|
|
|
462
494
|
|
|
463
495
|
def remove_default_constraint(table_name, column_name)
|
|
464
496
|
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
|
465
|
-
execute_procedure(:sp_helpconstraint, table_name,
|
|
466
|
-
row[
|
|
497
|
+
execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
|
|
498
|
+
row["constraint_type"] == "DEFAULT on column #{column_name}"
|
|
467
499
|
end.each do |row|
|
|
468
500
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
|
469
501
|
end
|
|
@@ -479,12 +511,12 @@ module ActiveRecord
|
|
|
479
511
|
|
|
480
512
|
def get_table_name(sql)
|
|
481
513
|
tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
514
|
+
Regexp.last_match[3] || Regexp.last_match[4]
|
|
515
|
+
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
|
516
|
+
Regexp.last_match[1]
|
|
517
|
+
else
|
|
518
|
+
nil
|
|
519
|
+
end
|
|
488
520
|
SQLServer::Utils.extract_identifiers(tn).object
|
|
489
521
|
end
|
|
490
522
|
|
|
@@ -500,22 +532,22 @@ module ActiveRecord
|
|
|
500
532
|
|
|
501
533
|
def view_table_name(table_name)
|
|
502
534
|
view_info = view_information(table_name)
|
|
503
|
-
view_info ? get_table_name(view_info[
|
|
535
|
+
view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
|
|
504
536
|
end
|
|
505
537
|
|
|
506
538
|
def view_information(table_name)
|
|
507
539
|
@view_information ||= {}
|
|
508
540
|
@view_information[table_name] ||= begin
|
|
509
541
|
identifier = SQLServer::Utils.extract_identifiers(table_name)
|
|
510
|
-
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}",
|
|
542
|
+
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
|
|
511
543
|
if view_info
|
|
512
544
|
view_info = view_info.with_indifferent_access
|
|
513
545
|
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
|
514
546
|
view_info[:VIEW_DEFINITION] = begin
|
|
515
|
-
select_values("EXEC sp_helptext #{identifier.object_quoted}",
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
547
|
+
select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
|
|
548
|
+
rescue
|
|
549
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
|
550
|
+
nil
|
|
519
551
|
end
|
|
520
552
|
end
|
|
521
553
|
end
|
|
@@ -526,14 +558,14 @@ module ActiveRecord
|
|
|
526
558
|
def views_real_column_name(table_name, column_name)
|
|
527
559
|
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
|
528
560
|
return column_name unless view_definition
|
|
561
|
+
|
|
529
562
|
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
|
530
563
|
match_data ? match_data[1] : column_name
|
|
531
564
|
end
|
|
532
565
|
|
|
533
|
-
def create_table_definition(*args)
|
|
534
|
-
SQLServer::TableDefinition.new(*args)
|
|
566
|
+
def create_table_definition(*args, **options)
|
|
567
|
+
SQLServer::TableDefinition.new(self, *args, **options)
|
|
535
568
|
end
|
|
536
|
-
|
|
537
569
|
end
|
|
538
570
|
end
|
|
539
571
|
end
|