activerecord-jdbc-alt-adapter 71.0.0-java → 72.0.0.rc3-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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/activerecord-jdbc-adapter.gemspec +1 -1
  4. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  5. data/lib/arel/visitors/sqlserver.rb +13 -9
  6. data/lib/arjdbc/abstract/core.rb +10 -1
  7. data/lib/arjdbc/abstract/database_statements.rb +6 -3
  8. data/lib/arjdbc/abstract/transaction_support.rb +3 -1
  9. data/lib/arjdbc/jdbc/adapter.rb +0 -1
  10. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  11. data/lib/arjdbc/mssql/adapter.rb +18 -15
  12. data/lib/arjdbc/mssql/adapter_hash_config.rb +53 -0
  13. data/lib/arjdbc/mssql/column.rb +16 -7
  14. data/lib/arjdbc/mssql/database_statements.rb +11 -4
  15. data/lib/arjdbc/mssql/quoting.rb +56 -36
  16. data/lib/arjdbc/mssql/schema_creation.rb +16 -0
  17. data/lib/arjdbc/mssql/schema_statements.rb +44 -19
  18. data/lib/arjdbc/mssql.rb +1 -1
  19. data/lib/arjdbc/mysql/adapter.rb +8 -11
  20. data/lib/arjdbc/mysql/adapter_hash_config.rb +159 -0
  21. data/lib/arjdbc/mysql.rb +1 -1
  22. data/lib/arjdbc/postgresql/adapter.rb +17 -11
  23. data/lib/arjdbc/postgresql/adapter_hash_config.rb +98 -0
  24. data/lib/arjdbc/postgresql/base/array_encoder.rb +3 -1
  25. data/lib/arjdbc/postgresql/oid_types.rb +2 -2
  26. data/lib/arjdbc/postgresql.rb +1 -1
  27. data/lib/arjdbc/sqlite3/adapter.rb +193 -90
  28. data/lib/arjdbc/sqlite3/adapter_hash_config.rb +91 -0
  29. data/lib/arjdbc/sqlite3/column.rb +17 -3
  30. data/lib/arjdbc/sqlite3/pragmas.rb +105 -0
  31. data/lib/arjdbc/sqlite3.rb +1 -1
  32. data/lib/arjdbc/version.rb +1 -1
  33. data/lib/arjdbc.rb +16 -1
  34. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +5 -0
  35. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +18 -18
  36. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +12 -2
  37. metadata +11 -10
  38. data/lib/arjdbc/jdbc/base_ext.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 973f3cfa750c3424b5de9105f177584edea8ebe705d50b20e4b9f6d989a4919f
4
- data.tar.gz: e7f86bfeb49f90f2767e3bdcc3e0101a58149e97421e8d7b96da8f59fc469959
3
+ metadata.gz: 7f13e7b4fa08bfb67f978fad2353bd633bd0420848babe30b611949d69667e77
4
+ data.tar.gz: 35ce8492f5985337a6f2ca02beac9eb38a0a3b9fc5957d816c39bf2da0790687
5
5
  SHA512:
6
- metadata.gz: f276911281e38f162526b603959f5798fa5d09c73284a79078376126b5fade87add2e4a4345242029a4b4e39097b5a6964651483576f51ed8bee3ac7b6e586a3
7
- data.tar.gz: 8e5d458add630631bfaa82605f20d28c80310e25787c9db2c94c1b8cc677ac8ff6e0730c97b6271a333cbfe919da8a35ad8deb7911aeedc89c3021629e0c5370
6
+ metadata.gz: 897405b85f5f90c962ca468c600ec44ca12d3cb0f058be273d9944dc998531e03daebf6f80a0cd8ad9cf7367fa90a7b811e7fc5282fc46a503dd987ef628f1f4
7
+ data.tar.gz: 6a85da335eaf8ecfd708bc6641ec58c7665bdba75784e57d4c3ee33540c5e7d42904ae87587d00ebb304cebc3e94a0d0bebf3f48e74dc7a41103ca1f22960a42
data/README.md CHANGED
@@ -175,7 +175,7 @@ adapters are available:
175
175
 
