activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +141 -24
- data/.github/workflows/ruby.yml +12 -12
- data/.gitignore +7 -3
- 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/arjdbc/abstract/connection_management.rb +26 -10
- data/lib/arjdbc/abstract/core.rb +5 -12
- data/lib/arjdbc/abstract/database_statements.rb +35 -25
- data/lib/arjdbc/abstract/statement_cache.rb +2 -7
- data/lib/arjdbc/abstract/transaction_support.rb +37 -22
- 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 +101 -79
- 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 +25 -14
- data/lib/arjdbc/mssql/server_version.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +23 -9
- data/lib/arjdbc/mysql/adapter.rb +104 -27
- data/lib/arjdbc/postgresql/adapter.rb +71 -44
- data/lib/arjdbc/postgresql/oid_types.rb +8 -27
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/sqlite3/adapter.rb +205 -147
- 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 +3 -1
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +10 -12
- 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
|
@@ -169,9 +174,12 @@ module ActiveRecord
|
|
169
174
|
end
|
170
175
|
end
|
171
176
|
|
172
|
-
def rename_table(table_name,
|
173
|
-
|
174
|
-
|
177
|
+
def rename_table(table_name, new_name, **options)
|
178
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
179
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
180
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
181
|
+
execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
182
|
+
rename_table_indexes(table_name, new_name)
|
175
183
|
end
|
176
184
|
|
177
185
|
# This is the same as the abstract method
|
@@ -261,7 +269,10 @@ module ActiveRecord
|
|
261
269
|
options[:precision] = 7
|
262
270
|
end
|
263
271
|
|
264
|
-
|
272
|
+
# In SQL Server only the first column added should have the `ADD` keyword.
|
273
|
+
fragments = add_timestamps_for_alter(table_name, **options)
|
274
|
+
fragments[1..].each { |fragment| fragment.sub!('ADD ', '') }
|
275
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}")
|
265
276
|
end
|
266
277
|
|
267
278
|
def add_column(table_name, column_name, type, **options)
|
@@ -371,7 +382,7 @@ module ActiveRecord
|
|
371
382
|
MSSQL::TableDefinition.new(self, name, **options)
|
372
383
|
end
|
373
384
|
|
374
|
-
def new_column_from_field(table_name, field)
|
385
|
+
def new_column_from_field(table_name, field, definitions)
|
375
386
|
# NOTE: this method is used by the columns method in the abstract Class
|
376
387
|
# to map column_definitions. It would be good if column_definitions is
|
377
388
|
# 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
|