activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +17 -27
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +49 -41
- data/Guardfile +9 -8
- data/MIT-LICENSE +1 -1
- data/README.md +65 -42
- 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 +22 -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 +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
- 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 +10 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +51 -14
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
- 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/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
- 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 +5 -3
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
- 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/decimal_without_scale.rb +22 -0
- 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/type.rb +38 -35
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
- data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
- data/lib/active_record/sqlserver_base.rb +11 -9
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +177 -56
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/active_schema_test_sqlserver.rb +55 -0
- data/test/cases/adapter_test_sqlserver.rb +258 -173
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +1421 -397
- data/test/cases/column_test_sqlserver.rb +321 -315
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
- data/test/cases/fetch_test_sqlserver.rb +33 -21
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +15 -15
- data/test/cases/in_clause_test_sqlserver.rb +63 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +74 -27
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +59 -53
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +70 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +124 -109
- 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 +28 -35
- data/test/cases/specific_schema_test_sqlserver.rb +68 -65
- data/test/cases/transaction_test_sqlserver.rb +18 -20
- data/test/cases/trigger_test_sqlserver.rb +14 -13
- data/test/cases/utils_test_sqlserver.rb +70 -70
- data/test/cases/uuid_test_sqlserver.rb +13 -14
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- 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/composite_pk.rb +9 -0
- 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/sst_string_collation.rb +3 -0
- 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/sqlserver_specific_schema.rb +56 -21
- data/test/support/coerceable_test_sqlserver.rb +19 -13
- 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/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +15 -10
- data/test/support/sql_counter_sqlserver.rb +16 -15
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +47 -13
- data/.travis.yml +0 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module DatabaseStatements
|
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback, :waitfor) # :nodoc:
|
|
8
|
+
private_constant :READ_QUERY
|
|
9
|
+
|
|
10
|
+
def write_query?(sql) # :nodoc:
|
|
11
|
+
!READ_QUERY.match?(sql)
|
|
12
|
+
rescue ArgumentError # Invalid encoding
|
|
13
|
+
!READ_QUERY.match?(sql.b)
|
|
14
|
+
end
|
|
5
15
|
|
|
6
16
|
def execute(sql, name = nil)
|
|
17
|
+
sql = transform_query(sql)
|
|
18
|
+
if preventing_writes? && write_query?(sql)
|
|
19
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
materialize_transactions
|
|
23
|
+
mark_transaction_written_if_write(sql)
|
|
24
|
+
|
|
7
25
|
if id_insert_table_name = query_requires_identity_insert?(sql)
|
|
8
26
|
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
|
|
9
27
|
else
|
|
@@ -11,8 +29,16 @@ module ActiveRecord
|
|
|
11
29
|
end
|
|
12
30
|
end
|
|
13
31
|
|
|
14
|
-
def exec_query(sql, name =
|
|
15
|
-
|
|
32
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
|
|
33
|
+
sql = transform_query(sql)
|
|
34
|
+
if preventing_writes? && write_query?(sql)
|
|
35
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
materialize_transactions
|
|
39
|
+
mark_transaction_written_if_write(sql)
|
|
40
|
+
|
|
41
|
+
sp_executesql(sql, name, binds, prepare: prepare, async: async)
|
|
16
42
|
end
|
|
17
43
|
|
|
18
44
|
def exec_insert(sql, name = nil, binds = [], pk = nil, _sequence_name = nil)
|
|
@@ -24,17 +50,17 @@ module ActiveRecord
|
|
|
24
50
|
end
|
|
25
51
|
|
|
26
52
|
def exec_delete(sql, name, binds)
|
|
27
|
-
sql = sql.dup <<
|
|
53
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
28
54
|
super(sql, name, binds).rows.first.first
|
|
29
55
|
end
|
|
30
56
|
|
|
31
57
|
def exec_update(sql, name, binds)
|
|
32
|
-
sql = sql.dup <<
|
|
58
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
33
59
|
super(sql, name, binds).rows.first.first
|
|
34
60
|
end
|
|
35
61
|
|
|
36
62
|
def begin_db_transaction
|
|
37
|
-
do_execute
|
|
63
|
+
do_execute "BEGIN TRANSACTION", "TRANSACTION"
|
|
38
64
|
end
|
|
39
65
|
|
|
40
66
|
def transaction_isolation_levels
|
|
@@ -47,33 +73,35 @@ module ActiveRecord
|
|
|
47
73
|
end
|
|
48
74
|
|
|
49
75
|
def set_transaction_isolation_level(isolation_level)
|
|
50
|
-
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
|
|
76
|
+
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}", "TRANSACTION"
|
|
51
77
|
end
|
|
52
78
|
|
|
53
79
|
def commit_db_transaction
|
|
54
|
-
do_execute
|
|
80
|
+
do_execute "COMMIT TRANSACTION", "TRANSACTION"
|
|
55
81
|
end
|
|
56
82
|
|
|
57
83
|
def exec_rollback_db_transaction
|
|
58
|
-
do_execute
|
|
84
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION", "TRANSACTION"
|
|
59
85
|
end
|
|
60
86
|
|
|
61
87
|
include Savepoints
|
|
62
88
|
|
|
63
89
|
def create_savepoint(name = current_savepoint_name)
|
|
64
|
-
do_execute "SAVE TRANSACTION #{name}"
|
|
90
|
+
do_execute "SAVE TRANSACTION #{name}", "TRANSACTION"
|
|
65
91
|
end
|
|
66
92
|
|
|
67
93
|
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
|
68
|
-
do_execute "ROLLBACK TRANSACTION #{name}"
|
|
94
|
+
do_execute "ROLLBACK TRANSACTION #{name}", "TRANSACTION"
|
|
69
95
|
end
|
|
70
96
|
|
|
71
97
|
def release_savepoint(name = current_savepoint_name)
|
|
72
98
|
end
|
|
73
99
|
|
|
74
|
-
def case_sensitive_comparison(
|
|
100
|
+
def case_sensitive_comparison(attribute, value)
|
|
101
|
+
column = column_for_attribute(attribute)
|
|
102
|
+
|
|
75
103
|
if column.collation && !column.case_sensitive?
|
|
76
|
-
|
|
104
|
+
attribute.eq(Arel::Nodes::Bin.new(value))
|
|
77
105
|
else
|
|
78
106
|
super
|
|
79
107
|
end
|
|
@@ -89,12 +117,12 @@ module ActiveRecord
|
|
|
89
117
|
end
|
|
90
118
|
end
|
|
91
119
|
|
|
92
|
-
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}"
|
|
93
|
-
|
|
120
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}" }
|
|
121
|
+
total_sqls = Array.wrap(table_deletes + fixture_inserts)
|
|
94
122
|
|
|
95
123
|
disable_referential_integrity do
|
|
96
124
|
transaction(requires_new: true) do
|
|
97
|
-
|
|
125
|
+
total_sqls.each do |sql|
|
|
98
126
|
execute sql, "Fixtures Load"
|
|
99
127
|
yield if block_given?
|
|
100
128
|
end
|
|
@@ -107,11 +135,6 @@ module ActiveRecord
|
|
|
107
135
|
end
|
|
108
136
|
private :can_perform_case_insensitive_comparison_for?
|
|
109
137
|
|
|
110
|
-
def combine_multi_statements(total_sql)
|
|
111
|
-
total_sql
|
|
112
|
-
end
|
|
113
|
-
private :combine_multi_statements
|
|
114
|
-
|
|
115
138
|
def default_insert_value(column)
|
|
116
139
|
if column.is_identity?
|
|
117
140
|
table_name = quote(quote_table_name(column.table_name))
|
|
@@ -122,21 +145,39 @@ module ActiveRecord
|
|
|
122
145
|
end
|
|
123
146
|
private :default_insert_value
|
|
124
147
|
|
|
148
|
+
def build_insert_sql(insert) # :nodoc:
|
|
149
|
+
sql = +"INSERT #{insert.into}"
|
|
150
|
+
|
|
151
|
+
if returning = insert.send(:insert_all).returning
|
|
152
|
+
returning_sql = if returning.is_a?(String)
|
|
153
|
+
returning
|
|
154
|
+
else
|
|
155
|
+
returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
|
|
156
|
+
end
|
|
157
|
+
sql << " OUTPUT #{returning_sql}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
sql << " #{insert.values_list}"
|
|
161
|
+
sql
|
|
162
|
+
end
|
|
163
|
+
|
|
125
164
|
# === SQLServer Specific ======================================== #
|
|
126
165
|
|
|
127
166
|
def execute_procedure(proc_name, *variables)
|
|
167
|
+
materialize_transactions
|
|
168
|
+
|
|
128
169
|
vars = if variables.any? && variables.first.is_a?(Hash)
|
|
129
170
|
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
|
130
171
|
else
|
|
131
172
|
variables.map { |v| quote(v) }
|
|
132
|
-
end.join(
|
|
173
|
+
end.join(", ")
|
|
133
174
|
sql = "EXEC #{proc_name} #{vars}".strip
|
|
134
|
-
name =
|
|
175
|
+
name = "Execute Procedure"
|
|
135
176
|
log(sql, name) do
|
|
136
177
|
case @connection_options[:mode]
|
|
137
178
|
when :dblib
|
|
138
|
-
result =
|
|
139
|
-
options = { as: :hash, cache_rows: true, timezone: ActiveRecord
|
|
179
|
+
result = ensure_established_connection! { dblib_execute(sql) }
|
|
180
|
+
options = { as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc }
|
|
140
181
|
result.each(options) do |row|
|
|
141
182
|
r = row.with_indifferent_access
|
|
142
183
|
yield(r) if block_given?
|
|
@@ -156,20 +197,22 @@ module ActiveRecord
|
|
|
156
197
|
|
|
157
198
|
def use_database(database = nil)
|
|
158
199
|
return if sqlserver_azure?
|
|
200
|
+
|
|
159
201
|
name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
|
|
160
202
|
do_execute "USE #{name}" unless name.blank?
|
|
161
203
|
end
|
|
162
204
|
|
|
163
205
|
def user_options
|
|
164
206
|
return {} if sqlserver_azure?
|
|
165
|
-
|
|
207
|
+
|
|
208
|
+
rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
|
|
166
209
|
rows = rows.first if rows.size == 2 && rows.last.empty?
|
|
167
210
|
rows.reduce(HashWithIndifferentAccess.new) do |values, row|
|
|
168
211
|
if row.instance_of? Hash
|
|
169
|
-
set_option = row.values[0].gsub(/\s+/,
|
|
212
|
+
set_option = row.values[0].gsub(/\s+/, "_")
|
|
170
213
|
user_value = row.values[1]
|
|
171
|
-
elsif
|
|
172
|
-
set_option = row[0].gsub(/\s+/,
|
|
214
|
+
elsif row.instance_of? Array
|
|
215
|
+
set_option = row[0].gsub(/\s+/, "_")
|
|
173
216
|
user_value = row[1]
|
|
174
217
|
end
|
|
175
218
|
values[set_option] = user_value
|
|
@@ -179,9 +222,9 @@ module ActiveRecord
|
|
|
179
222
|
|
|
180
223
|
def user_options_dateformat
|
|
181
224
|
if sqlserver_azure?
|
|
182
|
-
select_value
|
|
225
|
+
select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
|
|
183
226
|
else
|
|
184
|
-
user_options[
|
|
227
|
+
user_options["dateformat"]
|
|
185
228
|
end
|
|
186
229
|
end
|
|
187
230
|
|
|
@@ -196,43 +239,44 @@ module ActiveRecord
|
|
|
196
239
|
WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
|
|
197
240
|
FROM [sys].[dm_exec_sessions]
|
|
198
241
|
WHERE [session_id] = @@SPID).squish
|
|
199
|
-
select_value sql,
|
|
242
|
+
select_value sql, "SCHEMA"
|
|
200
243
|
else
|
|
201
|
-
user_options[
|
|
244
|
+
user_options["isolation_level"]
|
|
202
245
|
end
|
|
203
246
|
end
|
|
204
247
|
|
|
205
248
|
def user_options_language
|
|
206
249
|
if sqlserver_azure?
|
|
207
|
-
select_value
|
|
250
|
+
select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
|
|
208
251
|
else
|
|
209
|
-
user_options[
|
|
252
|
+
user_options["language"]
|
|
210
253
|
end
|
|
211
254
|
end
|
|
212
255
|
|
|
213
256
|
def newid_function
|
|
214
|
-
select_value
|
|
257
|
+
select_value "SELECT NEWID()"
|
|
215
258
|
end
|
|
216
259
|
|
|
217
260
|
def newsequentialid_function
|
|
218
|
-
select_value
|
|
261
|
+
select_value "SELECT NEWSEQUENTIALID()"
|
|
219
262
|
end
|
|
220
263
|
|
|
221
|
-
|
|
222
264
|
protected
|
|
223
265
|
|
|
224
|
-
def sql_for_insert(sql, pk,
|
|
266
|
+
def sql_for_insert(sql, pk, binds)
|
|
225
267
|
if pk.nil?
|
|
226
268
|
table_name = query_requires_identity_insert?(sql)
|
|
227
269
|
pk = primary_key(table_name)
|
|
228
270
|
end
|
|
271
|
+
|
|
229
272
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
|
230
273
|
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
|
231
274
|
table_name ||= get_table_name(sql)
|
|
232
275
|
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
|
276
|
+
|
|
233
277
|
if exclude_output_inserted
|
|
234
|
-
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ?
|
|
235
|
-
|
|
278
|
+
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
|
|
279
|
+
<<~SQL.squish
|
|
236
280
|
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
|
237
281
|
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
|
|
238
282
|
SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
|
|
@@ -256,7 +300,10 @@ module ActiveRecord
|
|
|
256
300
|
|
|
257
301
|
# === SQLServer Specific (Executing) ============================ #
|
|
258
302
|
|
|
259
|
-
def do_execute(sql, name =
|
|
303
|
+
def do_execute(sql, name = "SQL")
|
|
304
|
+
materialize_transactions
|
|
305
|
+
mark_transaction_written_if_write(sql)
|
|
306
|
+
|
|
260
307
|
log(sql, name) { raw_connection_do(sql) }
|
|
261
308
|
end
|
|
262
309
|
|
|
@@ -282,11 +329,12 @@ module ActiveRecord
|
|
|
282
329
|
|
|
283
330
|
def sp_executesql_sql_type(attr)
|
|
284
331
|
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
|
|
332
|
+
|
|
285
333
|
case value = attr.value_for_database
|
|
286
334
|
when Numeric
|
|
287
|
-
value > 2_147_483_647 ?
|
|
335
|
+
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
|
|
288
336
|
else
|
|
289
|
-
|
|
337
|
+
"nvarchar(max)".freeze
|
|
290
338
|
end
|
|
291
339
|
end
|
|
292
340
|
|
|
@@ -301,16 +349,16 @@ module ActiveRecord
|
|
|
301
349
|
end
|
|
302
350
|
|
|
303
351
|
def sp_executesql_sql(sql, types, params, name)
|
|
304
|
-
if name ==
|
|
352
|
+
if name == "EXPLAIN"
|
|
305
353
|
params.each.with_index do |param, index|
|
|
306
354
|
substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
|
|
307
355
|
sql = sql.sub substitute_at_finder, param.to_s
|
|
308
356
|
end
|
|
309
357
|
else
|
|
310
|
-
types = quote(types.join(
|
|
311
|
-
params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(
|
|
358
|
+
types = quote(types.join(", "))
|
|
359
|
+
params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
|
|
312
360
|
sql = "EXEC sp_executesql #{quote(sql)}"
|
|
313
|
-
sql
|
|
361
|
+
sql += ", #{types}, #{params}" unless params.empty?
|
|
314
362
|
end
|
|
315
363
|
sql
|
|
316
364
|
end
|
|
@@ -318,7 +366,8 @@ module ActiveRecord
|
|
|
318
366
|
def raw_connection_do(sql)
|
|
319
367
|
case @connection_options[:mode]
|
|
320
368
|
when :dblib
|
|
321
|
-
|
|
369
|
+
result = ensure_established_connection! { dblib_execute(sql) }
|
|
370
|
+
result.do
|
|
322
371
|
end
|
|
323
372
|
ensure
|
|
324
373
|
@update_sql = false
|
|
@@ -336,8 +385,10 @@ module ActiveRecord
|
|
|
336
385
|
|
|
337
386
|
def exclude_output_inserted_table_name?(table_name, sql)
|
|
338
387
|
return false unless exclude_output_inserted_table_names?
|
|
388
|
+
|
|
339
389
|
table_name ||= get_table_name(sql)
|
|
340
390
|
return false unless table_name
|
|
391
|
+
|
|
341
392
|
self.class.exclude_output_inserted_table_names[table_name]
|
|
342
393
|
end
|
|
343
394
|
|
|
@@ -366,8 +417,8 @@ module ActiveRecord
|
|
|
366
417
|
|
|
367
418
|
# === SQLServer Specific (Selecting) ============================ #
|
|
368
419
|
|
|
369
|
-
def raw_select(sql, name =
|
|
370
|
-
log(sql, name, binds) { _raw_select(sql, options) }
|
|
420
|
+
def raw_select(sql, name = "SQL", binds = [], options = {})
|
|
421
|
+
log(sql, name, binds, async: options[:async]) { _raw_select(sql, options) }
|
|
371
422
|
end
|
|
372
423
|
|
|
373
424
|
def _raw_select(sql, options = {})
|
|
@@ -380,7 +431,7 @@ module ActiveRecord
|
|
|
380
431
|
def raw_connection_run(sql)
|
|
381
432
|
case @connection_options[:mode]
|
|
382
433
|
when :dblib
|
|
383
|
-
|
|
434
|
+
ensure_established_connection! { dblib_execute(sql) }
|
|
384
435
|
end
|
|
385
436
|
end
|
|
386
437
|
|
|
@@ -399,7 +450,7 @@ module ActiveRecord
|
|
|
399
450
|
|
|
400
451
|
def handle_to_names_and_values_dblib(handle, options = {})
|
|
401
452
|
query_options = {}.tap do |qo|
|
|
402
|
-
qo[:timezone] = ActiveRecord
|
|
453
|
+
qo[:timezone] = ActiveRecord.default_timezone || :utc
|
|
403
454
|
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
|
|
404
455
|
end
|
|
405
456
|
results = handle.each(query_options)
|
|
@@ -415,6 +466,20 @@ module ActiveRecord
|
|
|
415
466
|
handle
|
|
416
467
|
end
|
|
417
468
|
|
|
469
|
+
def dblib_execute(sql)
|
|
470
|
+
@connection.execute(sql).tap do |result|
|
|
471
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
472
|
+
# Getting around this by raising an exception ourselves while this PR
|
|
473
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
474
|
+
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def ensure_established_connection!
|
|
479
|
+
raise TinyTds::Error, 'SQL Server client is not connected' unless @connection
|
|
480
|
+
|
|
481
|
+
yield
|
|
482
|
+
end
|
|
418
483
|
end
|
|
419
484
|
end
|
|
420
485
|
end
|
|
@@ -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)
|
|
@@ -17,7 +18,7 @@ module ActiveRecord
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def current_database
|
|
20
|
-
select_value
|
|
21
|
+
select_value "SELECT DB_NAME()"
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def charset
|
|
@@ -30,20 +31,20 @@ module ActiveRecord
|
|
|
30
31
|
|
|
31
32
|
private
|
|
32
33
|
|
|
33
|
-
def create_database_options(options={})
|
|
34
|
+
def create_database_options(options = {})
|
|
34
35
|
keys = [:collate]
|
|
35
36
|
copts = @connection_options
|
|
36
37
|
options = {
|
|
37
38
|
collate: copts[:collation]
|
|
38
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
39
40
|
v.present?
|
|
40
|
-
}.slice(*keys).map { |k,v|
|
|
41
|
+
}.slice(*keys).map { |k, v|
|
|
41
42
|
"#{k.to_s.upcase} #{v}"
|
|
42
|
-
}.join(
|
|
43
|
+
}.join(" ")
|
|
43
44
|
options
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def create_database_edition_options(options={})
|
|
47
|
+
def create_database_edition_options(options = {})
|
|
47
48
|
keys = [:maxsize, :edition, :service_objective]
|
|
48
49
|
copts = @connection_options
|
|
49
50
|
edition_options = {
|
|
@@ -52,17 +53,13 @@ module ActiveRecord
|
|
|
52
53
|
service_objective: copts[:azure_service_objective]
|
|
53
54
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
54
55
|
v.present?
|
|
55
|
-
}.slice(*keys).map { |k,v|
|
|
56
|
+
}.slice(*keys).map { |k, v|
|
|
56
57
|
"#{k.to_s.upcase} = #{v}"
|
|
57
|
-
}.join(
|
|
58
|
+
}.join(", ")
|
|
58
59
|
edition_options = "( #{edition_options} )" if edition_options.present?
|
|
59
60
|
edition_options
|
|
60
61
|
end
|
|
61
|
-
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
@@ -1,22 +1,24 @@
|
|
|
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)
|
|
12
|
-
|
|
13
|
+
simple_type = SqlTypeMetadata.new(
|
|
13
14
|
sql_type: sql_type,
|
|
14
15
|
type: cast_type.type,
|
|
15
16
|
limit: cast_type.limit,
|
|
16
17
|
precision: cast_type.precision,
|
|
17
|
-
scale: cast_type.scale
|
|
18
|
-
sqlserver_options: sqlserver_options
|
|
18
|
+
scale: cast_type.scale
|
|
19
19
|
)
|
|
20
|
+
|
|
21
|
+
SQLServer::TypeMetadata.new(simple_type, **sqlserver_options)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def quote_string(s)
|
|
@@ -61,17 +63,53 @@ module ActiveRecord
|
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def quoted_date(value)
|
|
64
|
-
if value.acts_like?(:
|
|
65
|
-
Type::Date.new.serialize(value)
|
|
66
|
-
else value.acts_like?(:time)
|
|
66
|
+
if value.acts_like?(:time)
|
|
67
67
|
Type::DateTime.new.serialize(value)
|
|
68
|
+
elsif value.acts_like?(:date)
|
|
69
|
+
Type::Date.new.serialize(value)
|
|
70
|
+
else
|
|
71
|
+
value
|
|
68
72
|
end
|
|
69
73
|
end
|
|
70
74
|
|
|
75
|
+
def column_name_matcher
|
|
76
|
+
COLUMN_NAME
|
|
77
|
+
end
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
def column_name_with_order_matcher
|
|
80
|
+
COLUMN_NAME_WITH_ORDER
|
|
81
|
+
end
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
COLUMN_NAME = /
|
|
84
|
+
\A
|
|
85
|
+
(
|
|
86
|
+
(?:
|
|
87
|
+
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
|
88
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
89
|
+
)
|
|
90
|
+
(?:\s+AS\s+(?:\w+|\[\w+\]))?
|
|
91
|
+
)
|
|
92
|
+
(?:\s*,\s*\g<1>)*
|
|
93
|
+
\z
|
|
94
|
+
/ix
|
|
95
|
+
|
|
96
|
+
COLUMN_NAME_WITH_ORDER = /
|
|
97
|
+
\A
|
|
98
|
+
(
|
|
99
|
+
(?:
|
|
100
|
+
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
|
101
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
102
|
+
)
|
|
103
|
+
(?:\s+ASC|\s+DESC)?
|
|
104
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
105
|
+
)
|
|
106
|
+
(?:\s*,\s*\g<1>)*
|
|
107
|
+
\z
|
|
108
|
+
/ix
|
|
109
|
+
|
|
110
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
|
111
|
+
|
|
112
|
+
def quote(value)
|
|
75
113
|
case value
|
|
76
114
|
when Type::Binary::Data
|
|
77
115
|
"0x#{value.hex}"
|
|
@@ -84,7 +122,7 @@ module ActiveRecord
|
|
|
84
122
|
end
|
|
85
123
|
end
|
|
86
124
|
|
|
87
|
-
def
|
|
125
|
+
def type_cast(value)
|
|
88
126
|
case value
|
|
89
127
|
when ActiveRecord::Type::SQLServer::Data
|
|
90
128
|
value.to_s
|
|
@@ -92,7 +130,6 @@ module ActiveRecord
|
|
|
92
130
|
super
|
|
93
131
|
end
|
|
94
132
|
end
|
|
95
|
-
|
|
96
133
|
end
|
|
97
134
|
end
|
|
98
135
|
end
|
|
@@ -1,20 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
|
-
class SchemaCreation <
|
|
5
|
-
|
|
6
|
+
class SchemaCreation < SchemaCreation
|
|
6
7
|
private
|
|
7
8
|
|
|
9
|
+
def supports_index_using?
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
def visit_TableDefinition(o)
|
|
14
|
+
if_not_exists = o.if_not_exists
|
|
15
|
+
|
|
9
16
|
if o.as
|
|
10
17
|
table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
|
|
11
18
|
query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
|
|
12
19
|
projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
|
|
13
|
-
|
|
20
|
+
sql = "SELECT #{projections} INTO #{table_name} FROM #{source}"
|
|
14
21
|
else
|
|
15
22
|
o.instance_variable_set :@as, nil
|
|
16
|
-
|
|
23
|
+
o.instance_variable_set :@if_not_exists, false
|
|
24
|
+
sql = super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if if_not_exists
|
|
28
|
+
o.instance_variable_set :@if_not_exists, true
|
|
29
|
+
table_name = o.temporary ? "##{o.name}" : o.name
|
|
30
|
+
sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='#{table_name}' and xtype='U') #{sql}"
|
|
17
31
|
end
|
|
32
|
+
|
|
33
|
+
sql
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def visit_CreateIndexDefinition(o)
|
|
37
|
+
index = o.index
|
|
38
|
+
|
|
39
|
+
sql = []
|
|
40
|
+
sql << "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}')" if o.if_not_exists
|
|
41
|
+
sql << "CREATE"
|
|
42
|
+
sql << "UNIQUE" if index.unique
|
|
43
|
+
sql << index.type.upcase if index.type
|
|
44
|
+
sql << "INDEX"
|
|
45
|
+
sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
|
|
46
|
+
sql << "(#{quoted_columns(index)})"
|
|
47
|
+
sql << "WHERE #{index.where}" if index.where
|
|
48
|
+
|
|
49
|
+
sql.join(" ")
|
|
18
50
|
end
|
|
19
51
|
|
|
20
52
|
def add_column_options!(sql, options)
|
|
@@ -22,6 +54,9 @@ module ActiveRecord
|
|
|
22
54
|
if options[:null] == false
|
|
23
55
|
sql << " NOT NULL"
|
|
24
56
|
end
|
|
57
|
+
if options[:collation].present?
|
|
58
|
+
sql << " COLLATE #{options[:collation]}"
|
|
59
|
+
end
|
|
25
60
|
if options[:is_identity] == true
|
|
26
61
|
sql << " IDENTITY(1,1)"
|
|
27
62
|
end
|
|
@@ -34,7 +69,7 @@ module ActiveRecord
|
|
|
34
69
|
def action_sql(action, dependency)
|
|
35
70
|
case dependency
|
|
36
71
|
when :restrict
|
|
37
|
-
raise ArgumentError,
|
|
72
|
+
raise ArgumentError, <<~MSG.squish
|
|
38
73
|
'#{dependency}' is not supported for :on_update or :on_delete.
|
|
39
74
|
Supported values are: :nullify, :cascade
|
|
40
75
|
MSG
|
|
@@ -50,7 +85,6 @@ module ActiveRecord
|
|
|
50
85
|
def options_primary_key_with_nil_default?(options)
|
|
51
86
|
options[:primary_key] && options.include?(:default) && options[:default].nil?
|
|
52
87
|
end
|
|
53
|
-
|
|
54
88
|
end
|
|
55
89
|
end
|
|
56
90
|
end
|