aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
blob: 8865e7c703fb36ff32b2403ebfeea7214c2544c9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
require "active_support/core_ext/string/strip"

module ActiveRecord
  module ConnectionAdapters
    class AbstractAdapter
      class SchemaCreation # :nodoc:
        def initialize(conn)
          @conn = conn
          @cache = {}
        end

        def accept(o)
          m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
          send m, o
        end

        delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options, to: :@conn
        private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options

        private

          def visit_AlterTable(o)
            sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup
            sql << o.adds.map { |col| accept col }.join(" ")
            sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
            sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
          end

          def visit_ColumnDefinition(o)
            o.sql_type = type_to_sql(o.type, o.options)
            column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup
            add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
            column_sql
          end

          def visit_AddColumnDefinition(o)
            "ADD #{accept(o.column)}".dup
          end

          def visit_TableDefinition(o)
            create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup

            statements = o.columns.map { |c| accept c }
            statements << accept(o.primary_keys) if o.primary_keys

            if supports_indexes_in_create?
              statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
            end

            if supports_foreign_keys_in_create?
              statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
            end

            create_sql << "(#{statements.join(', ')})" if statements.present?
            add_table_options!(create_sql, table_options(o))
            create_sql << " AS #{to_sql(o.as)}" if o.as
            create_sql
          end

          def visit_PrimaryKeyDefinition(o)
            "PRIMARY KEY (#{o.name.join(', ')})"
          end

          def visit_ForeignKeyDefinition(o)
            sql = <<-SQL.strip_heredoc
              CONSTRAINT #{quote_column_name(o.name)}
              FOREIGN KEY (#{quote_column_name(o.column)})
                REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
            SQL
            sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
            sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
            sql
          end

          def visit_AddForeignKey(o)
            "ADD #{accept(o)}"
          end

          def visit_DropForeignKey(name)
            "DROP CONSTRAINT #{quote_column_name(name)}"
          end

          def table_options(o)
            table_options = {}
            table_options[:comment] = o.comment
            table_options[:options] = o.options
            table_options
          end

          def add_table_options!(create_sql, options)
            if options_sql = options[:options]
              create_sql << " #{options_sql}"
            end
          end

          def column_options(o)
            o.options.merge(column: o)
          end

          def add_column_options!(sql, options)
            sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
            # must explicitly check for :null to allow change_column to work on migrations
            if options[:null] == false
              sql << " NOT NULL"
            end
            if options[:auto_increment] == true
              sql << " AUTO_INCREMENT"
            end
            if options[:primary_key] == true
              sql << " PRIMARY KEY"
            end
            sql
          end

          def to_sql(sql)
            sql = sql.to_sql if sql.respond_to?(:to_sql)
            sql
          end

          def foreign_key_in_create(from_table, to_table, options)
            options = foreign_key_options(from_table, to_table, options)
            accept ForeignKeyDefinition.new(from_table, to_table, options)
          end

          def action_sql(action, dependency)
            case dependency
            when :nullify then "ON #{action} SET NULL"
            when :cascade  then "ON #{action} CASCADE"
            when :restrict then "ON #{action} RESTRICT"
            else
              raise ArgumentError, <<-MSG.strip_heredoc
                '#{dependency}' is not supported for :on_update or :on_delete.
                Supported values are: :nullify, :cascade, :restrict
              MSG
            end
          end
      end
    end
    SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc:
  end
end