Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#### Added

* ...
* Support for `supports_datetime_with_precision`.

#### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
when 5..8 then 'bigint'
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
when 'datetime2'
column_type_sql = super
if precision
if (0..7) === precision
column_type_sql << "(#{precision})"
else
raise(ActiveRecordError, "The dattime2 type has precision of #{precision}. The allowed range of precision is from 0 to 7")
end
end
column_type_sql
else
super
end
Expand All @@ -202,6 +212,10 @@ def columns_for_distinct(columns, orders)
[super, *order_columns].join(', ')
end

def update_table_definition(table_name, base)
SQLServer::Table.new(table_name, base)
end

def change_column_null(table_name, column_name, allow_null, default = nil)
table_id = SQLServer::Utils.extract_identifiers(table_name)
column_id = SQLServer::Utils.extract_identifiers(column_name)
Expand Down
86 changes: 55 additions & 31 deletions lib/active_record/connection_adapters/sqlserver/table_definition.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module ActiveRecord
module ConnectionAdapters
module SQLServer
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition

module ColumnMethods

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

def real(name, options = {})
column(name, :real, options)
def real(*args, **options)
args.each { |name| column(name, :real, options) }
end

def money(*args, **options)
args.each { |name| column(name, :money, options) }
end

def money(name, options = {})
column(name, :money, options)
def datetime(*args, **options)
args.each do |name|
if options[:precision]
datetime2(name, options)
else
column(name, :datetime, options)
end
end
end

def datetime2(name, options = {})
column(name, :datetime2, options)
def datetime2(*args, **options)
args.each { |name| column(name, :datetime2, options) }
end

def datetimeoffset(name, options = {})
column(name, :datetimeoffset, options)
def datetimeoffset(*args, **options)
args.each { |name| column(name, :datetimeoffset, options) }
end

def smallmoney(name, options = {})
column(name, :smallmoney, options)
def smallmoney(*args, **options)
args.each { |name| column(name, :smallmoney, options) }
end

def char(name, options = {})
column(name, :char, options)
def char(*args, **options)
args.each { |name| column(name, :char, options) }
end

def varchar(name, options = {})
column(name, :varchar, options)
def varchar(*args, **options)
args.each { |name| column(name, :varchar, options) }
end

def varchar_max(name, options = {})
column(name, :varchar_max, options)
def varchar_max(*args, **options)
args.each { |name| column(name, :varchar_max, options) }
end

def text_basic(name, options = {})
column(name, :text_basic, options)
def text_basic(*args, **options)
args.each { |name| column(name, :text_basic, options) }
end

def nchar(name, options = {})
column(name, :nchar, options)
def nchar(*args, **options)
args.each { |name| column(name, :nchar, options) }
end

def ntext(name, options = {})
column(name, :ntext, options)
def ntext(*args, **options)
args.each { |name| column(name, :ntext, options) }
end

def binary_basic(name, options = {})
column(name, :binary_basic, options)
def binary_basic(*args, **options)
args.each { |name| column(name, :binary_basic, options) }
end

def varbinary(name, options = {})
column(name, :varbinary, options)
def varbinary(*args, **options)
args.each { |name| column(name, :varbinary, options) }
end

def uuid(name, options = {})
column(name, :uniqueidentifier, options)
def uuid(*args, **options)
args.each { |name| column(name, :uniqueidentifier, options) }
end

def ss_timestamp(name, options = {})
column(name, :ss_timestamp, options)
def ss_timestamp(*args, **options)
args.each { |name| column(name, :ss_timestamp, options) }
end

end

class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods

def new_column_definition(name, type, options)
type = :datetime2 if type == :datetime && options[:precision]
super name, type, options
end
end

class Table < ActiveRecord::ConnectionAdapters::Table
include ColumnMethods
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class Money < Decimal

def initialize(options = {})
def initialize(*args)
super
@precision = 19
@scale = 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class SmallMoney < Money

def initialize(options = {})
def initialize(*args)
super
@precision = 10
@scale = 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ module TimeValueFractional

