activerecord-jdbcsqlserver-adapter 50.0.0 → 52.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.travis.yml +4 -5
- data/CHANGELOG.md +22 -101
- data/{Dockerfile → Dockerfile.ci} +0 -0
- data/Gemfile +1 -3
- data/README.md +5 -9
- data/VERSION +1 -1
- data/activerecord-jdbcsqlserver-adapter.gemspec +2 -2
- data/appveyor.yml +1 -1
- data/docker-compose.ci.yml +7 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +51 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +18 -20
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +5 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +43 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +94 -28
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +5 -25
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -1
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +23 -2
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +110 -74
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +15 -7
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -6
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +47 -24
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -3
- data/lib/activerecord-jdbcsqlserver-adapter.rb +4 -1
- data/lib/arel/visitors/sqlserver.rb +17 -4
- data/lib/arel_sqlserver.rb +0 -1
- data/lib/jdbc_mssql_driver_loader.rb +22 -0
- data/test/bin/install-freetds.sh +18 -0
- data/test/bin/setup.sh +19 -0
- data/test/cases/adapter_test_sqlserver.rb +43 -39
- data/test/cases/change_column_null_test_sqlserver.rb +42 -0
- data/test/cases/coerced_tests.rb +419 -39
- data/test/cases/column_test_sqlserver.rb +496 -462
- data/test/cases/connection_test_sqlserver.rb +2 -2
- data/test/cases/fetch_test_sqlserver.rb +5 -5
- data/test/cases/helper_sqlserver.rb +12 -1
- data/test/cases/json_test_sqlserver.rb +6 -6
- data/test/cases/migration_test_sqlserver.rb +13 -3
- data/test/cases/order_test_sqlserver.rb +19 -19
- data/test/cases/pessimistic_locking_test_sqlserver.rb +37 -20
- data/test/cases/rake_test_sqlserver.rb +20 -20
- data/test/cases/schema_dumper_test_sqlserver.rb +44 -43
- data/test/cases/schema_test_sqlserver.rb +2 -2
- data/test/cases/showplan_test_sqlserver.rb +25 -10
- data/test/cases/specific_schema_test_sqlserver.rb +11 -17
- data/test/cases/transaction_test_sqlserver.rb +9 -9
- data/test/cases/trigger_test_sqlserver.rb +31 -0
- data/test/cases/utils_test_sqlserver.rb +36 -36
- data/test/cases/uuid_test_sqlserver.rb +8 -8
- data/test/config.yml +2 -2
- data/test/migrations/create_clients_and_change_column_null.rb +23 -0
- data/test/models/sqlserver/trigger.rb +7 -0
- data/test/models/sqlserver/trigger_history.rb +3 -0
- data/test/schema/datatypes/2012.sql +1 -0
- data/test/schema/sqlserver_specific_schema.rb +47 -5
- data/test/support/core_ext/query_cache.rb +29 -0
- data/test/support/sql_counter_sqlserver.rb +1 -1
- metadata +32 -15
- data/RAILS5-TODO.md +0 -5
- data/test/models/sqlserver/dot_table_name.rb +0 -3
@@ -5,10 +5,13 @@ module ActiveRecord
|
|
5
5
|
module ColumnMethods
|
6
6
|
|
7
7
|
def primary_key(name, type = :primary_key, **options)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
if [:integer, :bigint].include?(type)
|
9
|
+
options[:is_identity] = true unless options.key?(:default)
|
10
|
+
elsif type == :uuid
|
11
|
+
options[:default] = options.fetch(:default, 'NEWID()')
|
12
|
+
options[:primary_key] = true
|
13
|
+
end
|
14
|
+
super
|
12
15
|
end
|
13
16
|
|
14
17
|
def primary_key_nonclustered(*args, **options)
|
@@ -98,9 +101,14 @@ module ActiveRecord
|
|
98
101
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
99
102
|
include ColumnMethods
|
100
103
|
|
101
|
-
def new_column_definition(name, type, options)
|
102
|
-
|
103
|
-
|
104
|
+
def new_column_definition(name, type, **options)
|
105
|
+
case type
|
106
|
+
when :datetime
|
107
|
+
type = :datetime2 if options[:precision]
|
108
|
+
when :primary_key
|
109
|
+
options[:is_identity] = true
|
110
|
+
end
|
111
|
+
super
|
104
112
|
end
|
105
113
|
end
|
106
114
|
|
@@ -26,13 +26,13 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
end
|
28
28
|
|
29
|
-
Transaction.send :
|
29
|
+
Transaction.send :prepend, SQLServerTransaction
|
30
30
|
|
31
31
|
module SQLServerRealTransaction
|
32
32
|
|
33
33
|
attr_reader :starting_isolation_level
|
34
34
|
|
35
|
-
def initialize(connection, options,
|
35
|
+
def initialize(connection, options, *args)
|
36
36
|
@connection = connection
|
37
37
|
@starting_isolation_level = current_isolation_level if options[:isolation]
|
38
38
|
super
|
@@ -58,7 +58,6 @@ module ActiveRecord
|
|
58
58
|
|
59
59
|
end
|
60
60
|
|
61
|
-
RealTransaction.send :
|
62
|
-
|
61
|
+
RealTransaction.send :prepend, SQLServerRealTransaction
|
63
62
|
end
|
64
63
|
end
|
@@ -51,7 +51,9 @@ module ActiveRecord
|
|
51
51
|
private
|
52
52
|
|
53
53
|
def fast_string_to_time(string)
|
54
|
-
|
54
|
+
time = ActiveSupport::TimeZone['UTC'].strptime(string, fast_string_to_time_format)
|
55
|
+
new_time(time.year, time.month, time.day, time.hour,
|
56
|
+
time.min, time.sec, Rational(time.nsec, 1_000))
|
55
57
|
rescue ArgumentError
|
56
58
|
super
|
57
59
|
end
|
@@ -59,11 +61,6 @@ module ActiveRecord
|
|
59
61
|
def fast_string_to_time_format
|
60
62
|
"#{::Time::DATE_FORMATS[:_sqlserver_datetime]}.%N".freeze
|
61
63
|
end
|
62
|
-
|
63
|
-
def fast_string_to_time_zone
|
64
|
-
::Time.zone || ActiveSupport::TimeZone['UTC']
|
65
|
-
end
|
66
|
-
|
67
64
|
end
|
68
65
|
end
|
69
66
|
end
|
@@ -3,9 +3,12 @@ require 'active_record'
|
|
3
3
|
require 'arel_sqlserver'
|
4
4
|
require 'active_record/connection_adapters/abstract_adapter'
|
5
5
|
require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
|
6
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/calculations'
|
6
7
|
require 'active_record/connection_adapters/sqlserver/core_ext/explain' unless defined? JRUBY_VERSION
|
7
8
|
require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
|
8
9
|
require 'active_record/connection_adapters/sqlserver/core_ext/attribute_methods'
|
10
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/finder_methods'
|
11
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/query_methods'
|
9
12
|
require 'active_record/connection_adapters/sqlserver/version'
|
10
13
|
require 'active_record/connection_adapters/sqlserver/type'
|
11
14
|
require 'active_record/connection_adapters/sqlserver/database_limits'
|
@@ -33,7 +36,6 @@ module ActiveRecord
|
|
33
36
|
SQLServer::Quoting,
|
34
37
|
SQLServer::DatabaseStatements,
|
35
38
|
SQLServer::Showplan,
|
36
|
-
SQLServer::SchemaDumper,
|
37
39
|
SQLServer::SchemaStatements,
|
38
40
|
SQLServer::DatabaseLimits,
|
39
41
|
SQLServer::DatabaseTasks
|
@@ -52,15 +54,20 @@ module ActiveRecord
|
|
52
54
|
|
53
55
|
ADAPTER_NAME = 'SQLServer'.freeze
|
54
56
|
|
57
|
+
# Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
|
58
|
+
DEFAULT_TIME_PRECISION = 7
|
59
|
+
|
55
60
|
attr_reader :spid
|
56
61
|
|
57
62
|
cattr_accessor :cs_equality_operator, instance_accessor: false
|
58
63
|
cattr_accessor :use_output_inserted, instance_accessor: false
|
64
|
+
cattr_accessor :exclude_output_inserted_table_names, instance_accessor: false
|
59
65
|
cattr_accessor :showplan_option, instance_accessor: false
|
60
66
|
cattr_accessor :lowercase_schema_reflection
|
61
67
|
|
62
68
|
self.cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
|
63
69
|
self.use_output_inserted = true
|
70
|
+
self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
|
64
71
|
|
65
72
|
def initialize(connection, logger = nil, config = {})
|
66
73
|
super(connection, logger, config)
|
@@ -85,14 +92,6 @@ module ActiveRecord
|
|
85
92
|
SQLServer::SchemaCreation.new self
|
86
93
|
end
|
87
94
|
|
88
|
-
def supports_migrations?
|
89
|
-
true
|
90
|
-
end
|
91
|
-
|
92
|
-
def supports_primary_key?
|
93
|
-
true
|
94
|
-
end
|
95
|
-
|
96
95
|
def supports_ddl_transactions?
|
97
96
|
true
|
98
97
|
end
|
@@ -153,16 +152,20 @@ module ActiveRecord
|
|
153
152
|
false
|
154
153
|
end
|
155
154
|
|
155
|
+
def supports_savepoints?
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
156
159
|
def supports_in_memory_oltp?
|
157
160
|
@version_year >= 2014
|
158
161
|
end
|
159
162
|
|
160
163
|
def disable_referential_integrity
|
161
164
|
tables = tables_with_referential_integrity
|
162
|
-
tables.each { |t| do_execute "ALTER TABLE #{t} NOCHECK CONSTRAINT ALL" }
|
165
|
+
tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
|
163
166
|
yield
|
164
167
|
ensure
|
165
|
-
tables.each { |t| do_execute "ALTER TABLE #{t} CHECK CONSTRAINT ALL" }
|
168
|
+
tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} CHECK CONSTRAINT ALL" }
|
166
169
|
end
|
167
170
|
|
168
171
|
# === Abstract Adapter (Connection Management) ================== #
|
@@ -206,7 +209,7 @@ module ActiveRecord
|
|
206
209
|
|
207
210
|
def tables_with_referential_integrity
|
208
211
|
schemas_and_tables = select_rows <<-SQL.strip_heredoc
|
209
|
-
SELECT s.name AS schema_name, o.name AS table_name
|
212
|
+
SELECT DISTINCT s.name AS schema_name, o.name AS table_name
|
210
213
|
FROM sys.foreign_keys i
|
211
214
|
INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
|
212
215
|
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
@@ -229,7 +232,7 @@ module ActiveRecord
|
|
229
232
|
end
|
230
233
|
|
231
234
|
def sqlserver_azure?
|
232
|
-
|
235
|
+
!!(sqlserver_version =~ /Azure/i)
|
233
236
|
end
|
234
237
|
|
235
238
|
def database_prefix_remote_server?
|
@@ -242,6 +245,14 @@ module ActiveRecord
|
|
242
245
|
@connection_options[:database_prefix]
|
243
246
|
end
|
244
247
|
|
248
|
+
def database_prefix_identifier(name)
|
249
|
+
if database_prefix_remote_server?
|
250
|
+
SQLServer::Utils.extract_identifiers("#{database_prefix}#{name}")
|
251
|
+
else
|
252
|
+
SQLServer::Utils.extract_identifiers(name)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
245
256
|
def version
|
246
257
|
self.class::VERSION
|
247
258
|
end
|
@@ -262,7 +273,7 @@ module ActiveRecord
|
|
262
273
|
|
263
274
|
# === Abstract Adapter (Misc Support) =========================== #
|
264
275
|
|
265
|
-
def initialize_type_map(m)
|
276
|
+
def initialize_type_map(m = type_map)
|
266
277
|
m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
|
267
278
|
# Exact Numerics
|
268
279
|
register_class_with_limit m, 'bigint(8)', SQLServer::Type::BigInteger
|
@@ -302,8 +313,7 @@ module ActiveRecord
|
|
302
313
|
end
|
303
314
|
m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
|
304
315
|
m.register_type %r{\Atime}i do |sql_type|
|
305
|
-
|
306
|
-
precision = extract_precision(sql_type)
|
316
|
+
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
307
317
|
SQLServer::Type::Time.new precision: precision
|
308
318
|
end
|
309
319
|
# Character Strings
|
@@ -339,6 +349,20 @@ module ActiveRecord
|
|
339
349
|
NoDatabaseError.new(message)
|
340
350
|
when /data would be truncated/
|
341
351
|
ValueTooLong.new(message)
|
352
|
+
when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
|
353
|
+
pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
|
354
|
+
MismatchedForeignKey.new(
|
355
|
+
self,
|
356
|
+
message: message,
|
357
|
+
table: fk_id.schema,
|
358
|
+
foreign_key: fk_id.object,
|
359
|
+
target_table: pk_id.schema,
|
360
|
+
primary_key: pk_id.object
|
361
|
+
)
|
362
|
+
when /Cannot insert the value NULL into column.*does not allow nulls/
|
363
|
+
NotNullViolation.new(message)
|
364
|
+
when /Arithmetic overflow error/
|
365
|
+
RangeError.new(message)
|
342
366
|
else
|
343
367
|
super
|
344
368
|
end
|
@@ -430,16 +454,15 @@ module ActiveRecord
|
|
430
454
|
end
|
431
455
|
|
432
456
|
def version_year
|
433
|
-
return
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
/SQL Server (\d+)/.match(vstring).to_a.last.to_s.to_i
|
438
|
-
rescue Exception => e
|
439
|
-
2016
|
440
|
-
end
|
457
|
+
return 2016 if sqlserver_version =~ /vNext/
|
458
|
+
/SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
|
459
|
+
rescue StandardError => e
|
460
|
+
2016
|
441
461
|
end
|
442
462
|
|
463
|
+
def sqlserver_version
|
464
|
+
@sqlserver_version ||= _raw_select('SELECT @@version', fetch: :rows).first.first.to_s
|
465
|
+
end
|
443
466
|
end
|
444
467
|
end
|
445
468
|
end
|
@@ -48,10 +48,12 @@ module ActiveRecord
|
|
48
48
|
create true
|
49
49
|
end
|
50
50
|
|
51
|
-
def structure_dump(filename)
|
51
|
+
def structure_dump(filename, extra_flags)
|
52
|
+
server_arg = "-S #{Shellwords.escape(configuration['host'])}"
|
53
|
+
server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration['port']
|
52
54
|
command = [
|
53
55
|
"defncopy",
|
54
|
-
|
56
|
+
server_arg,
|
55
57
|
"-D #{Shellwords.escape(configuration['database'])}",
|
56
58
|
"-U #{Shellwords.escape(configuration['username'])}",
|
57
59
|
"-P #{Shellwords.escape(configuration['password'])}",
|
@@ -71,7 +73,7 @@ module ActiveRecord
|
|
71
73
|
File.open(filename, "w") { |file| file.puts dump }
|
72
74
|
end
|
73
75
|
|
74
|
-
def structure_load(filename)
|
76
|
+
def structure_load(filename, extra_flags)
|
75
77
|
connection.execute File.read(filename)
|
76
78
|
end
|
77
79
|
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# Our core date/time overrides to support prepared statements
|
2
2
|
require 'active_record/connection_adapters/sqlserver/core_ext/date_time'
|
3
3
|
|
4
|
+
require 'active_support' # Need this for the next line
|
5
|
+
require 'active_record/log_subscriber' # Need to make sure this is loaded before we load Core for monkey patching
|
6
|
+
|
4
7
|
# Load the jar file for the jdbc driver
|
5
|
-
|
8
|
+
require_relative './jdbc_mssql_driver_loader'
|
6
9
|
|
7
10
|
# Standadard arjdbc functionality
|
8
11
|
require 'arjdbc/abstract/connection_management'
|
@@ -14,7 +14,7 @@ module Arel
|
|
14
14
|
# SQLServer ToSql/Visitor (Overides)
|
15
15
|
|
16
16
|
def visit_Arel_Nodes_BindParam o, collector
|
17
|
-
collector.add_bind(o) { |i| "@#{i-1}" }
|
17
|
+
collector.add_bind(o.value) { |i| "@#{i-1}" }
|
18
18
|
end unless defined? JRUBY_VERSION # converts bind argument markers "?" to "@n", but JDBC wants "?"
|
19
19
|
|
20
20
|
def visit_Arel_Nodes_Bin o, collector
|
@@ -95,17 +95,29 @@ module Arel
|
|
95
95
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
96
96
|
end
|
97
97
|
if o.right.any?
|
98
|
-
collector <<
|
98
|
+
collector << SPACE if o.left
|
99
99
|
collector = inject_join o.right, collector, ' '
|
100
100
|
end
|
101
101
|
collector
|
102
102
|
end
|
103
103
|
|
104
|
+
def visit_Arel_Nodes_InnerJoin o, collector
|
105
|
+
collector << "INNER JOIN "
|
106
|
+
collector = visit o.left, collector
|
107
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
108
|
+
if o.right
|
109
|
+
collector << SPACE
|
110
|
+
visit(o.right, collector)
|
111
|
+
else
|
112
|
+
collector
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
104
116
|
def visit_Arel_Nodes_OuterJoin o, collector
|
105
117
|
collector << "LEFT OUTER JOIN "
|
106
118
|
collector = visit o.left, collector
|
107
119
|
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
108
|
-
collector <<
|
120
|
+
collector << SPACE
|
109
121
|
visit o.right, collector
|
110
122
|
end
|
111
123
|
|
@@ -190,7 +202,8 @@ module Arel
|
|
190
202
|
|
191
203
|
def primary_Key_From_Table t
|
192
204
|
return unless t
|
193
|
-
column_name = schema_cache.primary_keys(t.name) ||
|
205
|
+
column_name = @connection.schema_cache.primary_keys(t.name) ||
|
206
|
+
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
194
207
|
column_name ? t[column_name] : nil
|
195
208
|
end
|
196
209
|
|
data/lib/arel_sqlserver.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
module JdbcMssqlDriverLoader
|
2
|
+
def self.check_and_maybe_load_driver
|
3
|
+
driver_name = "com.microsoft.sqlserver.jdbc.SQLServerDriver"
|
4
|
+
if (Java::JavaClass.for_name(driver_name) rescue nil)
|
5
|
+
driver = Java::ComMicrosoftSqlserverJdbc::SQLServerDriver.new
|
6
|
+
which = driver
|
7
|
+
.getClass().getClassLoader().loadClass(driver_name)
|
8
|
+
.getProtectionDomain().getCodeSource().getLocation().to_s
|
9
|
+
warn "You already required a mssql jdbc driver (#{which}), skipping gem jdbc-mssql"
|
10
|
+
|
11
|
+
major_version = driver.major_version
|
12
|
+
required_major_version = 8
|
13
|
+
if major_version < required_major_version
|
14
|
+
raise "MSSQL jdbc driver version is to old (given major version #{major_version} < required major version #{required_major_version})"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
require "jdbc/mssql"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
check_and_maybe_load_driver
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -x
|
4
|
+
set -e
|
5
|
+
|
6
|
+
FREETDS_VERSION=1.00.21
|
7
|
+
|
8
|
+
wget http://www.freetds.org/files/stable/freetds-$FREETDS_VERSION.tar.gz
|
9
|
+
tar -xzf freetds-$FREETDS_VERSION.tar.gz
|
10
|
+
cd freetds-$FREETDS_VERSION
|
11
|
+
./configure --prefix=/opt/local \
|
12
|
+
--with-openssl=/opt/local \
|
13
|
+
--with-tdsver=7.3
|
14
|
+
make
|
15
|
+
make install
|
16
|
+
cd ..
|
17
|
+
rm -rf freetds-$FREETDS_VERSION
|
18
|
+
rm freetds-$FREETDS_VERSION.tar.gz
|