176
176
  ```yml
177
177
  development:
178
- adapter: mysql2 # or mysql
178
+ adapter: mysql2
179
179
  database: blog_development
180
180
  username: blog
181
181
  password: 1234
@@ -199,7 +199,7 @@ or preferably using the *properties:* syntax:
199
199
 
200
200
  ```yml
201
201
  production:
202
- adapter: mysql
202
+ adapter: mysql2
203
203
  username: blog
204
204
  password: blog
205
205
  url: "jdbc:mysql://localhost:3306/blog?profileSQL=true"
@@ -41,7 +41,7 @@ Gem::Specification.new do |gem|
41
41
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
42
42
  gem.test_files = gem.files.grep(%r{^test/})
43
43
 
44
- gem.add_dependency 'activerecord', '~> 7.1.3'
44
+ gem.add_dependency "activerecord", "~> 7.2.2"
45
45
 
46
46
  #gem.add_development_dependency 'test-unit', '2.5.4'
47
47
  #gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
@@ -43,7 +43,7 @@ Gem::Specification.new do |gem|
43
43
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
44
44
  gem.test_files = gem.files.grep(%r{^test/})
45
45
 
46
- gem.add_dependency 'activerecord', '~> 7.1.3'
46
+ gem.add_dependency "activerecord", "~> 7.2.2"
47
47
 
48
48
  #gem.add_development_dependency 'test-unit', '2.5.4'
49
49
  #gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
@@ -83,15 +83,17 @@ module Arel
83
83
  @select_statement = nil
84
84
  end
85
85
 
86
- def visit_Arel_Table o, collector
86
+ def visit_Arel_Table(o, collector)
87
87
  # Apparently, o.engine.connection can actually be a different adapter
88
88
  # than sqlserver. Can be removed if fixed in ActiveRecord. See:
89
89
  # github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
90
90
  table_name = begin
91
- if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
92
- remote_server_table_name(o)
93
- else
94
- quote_table_name(o.name)
91
+ o.class.engine.with_connection do |connection|
92
+ if connection.respond_to?(:sqlserver?) && connection.database_prefix_remote_server?
93
+ remote_server_table_name(o)
94
+ else
95
+ quote_table_name(o.name)
96
+ end
95
97
  end
96
98
  rescue Exception => e
97
99
  quote_table_name(o.name)
@@ -259,10 +261,12 @@ module Arel
259
261
  column_name ? t[column_name] : nil
260
262
  end
261
263
 
262
- def remote_server_table_name o
263
- ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
264
- "#{o.class.engine.connection.database_prefix}#{o.name}"
265
- ).quoted
264
+ def remote_server_table_name(o)
265
+ o.class.engine.with_connection do |connection|
266
+ ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
267
+ "#{connection.database_prefix}#{o.name}"
268
+ ).quoted
269
+ end
266
270
  end
267
271
 
268
272
  # Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
@@ -60,7 +60,16 @@ module ArJdbc
60
60
  # this version of log() automatically fills type_casted_binds from binds if necessary
61
61
  def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false)
62
62
  if binds.any? && (type_casted_binds.nil? || type_casted_binds.empty?)
63
- type_casted_binds = ->{ binds.map(&:value_for_database) } # extract_raw_bind_values
63
+ type_casted_binds = lambda {
64
+ # extract_raw_bind_values
65
+ binds.map do |bind|
66
+ if bind.respond_to?(:value_for_database)
67
+ bind.value_for_database
68
+ else
69
+ bind
70
+ end
71
+ end
72
+ }
64
73
  end
65
74
  super
66
75
  end
@@ -44,14 +44,17 @@ module ArJdbc
44
44
 
45
45
  binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
46
46
 
47
+ # puts "internal----->sql: #{sql}, binds: #{binds}"
48
+ type_casted_binds = type_casted_binds(binds)
49
+
47
50
  with_raw_connection do |conn|
