activerecord-sqlserver-adapter 4.2.6 → 4.2.8
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/CHANGELOG.md +34 -0
- data/Gemfile +9 -0
- data/README.md +40 -26
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter.gemspec +0 -10
- data/appveyor.yml +15 -3
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +35 -11
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -16
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +12 -2
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +8 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +18 -12
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +13 -29
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +72 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +12 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +41 -5
- data/lib/active_record/sqlserver_base.rb +2 -4
- data/lib/arel/visitors/sqlserver.rb +19 -0
- data/test/cases/adapter_test_sqlserver.rb +24 -0
- data/test/cases/coerced_tests.rb +14 -0
- data/test/cases/column_test_sqlserver.rb +120 -47
- data/test/cases/connection_test_sqlserver.rb +3 -3
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
- data/test/cases/helper_sqlserver.rb +24 -16
- data/test/cases/migration_test_sqlserver.rb +0 -5
- data/test/cases/rake_test_sqlserver.rb +29 -10
- data/test/cases/schema_dumper_test_sqlserver.rb +28 -11
- data/test/cases/transaction_test_sqlserver.rb +2 -2
- data/test/cases/utils_test_sqlserver.rb +44 -14
- data/test/config.yml +2 -0
- data/test/debug.rb +14 -0
- data/test/schema/datatypes/2012.sql +8 -18
- data/test/schema/sqlserver_specific_schema.rb +14 -12
- data/test/support/connection_reflection.rb +37 -0
- metadata +13 -143
- data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +0 -32
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SQLServer
|
4
|
+
module Type
|
5
|
+
class DateTimeOffset < DateTime2
|
6
|
+
|
7
|
+
def type
|
8
|
+
:datetimeoffset
|
9
|
+
end
|
10
|
+
|
11
|
+
def type_cast_for_database(value)
|
12
|
+
return super unless value.acts_like?(:time)
|
13
|
+
value.to_s :_sqlserver_datetimeoffset
|
14
|
+
end
|
15
|
+
|
16
|
+
def type_cast_for_schema(value)
|
17
|
+
type_cast_for_database(value).inspect
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def zone_conversion(value)
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,15 +4,15 @@ module ActiveRecord
|
|
4
4
|
module Type
|
5
5
|
class SmallDateTime < DateTime
|
6
6
|
|
7
|
+
def type
|
8
|
+
:smalldatetime
|
9
|
+
end
|
7
10
|
|
8
|
-
private
|
9
11
|
|
10
|
-
|
11
|
-
0
|
12
|
-
end
|
12
|
+
private
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def cast_fractional(value)
|
15
|
+
value.change usec: 0
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
@@ -1,52 +1,36 @@
|
|
1
|
-
Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module ConnectionAdapters
|
5
3
|
module SQLServer
|
6
4
|
module Type
|
7
5
|
class Time < ActiveRecord::Type::Time
|
8
6
|
|
9
|
-
|
10
|
-
super
|
11
|
-
@precision = nil if @precision == 7
|
12
|
-
end
|
7
|
+
include TimeValueFractional2
|
13
8
|
|
14
9
|
def type_cast_for_database(value)
|
15
|
-
return
|
16
|
-
|
10
|
+
return super unless value.acts_like?(:time)
|
11
|
+
time = value.to_s(:_sqlserver_time)
|
12
|
+
"#{time}".tap do |v|
|
13
|
+
fraction = quote_fractional(value)
|
14
|
+
v << ".#{fraction}" unless fraction.to_i.zero?
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
18
|
def type_cast_for_schema(value)
|
20
|
-
value.
|
21
|
-
end
|
22
|
-
|
23
|
-
def quote_ss(value)
|
24
|
-
return unless value
|
25
|
-
value = cast_value(value) if value.acts_like?(:string)
|
26
|
-
date = value.to_s(:_sqlserver_time)
|
27
|
-
frac = quote_usec(value)
|
28
|
-
"'#{date}.#{frac}'"
|
19
|
+
type_cast_for_database(value).inspect
|
29
20
|
end
|
30
21
|
|
31
22
|
|
32
23
|
private
|
33
24
|
|
34
25
|
def cast_value(value)
|
35
|
-
value = value.
|
26
|
+
value = value.acts_like?(:time) ? value : super
|
36
27
|
return if value.blank?
|
37
|
-
value.change
|
38
|
-
|
39
|
-
|
40
|
-
def cast_usec(value)
|
41
|
-
(usec_to_seconds_frction(value) * 1_000_000).to_i
|
42
|
-
end
|
43
|
-
|
44
|
-
def usec_to_seconds_frction(value)
|
45
|
-
(value.usec.to_f / 1_000_000.0).round(precision || 7)
|
28
|
+
value = value.change year: 2000, month: 01, day: 01
|
29
|
+
cast_fractional(value)
|
46
30
|
end
|
47
31
|
|
48
|
-
def
|
49
|
-
|
32
|
+
def fractional_scale
|
33
|
+
precision
|
50
34
|
end
|
51
35
|
|
52
36
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module SQLServer
|
4
|
+
module Type
|
5
|
+
|
6
|
+
module TimeValueFractional
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def cast_fractional(value)
|
11
|
+
return value if !value.respond_to?(fractional_property) || value.send(fractional_property).zero?
|
12
|
+
seconds = value.send(fractional_property).to_f / fractional_operator.to_f
|
13
|
+
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
|
14
|
+
frac_seconds = (seconds * fractional_operator).to_i
|
15
|
+
value.change fractional_property => frac_seconds
|
16
|
+
end
|
17
|
+
|
18
|
+
def quote_fractional(value)
|
19
|
+
seconds = (value.send(fractional_property).to_f / fractional_operator.to_f).round(fractional_scale)
|
20
|
+
seconds.to_s.split('.').last.to(fractional_scale-1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def fractional_property
|
24
|
+
:usec
|
25
|
+
end
|
26
|
+
|
27
|
+
def fractional_digits
|
28
|
+
6
|
29
|
+
end
|
30
|
+
|
31
|
+
def fractional_operator
|
32
|
+
10 ** fractional_digits
|
33
|
+
end
|
34
|
+
|
35
|
+
def fractional_precision
|
36
|
+
0.00333
|
37
|
+
end
|
38
|
+
|
39
|
+
def fractional_scale
|
40
|
+
3
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
module TimeValueFractional2
|
46
|
+
|
47
|
+
include TimeValueFractional
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def fractional_property
|
52
|
+
:nsec
|
53
|
+
end
|
54
|
+
|
55
|
+
def fractional_digits
|
56
|
+
9
|
57
|
+
end
|
58
|
+
|
59
|
+
def fractional_precision
|
60
|
+
0.0000001
|
61
|
+
end
|
62
|
+
|
63
|
+
def fractional_scale
|
64
|
+
precision
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -43,6 +43,10 @@ module ActiveRecord
|
|
43
43
|
[server_quoted, database_quoted].compact.join(SEPARATOR)
|
44
44
|
end
|
45
45
|
|
46
|
+
def fully_qualified?
|
47
|
+
parts.compact.size == 4
|
48
|
+
end
|
49
|
+
|
46
50
|
def to_s
|
47
51
|
quoted
|
48
52
|
end
|
@@ -51,6 +55,10 @@ module ActiveRecord
|
|
51
55
|
parts.map{ |p| quote(p) if p }.join SEPARATOR
|
52
56
|
end
|
53
57
|
|
58
|
+
def quoted_raw
|
59
|
+
quote @raw_name
|
60
|
+
end
|
61
|
+
|
54
62
|
def ==(o)
|
55
63
|
o.class == self.class && o.parts == parts
|
56
64
|
end
|
@@ -110,6 +118,10 @@ module ActiveRecord
|
|
110
118
|
s.to_s.gsub /\'/, "''"
|
111
119
|
end
|
112
120
|
|
121
|
+
def quoted_raw(name)
|
122
|
+
SQLServer::Utils::Name.new(name).quoted_raw
|
123
|
+
end
|
124
|
+
|
113
125
|
def unquote_string(s)
|
114
126
|
s.to_s.gsub(/\'\'/, "'")
|
115
127
|
end
|
@@ -118,18 +130,6 @@ module ActiveRecord
|
|
118
130
|
SQLServer::Utils::Name.new(name)
|
119
131
|
end
|
120
132
|
|
121
|
-
def with_sqlserver_db_date_formats
|
122
|
-
old_db_format_date = Date::DATE_FORMATS[:db]
|
123
|
-
old_db_format_time = Time::DATE_FORMATS[:db]
|
124
|
-
date_format = Date::DATE_FORMATS[:_sqlserver_dateformat]
|
125
|
-
Date::DATE_FORMATS[:db] = "#{date_format}"
|
126
|
-
Time::DATE_FORMATS[:db] = "#{date_format} %H:%M:%S"
|
127
|
-
yield
|
128
|
-
ensure
|
129
|
-
Date::DATE_FORMATS[:db] = old_db_format_date
|
130
|
-
Time::DATE_FORMATS[:db] = old_db_format_time
|
131
|
-
end
|
132
|
-
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
@@ -54,7 +54,6 @@ module ActiveRecord
|
|
54
54
|
@visitor = Arel::Visitors::SQLServer.new self
|
55
55
|
@prepared_statements = true
|
56
56
|
# Our Responsibility
|
57
|
-
@config = config
|
58
57
|
@connection_options = config
|
59
58
|
connect
|
60
59
|
@sqlserver_azure = !!(select_value('SELECT @@version', 'SCHEMA') =~ /Azure/i)
|
@@ -121,10 +120,11 @@ module ActiveRecord
|
|
121
120
|
end
|
122
121
|
|
123
122
|
def disable_referential_integrity
|
124
|
-
|
123
|
+
tables = tables_with_referential_integrity
|
124
|
+
tables.each { |t| do_execute "ALTER TABLE #{t} NOCHECK CONSTRAINT ALL" }
|
125
125
|
yield
|
126
126
|
ensure
|
127
|
-
do_execute "
|
127
|
+
tables.each { |t| do_execute "ALTER TABLE #{t} CHECK CONSTRAINT ALL" }
|
128
128
|
end
|
129
129
|
|
130
130
|
# === Abstract Adapter (Connection Management) ================== #
|
@@ -162,6 +162,19 @@ module ActiveRecord
|
|
162
162
|
|
163
163
|
# === Abstract Adapter (Misc Support) =========================== #
|
164
164
|
|
165
|
+
def tables_with_referential_integrity
|
166
|
+
schemas_and_tables = select_rows <<-SQL.strip_heredoc
|
167
|
+
SELECT s.name, o.name
|
168
|
+
FROM sys.foreign_keys i
|
169
|
+
INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
|
170
|
+
INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
|
171
|
+
SQL
|
172
|
+
schemas_and_tables.map do |schema_table|
|
173
|
+
schema, table = schema_table
|
174
|
+
"#{SQLServer::Utils.quoted_raw(schema)}.#{SQLServer::Utils.quoted_raw(table)}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
165
178
|
def pk_and_sequence_for(table_name)
|
166
179
|
pk = primary_key(table_name)
|
167
180
|
pk ? [pk, nil] : nil
|
@@ -181,6 +194,16 @@ module ActiveRecord
|
|
181
194
|
@sqlserver_azure
|
182
195
|
end
|
183
196
|
|
197
|
+
def database_prefix_remote_server?
|
198
|
+
return false if database_prefix.blank?
|
199
|
+
name = SQLServer::Utils.extract_identifiers(database_prefix)
|
200
|
+
name.fully_qualified? && name.object.blank?
|
201
|
+
end
|
202
|
+
|
203
|
+
def database_prefix
|
204
|
+
@connection_options[:database_prefix]
|
205
|
+
end
|
206
|
+
|
184
207
|
def version
|
185
208
|
self.class::VERSION
|
186
209
|
end
|
@@ -221,6 +244,14 @@ module ActiveRecord
|
|
221
244
|
# Date and Time
|
222
245
|
m.register_type 'date', SQLServer::Type::Date.new
|
223
246
|
m.register_type 'datetime', SQLServer::Type::DateTime.new
|
247
|
+
m.register_type %r{\Adatetime2}i do |sql_type|
|
248
|
+
precision = extract_precision(sql_type)
|
249
|
+
SQLServer::Type::DateTime2.new precision: precision
|
250
|
+
end
|
251
|
+
m.register_type %r{\Adatetimeoffset}i do |sql_type|
|
252
|
+
precision = extract_precision(sql_type)
|
253
|
+
SQLServer::Type::DateTimeOffset.new precision: precision
|
254
|
+
end
|
224
255
|
m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
|
225
256
|
m.register_type %r{\Atime}i do |sql_type|
|
226
257
|
scale = extract_scale(sql_type)
|
@@ -353,8 +384,13 @@ module ActiveRecord
|
|
353
384
|
a, b, c = @database_dateformat.each_char.to_a
|
354
385
|
[a, b, c].each { |f| f.upcase! if f == 'y' }
|
355
386
|
dateformat = "%#{a}-%#{b}-%#{c}"
|
356
|
-
::Date::DATE_FORMATS[:_sqlserver_dateformat]
|
357
|
-
::Time::DATE_FORMATS[:_sqlserver_dateformat]
|
387
|
+
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
388
|
+
::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
389
|
+
::Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
|
390
|
+
::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
|
391
|
+
::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
|
392
|
+
time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
|
393
|
+
}
|
358
394
|
end
|
359
395
|
|
360
396
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
|
3
|
-
|
4
|
-
def self.sqlserver_connection(config) #:nodoc:
|
2
|
+
module ConnectionHandling
|
3
|
+
def sqlserver_connection(config) #:nodoc:
|
5
4
|
config = config.symbolize_keys
|
6
5
|
config.reverse_merge! mode: :dblib
|
7
6
|
mode = config[:mode].to_s.downcase.underscore.to_sym
|
@@ -17,6 +16,5 @@ module ActiveRecord
|
|
17
16
|
end
|
18
17
|
ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(mode: mode))
|
19
18
|
end
|
20
|
-
|
21
19
|
end
|
22
20
|
end
|
@@ -73,6 +73,19 @@ module Arel
|
|
73
73
|
@select_statement = nil
|
74
74
|
end
|
75
75
|
|
76
|
+
def visit_Arel_Table o, collector
|
77
|
+
table_name = if o.engine.connection.database_prefix_remote_server?
|
78
|
+
remote_server_table_name(o)
|
79
|
+
else
|
80
|
+
quote_table_name(o.name)
|
81
|
+
end
|
82
|
+
if o.table_alias
|
83
|
+
collector << "#{table_name} #{quote_table_name o.table_alias}"
|
84
|
+
else
|
85
|
+
collector << table_name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
76
89
|
def visit_Arel_Nodes_JoinSource o, collector
|
77
90
|
if o.left
|
78
91
|
collector = visit o.left, collector
|
@@ -185,6 +198,12 @@ module Arel
|
|
185
198
|
column_name ? t[column_name] : nil
|
186
199
|
end
|
187
200
|
|
201
|
+
def remote_server_table_name o
|
202
|
+
ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
|
203
|
+
"#{o.engine.connection.database_prefix}#{o.name}"
|
204
|
+
).quoted
|
205
|
+
end
|
206
|
+
|
188
207
|
end
|
189
208
|
end
|
190
209
|
end
|
@@ -240,6 +240,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
240
240
|
describe 'database statements' do
|
241
241
|
|
242
242
|
it "run the database consistency checker useroptions command" do
|
243
|
+
skip 'on azure' if connection_sqlserver_azure?
|
243
244
|
keys = [:textsize, :language, :isolation_level, :dateformat]
|
244
245
|
user_options = connection.user_options
|
245
246
|
keys.each do |key|
|
@@ -249,6 +250,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
249
250
|
end
|
250
251
|
|
251
252
|
it "return a underscored key hash with indifferent access of the results" do
|
253
|
+
skip 'on azure' if connection_sqlserver_azure?
|
252
254
|
user_options = connection.user_options
|
253
255
|
assert_equal 'read committed', user_options['isolation_level']
|
254
256
|
assert_equal 'read committed', user_options[:isolation_level]
|
@@ -416,5 +418,27 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
416
418
|
|
417
419
|
end
|
418
420
|
|
421
|
+
describe 'database_prefix_remote_server?' do
|
422
|
+
|
423
|
+
after do
|
424
|
+
connection_options.delete(:database_prefix)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'returns false if database_prefix is not configured' do
|
428
|
+
assert_equal false, connection.database_prefix_remote_server?
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'returns true if database_prefix has been set' do
|
432
|
+
connection_options[:database_prefix] = "server.database.schema."
|
433
|
+
assert_equal true, connection.database_prefix_remote_server?
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'returns false if database_prefix has been set incorrectly' do
|
437
|
+
connection_options[:database_prefix] = "server.database.schema"
|
438
|
+
assert_equal false, connection.database_prefix_remote_server?
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
|
419
443
|
end
|
420
444
|
|