字符列被静默截断

时间:2017-06-30 23:10:26

标签: sql-server ruby rails-activerecord

当使用ActiveRecord将字符串存储到SqlServer nvchar列中,并且字符串的长度大于列的最大长度时,字符串将被静默截断为列的大小。 如何发出警告或错误而不是默默地截断值?

重现问题的代码

数据库模型:

class Email < ActiveRecord::Base
  self.table_name = "Email"
end

以静默方式截断的插入内容:

 Email.create!(Address: "X" * 76)
 email.reload
 p email.Address.size    # => 75

insert语句的日志:

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]

版本

  • activerecord(4.2.8)
  • activerecord-sqlserver-adapter(4.2.15)
  • Ruby 2.4.1
  • SqlServer 2014

其他信息

  • 这是一个ActiveRecord项目,没有 Rails。

2 个答案:

答案 0 :(得分:3)

为了使SqlServer响应文本溢出并出现错误,需要做两件事:

  • SqlServer的ANSI_WARNINGS连接设置必须为ON
  • 您必须阻止适配器截断列。

设置ANSI_WARNINGS

ANSI_WARNINGS设置的效果之一是在字符列溢出时中止INSERT或UPDATE。有一个设置可以控制这种效果:

  

如果对字符,Unicode或二进制列尝试INSERT或UPDATE,其中新值的长度超过列的最大大小。如果SET ANSI_WARNINGS为ON,则按照ISO标准的规定取消INSERT或UPDATE。

此设置可能已经开启,但您可以使用此猴子补丁强制启用它:

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

防止适配器截断列

您还必须阻止适配器截断此列。这个猴子补丁会这样做:

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

现在文本溢出引发异常

有了这些猴子补丁,溢出了一个字符列:

email = Dest::Email.create!(Address: "X" * 76)

会导致异常:

 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'

答案 1 :(得分:1)

您可以使用ActiveRecord验证,如下所示:

class Email < ActiveRecord::Base
   self.table_name = "Email"
   validates :Address, length: { maximum: 75 }
end

在这种情况下,Email.create!(Address: "X" * 76)会引发异常。

您可以通过执行以下操作来访问错误:

email = Email.create(Address: "X" * 76)
email.valid? # => false
email.errors[:Address] # => ["is too long (maximum is 75 characters)"]

您有更多可用信息here