48
51
  if without_prepared_statement?(binds)
49
52
  log(sql, name, async: async) { conn.execute_query(sql) }
50
53
  else
51
- log(sql, name, binds, async: async) do
54
+ log(sql, name, type_casted_binds, async: async) do
52
55
  # this is different from normal AR that always caches
53
56
  cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
54
- conn.execute_prepared_query(sql, binds, cached_statement)
57
+ conn.execute_prepared_query(sql, type_casted_binds, cached_statement)
55
58
  end
56
59
  end
57
60
  end
@@ -79,7 +82,7 @@ module ArJdbc
79
82
  alias :exec_delete :exec_update
80
83
 
81
84
  # overridden to support legacy binds
82
- def select_all(arel, name = nil, binds = NO_BINDS, preparable: nil, async: false)
85
+ def select_all(arel, name = nil, binds = NO_BINDS, preparable: nil, async: false, allow_retry: false)
83
86
  binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
84
87
  super
85
88
  end
@@ -26,7 +26,9 @@ module ArJdbc
26
26
  def begin_db_transaction
27
27
  log('BEGIN', 'TRANSACTION') do
28
28
  with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
29
- conn.begin
29
+ result = conn.begin
30
+ verified!
31
+ result
30
32
  end
31
33
  end
32
34
  end
@@ -5,7 +5,6 @@ require 'active_record/connection_adapters/abstract_adapter'
5
5
 
6
6
  require 'arjdbc/version'
7
7
  require 'arjdbc/jdbc/java'
8
- require 'arjdbc/jdbc/base_ext'
9
8
  require 'arjdbc/jdbc/error'
10
9
  require 'arjdbc/jdbc/connection_methods'
11
10
  require 'arjdbc/jdbc/column'
Binary file
@@ -30,6 +30,8 @@ require 'arjdbc/mssql/errors'
30
30
  require 'arjdbc/mssql/schema_creation'
31
31
  require 'arjdbc/mssql/database_limits'
32
32
 
33
+ require "arjdbc/mssql/adapter_hash_config"
34
+
33
35
  require "arjdbc/abstract/relation_query_attribute_monkey_patch"
34
36
 
35
37
  module ActiveRecord
@@ -44,6 +46,7 @@ module ActiveRecord
44
46
  # include ArJdbc::Abstract::DatabaseStatements
45
47
  # include ArJdbc::Abstract::StatementCache
46
48
  include ArJdbc::Abstract::TransactionSupport
49
+ include ArJdbc::MSSQLConfig
47
50
 
48
51
  include MSSQL::Quoting
49
52
  include MSSQL::SchemaStatements
@@ -51,6 +54,7 @@ module ActiveRecord
51
54
  include MSSQL::ExplainSupport
52
55
  include MSSQL::DatabaseLimits
53
56
 
57
+ # Latin1-General, case-sensitive, accent-sensitive, kanatype-insensitive, width-sensitive
54
58
  @cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
55
59
 
56
60
  class << self
@@ -81,7 +85,8 @@ module ActiveRecord
81
85
  # configure_connection happens in super
82
86
  super
83
87
 
84
- conn_params = @config.compact
88
+ # assign arjdbc extra connection params
89
+ conn_params = build_connection_config(@config.compact)
85
90
 
86
91
  @raw_connection = nil
87
92
 
@@ -261,29 +266,25 @@ module ActiveRecord
261
266
 
262
267
  alias_method :current_schema=, :default_schema=
263
268
 
264
- # FIXME: This needs to be fixed when we implement the collation per
265
- # column basis. At the moment we only use the global database collation
266
- def default_uniqueness_comparison(attribute, value) # :nodoc:
269
+ # FIXME: This needs to be fixed when we implement the collation per column
270
+ def case_sensitive_comparison(attribute, value)
267
271
  column = column_for_attribute(attribute)
268
272
 
