6

我试图在我的 Postgres 9.2 数据库中访问列的默认值。通过使用原始 SQL,我可以验证列默认值是“users_next_id()”:

> db = ActiveRecord::Base.connection
> db.execute("SELECT table_name,column_name,column_default 
FROM information_schema.columns 
WHERE table_name = 'users' and column_name ='id'").first

=> {"table_name"=>"users",
 "column_name"=>"id",
 "column_default"=>"users_next_id()"}

但是当我使用 AR 的 'columns' 方法时,默认值似乎是 nil:

[26] pry(main)> db.columns('users')[0]=> #<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x007feb397ba6e8
 @coder=nil,
 @default=nil,
 @limit=8,
 @name="id",
 @null=false,
 @precision=nil,
 @primary=nil,
 @scale=nil,
 @sql_type="bigint",
 @type=:integer>

这不会引起任何问题(除了让我感到困惑),但这是预期的行为吗?我是否对“列”方法做出了不正确的假设?

4

1 回答 1

6

当 ActiveRecord 需要了解一个表时,它会执行与您的查询类似的information_schema查询,但 AR 将通过PostgreSQL 特定的系统表来代替:

  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
         pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
    FROM pg_attribute a LEFT JOIN pg_attrdef d
      ON a.attrelid = d.adrelid AND a.attnum = d.adnum
   WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
     AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum

PostgreSQL 适配器源中搜索“regclass”,您将看到 AR 将用来确定表结构的其他一些查询。

上述查询中的pg_get_expr调用是列的默认值的来源。

该查询的结果或多或少直接进入PostgreSQLColumn.new

def columns(table_name, name = nil)
  # Limit, precision, and scale are all handled by the superclass.
  column_definitions(table_name).collect do |column_name, type, default, notnull|
    PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
  end
end

构造PostgreSQLColumn函数将用于extract_value_from_defaultRuby-ify 默认值;in的结尾switchextract_value_from_default很有趣:

else
  # Anything else is blank, some user type, or some function
  # and we can't know the value of that, so return nil.
  nil

因此,如果默认值绑定到一个序列(idPostgreSQL 中的列将是),那么默认值将作为类似于以下的函数调用从数据库中出来:

nextval('models_id_seq'::regclass)

这最终会出现在上面的else分支中,并且column.default.nil?是真的。

对于id列,这不是问题,AR 期望数据库为id列提供值,因此它不关心默认值是什么。

如果列的默认值是 AR 无法理解的,例如函数调用,例如md5(random()::text). 问题是 AR 会将所有属性初始化为它们的默认值——正如Model.columns你所见,而不是数据库所见——当你说Model.new. 例如,在控制台中,您会看到如下内容:

 > Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>

So if def_is_function actually uses a function call as its default value, AR will ignore that and try to insert a NULL as that column's value. That NULL will prevent the default value from being used and you'll end up with a confusing mess. Defaults that AR can understand (such as strings and numbers) work just fine though.

The result is that you can't really use non-trivial default column values with ActiveRecord, if you want a non-trivial value then you have to do in Ruby through one of the ActiveRecord callbacks (such as before_create).

IMO it would be much better if AR left the default values up to the database if it didn't understand them: leaving them out of the INSERT or using DEFAULT in the VALUES would produce much better results; AR would, of course, have to reload newly created objects from the database in order to get all the proper defaults but you'd only need the reload if there were defaults that AR didn't understand. If the else in extract_value_from_default used a special "I don't know what this means" flag instead of nil then the "I need to reload this object after the first save" condition would be trivial to detect and you'd only reload when necessary.


The above is PostgreSQL-specific but the process should be similar for other databases; however, I make no guarantees.

于 2013-03-06T04:29:35.807 回答