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.
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