269
- if [:string, :text].include?(column.type) && collation && !collation.match(/_CS/) && !value.nil?
270
- # NOTE: there is a deprecation warning here in the mysql adapter
271
- # no sure if it's required.
273
+ case_sensitive = collation && collation.match(/_CS/)
274
+
275
+ if %i[string text].include?(column.type) && !case_sensitive && !value.nil?
272
276
  attribute.eq(Arel::Nodes::Bin.new(value))
273
277
  else
274
278
  super
275
279
  end
276
280
  end
277
281
 
278
- def case_sensitive_comparison(attribute, value)
279
- column = column_for_attribute(attribute)
282
+ def can_perform_case_insensitive_comparison_for?(column)
283
+ case_sensitive = collation && collation.match(/_CS/)
280
284
 
281
- if [:string, :text].include?(column.type) && collation && !collation.match(/_CS/) && !value.nil?
282
- attribute.eq(Arel::Nodes::Bin.new(value))
283
- else
284
- super
285
- end
285
+ %i[string text].include?(column.type) && !case_sensitive
286
286
  end
287
+ private :can_perform_case_insensitive_comparison_for?
287
288
 
288
289
  def configure_connection
289
290
  # Here goes initial settings per connection
@@ -340,7 +341,7 @@ module ActiveRecord
340
341
 
341
342
  schemas_and_tables.map do |schema_table|
342
343
  schema, table = schema_table
343
- "#{quote_name_part(schema)}.#{quote_name_part(table)}"
344
+ "#{self.class.mssql_quote_name_part(schema)}.#{self.class.mssql_quote_name_part(table)}"
344
345
  end
345
346
  end
346
347
 
@@ -481,6 +482,8 @@ module ActiveRecord
481
482
  ConnectionNotEstablished.new(exception)
482
483
  when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
483
484
  RecordNotUnique.new(message, sql: sql, binds: binds)
485
+ when /Violation of PRIMARY KEY constraint .* Cannot insert duplicate key in object .* The duplicate key value is/i
486
+ RecordNotUnique.new(message, sql: sql, binds: binds)
484
487
  when /Lock request time out period exceeded/i
485
488
  LockTimeout.new(message, sql: sql, binds: binds)
