Repository: rails/rails
File: activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb (Ruby)

Code before:

            if options.key?(:null)
              change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
            end

            change_column_sql
          end

          def visit_ChangeColumnDefaultDefinition(o)
            sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} "
            if o.default.nil?
              sql << "DROP DEFAULT"
            else
              sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}"
            end
          end

          def add_column_options!(sql, options)
            if options[:collation]
              sql << " COLLATE \"#{options[:collation]}\""
            end

            if as = options[:as]
              stored = options[:stored]

              if stored != true && database_version < 180_000
                raise ArgumentError, <<~MSG
                  PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
                  Specify 'stored: true' option for '#{options[:column].name}'
                MSG
              end

              sql << " GENERATED ALWAYS AS (#{as})"
              sql << (stored ? " STORED" : " VIRTUAL")
            end
            super
          end

          def quoted_include_columns(o)
            String === o ? o : quoted_include_columns_for_index(o)
          end

          # Returns any SQL string to go between CREATE and TABLE. May be nil.
          def table_modifier_in_create(o)
            # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
            # tables are already UNLOGGED.
            if o.temporary
              " TEMPORARY"
            elsif o.unlogged
              " UNLOGGED"
            end

Review comment (suggestion):
```suggestion
              if stored != true && database_version < 18_00_00
```

Code after:

            if options.key?(:null)
              change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
            end

            change_column_sql
          end

          def visit_ChangeColumnDefaultDefinition(o)
            sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} "
            if o.default.nil?
              sql << "DROP DEFAULT"
            else
              sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}"
            end
          end

          def add_column_options!(sql, options)
            if options[:collation]
              sql << " COLLATE \"#{options[:collation]}\""
            end

            if as = options[:as]
              stored = options[:stored]

              if stored != true && database_version < 18_00_00
                raise ArgumentError, <<~MSG
                  PostgreSQL versions before 18 do not support VIRTUAL (not persisted) generated columns.
                  Specify 'stored: true' option for '#{options[:column].name}'
                MSG
              end

              sql << " GENERATED ALWAYS AS (#{as})"
              sql << (stored ? " STORED" : " VIRTUAL")
            end
            super
          end

          def quoted_include_columns(o)
            String === o ? o : quoted_include_columns_for_index(o)
          end

          # Returns any SQL string to go between CREATE and TABLE. May be nil.
          def table_modifier_in_create(o)
            # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
            # tables are already UNLOGGED.
            if o.temporary
              " TEMPORARY"
            elsif o.unlogged
              " UNLOGGED"
            end