def apply_seconds_precision(value)
return value if !value.respond_to?(fractional_property) || value.send(fractional_property).zero?
frac_seconds = if fractional_scale == 0
0
else
seconds = value.send(fractional_property).to_f / fractional_operator.to_f
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
(seconds * fractional_operator).to_i
end
value.change fractional_property => frac_seconds
value.change fractional_property => seconds_precision(value)
end

def seconds_precision(value)
return 0 if fractional_scale == 0
seconds = value.send(fractional_property).to_f / fractional_operator.to_f
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
(seconds * fractional_operator).round(0).to_i
end

def quote_fractional(value)
seconds = (value.send(fractional_property).to_f / fractional_operator.to_f).round(fractional_scale)
return 0 if fractional_scale == 0
frac_seconds = seconds_precision(value)
seconds = (frac_seconds.to_f / fractional_operator.to_f).round(fractional_scale)
seconds.to_d.to_s.split('.').last.to(fractional_scale-1)
end

Expand Down Expand Up @@ -52,6 +54,11 @@ module TimeValueFractional2

private

def seconds_precision(value)
seconds = super
seconds > fractional_max ? fractional_scale_max : seconds
end

def fractional_property
:nsec
end
Expand All @@ -68,6 +75,14 @@ def fractional_scale
precision
end

def fractional_max
999999999
end

def fractional_scale_max
('9' * fractional_scale) + ('0' * (fractional_digits - fractional_scale))
end

end

end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class UnicodeVarchar < UnicodeChar

def initialize(options = {})
def initialize(*args)
super
@limit = 4000 if @limit.to_i == 0
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class UnicodeVarcharMax < UnicodeVarchar

def initialize(options = {})
def initialize(*args)
super
@limit = 2_147_483_647
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class Varbinary < Binary

def initialize(options = {})
def initialize(*args)
super
@limit = 8000 if @limit.to_i == 0
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class VarbinaryMax < Varbinary

def initialize(options = {})
def initialize(*args)
super
@limit = 2_147_483_647
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class Varchar < Char

def initialize(options = {})
def initialize(*args)
super
@limit = 8000 if @limit.to_i == 0
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SQLServer
module Type
class VarcharMax < Varchar

def initialize(options = {})
def initialize(*args)
super
@limit = 2_147_483_647
end
Expand Down
11 changes: 7 additions & 4 deletions lib/active_record/connection_adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def supports_views?
end

def supports_datetime_with_precision?
false
true
end

def supports_json?
Expand Down Expand Up @@ -268,10 +268,13 @@ def initialize_type_map(m)
m.register_type 'real', SQLServer::Type::Real.new
# Date and Time
m.register_type 'date', SQLServer::Type::Date.new
m.register_type 'datetime', SQLServer::Type::DateTime.new
m.register_type %r{\Adatetime2}i do |sql_type|
m.register_type %r{\Adatetime} do |sql_type|
precision = extract_precision(sql_type)
SQLServer::Type::DateTime2.new precision: precision
if precision
SQLServer::Type::DateTime2.new precision: precision
else
SQLServer::Type::DateTime.new
end
end
m.register_type %r{\Adatetimeoffset}i do |sql_type|
precision = extract_precision(sql_type)
Expand Down
26 changes: 26 additions & 0 deletions test/cases/coerced_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,29 @@ def test_types_of_virtual_columns_are_not_changed_on_round_trip_coerced
assert_equal 5, dumped.posts_count
end
end




class DateTimePrecisionTest < ActiveRecord::TestCase
# Original test had `7` which we support vs `8` which we use.
coerce_tests! :test_invalid_datetime_precision_raises_error
def test_invalid_datetime_precision_raises_error_coerced
assert_raises ActiveRecord::ActiveRecordError do
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 8
end
end
end

# Original test uses `datetime` vs `datetime2` type which we dynamically use.
coerce_tests! :test_schema_dump_includes_datetime_precision
def test_schema_dump_includes_datetime_precision_coerced
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 6
end
output = dump_table_schema("foos")
assert_match %r{t\.datetime2\s+"created_at",\s+precision: 6,\s+null: false$}, output
assert_match %r{t\.datetime2\s+"updated_at",\s+precision: 6,\s+null: false$}, output
end
end
Loading