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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/Gemfile +9 -0
  4. data/README.md +40 -26
  5. data/VERSION +1 -0
  6. data/activerecord-sqlserver-adapter.gemspec +0 -10
  7. data/appveyor.yml +15 -3
  8. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +3 -3
  9. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +35 -11
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -16
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +12 -2
  12. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +8 -0
  13. data/lib/active_record/connection_adapters/sqlserver/type.rb +3 -1
  14. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +9 -0
  15. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +18 -12
  16. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  17. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
  18. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +6 -6
  19. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +13 -29
  20. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +72 -0
  21. data/lib/active_record/connection_adapters/sqlserver/utils.rb +12 -12
  22. data/lib/active_record/connection_adapters/sqlserver/version.rb +1 -1
  23. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +41 -5
  24. data/lib/active_record/sqlserver_base.rb +2 -4
  25. data/lib/arel/visitors/sqlserver.rb +19 -0
  26. data/test/cases/adapter_test_sqlserver.rb +24 -0
  27. data/test/cases/coerced_tests.rb +14 -0
  28. data/test/cases/column_test_sqlserver.rb +120 -47
  29. data/test/cases/connection_test_sqlserver.rb +3 -3
  30. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  31. data/test/cases/helper_sqlserver.rb +24 -16
  32. data/test/cases/migration_test_sqlserver.rb +0 -5
  33. data/test/cases/rake_test_sqlserver.rb +29 -10
  34. data/test/cases/schema_dumper_test_sqlserver.rb +28 -11
  35. data/test/cases/transaction_test_sqlserver.rb +2 -2
  36. data/test/cases/utils_test_sqlserver.rb +44 -14
  37. data/test/config.yml +2 -0
  38. data/test/debug.rb +14 -0
  39. data/test/schema/datatypes/2012.sql +8 -18
  40. data/test/schema/sqlserver_specific_schema.rb +14 -12
  41. data/test/support/connection_reflection.rb +37 -0
  42. metadata +13 -143
  43. data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +0 -32
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module Type
5
+ class DateTime2 < DateTime
6
+
7
+ include TimeValueFractional2
8
+
9
+ def type
10
+ :datetime2
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -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
- def cast_usec(value)
11
- 0
12
- end
12
+ private
13
13
 
14
- def cast_usec_for_database(value)
15
- '.000'
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
- def initialize(options = {})
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 if value.nil?
16
- Quoter.new super, self
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.acts_like?(:string) ? "'#{value}'" : super
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.respond_to?(:usec) ? value.change(year: 2000, month: 01, day: 01) : super
26
+ value = value.acts_like?(:time) ? value : super
36
27
  return if value.blank?
37
- value.change usec: cast_usec(value)
38
- end
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 quote_usec(value)
49
- usec_to_seconds_frction(value).to_s.split('.').last
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
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module SQLServer
4
4
  module Version
5
5
 
6
- VERSION = '4.2.6'
6
+ VERSION = File.read(File.expand_path("../../../../../VERSION", __FILE__)).chomp
7
7
 
8
8
  end
9
9
  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
- do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
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 "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
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] = dateformat
357
- ::Time::DATE_FORMATS[:_sqlserver_dateformat] = 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
- class Base
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