Add support for generated columns in PostgreSQL#39368
Add support for generated columns in PostgreSQL#39368fanfilmu wants to merge 1 commit intorails:mainfrom
Conversation
5289960 to
3f7dfc4
Compare
3f7dfc4 to
0f9610d
Compare
|
+1 for generated column support on Postgres. For reference, here's the initial commit for adding generated columns to the MySQL adapter: 65bf1c6 I think we should have the If |
0f9610d to
39d2b52
Compare
|
Thank you for your comment @sergiopantoja ! I added handling of the
EDIT: It will require some work on the adapter anyway, due to schema dumping. Currently PostgreSQL marks (stored) generated columns with So I guess raising error is the way to go! |
|
Has there been any discussion of including these values in the There is some related discussion in #34237 As pointed out by @matthewd in #34237 (comment), the |
|
@md5 You are correct, they do not get populated after insert/update, I have noticed it too. I figure it's a bit different feature though, maybe you can try asking around in https://discuss.rubyonrails.org/ ? |
39d2b52 to
9ff315c
Compare
9ff315c to
fc71338
Compare
|
@fanfilmu yes, I think you are right. The situation will be similar to the current situation with function-valued defaults, which also require a |
|
I might be wrong but we need to exclude those column while loading fixtures. Currently build_fixture_sql tries to stuff those with DEFAULT. I do not see that addressed in the patch. There seem to be a bug in PG as DEFAULT is an allowed keyword for inserts into generated columns, but multiple rows fail to insert https://www.db-fiddle.com/f/2NxbvvNvsFBTzYEif2jTWK/0 . Here is the PG mailing list thread. Backport for 5.2.x if anybody needs https://github.com/mlt/rails/commits/ar-postgres-virtual-columns-52 |
|
We recently upgraded from PG10 to 12 and are about to add quite a few generated columns. Anything I can do here to help get this PR merged (and backported to rails 5.2)? |
|
@marksiemers AFAIK new features are not backported. |
Thanks @simi - we'll plan to upgrade to rails 6 (assuming we can make sure the apartment gem still works). Anything that can be done to help get this merged and available in Rails 6? |
| raise ArgumentError, <<~MSG | ||
| PostgreSQL currently does not support VIRTUAL (not persisted) generated columns. | ||
| Specify 'stored: true' option for '#{options[:column].name}' | ||
| MSG |
There was a problem hiding this comment.
If we omit STORED, does it result in an error? That may be preferable, considering future versions may support non-stored virtual columns.
There was a problem hiding this comment.
As of PG 12 and PG 13, it is required:
GENERATED ALWAYS AS ( generation_expr ) STORED
...
The keyword STORED is required to signify that the column will be computed on write and will be stored on disk.
Source: https://www.postgresql.org/docs/12/sql-createtable.html
Source: https://www.postgresql.org/docs/13/sql-createtable.html
It looks like their plan is to require the keyword STORED and in future versions, require the keyword VIRTUAL
There was a problem hiding this comment.
Can we implement this with an expectation of compatibility with future PG versions?
There was a problem hiding this comment.
Predicting the future is always a bit tricky :)
In regard to your original suggestion, the thought is to let the exception happen deeper down the stack (with the pg gem) and let that error be the guide - as opposed to creating this error. Is that right?
If that is correct, I think the trade-off is:
-
As written
- Con: More brittle in handling postgres updates
- Pro: Gives the developer a precise fix in the error message (
Specify 'stored: true' option for ...)
-
Letting pg gem raise the error
- Pro: handles future postgresql updates more accurately/flexibly
- Con: Leaves it up to the developer to google the error to find the fix
@fanfilmu - WDYT?
There was a problem hiding this comment.
Giving the developer a precise fix in the error message is definitely a nice feature.
| ```ruby | ||
| create_table :users do |t| | ||
| t.string :name | ||
| t.virtual :name_upcased, type: :string, as: 'upper(name)' |
There was a problem hiding this comment.
This will fail without stored: true ?
There was a problem hiding this comment.
It seems like a good idea to add stored: true to this example, even if it wouldn't fail.
| pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, | ||
| c.collname, col_description(a.attrelid, a.attnum) AS comment | ||
| c.collname, col_description(a.attrelid, a.attnum) AS comment, | ||
| #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated |
There was a problem hiding this comment.
In PG 12, do non-generated columns return an empty string here (quote('')) or NULL?
There was a problem hiding this comment.
I tested on our PG12 server, and it looks like an empty string (pgAdmin will show [null] for NULL values):

From this query:
SELECT attgenerated
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
LEFT JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
WHERE a.attnum > 0 AND NOT a.attisdropped
LIMIT 10;| ## all documents matching 'cat & dog' | ||
| Document.where("textsearchable_index_col @@ to_tsquery(?)", "cat & dog") | ||
| ``` | ||
|
|
| end | ||
|
|
||
| def virtual? | ||
| @generated == "s" |
There was a problem hiding this comment.
Should this be any non-nil value? Assuming "s" is specific to stored columns, anticipating that non-stored columns may be supported in PG 13+.
There was a problem hiding this comment.
It may be some time before virtual are supported. PG12 nor 13 support it.
The current devel documentation also states that only stored are supported: https://www.postgresql.org/docs/devel/ddl-generated-columns.html
Would something like this work?
VIRTUAL_COLUMN_KEYS = %w[s].freeze # when PG supports virtual switch to %w[s v].freeze
# ...
def virtual?
VIRTUAL_COLUMN_KEYS.include?(@generated)
endThere was a problem hiding this comment.
# when PG supports virtual switch to %w[s v].freezeThis makes Rails brittle with respect to specific PostgreSQL versions supported, however.
There was a problem hiding this comment.
I see, so the alternative would be to check for a non-null non-empty string value. The presumption being that will be definitive no matter the value of the string.
Sounds good to me.
|
@fanfilmu - Do you have time to make the requested updates? Would you like someone else to take a shot at it? |
Agreed, especially since this bug still exists in PG 13 and is breaking fixtures. |
|
Would love to see this hit master :) Any work I can do ? |
| # db/migrate/20131220144913_create_users.rb | ||
| create_table :users do |t| | ||
| t.string :name | ||
| t.virtual :name_upcased, type: :string, as: 'upper(name)' |
There was a problem hiding this comment.
| t.virtual :name_upcased, type: :string, as: 'upper(name)' | |
| t.virtual :name_upcased, type: :string, as: 'upper(name)', stored: true |
|
|
||
| def new_column_definition(name, type, **options) # :nodoc: | ||
| case type | ||
| when :virtual |
There was a problem hiding this comment.
Has there been discussion about the history behind the virtual name/keyword here?
The entry in the PostgreSQL v13 documentation is under the "Generated Columns" heading, the SQL command itself contains GENERATED ALWAYS AS, and the opposite of the STORED flag at the end of the statement is presumed to be VIRTUAL. As of January 2021, the STORED option is required.
My gut tells me that generated would be a more appropriate name in all the cases where this PR introduces virtual. What am I missing here?
There was a problem hiding this comment.
Yes, in this comment: #39368 (comment)
I think we should have the stored: true option. Postgres 12 only supports STORED for now, but this would make the t.virtual signature match the MySQL adapter and make this ready for when Postgres does eventually add the VIRTUAL option.
The idea is that using the name virtual with a stored option should:
- Match an existing method for MySQL and make this feature more DB-agnostic
- Support a (non-stored) "virtual" column once postgresql supports it
There was a problem hiding this comment.
Ah, thanks for clarifying @marksiemers. I was unaware of the MySQL analog.
|
Hello. I need this feature very much! |
|
Currently if we wanna use it ^ production use case. it works okay, but somehow it generates wrong e.g. Temporary solution is just to remove That's not that handy, so having this type of functionality in Rails will be really nice. |
|
@dsounded to make that accurate, you could set |
|
@ghiculescu Yeah I understand that, but if this is going to be merged soon then I can wait for it, I'd like to avoid |
|
Adding my interest for this PR (or the redux by @MSNexploder). Our team is also using schema.rb currently for a large project and switching to structure.sql feels like it would be a big shift for the team. Not sure how to get around the issues mentioned above without it though. |
|
Any update about this change? I tried to add a generated column and the fixtures started to fail. |
|
@JuanCrg90 If you wait long enough, it should work with PG 14+. The bug with DEFAULT in multirow INSERT that causes fixtures to fail was fixed. |
|
This was implemented in #41856 so I'm going to close this one. Thanks for everyone who helped on it. |
Summary
Related topic: https://discuss.rubyonrails.org/t/activerecord-postgresql-support-for-generated-virtual-columns/75302
This PR adds support for generated (virtual) columns to PostgreSQL adapter.
Those are available in PSQL since version 12: https://www.postgresql.org/docs/12/ddl-generated-columns.html
Other Information
I heavily relied on the MySQL implementation for this case, with small adjustments - for example, generated columns are always stored in PostgreSQL, so there is no
storedoption.I was running the tests for PSQL locally (
test:postgresql) - they pass against version 11 and 12. I don't think there is a Postgres version specified in CI?Also, it's my first contribution to Rails, so please let me know if anything is out of order here :)