schema_statements.rb 25.8 KB
Newer Older
1 2
module ActiveRecord
  module ConnectionAdapters
3
    module PostgreSQL
4 5 6
      class SchemaCreation < AbstractAdapter::SchemaCreation
        private

7
        def visit_ColumnDefinition(o)
8
          o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
9 10
          super
        end
11 12 13 14 15 16 17

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

20 21 22 23 24 25 26 27 28
      module SchemaStatements
        # Drops the database specified on the +name+ attribute
        # and creates it again using the provided +options+.
        def recreate_database(name, options = {}) #:nodoc:
          drop_database(name)
          create_database(name, options)
        end

        # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
29
        # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
30 31 32 33 34
        # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
        # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
        #
        # Example:
        #   create_database config[:database], config
A
AvnerCohen 已提交
35
        #   create_database 'foo_development', encoding: 'unicode'
36
        def create_database(name, options = {})
37
          options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
38

39 40
          option_string = options.inject("") do |memo, (key, value)|
            memo += case key
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
            when :owner
              " OWNER = \"#{value}\""
            when :template
              " TEMPLATE = \"#{value}\""
            when :encoding
              " ENCODING = '#{value}'"
            when :collation
              " LC_COLLATE = '#{value}'"
            when :ctype
              " LC_CTYPE = '#{value}'"
            when :tablespace
              " TABLESPACE = \"#{value}\""
            when :connection_limit
              " CONNECTION LIMIT = #{value}"
            else
              ""
            end
          end

          execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
        end

        # Drops a PostgreSQL database.
        #
        # Example:
        #   drop_database 'matt_development'
        def drop_database(name) #:nodoc:
          execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
        end

71
        # Returns the list of all tables in the schema search path.
72
        def tables(name = nil)
73 74 75 76 77 78
          if name
            ActiveSupport::Deprecation.warn(<<-MSG.squish)
              Passing arguments to #tables is deprecated without replacement.
            MSG
          end

79
          select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
80 81
        end

82 83 84 85 86 87 88 89 90 91
        def data_sources # :nodoc
          select_values(<<-SQL, 'SCHEMA')
            SELECT c.relname
            FROM pg_class c
            LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
            WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
            AND n.nspname = ANY (current_schemas(false))
          SQL
        end

92 93 94 95
        # Returns true if table exists.
        # If the schema is not specified as part of +name+ then it will only find tables within
        # the current schema search path (regardless of permissions to access tables in other schemas)
        def table_exists?(name)
96 97 98 99 100 101 102 103 104 105
          ActiveSupport::Deprecation.warn(<<-MSG.squish)
            #table_exists? currently checks both tables and views.
            This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
            Use #data_source_exists? instead.
          MSG

          data_source_exists?(name)
        end

        def data_source_exists?(name)
106 107
          name = Utils.extract_schema_qualified_name(name.to_s)
          return false unless name.identifier
108

109
          select_value(<<-SQL, 'SCHEMA').to_i > 0
110 111 112
              SELECT COUNT(*)
              FROM pg_class c
              LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
113
              WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
114 115
              AND c.relname = '#{name.identifier}'
              AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
116 117 118
          SQL
        end

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        def views # :nodoc:
          select_values(<<-SQL, 'SCHEMA')
            SELECT c.relname
            FROM pg_class c
            LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
            WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
            AND n.nspname = ANY (current_schemas(false))
          SQL
        end

        def view_exists?(view_name) # :nodoc:
          name = Utils.extract_schema_qualified_name(view_name.to_s)
          return false unless name.identifier

          select_values(<<-SQL, 'SCHEMA').any?
            SELECT c.relname
            FROM pg_class c
            LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
            WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
            AND c.relname = '#{name.identifier}'
            AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
          SQL
        end

143
        def drop_table(table_name, options = {}) # :nodoc:
144
          execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
145 146
        end

147 148
        # Returns true if schema exists.
        def schema_exists?(name)
149
          select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
150 151
        end

152
        # Verifies existence of an index with a given name.
153
        def index_name_exists?(table_name, index_name, default)
154 155 156
          table = Utils.extract_schema_qualified_name(table_name.to_s)
          index = Utils.extract_schema_qualified_name(index_name.to_s)

157
          select_value(<<-SQL, 'SCHEMA').to_i > 0
158 159 160 161
            SELECT COUNT(*)
            FROM pg_class t
            INNER JOIN pg_index d ON t.oid = d.indrelid
            INNER JOIN pg_class i ON d.indexrelid = i.oid
162
            LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
163
            WHERE i.relkind = 'i'
164 165 166
              AND i.relname = '#{index.identifier}'
              AND t.relname = '#{table.identifier}'
              AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'}
167 168 169
          SQL
        end

170 171
        # Returns an array of indexes for the given table.
        def indexes(table_name, name = nil)
