Skip to content

Commit 2578f0b

Browse files
committed
[Rails5] Support for supports_datetime_with_precision.
1 parent 4b341ef commit 2578f0b

File tree

8 files changed

+179
-69
lines changed

8 files changed

+179
-69
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#### Added
55

6-
* ...
6+
* Support for `supports_datetime_with_precision`.
77

88
#### Changed
99

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
188188
when 5..8 then 'bigint'
189189
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
190190
end
191+
when 'datetime2'
192+
column_type_sql = super
193+
if precision
194+
if (0..7) === precision
195+
column_type_sql << "(#{precision})"
196+
else
197+
raise(ActiveRecordError, "The dattime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
198+
end
199+
end
200+
column_type_sql
191201
else
192202
super
193203
end
@@ -202,6 +212,10 @@ def columns_for_distinct(columns, orders)
202212
[super, *order_columns].join(', ')
203213
end
204214

215+
def update_table_definition(table_name, base)
216+
SQLServer::Table.new(table_name, base)
217+
end
218+
205219
def change_column_null(table_name, column_name, allow_null, default = nil)
206220
table_id = SQLServer::Utils.extract_identifiers(table_name)
207221
column_id = SQLServer::Utils.extract_identifiers(column_name)
Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module ActiveRecord
22
module ConnectionAdapters
33
module SQLServer
4-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
4+
5+
module ColumnMethods
56

67
def primary_key(name, type = :primary_key, **options)
78
return super unless type == :uuid
@@ -10,67 +11,90 @@ def primary_key(name, type = :primary_key, **options)
1011
column name, type, options
1112
end
1213

13-
def real(name, options = {})
14-
column(name, :real, options)
14+
def real(*args, **options)
15+
args.each { |name| column(name, :real, options) }
16+
end
17+
18+
def money(*args, **options)
19+
args.each { |name| column(name, :money, options) }
1520
end
1621

17-
def money(name, options = {})
18-
column(name, :money, options)
22+
def datetime(*args, **options)
23+
args.each do |name|
24+
if options[:precision]
25+
datetime2(name, options)
26+
else
27+
column(name, :datetime, options)
28+
end
29+
end
1930
end
2031

21-
def datetime2(name, options = {})
22-
column(name, :datetime2, options)
32+
def datetime2(*args, **options)
33+
args.each { |name| column(name, :datetime2, options) }
2334
end
2435

25-
def datetimeoffset(name, options = {})
26-
column(name, :datetimeoffset, options)
36+
def datetimeoffset(*args, **options)
37+
args.each { |name| column(name, :datetimeoffset, options) }
2738
end
2839

29-
def smallmoney(name, options = {})
30-
column(name, :smallmoney, options)
40+
def smallmoney(*args, **options)
41+
args.each { |name| column(name, :smallmoney, options) }
3142
end
3243

33-
def char(name, options = {})
34-
column(name, :char, options)
44+
def char(*args, **options)
45+
args.each { |name| column(name, :char, options) }
3546
end
3647

37-
def varchar(name, options = {})
38-
column(name, :varchar, options)
48+
def varchar(*args, **options)
49+
args.each { |name| column(name, :varchar, options) }
3950
end
4051

41-
def varchar_max(name, options = {})
42-
column(name, :varchar_max, options)
52+
def varchar_max(*args, **options)
53+
args.each { |name| column(name, :varchar_max, options) }
4354
end
4455

45-
def text_basic(name, options = {})
46-
column(name, :text_basic, options)
56+
def text_basic(*args, **options)
57+
args.each { |name| column(name, :text_basic, options) }
4758
end
4859

49-
def nchar(name, options = {})
50-
column(name, :nchar, options)
60+
def nchar(*args, **options)
61+
args.each { |name| column(name, :nchar, options) }
5162
end
5263

53-
def ntext(name, options = {})
54-
column(name, :ntext, options)
64+
def ntext(*args, **options)
65+
args.each { |name| column(name, :ntext, options) }
5566
end
5667

57-
def binary_basic(name, options = {})
58-
column(name, :binary_basic, options)
68+
def binary_basic(*args, **options)
69+
args.each { |name| column(name, :binary_basic, options) }
5970
end
6071

61-
def varbinary(name, options = {})
62-
column(name, :varbinary, options)
72+
def varbinary(*args, **options)
73+
args.each { |name| column(name, :varbinary, options) }
6374
end
6475

65-
def uuid(name, options = {})
66-
column(name, :uniqueidentifier, options)
76+
def uuid(*args, **options)
77+
args.each { |name| column(name, :uniqueidentifier, options) }
6778
end
6879

69-
def ss_timestamp(name, options = {})
70-
column(name, :ss_timestamp, options)
80+
def ss_timestamp(*args, **options)
81+
args.each { |name| column(name, :ss_timestamp, options) }
7182
end
7283

7384
end
85+
86+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
87+
include ColumnMethods
88+
89+
def new_column_definition(name, type, options)
90+
type = :datetime2 if type == :datetime && options[:precision]
91+
super name, type, options
92+
end
93+
end
94+
95+
class Table < ActiveRecord::ConnectionAdapters::Table
96+
include ColumnMethods
97+
end
7498
end
7599
end
76100
end

lib/active_record/connection_adapters/sqlserver/type/datetime.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@ class DateTime < ActiveRecord::Type::DateTime
66

77
include TimeValueFractional
88

9-
def initialize(*args)
10-
super
11-
@precision = nil if self.class == DateTime
12-
end
13-
149
def sqlserver_type
1510
'datetime'.freeze
1611
end

lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ module TimeValueFractional
99

1010
def apply_seconds_precision(value)
1111
return value if !value.respond_to?(fractional_property) || value.send(fractional_property).zero?
12-
frac_seconds = if fractional_scale == 0
13-
0
14-
else
15-
seconds = value.send(fractional_property).to_f / fractional_operator.to_f
16-
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
17-
(seconds * fractional_operator).to_i
18-
end
19-
value.change fractional_property => frac_seconds
12+
value.change fractional_property => seconds_precision(value)
13+
end
14+
15+
def seconds_precision(value)
16+
return 0 if fractional_scale == 0
17+
seconds = value.send(fractional_property).to_f / fractional_operator.to_f
18+
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
19+
(seconds * fractional_operator).round(0).to_i
2020
end
2121

2222
def quote_fractional(value)
23-
seconds = (value.send(fractional_property).to_f / fractional_operator.to_f).round(fractional_scale)
23+
return 0 if fractional_scale == 0
24+
frac_seconds = seconds_precision(value)
25+
seconds = (frac_seconds.to_f / fractional_operator.to_f).round(fractional_scale)
2426
seconds.to_d.to_s.split('.').last.to(fractional_scale-1)
2527
end
2628

@@ -52,6 +54,11 @@ module TimeValueFractional2
5254

5355
private
5456

57+
def seconds_precision(value)
58+
seconds = super
59+
seconds > fractional_max ? fractional_scale_max : seconds
60+
end
61+
5562
def fractional_property
5663
:nsec
5764
end
@@ -68,6 +75,14 @@ def fractional_scale
6875
precision
6976
end
7077

78+
def fractional_max
79+
999999999
80+
end
81+
82+
def fractional_scale_max
83+
('9' * fractional_scale) + ('0' * (fractional_digits - fractional_scale))
84+
end
85+
7186
end
7287

7388
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def supports_views?
128128
end
129129

130130
def supports_datetime_with_precision?
131-
false
131+
true
132132
end
133133

134134
def supports_json?
@@ -268,10 +268,13 @@ def initialize_type_map(m)
268268
m.register_type 'real', SQLServer::Type::Real.new
269269
# Date and Time
270270
m.register_type 'date', SQLServer::Type::Date.new
271-
m.register_type 'datetime', SQLServer::Type::DateTime.new
272-
m.register_type %r{\Adatetime2}i do |sql_type|
271+
m.register_type %r{\Adatetime} do |sql_type|
273272
precision = extract_precision(sql_type)
274-
SQLServer::Type::DateTime2.new precision: precision
273+
if precision
274+
SQLServer::Type::DateTime2.new precision: precision
275+
else
276+
SQLServer::Type::DateTime.new
277+
end
275278
end
276279
m.register_type %r{\Adatetimeoffset}i do |sql_type|
277280
precision = extract_precision(sql_type)

test/cases/coerced_tests.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,3 +612,29 @@ def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
612612
assert_equal 5, dumped.posts_count
613613
end
614614
end
615+
616+
617+
618+
619+
class DateTimePrecisionTest < ActiveRecord::TestCase
620+
# Original test had `7` which we support vs `8` which we use.
621+
coerce_tests! :test_invalid_datetime_precision_raises_error
622+
def test_invalid_datetime_precision_raises_error_coerced
623+
assert_raises ActiveRecord::ActiveRecordError do
624+
@connection.create_table(:foos, force: true) do |t|
625+
t.timestamps precision: 8
626+
end
627+
end
628+
end
629+
630+
# Original test uses `datetime` vs `datetime2` type which we dynamically use.
631+
coerce_tests! :test_schema_dump_includes_datetime_precision
632+
def test_schema_dump_includes_datetime_precision_coerced
633+
@connection.create_table(:foos, force: true) do |t|
634+
t.timestamps precision: 6
635+
end
636+
output = dump_table_schema("foos")
637+
assert_match %r{t\.datetime2\s+"created_at",\s+precision: 6,\s+null: false$}, output
638+
assert_match %r{t\.datetime2\s+"updated_at",\s+precision: 6,\s+null: false$}, output
639+
end
640+
end

0 commit comments

Comments
 (0)