activerecord-sqlserver-adapter 4.2.6 → 4.2.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|