activerecord-jdbc-alt-adapter 70.1.0-java → 71.0.0.alpha1-java
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/.github/workflows/main.yml +135 -21
- data/.github/workflows/ruby.yml +10 -10
- data/.gitignore +1 -0
- data/.solargraph.yml +15 -0
- data/Gemfile +17 -4
- data/README.md +7 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arel/visitors/sqlserver.rb +10 -0
- data/lib/arjdbc/abstract/connection_management.rb +23 -10
- data/lib/arjdbc/abstract/core.rb +5 -6
- data/lib/arjdbc/abstract/database_statements.rb +35 -25
- data/lib/arjdbc/abstract/statement_cache.rb +1 -6
- data/lib/arjdbc/abstract/transaction_support.rb +37 -9
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mssql/adapter.rb +93 -80
- data/lib/arjdbc/mssql/column.rb +1 -0
- data/lib/arjdbc/mssql/connection_methods.rb +7 -55
- data/lib/arjdbc/mssql/database_statements.rb +182 -71
- data/lib/arjdbc/mssql/explain_support.rb +8 -5
- data/lib/arjdbc/mssql/schema_creation.rb +1 -1
- data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
- data/lib/arjdbc/mssql/schema_statements.rb +19 -11
- data/lib/arjdbc/mssql/server_version.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +23 -9
- data/lib/arjdbc/mysql/adapter.rb +64 -22
- data/lib/arjdbc/mysql/connection_methods.rb +43 -42
- data/lib/arjdbc/sqlite3/adapter.rb +218 -135
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +11 -14
- data/lib/arel/visitors/sql_server/ng42.rb +0 -294
- data/lib/arel/visitors/sql_server.rb +0 -124
- data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
- data/lib/arjdbc/mssql/lock_methods.rb +0 -77
- data/lib/arjdbc/mssql/old_adapter.rb +0 -804
- data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -1,62 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
ArJdbc::ConnectionMethods.module_eval do
|
4
|
-
|
5
|
-
|
6
|
-
# uses the (open-source) jTDS driver.
|
7
|
-
# If you'd like to use the "official" MS's SQL-JDBC driver, it's preferable
|
8
|
-
# to use the {#sqlserver_connection} method (set `adapter: sqlserver`).
|
9
|
-
def mssql_connection(config)
|
10
|
-
# NOTE: this detection ain't perfect and is only meant as a temporary hack
|
11
|
-
# users will get a deprecation eventually to use `adapter: sqlserver` ...
|
12
|
-
if config[:driver] =~ /SQLServerDriver$/ || config[:url] =~ /^jdbc:sqlserver:/
|
13
|
-
return sqlserver_connection(config)
|
14
|
-
end
|
15
|
-
|
16
|
-
config = config.deep_dup
|
17
|
-
|
18
|
-
config[:adapter_spec] ||= ::ArJdbc::MSSQL
|
19
|
-
config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
|
20
|
-
|
21
|
-
return jndi_connection(config) if jndi_config?(config)
|
22
|
-
|
23
|
-
begin
|
24
|
-
require 'jdbc/jtds'
|
25
|
-
# NOTE: the adapter has only support for working with the
|
26
|
-
# open-source jTDS driver (won't work with MS's driver) !
|
27
|
-
::Jdbc::JTDS.load_driver(:require) if defined?(::Jdbc::JTDS.load_driver)
|
28
|
-
rescue LoadError => e # assuming driver.jar is on the class-path
|
29
|
-
raise e unless e.message.to_s.index('no such file to load')
|
30
|
-
end
|
31
|
-
|
32
|
-
config[:host] ||= 'localhost'
|
33
|
-
config[:port] ||= 1433
|
34
|
-
config[:driver] ||= defined?(::Jdbc::JTDS.driver_name) ? ::Jdbc::JTDS.driver_name : 'net.sourceforge.jtds.jdbc.Driver'
|
35
|
-
config[:connection_alive_sql] ||= 'SELECT 1'
|
36
|
-
|
37
|
-
config[:url] ||= begin
|
38
|
-
url = ''.dup
|
39
|
-
url << "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
|
40
|
-
# Instance is often a preferrable alternative to port when dynamic ports are used.
|
41
|
-
# If instance is specified then port is essentially ignored.
|
42
|
-
url << ";instance=#{config[:instance]}" if config[:instance]
|
43
|
-
# This will enable windows domain-based authentication and will require the JTDS native libraries be available.
|
44
|
-
url << ";domain=#{config[:domain]}" if config[:domain]
|
45
|
-
# AppName is shown in sql server as additional information against the connection.
|
46
|
-
url << ";appname=#{config[:appname]}" if config[:appname]
|
47
|
-
url
|
48
|
-
end
|
49
|
-
|
50
|
-
unless config[:domain]
|
51
|
-
config[:username] ||= 'sa'
|
52
|
-
config[:password] ||= ''
|
53
|
-
end
|
54
|
-
jdbc_connection(config)
|
4
|
+
def mssql_adapter_class
|
5
|
+
ConnectionAdapters::MSSQLAdapter
|
55
6
|
end
|
56
|
-
alias_method :jdbcmssql_connection, :mssql_connection
|
57
7
|
|
58
|
-
#
|
59
|
-
def
|
8
|
+
# NOTE: Assumes SQLServer SQL-JDBC driver on the class-path.
|
9
|
+
def mssql_connection(config)
|
60
10
|
config = config.deep_dup
|
61
11
|
|
62
12
|
config[:adapter_spec] ||= ::ArJdbc::MSSQL
|
@@ -89,6 +39,8 @@ ArJdbc::ConnectionMethods.module_eval do
|
|
89
39
|
|
90
40
|
jdbc_connection(config)
|
91
41
|
end
|
92
|
-
alias_method :jdbcsqlserver_connection, :sqlserver_connection
|
93
42
|
|
43
|
+
alias_method :jdbcmssql_connection, :mssql_connection
|
44
|
+
alias_method :sqlserver_connection, :mssql_connection
|
45
|
+
alias_method :jdbcsqlserver_connection, :mssql_connection
|
94
46
|
end
|
@@ -4,23 +4,22 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module MSSQL
|
6
6
|
module DatabaseStatements
|
7
|
-
|
8
7
|
def exec_proc(proc_name, *variables)
|
9
|
-
vars =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
vars = if variables.any? && variables.first.is_a?(Hash)
|
9
|
+
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
10
|
+
else
|
11
|
+
variables.map { |v| quote(v) }
|
12
|
+
end.join(', ')
|
13
|
+
|
15
14
|
sql = "EXEC #{proc_name} #{vars}".strip
|
16
15
|
log(sql, 'Execute Procedure') do
|
17
|
-
result =
|
18
|
-
|
16
|
+
result = internal_execute(sql)
|
17
|
+
|
18
|
+
result.map do |row|
|
19
19
|
row = row.is_a?(Hash) ? row.with_indifferent_access : row
|
20
20
|
yield(row) if block_given?
|
21
21
|
row
|
22
22
|
end
|
23
|
-
result
|
24
23
|
end
|
25
24
|
end
|
26
25
|
alias_method :execute_procedure, :exec_proc # AR-SQLServer-Adapter naming
|
@@ -34,47 +33,22 @@ module ActiveRecord
|
|
34
33
|
!READ_QUERY.match?(sql)
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
if insert_sql?(sql)
|
40
|
-
table_name_for_identity_insert = identity_insert_table_name(sql)
|
41
|
-
|
42
|
-
if table_name_for_identity_insert
|
43
|
-
with_identity_insert_enabled(table_name_for_identity_insert) do
|
44
|
-
super
|
45
|
-
end
|
46
|
-
else
|
47
|
-
super
|
48
|
-
end
|
49
|
-
else
|
50
|
-
super
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
55
|
-
table_name_for_identity_insert = identity_insert_table_name(sql)
|
56
|
-
|
57
|
-
if table_name_for_identity_insert
|
58
|
-
with_identity_insert_enabled(table_name_for_identity_insert) do
|
59
|
-
super
|
60
|
-
end
|
61
|
-
else
|
62
|
-
super
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Not a rails method, own method to test different isolation
|
67
|
-
# levels supported by the mssql adapter.
|
36
|
+
# Internal method to test different isolation levels supported by this
|
37
|
+
# mssql adapter. NOTE: not a active record method
|
68
38
|
def supports_transaction_isolation_level?(level)
|
69
|
-
|
39
|
+
raw_jdbc_connection.supports_transaction_isolation?(level)
|
70
40
|
end
|
71
41
|
|
42
|
+
# Internal method to test different isolation levels supported by this
|
43
|
+
# mssql adapter. Not a active record method
|
72
44
|
def transaction_isolation=(value)
|
73
|
-
|
45
|
+
raw_jdbc_connection.set_transaction_isolation(value)
|
74
46
|
end
|
75
47
|
|
48
|
+
# Internal method to test different isolation levels supported by this
|
49
|
+
# mssql adapter. Not a active record method
|
76
50
|
def transaction_isolation
|
77
|
-
|
51
|
+
raw_jdbc_connection.get_transaction_isolation
|
78
52
|
end
|
79
53
|
|
80
54
|
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
@@ -116,8 +90,163 @@ module ActiveRecord
|
|
116
90
|
end
|
117
91
|
end
|
118
92
|
|
93
|
+
def internal_exec_query(sql, name = 'SQL', binds = [], prepare: false, async: false)
|
94
|
+
sql = transform_query(sql)
|
95
|
+
|
96
|
+
check_if_write_query(sql)
|
97
|
+
|
98
|
+
mark_transaction_written_if_write(sql)
|
99
|
+
|
100
|
+
# binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
|
101
|
+
|
102
|
+
# puts "internal----->sql: #{sql}, binds: #{binds}"
|
103
|
+
if without_prepared_statement?(binds)
|
104
|
+
log(sql, name) do
|
105
|
+
with_raw_connection do |conn|
|
106
|
+
result = conditional_indentity_insert(sql) do
|
107
|
+
conn.execute_query(sql)
|
108
|
+
end
|
109
|
+
verified!
|
110
|
+
result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
log(sql, name, binds) do
|
115
|
+
with_raw_connection do |conn|
|
116
|
+
# this is different from normal AR that always caches
|
117
|
+
cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
|
118
|
+
|
119
|
+
result = conditional_indentity_insert(sql) do
|
120
|
+
conn.execute_prepared_query(sql, binds, cached_statement)
|
121
|
+
end
|
122
|
+
verified!
|
123
|
+
result
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def exec_update(sql, name = nil, binds = [])
|
130
|
+
sql = transform_query(sql)
|
131
|
+
|
132
|
+
check_if_write_query(sql)
|
133
|
+
|
134
|
+
mark_transaction_written_if_write(sql)
|
135
|
+
|
136
|
+
# puts "exec_update----->sql: #{sql}, binds: #{binds}"
|
137
|
+
if without_prepared_statement?(binds)
|
138
|
+
log(sql, name) do
|
139
|
+
with_raw_connection do |conn|
|
140
|
+
result = conn.execute_update(sql)
|
141
|
+
verified!
|
142
|
+
result
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
log(sql, name, binds) do
|
147
|
+
with_raw_connection do |conn|
|
148
|
+
result = conn.execute_prepared_update(sql, binds)
|
149
|
+
verified!
|
150
|
+
result
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
alias :exec_delete :exec_update
|
156
|
+
|
157
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
158
|
+
sql = transform_query(sql)
|
159
|
+
|
160
|
+
check_if_write_query(sql)
|
161
|
+
|
162
|
+
mark_transaction_written_if_write(sql)
|
163
|
+
|
164
|
+
# puts "exec_insert----->sql: #{sql}, binds: #{binds}"
|
165
|
+
if without_prepared_statement?(binds)
|
166
|
+
log(sql, name) do
|
167
|
+
with_raw_connection do |conn|
|
168
|
+
result = conditional_indentity_insert(sql) do
|
169
|
+
conn.execute_insert_pk(sql, pk)
|
170
|
+
end
|
171
|
+
verified!
|
172
|
+
result
|
173
|
+
end
|
174
|
+
end
|
175
|
+
else
|
176
|
+
log(sql, name, binds) do
|
177
|
+
with_raw_connection do |conn|
|
178
|
+
result = conditional_indentity_insert(sql) do
|
179
|
+
conn.execute_insert_pk(sql, binds, pk)
|
180
|
+
end
|
181
|
+
verified!
|
182
|
+
result
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
119
188
|
private
|
120
189
|
|
190
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
191
|
+
# puts "raw_execute----->sql: #{sql}"
|
192
|
+
log(sql, name, async: async) do
|
193
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
194
|
+
result = conditional_indentity_insert(sql) { conn.execute(sql) }
|
195
|
+
verified!
|
196
|
+
result
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def sql_for_insert(sql, pk, binds, returning) # :nodoc:
|
202
|
+
return [sql, binds]
|
203
|
+
|
204
|
+
# TODO: Add/Implement and support for insert returning values when
|
205
|
+
# upgrading to rails 7.2
|
206
|
+
return [sql, binds] unless supports_insert_returning?
|
207
|
+
|
208
|
+
if pk.nil?
|
209
|
+
# Extract the table from the insert sql. Yuck.
|
210
|
+
table_name = identity_insert_table_name(sql)
|
211
|
+
pk = primary_key(table_name) if table_name
|
212
|
+
end
|
213
|
+
|
214
|
+
returning_columns = returning || Array(pk)
|
215
|
+
|
216
|
+
returning_columns_stmt = returning_columns.map do |column|
|
217
|
+
"INSERTED.#{quote_column_name(column)}"
|
218
|
+
end.join(', ')
|
219
|
+
|
220
|
+
|
221
|
+
return [sql, binds] unless returning_columns.any?
|
222
|
+
|
223
|
+
index = sql.index(/VALUES\s\(\?/) || sql.index(/DEFAULT VALUES/)
|
224
|
+
|
225
|
+
insert_into = sql[0..(index - 1)].strip
|
226
|
+
|
227
|
+
values_list = sql[index..]
|
228
|
+
|
229
|
+
sql = "#{insert_into} OUTPUT #{returning_columns_stmt} #{values_list}"
|
230
|
+
|
231
|
+
[sql, binds]
|
232
|
+
end
|
233
|
+
|
234
|
+
def conditional_indentity_insert(sql, &block)
|
235
|
+
table_name_for_identity_insert = identity_insert_table_name(sql)
|
236
|
+
|
237
|
+
if table_name_for_identity_insert
|
238
|
+
with_identity_insert_enabled(table_name_for_identity_insert) do
|
239
|
+
block.call
|
240
|
+
end
|
241
|
+
else
|
242
|
+
block.call
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def raw_jdbc_connection
|
247
|
+
@raw_connection
|
248
|
+
end
|
249
|
+
|
121
250
|
# It seems the truncate_tables is mostly used for testing
|
122
251
|
# this a workaround to the fact that SQL Server truncate tables
|
123
252
|
# referenced by a foreign key, it may not be required to reset
|
@@ -151,24 +280,21 @@ module ActiveRecord
|
|
151
280
|
end
|
152
281
|
end
|
153
282
|
|
154
|
-
def insert_sql?(sql)
|
155
|
-
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
156
|
-
end
|
157
|
-
|
158
283
|
def identity_insert_table_name(sql)
|
159
|
-
|
284
|
+
return unless ArJdbc::MSSQL::Utils.insert_sql?(sql)
|
285
|
+
|
286
|
+
table_name = ArJdbc::MSSQL::Utils.get_table_name(sql)
|
287
|
+
|
160
288
|
id_column = identity_column_name(table_name)
|
289
|
+
|
161
290
|
if id_column && sql.strip =~ /INSERT INTO [^ ]+ ?\((.+?)\)/i
|
162
|
-
insert_columns = $1.split(/, */).map{|w| ArJdbc::MSSQL::Utils.unquote_column_name(w)}
|
291
|
+
insert_columns = $1.split(/, */).map { |w| ArJdbc::MSSQL::Utils.unquote_column_name(w) }
|
163
292
|
return table_name if insert_columns.include?(id_column)
|
164
293
|
end
|
165
294
|
end
|
166
295
|
|
167
296
|
def identity_column_name(table_name)
|
168
|
-
|
169
|
-
return column.name if column.identity?
|
170
|
-
end
|
171
|
-
nil
|
297
|
+
schema_cache.columns(table_name).find(&:identity?)&.name
|
172
298
|
end
|
173
299
|
|
174
300
|
# Turns IDENTITY_INSERT ON for table during execution of the block
|
@@ -183,29 +309,14 @@ module ActiveRecord
|
|
183
309
|
|
184
310
|
def set_identity_insert(table_name, enable = true)
|
185
311
|
if enable
|
186
|
-
|
312
|
+
internal_execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} ON")
|
187
313
|
else
|
188
|
-
|
314
|
+
internal_execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} OFF")
|
189
315
|
end
|
190
316
|
rescue Exception => e
|
191
317
|
raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned" +
|
192
318
|
" #{enable ? 'ON' : 'OFF'} for table #{table_name} due : #{e.inspect}"
|
193
319
|
end
|
194
|
-
|
195
|
-
def get_table_name(sql, qualified = nil)
|
196
|
-
if sql =~ TABLE_NAME_INSERT_UPDATE
|
197
|
-
tn = $2 || $3
|
198
|
-
qualified ? tn : ArJdbc::MSSQL::Utils.unqualify_table_name(tn)
|
199
|
-
elsif sql =~ TABLE_NAME_FROM
|
200
|
-
qualified ? $1 : ArJdbc::MSSQL::Utils.unqualify_table_name($1)
|
201
|
-
else
|
202
|
-
nil
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
TABLE_NAME_INSERT_UPDATE = /^\s*(INSERT|EXEC sp_executesql N'INSERT)(?:\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
207
|
-
|
208
|
-
TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
|
209
320
|
end
|
210
321
|
end
|
211
322
|
end
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
!DISABLED
|
18
18
|
end
|
19
19
|
|
20
|
-
def explain(arel, binds = [])
|
20
|
+
def explain(arel, binds = [], options = [])
|
21
21
|
return if DISABLED
|
22
22
|
|
23
23
|
if arel.respond_to?(:to_sql)
|
@@ -25,12 +25,15 @@ module ActiveRecord
|
|
25
25
|
else
|
26
26
|
raw_sql, raw_binds = arel, binds
|
27
27
|
end
|
28
|
+
|
28
29
|
# sql = to_sql(arel, binds)
|
29
30
|
# result = with_showplan_on { exec_query(sql, 'EXPLAIN', binds) }
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
|
32
|
+
sql = interpolate_sql_statement(raw_sql, raw_binds)
|
33
|
+
|
34
|
+
result = with_showplan_on do
|
35
|
+
exec_query(sql, 'EXPLAIN', [])
|
36
|
+
end
|
34
37
|
PrinterTable.new(result).pp
|
35
38
|
end
|
36
39
|
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
|
29
29
|
end
|
30
30
|
|
31
|
-
if
|
31
|
+
if use_foreign_keys?
|
32
32
|
statements.concat(o.foreign_keys.map { |fk| accept fk })
|
33
33
|
end
|
34
34
|
|
@@ -70,6 +70,10 @@ module ActiveRecord
|
|
70
70
|
def uuid(*args, **options)
|
71
71
|
args.each { |name| column(name, :uniqueidentifier, **options) }
|
72
72
|
end
|
73
|
+
|
74
|
+
def json(*names, **options)
|
75
|
+
names.each { |name| column(name, :text, **options) }
|
76
|
+
end
|
73
77
|
end
|
74
78
|
|
75
79
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
@@ -106,6 +110,12 @@ module ActiveRecord
|
|
106
110
|
|
107
111
|
super
|
108
112
|
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def valid_column_definition_options
|
117
|
+
super + [:is_identity]
|
118
|
+
end
|
109
119
|
end
|
110
120
|
|
111
121
|
class Table < ActiveRecord::ConnectionAdapters::Table
|
@@ -18,6 +18,7 @@ module ActiveRecord
|
|
18
18
|
string: { name: 'nvarchar', limit: 4000 },
|
19
19
|
text: { name: 'nvarchar(max)' },
|
20
20
|
binary: { name: 'varbinary(max)' },
|
21
|
+
json: { name: 'nvarchar(max)' },
|
21
22
|
# Other types or SQL Server specific
|
22
23
|
bigint: { name: 'bigint' },
|
23
24
|
smalldatetime: { name: 'smalldatetime' },
|
@@ -76,11 +77,11 @@ module ActiveRecord
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def primary_keys(table_name)
|
79
|
-
|
80
|
+
valid_raw_connection.primary_keys(table_name)
|
80
81
|
end
|
81
82
|
|
82
83
|
def foreign_keys(table_name)
|
83
|
-
|
84
|
+
valid_raw_connection.foreign_keys(table_name)
|
84
85
|
end
|
85
86
|
|
86
87
|
def charset
|
@@ -151,16 +152,20 @@ module ActiveRecord
|
|
151
152
|
# https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-2017
|
152
153
|
if options[:force] == :cascade
|
153
154
|
execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
155
|
+
raw_fktable = fkdata['FKTABLE_NAME']
|
156
|
+
remove_foreign_key(raw_fktable, name: fkdata['FK_NAME'])
|
157
|
+
|
158
|
+
fktable = quote_table_name(fkdata['FKTABLE_NAME'])
|
159
|
+
fkcolmn = quote_column_name(fkdata['FKCOLUMN_NAME'])
|
160
|
+
|
161
|
+
pktable = quote_table_name(fkdata['PKTABLE_NAME'])
|
162
|
+
pkcolmn = quote_column_name(fkdata['PKCOLUMN_NAME'])
|
163
|
+
|
164
|
+
execute("DELETE FROM #{fktable} WHERE #{fkcolmn} IN ( SELECT #{pkcolmn} FROM #{pktable} )")
|
160
165
|
end
|
161
166
|
end
|
162
167
|
|
163
|
-
if options[:if_exists] &&
|
168
|
+
if options[:if_exists] && mssql_version.major < '13'
|
164
169
|
# this is for sql server 2012 and 2014
|
165
170
|
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
|
166
171
|
else
|
@@ -261,7 +266,10 @@ module ActiveRecord
|
|
261
266
|
options[:precision] = 7
|
262
267
|
end
|
263
268
|
|
264
|
-
|
269
|
+
# In SQL Server only the first column added should have the `ADD` keyword.
|
270
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
271
|
+
fragments[1..].each { |fragment| fragment.sub!('ADD ', '') }
|
272
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}")
|
265
273
|
end
|
266
274
|
|
267
275
|
def add_column(table_name, column_name, type, **options)
|
@@ -371,7 +379,7 @@ module ActiveRecord
|
|
371
379
|
MSSQL::TableDefinition.new(self, name, **options)
|
372
380
|
end
|
373
381
|
|
374
|
-
def new_column_from_field(table_name, field)
|
382
|
+
def new_column_from_field(table_name, field, definitions)
|
375
383
|
# NOTE: this method is used by the columns method in the abstract Class
|
376
384
|
# to map column_definitions. It would be good if column_definitions is
|
377
385
|
# implemented in ruby
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module MSSQL
|
6
|
+
class Version
|
7
|
+
attr_reader :major
|
8
|
+
attr_reader :complete
|
9
|
+
attr_reader :level
|
10
|
+
attr_reader :edition
|
11
|
+
|
12
|
+
VERSION_YEAR = {
|
13
|
+
'8' => '2000',
|
14
|
+
'9' => '2005',
|
15
|
+
'10' => '2008',
|
16
|
+
'11' => '2012',
|
17
|
+
'12' => '2014',
|
18
|
+
'13' => '2016',
|
19
|
+
'14' => '2017',
|
20
|
+
'15' => '2019',
|
21
|
+
'16' => '2022'
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(version_array = [])
|
25
|
+
@complete, @major, @level, @edition = version_array
|
26
|
+
end
|
27
|
+
|
28
|
+
def product_name
|
29
|
+
return system_name unless year
|
30
|
+
|
31
|
+
"#{system_name} #{year}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def system_name
|
35
|
+
'Microsoft SQL Server'
|
36
|
+
end
|
37
|
+
|
38
|
+
def year
|
39
|
+
VERSION_YEAR[major]
|
40
|
+
end
|
41
|
+
|
42
|
+
def min_year
|
43
|
+
VERSION_YEAR[min_major]
|
44
|
+
end
|
45
|
+
|
46
|
+
def min_major
|
47
|
+
'13'
|
48
|
+
end
|
49
|
+
|
50
|
+
def support_message
|
51
|
+
"This adapter supports #{system_name} >= #{min_year}."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/arjdbc/mssql/utils.rb
CHANGED
@@ -27,32 +27,47 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
27
27
|
module ArJdbc
|
28
28
|
module MSSQL
|
29
29
|
module Utils
|
30
|
+
TABLE_NAME_INSERT_UPDATE = /^\s*(INSERT|EXEC sp_executesql N'INSERT)(?:\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
30
31
|
|
31
|
-
|
32
|
+
TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
|
32
33
|
|
33
|
-
def
|
34
|
+
def self.insert_sql?(sql)
|
35
|
+
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.get_table_name(sql, qualified = nil)
|
39
|
+
if sql =~ TABLE_NAME_INSERT_UPDATE
|
40
|
+
tn = $2 || $3
|
41
|
+
qualified ? tn : unqualify_table_name(tn)
|
42
|
+
elsif sql =~ TABLE_NAME_FROM
|
43
|
+
qualified ? $1 : unqualify_table_name($1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.unquote_table_name(table_name)
|
34
48
|
remove_identifier_delimiters(table_name)
|
35
49
|
end
|
36
50
|
|
37
|
-
def unquote_column_name(column_name)
|
51
|
+
def self.unquote_column_name(column_name)
|
38
52
|
remove_identifier_delimiters(column_name)
|
39
53
|
end
|
40
54
|
|
41
|
-
def unquote_string(string)
|
55
|
+
def self.unquote_string(string)
|
42
56
|
string.to_s.gsub("''", "'")
|
43
57
|
end
|
44
58
|
|
45
|
-
def unqualify_table_name(table_name)
|
59
|
+
def self.unqualify_table_name(table_name)
|
46
60
|
return if table_name.blank?
|
61
|
+
|
47
62
|
remove_identifier_delimiters(table_name.to_s.split('.').last)
|
48
63
|
end
|
49
64
|
|
50
|
-
def unqualify_table_schema(table_name)
|
65
|
+
def self.unqualify_table_schema(table_name)
|
51
66
|
schema_name = table_name.to_s.split('.')[-2]
|
52
67
|
schema_name.nil? ? nil : remove_identifier_delimiters(schema_name)
|
53
68
|
end
|
54
69
|
|
55
|
-
def unqualify_db_name(table_name)
|
70
|
+
def self.unqualify_db_name(table_name)
|
56
71
|
table_names = table_name.to_s.split('.')
|
57
72
|
table_names.length == 3 ? remove_identifier_delimiters(table_names.first) : nil
|
58
73
|
end
|
@@ -60,10 +75,9 @@ module ArJdbc
|
|
60
75
|
# private
|
61
76
|
|
62
77
|
# See "Delimited Identifiers": http://msdn.microsoft.com/en-us/library/ms176027.aspx
|
63
|
-
def remove_identifier_delimiters(keyword)
|
78
|
+
def self.remove_identifier_delimiters(keyword)
|
64
79
|
keyword.to_s.tr("\]\[\"", '')
|
65
80
|
end
|
66
|
-
|
67
81
|
end
|
68
82
|
end
|
69
83
|
end
|