172 173 174 175 176 177 178 179 180 181 182 183
          table = Utils.extract_schema_qualified_name(table_name.to_s)

          result = query(<<-SQL, 'SCHEMA')
            SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
            FROM pg_class t
            INNER JOIN pg_index d ON t.oid = d.indrelid
            INNER JOIN pg_class i ON d.indexrelid = i.oid
            LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
            WHERE i.relkind = 'i'
              AND d.indisprimary = 'f'
              AND t.relname = '#{table.identifier}'
              AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
184 185 186 187 188
            ORDER BY i.relname
          SQL

          result.map do |row|
            index_name = row[0]
189 190
            unique = row[1]
            indkey = row[2].split(" ").map(&:to_i)
191 192 193
            inddef = row[3]
            oid = row[4]

194
            columns = Hash[query(<<-SQL, "SCHEMA")]
195 196 197 198 199 200 201 202
            SELECT a.attnum, a.attname
            FROM pg_attribute a
            WHERE a.attrelid = #{oid}
            AND a.attnum IN (#{indkey.join(",")})
            SQL

            column_names = columns.values_at(*indkey).compact

203 204 205 206 207 208 209 210 211
            unless column_names.empty?
              # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
              desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
              orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
              where = inddef.scan(/WHERE (.+)$/).flatten[0]
              using = inddef.scan(/USING (.+?) /).flatten[0].to_sym

              IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
            end
212 213 214 215 216 217
          end.compact
        end

        # Returns the list of all column definitions for a table.
        def columns(table_name)
          # Limit, precision, and scale are all handled by the superclass.
218
          column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
219 220
            oid = oid.to_i
            fmod = fmod.to_i
S
Sean Griffin 已提交
221 222
            type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
            default_value = extract_value_from_default(default)
223
            default_function = extract_default_function(default_value, default)
224
            new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
225 226 227
          end
        end

228 229
        def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
          PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
230 231
        end

232 233
        # Returns the current database name.
        def current_database
234
          select_value('select current_database()', 'SCHEMA')
235 236 237 238
        end

        # Returns the current schema name.
        def current_schema
239
          select_value('SELECT current_schema', 'SCHEMA')
240 241 242 243
        end

        # Returns the current database encoding format.
        def encoding
244
          select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
245 246 247 248
        end

        # Returns the current database collation.
        def collation
249
          select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
250 251 252 253
        end

        # Returns the current database ctype.
        def ctype
254
          select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
255 256 257 258
        end

        # Returns an array of schema names.
        def schema_names
259
          select_values(<<-SQL, 'SCHEMA')
260 261 262 263 264 265 266 267 268 269
            SELECT nspname
              FROM pg_namespace
             WHERE nspname !~ '^pg_.*'
               AND nspname NOT IN ('information_schema')
             ORDER by nspname;
          SQL
        end

        # Creates a schema for the given schema name.
        def create_schema schema_name
270
          execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
271 272 273
        end

        # Drops the schema for the given schema name.
274
        def drop_schema(schema_name, options = {})
275
          execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        end

        # Sets the schema search path to a string of comma-separated schema names.
        # Names beginning with $ have to be quoted (e.g. $user => '$user').
        # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
        #
        # This should be not be called manually but set in database.yml.
        def schema_search_path=(schema_csv)
          if schema_csv
            execute("SET search_path TO #{schema_csv}", 'SCHEMA')
            @schema_search_path = schema_csv
          end
        end

        # Returns the active schema search path.
        def schema_search_path
292
          @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
293 294 295 296
        end

        # Returns the current client message level.
        def client_min_messages
297
          select_value('SHOW client_min_messages', 'SCHEMA')
298 299 300 301 302 303 304 305 306 307 308
        end

        # Set the client message level.
        def client_min_messages=(level)
          execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
        end

        # Returns the sequence name for a table's primary key or some other specified key.
        def default_sequence_name(table_name, pk = nil) #:nodoc:
          result = serial_sequence(table_name, pk || 'id')
          return nil unless result
309
          Utils.extract_schema_qualified_name(result).to_s
310
        rescue ActiveRecord::StatementInvalid
311
          PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
312 313 314
        end

        def serial_sequence(table, column)
315
          select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
316 317
        end

318
        # Sets the sequence of a table's primary key to the specified value.
A
Aaron Patterson 已提交
319 320
        def set_pk_sequence!(table, value) #:nodoc:
          pk, sequence = pk_and_sequence_for(table)
321 322 323

          if pk
            if sequence
324
              quoted_sequence = quote_table_name(sequence)
325

326
              select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
327 328 329 330 331 332
            else
              @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
            end
          end
        end

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
        # Resets the sequence of a table's primary key to the maximum value.
        def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
          unless pk and sequence
            default_pk, default_sequence = pk_and_sequence_for(table)

            pk ||= default_pk
            sequence ||= default_sequence
          end

          if @logger && pk && !sequence
            @logger.warn "#{table} has primary key #{pk} with no default sequence"
          end

          if pk && sequence
            quoted_sequence = quote_table_name(sequence)

349
            select_value(<<-end_sql, 'SCHEMA')
350 351 352 353 354 355 356 357 358
              SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
            end_sql
          end
        end

        # Returns a table's primary key and belonging sequence.
        def pk_and_sequence_for(table) #:nodoc:
          # First try looking for a sequence with a dependency on the
          # given table's primary key.
359
          result = query(<<-end_sql, 'SCHEMA')[0]
360
            SELECT attr.attname, nsp.nspname, seq.relname
361 362 363
            FROM pg_class      seq,
                 pg_attribute  attr,
                 pg_depend     dep,
364 365
                 pg_constraint cons,
                 pg_namespace  nsp
366 367 368 369 370 371
            WHERE seq.oid           = dep.objid
              AND seq.relkind       = 'S'
              AND attr.attrelid     = dep.refobjid
              AND attr.attnum       = dep.refobjsubid
              AND attr.attrelid     = cons.conrelid
              AND attr.attnum       = cons.conkey[1]
372
              AND seq.relnamespace  = nsp.oid
373
              AND cons.contype      = 'p'
374
              AND dep.classid       = 'pg_class'::regclass
375 376 377 378
              AND dep.refobjid      = '#{quote_table_name(table)}'::regclass
          end_sql

          if result.nil? or result.empty?
379
            result = query(<<-end_sql, 'SCHEMA')[0]
380
              SELECT attr.attname, nsp.nspname,
381
                CASE
382
                  WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
383 384 385 386
                  WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
                    substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
                           strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
                  ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
387 388 389 390 391
                END
              FROM pg_class       t
              JOIN pg_attribute   attr ON (t.oid = attrelid)
              JOIN pg_attrdef     def  ON (adrelid = attrelid AND adnum = attnum)
              JOIN pg_constraint  cons ON (conrelid = adrelid AND adnum = conkey[1])
392
              JOIN pg_namespace   nsp  ON (t.relnamespace = nsp.oid)
393 394
              WHERE t.oid = '#{quote_table_name(table)}'::regclass
                AND cons.contype = 'p'
395
                AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
396 397 398
            end_sql
          end

399 400 401 402 403 404
          pk = result.shift
          if result.last
            [pk, PostgreSQL::Name.new(*result)]
          else
            [pk, nil]
          end
405 406 407 408
        rescue
          nil
        end

409 410 411 412 413 414 415 416 417 418 419 420 421
        def primary_keys(table_name) # :nodoc:
          select_values(<<-SQL.strip_heredoc, 'SCHEMA')
            WITH pk_constraint AS (
              SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
              WHERE contype = 'p'
                AND conrelid = '#{quote_table_name(table_name)}'::regclass
            ), cons AS (
              SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
            )
            SELECT attr.attname FROM pg_attribute attr
            INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
            ORDER BY cons.rownum
          SQL
422 423 424
        end

        # Renames a table.
425 426
        # Also renames a table's primary key sequence if the sequence name exists and
        # matches the Active Record default.
427 428 429
        #
        # Example:
        #   rename_table('octopuses', 'octopi')
430
        def rename_table(table_name, new_name)
431
          clear_cache!
432
          execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
433
          pk, seq = pk_and_sequence_for(new_name)
434
          if seq && seq.identifier == "#{table_name}_#{pk}_seq"
435
            new_seq = "#{new_name}_#{pk}_seq"
436 437
            idx = "#{table_name}_pkey"
            new_idx = "#{new_name}_pkey"
438
            execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
439
            execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
440
          end
441 442

          rename_table_indexes(table_name, new_name)
443 444
        end

T
Tony Miller 已提交
445
        def add_column(table_name, column_name, type, options = {}) #:nodoc:
446
          clear_cache!
447
          super
448 449
        end

450
        def change_column(table_name, column_name, type, options = {}) #:nodoc:
451 452
          clear_cache!
          quoted_table_name = quote_table_name(table_name)
453
          quoted_column_name = quote_column_name(column_name)
454
          sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
455
          sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
456 457 458
          if options[:collation]
            sql << " COLLATE \"#{options[:collation]}\""
          end
459 460 461
          if options[:using]
            sql << " USING #{options[:using]}"
          elsif options[:cast_as]
462
            cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
463
            sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
464
          end
465
          execute sql
466 467 468 469 470 471

          change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
          change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
        end

        # Changes the default value of a table column.
472
        def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
473
          clear_cache!
474
          column = column_for(table_name, column_name)
475
          return unless column
476

477
          default = extract_new_default_value(default_or_changes)
478 479 480 481 482 483
          alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
          if default.nil?
            # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
            # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
            execute alter_column_query % "DROP DEFAULT"
          else
484
            execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
485
          end
486 487
        end

488
        def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
489 490
          clear_cache!
          unless null || default.nil?
491
            column = column_for(table_name, column_name)
492
            execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
493 494 495 496 497
          end
          execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
        end

        # Renames a column in a table.
498
        def rename_column(table_name, column_name, new_column_name) #:nodoc:
499 500
          clear_cache!
          execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
501
          rename_column_indexes(table_name, column_name, new_column_name)
502 503
        end

D
doabit 已提交
504
        def add_index(table_name, column_name, options = {}) #:nodoc:
505 506
          index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
          execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
D
doabit 已提交
507 508
        end

509 510 511
        def remove_index(table_name, options = {}) #:nodoc:
          index_name = index_name_for_remove(table_name, options)
          algorithm =
512
            if Hash === options && options.key?(:algorithm)
513 514 515 516 517
              index_algorithms.fetch(options[:algorithm]) do
                raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
              end
            end
          execute "DROP INDEX #{algorithm} #{quote_table_name(index_name)}"
518 519
        end

520 521
        # Renames an index of a table. Raises error if length of new
        # index name is greater than allowed limit.
522
        def rename_index(table_name, old_name, new_name)
523 524
          validate_index_length!(table_name, new_name)

525 526 527
          execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
        end

528
        def foreign_keys(table_name)
529
          fk_info = select_all <<-SQL.strip_heredoc
530
            SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
531 532 533 534 535 536 537 538 539 540
            FROM pg_constraint c
            JOIN pg_class t1 ON c.conrelid = t1.oid
            JOIN pg_class t2 ON c.confrelid = t2.oid
            JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
            JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
            JOIN pg_namespace t3 ON c.connamespace = t3.oid
            WHERE c.contype = 'f'
              AND t1.relname = #{quote(table_name)}
              AND t3.nspname = ANY (current_schemas(false))
            ORDER BY c.conname
541 542 543 544 545 546
          SQL

          fk_info.map do |row|
            options = {
              column: row['column'],
              name: row['name'],
547 548 549
              primary_key: row['primary_key']
            }

Y
Yves Senn 已提交
550 551
            options[:on_delete] = extract_foreign_key_action(row['on_delete'])
            options[:on_update] = extract_foreign_key_action(row['on_update'])
552

Y
Yves Senn 已提交
553 554 555 556 557 558 559 560 561
            ForeignKeyDefinition.new(table_name, row['to_table'], options)
          end
        end

        def extract_foreign_key_action(specifier) # :nodoc:
          case specifier
          when 'c'; :cascade
          when 'n'; :nullify
          when 'r'; :restrict
562 563 564
          end
        end

565 566 567 568 569
        def index_name_length
          63
        end

        # Maps logical Rails types to PostgreSQL-specific data types.
570 571
        def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
          sql = case type.to_s
572 573
          when 'binary'
            # PostgreSQL doesn't support limits on binary (bytea) columns.
574
            # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
575 576 577 578
            case limit
            when nil, 0..0x3fffffff; super(type)
            else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
            end
579 580
          when 'text'
            # PostgreSQL doesn't support limits on text columns.
581
            # The hard limit is 1GB, according to section 8.3 in the manual.
582 583 584 585
            case limit
            when nil, 0..0x3fffffff; super(type)
            else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
            end
586 587
          when 'integer'
            case limit
588 589 590 591
            when 1, 2; 'smallint'
            when nil, 3, 4; 'integer'
            when 5..8; 'bigint'
            else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
592 593
            end
          else
594
            super(type, limit, precision, scale)
595
          end
596 597 598

          sql << '[]' if array && type != :primary_key
          sql
599 600 601 602
        end

        # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
        # requires that the ORDER BY include the distinct column.
603
        def columns_for_distinct(columns, orders) #:nodoc:
604
          order_columns = orders.reject(&:blank?).map{ |s|
605 606 607
              # Convert Arel node to string
              s = s.to_sql unless s.is_a?(String)
              # Remove any ASC/DESC modifiers
608 609
              s.gsub(/\s+(?:ASC|DESC)\b/i, '')
               .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
610 611
            }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }

612
          [super, *order_columns].join(', ')
613
        end
S
Sean Griffin 已提交
614 615 616 617 618 619 620 621 622 623 624 625

        def fetch_type_metadata(column_name, sql_type, oid, fmod)
          cast_type = get_oid_type(oid, fmod, column_name, sql_type)
          simple_type = SqlTypeMetadata.new(
            sql_type: sql_type,
            type: cast_type.type,
            limit: cast_type.limit,
            precision: cast_type.precision,
            scale: cast_type.scale,
          )
          PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
        end
626 627 628 629
      end
    end
  end
end