Reputation: 107989
When using ActiveRecord to store a string into a SqlServer nvchar column, and the length of the string is larger than the maximum length of the column, the string is silently truncated to the column's size. How can I make a warning or error be issued instead of silently truncating the value?
Database model:
class Email < ActiveRecord::Base
self.table_name = "Email"
end
The insert that silently truncates:
Email.create!(Address: "X" * 76)
email.reload
p email.Address.size # => 75
The log of the insert statement:
D, [2017-06-30T16:04:35.320283 #9061] DEBUG -- : dest SQL (0.5ms) EXEC sp_executesql N'INSERT INTO [Email] ([Address]) OUTPUT INSERTED.[Id] VALUES (@0)', N'@0 nvarchar(75)', @0 = N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' [["Address", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]]
CREATE TABLE [dbo].[Email](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Address] [nvarchar](75) NOT NULL,
CONSTRAINT [PK_dbo.Email] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Upvotes: 2
Views: 494
Reputation: 107989
In order to cause SqlServer to respond to a text overflow with an error, two things are required:
Among the effects of the ANSI_WARNINGS setting is to abort an INSERT or UPDATE if a character column is overflowed. There is a setting that controls that effect:
If an INSERT or UPDATE is tried on a character, Unicode, or binary column in which the length of a new value exceeds the maximum size of the column. If SET ANSI_WARNINGS is ON, the INSERT or UPDATE is canceled as specified by the ISO standard.
This setting is probably already on, but you can force it on with this monkey-patch:
class ActiveRecord::ConnectionAdapters::SQLServerAdapter
orig_configure_connection = instance_method(:configure_connection)
define_method(:configure_connection) do
orig_configure_connection.bind(self).call
@connection.execute('SET ANSI_WARNINGS ON').do
end
end
You must also prevent the adapter from truncating this column. This monkey-patch will do it:
class ActiveRecord::ConnectionAdapters::SQLServerColumn
orig_sql_type_for_statement = instance_method(:sql_type_for_statement)
define_method(:sql_type_for_statement) do
if sql_type =~ /\A(nvarchar|nchar|varchar|char|text|ntext|image|varbinary|xml)\(\d+\)\Z/
"#{$1}(max)"
else
orig_sql_type_for_statement.bind(self).call
end
end
end
With these monkey-patches in place, overflowing a character column:
email = Dest::Email.create!(Address: "X" * 76)
will cause an exception:
ActiveRecord::StatementInvalid:
TinyTds::Error: String or binary data would be truncated.: EXEC sp_executesql N'INSERT INTO [Email] ([Address]) OUTPUT INSERTED.[Id] VALUES (@0)', N'@0 nvarchar(max)', @0 = N'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
Upvotes: 4
Reputation: 197
You may use ActiveRecord validations as following:
class Email < ActiveRecord::Base
self.table_name = "Email"
validates :Address, length: { maximum: 75 }
end
In this case, Email.create!(Address: "X" * 76)
will raise an exception.
You can access the errors by doing the following:
email = Email.create(Address: "X" * 76)
email.valid? # => false
email.errors[:Address] # => ["is too long (maximum is 75 characters)"]
You have more information available here.
Upvotes: 1