486
489
  when /The .* statement conflicted with the FOREIGN KEY constraint/
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module MSSQLConfig
5
+ def build_connection_config(config)
6
+ config = config.deep_dup
7
+
8
+ load_jdbc_driver
9
+
10
+ config[:driver] ||= database_driver_name
11
+
12
+ config[:host] ||= "localhost"
13
+ config[:connection_alive_sql] ||= "SELECT 1"
14
+ config[:lock_timeout] ||= 5000
15
+
16
+ config[:url] ||= build_connection_url(config)
17
+
18
+ config
19
+ end
20
+
21
+ private
22
+
23
+ def load_jdbc_driver
24
+ require "jdbc/mssql"
25
+
26
+ ::Jdbc::Mssql.load_driver if defined?(::Jdbc::Mssql.load_driver)
27
+ rescue LoadError
28
+ # assuming driver.jar is on the class-path
29
+ end
30
+
31
+ def database_driver_name
32
+ "com.microsoft.sqlserver.jdbc.SQLServerDriver"
33
+ end
34
+
35
+ def build_connection_url(config)
36
+ url = "".dup
37
+ url << "jdbc:sqlserver://#{config[:host]}"
38
+ url << ( config[:port] ? ":#{config[:port]};" : ';' )
39
+ url << "databaseName=#{config[:database]};" if config[:database]
40
+ url << "instanceName=#{config[:instance]};" if config[:instance]
41
+ url << "sendTimeAsDatetime=#{config[:send_time_as_datetime] || false};"
42
+ url << "loginTimeout=#{config[:login_timeout].to_i};" if config[:login_timeout]
43
+ url << "lockTimeout=#{config[:lock_timeout].to_i};"
44
+ url << "encrypt=#{config[:encrypt]};" if config.key?(:encrypt)
45
+ url << "trustServerCertificate=#{config[:trust_server_certificate]};" if config.key?(:trust_server_certificate)
46
+ app = config[:application_name] || config[:appname] || config[:application]
47
+ url << "applicationName=#{app};" if app
48
+ isc = config[:integrated_security] # Win only - needs sqljdbc_auth.dll
49
+ url << "integratedSecurity=#{isc};" unless isc.nil?
50
+ url
51
+ end
52
+ end
53
+ end
@@ -9,17 +9,26 @@ module ActiveRecord
9
9
  def initialize(name, raw_default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
10
10
  @table_name = table_name
11
11
 
12
- default = extract_default(raw_default)
12
+ default_val, default_fun = extract_default(raw_default)
13
13
 
14
- super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
14
+ super(name, default_val, sql_type_metadata, null, default_fun, collation: collation, comment: comment)
15
15
  end
16
16
 
17
17
  def extract_default(value)
18
- # return nil if default does not match the patterns to avoid
19
- # any unexpected errors.
20
- return unless value =~ /^\(N?'(.*)'\)$/m || value =~ /^\(\(?(.*?)\)?\)$/
21
-
22
- unquote_string(Regexp.last_match[1])
18
+ return [nil, nil] unless value
19
+
20
+ case value
21
+ when /\A\(N?'(.*)'\)\Z/m
22
+ [unquote_string(Regexp.last_match[1]), nil]
23
+ when /\A\(\((.*)\)\)\Z/
24
+ [unquote_string(Regexp.last_match[1]), nil]
25
+ when /\A\((\w+\(\))\)\Z/
26
+ [nil, unquote_string(Regexp.last_match[1])]
27
+ else
28
+ # return nil if default does not match the patterns to avoid
29
+ # any unexpected errors.
30
+ [nil, nil]
31
+ end
23
32
  end
24
33
 
25
34
  def unquote_string(string)
@@ -31,6 +31,8 @@ module ActiveRecord
31
31
 
32
32
  def write_query?(sql) # :nodoc:
33
33
  !READ_QUERY.match?(sql)
34
+ rescue ArgumentError # Invalid encoding
35
+ !READ_QUERY.match?(sql.b)
34
36
  end
35
37
 
36
38
  # Internal method to test different isolation levels supported by this
@@ -90,7 +92,7 @@ module ActiveRecord
90
92
  end
91
93
  end
92
94
 
93
- def internal_exec_query(sql, name = 'SQL', binds = [], prepare: false, async: false)
95
+ def internal_exec_query(sql, name = 'SQL', binds = [], prepare: false, async: false, allow_retry: false)
94
96
  sql = transform_query(sql)
95
97
 
96
98
  check_if_write_query(sql)
@@ -100,6 +102,8 @@ module ActiveRecord
100
102
  # binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
101
103
 
102
104
  # puts "internal----->sql: #{sql}, binds: #{binds}"
105
+ type_casted_binds = type_casted_binds(binds)
106
+
103
107
  if without_prepared_statement?(binds)
104
108
  log(sql, name) do
105
109
  with_raw_connection do |conn|
@@ -111,13 +115,13 @@ module ActiveRecord
111
115
  end
112
116
  end
113
117
  else
114
- log(sql, name, binds) do
118
+ log(sql, name, type_casted_binds) do
115
119
  with_raw_connection do |conn|
116
120
  # this is different from normal AR that always caches
117
121
  cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
118
122
 
119
123
  result = conditional_indentity_insert(sql) do
120
- conn.execute_prepared_query(sql, binds, cached_statement)
124
+ conn.execute_prepared_query(sql, type_casted_binds, cached_statement)
121
125
  end
122
126
  verified!
123
127
  result
@@ -176,6 +180,9 @@ module ActiveRecord
176
180
  log(sql, name, binds) do
177
181
  with_raw_connection do |conn|
178
182
  result = conditional_indentity_insert(sql) do
183
+ # DEPRECATION WARNING: to_time will always preserve the timezone offset of the receiver in Rails 8.0.
184
+ # To opt in to the new behavior, set `ActiveSupport.to_time_preserves_timezone = true`.
185
+ # (called from block in execute_insert_pk
179
186
  conn.execute_insert_pk(sql, binds, pk)
180
187
  end
181
188
  verified!
@@ -244,7 +251,7 @@ module ActiveRecord
244
251
  end
245
252
 
246
253
  def raw_jdbc_connection
247
- @raw_connection
254
+ any_raw_connection
248
255
  end
249
256
 
250
257
  # It seems the truncate_tables is mostly used for testing
@@ -4,6 +4,62 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module MSSQL
6
6
  module Quoting
7
+ extend ActiveSupport::Concern
8
+
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # \[table_name\].\[column_name\] | function(one or no argument)
19
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
20
+ )
21
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ # \[table_name\].\[column_name\] | function(one or no argument)
34
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
35
+ )
36
+ (?:\s+ASC|\s+DESC)?
37
+ )
38
+ (?:\s*,\s*\g<1>)*
39
+ \z
40
+ /ix
41
+ end
42
+
43
+ def quote_column_name(name)
44
+ QUOTED_COLUMN_NAMES[name] ||= mssql_quote_column_name(name)
45
+ end
46
+
47
+ def quote_table_name(name)
48
+ QUOTED_TABLE_NAMES[name] ||= mssql_quote_column_name(name)
49
+ end
50
+
51
+ def mssql_quote_column_name(name)
52
+ name = name.to_s.split(".")
53
+ name.map! { |n| mssql_quote_name_part(n) } # "[#{name}]"
54
+ name.join(".")
55
+ end
56
+
57
+ # Implements the quoting style for SQL Server
58
+ def mssql_quote_name_part(part)
59
+ part =~ /^\[.*\]$/ ? part : "[#{part.gsub(']', ']]')}]"
60
+ end
61
+ end
62
+
7
63
  QUOTED_TRUE = '1'
