activerecord-jdbc-alt-adapter 70.1.0-java → 71.0.0.alpha1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +135 -21
  3. data/.github/workflows/ruby.yml +10 -10
  4. data/.gitignore +1 -0
  5. data/.solargraph.yml +15 -0
  6. data/Gemfile +17 -4
  7. data/README.md +7 -3
  8. data/RUNNING_TESTS.md +36 -0
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arel/visitors/sqlserver.rb +10 -0
  12. data/lib/arjdbc/abstract/connection_management.rb +23 -10
  13. data/lib/arjdbc/abstract/core.rb +5 -6
  14. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  15. data/lib/arjdbc/abstract/statement_cache.rb +1 -6
  16. data/lib/arjdbc/abstract/transaction_support.rb +37 -9
  17. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  18. data/lib/arjdbc/jdbc/column.rb +0 -34
  19. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  20. data/lib/arjdbc/mssql/adapter.rb +93 -80
  21. data/lib/arjdbc/mssql/column.rb +1 -0
  22. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  23. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  24. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  25. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  26. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  27. data/lib/arjdbc/mssql/schema_statements.rb +19 -11
  28. data/lib/arjdbc/mssql/server_version.rb +56 -0
  29. data/lib/arjdbc/mssql/utils.rb +23 -9
  30. data/lib/arjdbc/mysql/adapter.rb +64 -22
  31. data/lib/arjdbc/mysql/connection_methods.rb +43 -42
  32. data/lib/arjdbc/sqlite3/adapter.rb +218 -135
  33. data/lib/arjdbc/sqlite3/column.rb +103 -0
  34. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  35. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  36. data/lib/arjdbc/version.rb +1 -1
  37. data/rakelib/02-test.rake +1 -1
  38. data/rakelib/rails.rake +2 -0
  39. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
  40. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  41. metadata +11 -14
  42. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  43. data/lib/arel/visitors/sql_server.rb +0 -124
  44. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  45. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  46. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  47. 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
- # Default connection method for MS-SQL adapter (`adapter: mssql`),
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
- # @note Assumes SQLServer SQL-JDBC driver on the class-path.
59
- def sqlserver_connection(config)
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
- if variables.any? && variables.first.is_a?(Hash)
11
- variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
12
- else
13
- variables.map { |v| quote(v) }
14
- end.join(', ')
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 = @connection.execute_query_raw(sql)
18
- result.map! do |row|
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
- def execute(sql, name = nil)
38
- # with identity insert on block
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
- @connection.supports_transaction_isolation?(level)
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
- @connection.set_transaction_isolation(value)
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
- @connection.get_transaction_isolation
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
- table_name = get_table_name(sql)
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
- for column in schema_cache.columns(table_name)
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
- execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} ON")
312
+ internal_execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} ON")
187
313
  else
188
- execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} OFF")
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
- sql = interpolate_sql_statement(raw_sql, raw_binds)
31
- result = with_showplan_on do
32
- exec_query(sql, 'EXPLAIN', [])
33
- end
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 supports_foreign_keys?
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
- @connection.primary_keys(table_name)
80
+ valid_raw_connection.primary_keys(table_name)
80
81
  end
81
82
 
82
83
  def foreign_keys(table_name)
83
- @connection.foreign_keys(table_name)
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
- fktable = fkdata['FKTABLE_NAME']
155
- fkcolmn = fkdata['FKCOLUMN_NAME']
156
- pktable = fkdata['PKTABLE_NAME']
157
- pkcolmn = fkdata['PKCOLUMN_NAME']
158
- remove_foreign_key(fktable, name: fkdata['FK_NAME'])
159
- execute("DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )")
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] && mssql_major_version < 13
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
- super
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
@@ -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
- module_function
32
+ TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
32
33
 
33
- def unquote_table_name(table_name)
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