# frozen_string_literal: false # @!parse # class ActiveRecord::Migration # # Create a domain type # # # # @param [#to_s] name (nil) The qualified name of the type # # @option options [#to_s] :as (nil) The base type for the domain (alias: :type) # # @option options [#to_s] :collation (nil) The collation # # @option options [Boolean] :null (true) If a value of this type can be NULL # # @option options [#to_s] :default_sql (nil) The snippet for the default value of the domain # # @option options [#to_s] :comment (nil) The comment describing the constraint # # @yield [d] the block with the type's definition # # @yieldparam Object receiver of methods specifying the type # # @return [void] # # # # ```ruby # # create_domain "dict.us_postal_code", as: "text" do |d| # # d.collation "en_US" # # d.default_sql "'0000'::text" # # d.null false # # d.constraint <<~SQL, name: "code_valid" # # VALUE ~ '^\d{5}$' OR VALUE ~ '^\d{5}-\d{4}$' # # SQL # # d.comment "US postal code" # # end # # ``` # # # # It is always reversible. # def create_domain(name, **options, &block); end # end module PGTrunk::Operations::Domains # @private class CreateDomain < Base validates :type, presence: true validates :force, :if_exists, :new_name, absence: true from_sql do |_version| <<~SQL SELECT t.oid, (t.typnamespace::regnamespace || '.' || t.typname) AS name, ( CASE WHEN b.typnamespace != 'pg_catalog'::regnamespace THEN b.typnamespace::regnamespace || '.' || b.typname ELSE b.typname END ) AS "type", ( CASE WHEN c.collnamespace != 'pg_catalog'::regnamespace THEN c.collnamespace::regnamespace || '.' || c.collname WHEN c.collname != 'default' THEN c.collname END ) AS collation, (CASE WHEN t.typnotnull THEN false END) AS null, ( CASE WHEN t.typdefaultbin IS NOT NULL THEN pg_get_expr(t.typdefaultbin, 0, true) END ) AS default_sql, ( SELECT json_agg( json_build_object( 'name', c.conname, 'check', pg_get_expr(c.conbin, 0, true) ) ) FROM pg_constraint c WHERE c.contypid = t.oid ) AS constraints, d.description AS comment FROM pg_type t JOIN pg_trunk e ON e.oid = t.oid AND e.classid = 'pg_type'::regclass JOIN pg_type b ON b.oid = t.typbasetype LEFT JOIN pg_collation c ON c.oid = t.typcollation LEFT JOIN pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass WHERE t.typtype = 'd'; SQL end def to_sql(_version) [create_domain, *create_comment, register_domain].join(" ") end def invert DropDomain.new(**to_h) end private def create_domain sql = "CREATE DOMAIN #{name.to_sql} AS #{type.to_sql}" sql << " COLLATE #{collation.to_sql}" if collation.present? sql << " DEFAULT #{default_sql}" if default_sql.present? sql << " NOT NULL" if null == false constraints&.each do |c| sql << " CONSTRAINT #{c.name.inspect}" if c.name.present? sql << " CHECK (#{c.check})" end sql << ";" end def create_comment return if comment.blank? "COMMENT ON DOMAIN #{name.to_sql} IS $comment$#{comment}$comment$;" end def register_domain <<~SQL.squish INSERT INTO pg_trunk (oid, classid) SELECT oid, 'pg_type'::regclass FROM pg_type WHERE typname = #{name.quoted} AND typnamespace = #{name.namespace} AND typtype = 'd' ON CONFLICT DO NOTHING; SQL end end end