8
64
  QUOTED_FALSE = '0'
9
65
 
@@ -88,42 +144,6 @@ module ActiveRecord
88
144
  # @see #quote in old adapter
89
145
  BLOB_VALUE_MARKER = "''"
90
146
 
91
- def column_name_matcher
92
- COLUMN_NAME
93
- end
94
-
95
- def column_name_with_order_matcher
96
- COLUMN_NAME_WITH_ORDER
97
- end
98
-
99
- COLUMN_NAME = /
100
- \A
101
- (
102
- (?:
103
- # \[table_name\].\[column_name\] | function(one or no argument)
104
- ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
105
- )
106
- (?:\s+AS\s+(?:\w+|\[\w+\]))?
107
- )
108
- (?:\s*,\s*\g<1>)*
109
- \z
110
- /ix
111
-
112
- COLUMN_NAME_WITH_ORDER = /
113
- \A
114
- (
115
- (?:
116
- # \[table_name\].\[column_name\] | function(one or no argument)
117
- ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
118
- )
119
- (?:\s+ASC|\s+DESC)?
120
- )
121
- (?:\s*,\s*\g<1>)*
122
- \z
123
- /ix
124
-
125
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
126
-
127
147
  private
128
148
 
129
149
  def time_with_db_timezone(value)
@@ -38,6 +38,22 @@ module ActiveRecord
38
38
  end
39
39
  end
40
40
 
41
+ def visit_CreateIndexDefinition(o)
42
+ index = o.index
43
+
44
+ sql = []
45
+ sql << "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}')" if o.if_not_exists
46
+ sql << "CREATE"
47
+ sql << "UNIQUE" if index.unique
48
+ sql << index.type.upcase if index.type
49
+ sql << "INDEX"
50
+ sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
51
+ sql << "(#{quoted_columns(index)})"
52
+ sql << "WHERE #{index.where}" if index.where
53
+
54
+ sql.join(" ")
55
+ end
56
+
41
57
  def add_column_options!(sql, options)
