2 module ConnectionAdapters
# :nodoc:
3 module SchemaStatements
4 # Returns a Hash of mappings from the abstract data types to the native
5 # database types. See TableDefinition#column for details on the recognized
7 def native_database_types
11 # This is the maximum length a table alias can be
12 def table_alias_length
16 # Truncates a table alias according to the limits of the current adapter.
17 def table_alias_for(table_name
)
18 table_name
[0..table_alias_length-1
].gsub(/\./, '_')
21 # def tables(name = nil) end
23 def table_exists
?(table_name
)
24 tables
.include?(table_name
.to_s
)
27 # Returns an array of indexes for the given table.
28 # def indexes(table_name, name = nil) end
30 # Returns an array of Column objects for the table specified by +table_name+.
31 # See the concrete implementation for details on the expected parameter values.
32 def columns(table_name
, name
= nil) end
34 # Creates a new table with the name +table_name+. +table_name+ may either
35 # be a String or a Symbol.
37 # There are two ways to work with +create_table+. You can use the block
38 # form or the regular form, like this:
41 # # create_table() passes a TableDefinition object to the block.
42 # # This form will not only create the table, but also columns for the
44 # create_table(:suppliers) do |t|
45 # t.column :name, :string, :limit => 60
50 # # Creates a table called 'suppliers' with no columns.
51 # create_table(:suppliers)
52 # # Add a column to 'suppliers'.
53 # add_column(:suppliers, :name, :string, {:limit => 60})
55 # The +options+ hash can include the following keys:
57 # Whether to automatically add a primary key column. Defaults to true.
58 # Join tables for +has_and_belongs_to_many+ should set <tt>:id => false</tt>.
59 # [<tt>:primary_key</tt>]
60 # The name of the primary key, if one is to be added automatically.
63 # Any extra options you want appended to the table definition.
64 # [<tt>:temporary</tt>]
65 # Make a temporary table.
67 # Set to true to drop the table before creating it.
71 # ====== Add a backend specific option to the generated SQL (MySQL)
72 # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
74 # CREATE TABLE suppliers (
75 # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
76 # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
78 # ====== Rename the primary key column
79 # create_table(:objects, :primary_key => 'guid') do |t|
80 # t.column :name, :string, :limit => 80
83 # CREATE TABLE objects (
84 # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
88 # ====== Do not add a primary key column
89 # create_table(:categories_suppliers, :id => false) do |t|
90 # t.column :category_id, :integer
91 # t.column :supplier_id, :integer
94 # CREATE TABLE categories_suppliers (
99 # See also TableDefinition#column for details on how to create columns.
100 def create_table(table_name
, options
= {})
101 table_definition
= TableDefinition
.new(self)
102 table_definition
.primary_key(options
[:primary_key] || Base
.get_primary_key(table_name
)) unless options
[:id] == false
104 yield table_definition
106 if options
[:force] && table_exists
?(table_name
)
107 drop_table(table_name
, options
)
110 create_sql
= "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
111 create_sql
<< "#{quote_table_name(table_name)} ("
112 create_sql
<< table_definition
.to_sql
113 create_sql
<< ") #{options[:options]}"
117 # A block for changing columns in +table+.
120 # # change_table() yields a Table instance
121 # change_table(:suppliers) do |t|
122 # t.column :name, :string, :limit => 60
123 # # Other column alterations here
127 # ====== Add a column
128 # change_table(:suppliers) do |t|
129 # t.column :name, :string, :limit => 60
132 # ====== Add 2 integer columns
133 # change_table(:suppliers) do |t|
134 # t.integer :width, :height, :null => false, :default => 0
137 # ====== Add created_at/updated_at columns
138 # change_table(:suppliers) do |t|
142 # ====== Add a foreign key column
143 # change_table(:suppliers) do |t|
144 # t.references :company
147 # Creates a <tt>company_id(integer)</tt> column
149 # ====== Add a polymorphic foreign key column
150 # change_table(:suppliers) do |t|
151 # t.belongs_to :company, :polymorphic => true
154 # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
156 # ====== Remove a column
157 # change_table(:suppliers) do |t|
161 # ====== Remove several columns
162 # change_table(:suppliers) do |t|
163 # t.remove :company_id
164 # t.remove :width, :height
167 # ====== Remove an index
168 # change_table(:suppliers) do |t|
169 # t.remove_index :company_id
172 # See also Table for details on
173 # all of the various column transformation
174 def change_table(table_name
)
175 yield Table
.new(table_name
, self)
180 # rename_table('octopuses', 'octopi')
181 def rename_table(table_name
, new_name
)
182 raise NotImplementedError
, "rename_table is not implemented"
185 # Drops a table from the database.
186 def drop_table(table_name
, options
= {})
187 execute
"DROP TABLE #{quote_table_name(table_name)}"
190 # Adds a new column to the named table.
191 # See TableDefinition#column for details of the options you can use.
192 def add_column(table_name
, column_name
, type
, options
= {})
193 add_column_sql
= "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
194 add_column_options
!(add_column_sql
, options
)
195 execute(add_column_sql
)
198 # Removes the column(s) from the table definition.
200 # remove_column(:suppliers, :qualification)
201 # remove_columns(:suppliers, :qualification, :experience)
202 def remove_column(table_name
, *column_names
)
203 column_names
.flatten
.each
do |column_name
|
204 execute
"ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
207 alias :remove_columns :remove_column
209 # Changes the column's definition according to the new options.
210 # See TableDefinition#column for details of the options you can use.
212 # change_column(:suppliers, :name, :string, :limit => 80)
213 # change_column(:accounts, :description, :text)
214 def change_column(table_name
, column_name
, type
, options
= {})
215 raise NotImplementedError
, "change_column is not implemented"
218 # Sets a new default value for a column. If you want to set the default
219 # value to +NULL+, you are out of luck. You need to
220 # DatabaseStatements#execute the appropriate SQL statement yourself.
222 # change_column_default(:suppliers, :qualification, 'new')
223 # change_column_default(:accounts, :authorized, 1)
224 def change_column_default(table_name
, column_name
, default
)
225 raise NotImplementedError
, "change_column_default is not implemented"
230 # rename_column(:suppliers, :description, :name)
231 def rename_column(table_name
, column_name
, new_column_name
)
232 raise NotImplementedError
, "rename_column is not implemented"
235 # Adds a new index to the table. +column_name+ can be a single Symbol, or
236 # an Array of Symbols.
238 # The index will be named after the table and the first column name,
239 # unless you pass <tt>:name</tt> as an option.
241 # When creating an index on multiple columns, the first column is used as a name
242 # for the index. For example, when you specify an index on two columns
243 # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an
244 # index for the first column <tt>:first</tt>. Using just the first name for this index
245 # makes sense, because you will never have to create a singular index with this
249 # ====== Creating a simple index
250 # add_index(:suppliers, :name)
252 # CREATE INDEX suppliers_name_index ON suppliers(name)
253 # ====== Creating a unique index
254 # add_index(:accounts, [:branch_id, :party_id], :unique => true)
256 # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
257 # ====== Creating a named index
258 # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
260 # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
261 def add_index(table_name
, column_name
, options
= {})
262 column_names
= Array(column_name
)
263 index_name
= index_name(table_name
, :column => column_names
)
265 if Hash
=== options
# legacy support, since this param was a string
266 index_type
= options
[:unique] ? "UNIQUE" : ""
267 index_name
= options
[:name] || index_name
271 quoted_column_names
= column_names
.map
{ |e
| quote_column_name(e
) }.join(", ")
272 execute
"CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
275 # Remove the given index from the table.
277 # Remove the suppliers_name_index in the suppliers table.
278 # remove_index :suppliers, :name
279 # Remove the index named accounts_branch_id_index in the accounts table.
280 # remove_index :accounts, :column => :branch_id
281 # Remove the index named accounts_branch_id_party_id_index in the accounts table.
282 # remove_index :accounts, :column => [:branch_id, :party_id]
283 # Remove the index named by_branch_party in the accounts table.
284 # remove_index :accounts, :name => :by_branch_party
285 def remove_index(table_name
, options
= {})
286 execute
"DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
289 def index_name(table_name
, options
) #:nodoc:
290 if Hash
=== options
# legacy support
292 "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
296 raise ArgumentError
, "You must specify the index name"
299 index_name(table_name
, :column => options
)
303 # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
304 # entire structure of the database.
308 def dump_schema_information
#:nodoc:
309 sm_table
= ActiveRecord
::Migrator.schema_migrations_table_name
310 migrated
= select_values("SELECT version FROM #{sm_table}")
311 migrated
.map
{ |v
| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
314 # Should not be called normally, but this operation is non-destructive.
315 # The migrations module handles this automatically.
316 def initialize_schema_migrations_table
317 sm_table
= ActiveRecord
::Migrator.schema_migrations_table_name
319 unless tables
.detect
{ |t
| t
== sm_table
}
320 create_table(sm_table
, :id => false) do |schema_migrations_table
|
321 schema_migrations_table
.column
:version, :string, :null => false
323 add_index sm_table
, :version, :unique => true,
324 :name => 'unique_schema_migrations'
326 # Backwards-compatibility: if we find schema_info, assume we've
327 # migrated up to that point:
328 si_table
= Base
.table_name_prefix
+ 'schema_info' + Base
.table_name_suffix
330 if tables
.detect
{ |t
| t
== si_table
}
332 old_version
= select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
333 assume_migrated_upto_version(old_version
)
339 def assume_migrated_upto_version(version)
340 version = version.to_i
341 sm_table
= quote_table_name(ActiveRecord
::Migrator.schema_migrations_table_name
)
343 migrated
= select_values("SELECT version FROM #{sm_table}").map(&:to_i)
344 versions
= Dir
['db/migrate/[0-9]*_*.rb'].map
do |filename
|
345 filename
.split('/').last
.split('_').first
.to_i
348 unless migrated
.include?(version)
349 execute
"INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
353 (versions
- migrated
).each
do |v
|
354 if inserted
.include?(v
)
355 raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
357 execute
"INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
363 def type_to_sql(type
, limit
= nil, precision
= nil, scale
= nil) #:nodoc:
364 if native
= native_database_types
[type
]
365 column_type_sql
= (native
.is_a
?(Hash
) ? native
[:name] : native
).dup
367 if type
== :decimal # ignore limit, use precision and scale
368 scale
||= native
[:scale]
370 if precision
||= native
[:precision]
372 column_type_sql
<< "(#{precision},#{scale})"
374 column_type_sql
<< "(#{precision})"
377 raise ArgumentError
, "Error adding decimal column: precision cannot be empty if scale if specified"
380 elsif (type
!= :primary_key) && (limit
||= native
.is_a
?(Hash
) && native
[:limit])
381 column_type_sql
<< "(#{limit})"
390 def add_column_options
!(sql
, options
) #:nodoc:
391 sql
<< " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default
?(options
)
392 # must explicitly check for :null to allow change_column to work on migrations
393 if options
[:null] == false
398 # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
399 # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
401 # distinct("posts.id", "posts.created_at desc")
402 def distinct(columns
, order_by
)
403 "DISTINCT #{columns}"
406 # ORDER BY clause for the passed order option.
407 # PostgreSQL overrides this due to its stricter standards compliance.
408 def add_order_by_for_association_limiting
!(sql
, options
)
409 sql
<< " ORDER BY #{options[:order]}"
412 # Adds timestamps (created_at and updated_at) columns to the named table.
414 # add_timestamps(:suppliers)
415 def add_timestamps(table_name
)
416 add_column table_name
, :created_at, :datetime
417 add_column table_name
, :updated_at, :datetime
420 # Removes the timestamp columns (created_at and updated_at) from the table definition.
422 # remove_timestamps(:suppliers)
423 def remove_timestamps(table_name
)
424 remove_column table_name
, :updated_at
425 remove_column table_name
, :created_at
429 def options_include_default
?(options
)
430 options
.include?(:default) && !(options
[:null] == false && options
[:default].nil?)