42
58
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
43
59
 
@@ -80,8 +80,50 @@ module ActiveRecord
80
80
  valid_raw_connection.primary_keys(table_name)
81
81
  end
82
82
 
83
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
84
+ td = create_table_definition(table_name)
85
+ cd = td.new_column_definition(column_name, type, **options)
86
+ ChangeColumnDefinition.new(cd, column_name)
87
+ end
88
+
89
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
90
+ column = column_for(table_name, column_name)
91
+ return unless column
92
+
93
+ default = extract_new_default_value(default_or_changes)
94
+ ChangeColumnDefaultDefinition.new(column, default)
95
+ end
96
+
83
97
  def foreign_keys(table_name)
84
- valid_raw_connection.foreign_keys(table_name)
98
+ # valid_raw_connection.foreign_keys(table_name)
99
+ fk_info = execute_procedure(:sp_fkeys, nil, nil, nil, table_name, nil)
100
+
101
+ grouped_fk = fk_info.group_by { |row| row["FK_NAME"] }.values.each { |group| group.sort_by! { |row| row["KEY_SEQ"] } }
102
+ grouped_fk.map do |group|
103
+ row = group.first
104
+ options = {
105
+ name: row["FK_NAME"],
106
+ on_update: extract_foreign_key_action("update", row["FK_NAME"]),
107
+ on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
108
+ }
109
+
110
+ if group.one?
111
+ options[:column] = row["FKCOLUMN_NAME"]
112
+ options[:primary_key] = row["PKCOLUMN_NAME"]
113
+ else
114
+ options[:column] = group.map { |row| row["FKCOLUMN_NAME"] }
115
+ options[:primary_key] = group.map { |row| row["PKCOLUMN_NAME"] }
116
+ end
117
+
118
+ ForeignKeyDefinition.new(table_name, row["PKTABLE_NAME"], options)
119
+ end
120
+ end
121
+
122
+ def extract_foreign_key_action(action, fk_name)
123
+ case select_value("SELECT #{action}_referential_action_desc FROM sys.foreign_keys WHERE name = '#{fk_name}'")
124
+ when "CASCADE" then :cascade
125
+ when "SET_NULL" then :nullify
126
+ end
85
127
  end
86
128
 
87
129
  def charset
@@ -182,20 +224,8 @@ module ActiveRecord
182
224
  rename_table_indexes(table_name, new_name)
183
225
  end
184
226
 
185
- # This is the same as the abstract method
186
- def quote_table_name(name)
187
- quote_column_name(name)
188
- end
189
-
190
- # This overrides the abstract method to be specific to SQL Server.
191
- def quote_column_name(name)
192
- name = name.to_s.split('.')
193
- name.map! { |n| quote_name_part(n) } # "[#{name}]"
194
- name.join('.')
195
- end
196
-
197
227
  def quote_database_name(name)
198
- quote_name_part(name.to_s)
228
+ self.class.mssql_quote_name_part(name.to_s)
199
229
  end
200
230
 
201
231
  # @private these cannot specify a limit
@@ -439,11 +469,6 @@ module ActiveRecord
439
469
  result
440
470
  end
441
471
 
442
- # Implements the quoting style for SQL Server
443
- def quote_name_part(part)
444
- part =~ /^\[.*\]$/ ? part : "[#{part.gsub(']', ']]')}]"
445
- end
446
-
447
472
  def remove_check_constraints(table_name, column_name)
448
473
  constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", 'SCHEMA'
449
474
  constraints.each do |constraint|
data/lib/arjdbc/mssql.rb CHANGED
@@ -6,4 +6,4 @@ module ArJdbc
6
6
  MsSQL = MSSQL # compatibility with 1.2
7
7
  end
8
8
 
9
- ArJdbc.warn_unsupported_adapter 'mssql', [7, 1] # warns on AR >= 4.2
9
+ ArJdbc.warn_unsupported_adapter "mssql", [7, 2] # warns on AR >= 4.2