module Sequel::SchemaDumper
Constants
- IGNORE_INDEX_ERRORS_KEY
-
simplecov:disable
Public Instance Methods
Source
# File lib/sequel/extensions/schema_dumper.rb 38 def column_schema_to_ruby_type(schema) 39 type = schema[:db_type].downcase 40 if database_type == :oracle 41 type = type.sub(/ not null\z/, '') 42 end 43 case type 44 when /\A(medium|small)?int(?:eger)?(?:\((\d+)\))?( unsigned)?\z/ 45 if !$1 && $2 && $2.to_i >= 10 && $3 46 # Unsigned integer type with 10 digits can potentially contain values which 47 # don't fit signed integer type, so use bigint type in target database. 48 {:type=>:Bignum} 49 else 50 {:type=>Integer} 51 end 52 when /\Atinyint(?:\((\d+)\))?(?: unsigned)?\z/ 53 {:type =>schema[:type] == :boolean ? TrueClass : Integer} 54 when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/ 55 {:type=>:Bignum} 56 when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\))(?: unsigned)?\z/ 57 {:type=>Float} 58 when 'boolean', 'bit', 'bool' 59 {:type=>TrueClass} 60 when /\A(?:(?:tiny|medium|long|n)?text|clob)\z/ 61 {:type=>String, :text=>true} 62 when 'date' 63 {:type=>Date} 64 when /\A(?:small)?datetime\z/ 65 {:type=>DateTime} 66 when /\Atimestamp(?:\((\d+)\))?(?: with(?:out)? time zone)?\z/ 67 {:type=>DateTime, :size=>($1.to_i if $1)} 68 when /\Atime(?: with(?:out)? time zone)?\z/ 69 {:type=>Time, :only_time=>true} 70 when /\An?char(?:acter)?(?:\((\d+)\))?\z/ 71 {:type=>String, :size=>($1.to_i if $1), :fixed=>true} 72 when /\A(?:n?varchar2?|character varying|bpchar|string)(?:\((\d+)\))?\z/ 73 {:type=>String, :size=>($1.to_i if $1)} 74 when /\A(?:small)?money\z/ 75 {:type=>BigDecimal, :size=>[19,2]} 76 when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?(?: unsigned)?\z/ 77 s = [($1.to_i if $1), ($2.to_i if $2)].compact 78 {:type=>BigDecimal, :size=>(s.empty? ? nil : s)} 79 when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/ 80 {:type=>File, :size=>($1.to_i if $1)} 81 when /\A(?:year|(?:int )?identity)\z/ 82 {:type=>Integer} 83 else 84 {:type=>String} 85 end 86 end
Convert the column schema information to a hash of column options, one of which must be :type. The other options added should modify that type (e.g. :size). If a database type is not recognized, return it as a String type.
Source
# File lib/sequel/extensions/schema_dumper.rb 95 def dump_foreign_key_migration(options=OPTS) 96 ts = _dump_tables(options) 97 <<END_MIG 98 Sequel.migration do 99 change do 100 #{ts.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')} 101 end 102 end 103 END_MIG 104 end
Dump foreign key constraints for all tables as a migration. This complements the foreign_keys: false option to dump_schema_migration. This only dumps the constraints (not the columns) using alter_table/add_foreign_key with an array of columns.
Note that the migration this produces does not have a down block, so you cannot reverse it.
Source
# File lib/sequel/extensions/schema_dumper.rb 113 def dump_indexes_migration(options=OPTS) 114 ts = _dump_tables(options) 115 <<END_MIG 116 Sequel.migration do 117 change do 118 #{ts.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')} 119 end 120 end 121 END_MIG 122 end
Dump indexes for all tables as a migration. This complements the indexes: false option to dump_schema_migration. Options:
- :same_db
-
Create a dump for the same database type, so don’t ignore errors if the index statements fail.
- :index_names
-
If set to false, don’t record names of indexes. If set to :namespace, prepend the table name to the index name if the database does not use a global index namespace.
Source
# File lib/sequel/extensions/schema_dumper.rb 137 def dump_schema_migration(options=OPTS) 138 options = options.dup 139 if options[:indexes] == false && !options.has_key?(:foreign_keys) 140 # Unless foreign_keys option is specifically set, disable if indexes 141 # are disabled, as foreign keys that point to non-primary keys rely 142 # on unique indexes being created first 143 options[:foreign_keys] = false 144 end 145 146 ts = sort_dumped_tables(_dump_tables(options), options) 147 skipped_fks = if sfk = options[:skipped_foreign_keys] 148 # Handle skipped foreign keys by adding them at the end via 149 # alter_table/add_foreign_key. Note that skipped foreign keys 150 # probably result in a broken down migration. 151 sfka = sfk.sort.map{|table, fks| dump_add_fk_constraints(table, fks.values)} 152 sfka.join("\n\n").gsub(/^/, ' ') unless sfka.empty? 153 end 154 155 <<END_MIG 156 Sequel.migration do 157 change do 158 #{ts.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/, ' ')}#{"\n \n" if skipped_fks}#{skipped_fks} 159 end 160 end 161 END_MIG 162 end
Return a string that contains a Sequel migration that when run would recreate the database structure. Options:
- :same_db
-
Don’t attempt to translate database types to ruby types. If this isn’t set to true, all database types will be translated to ruby types, but there is no guarantee that the migration generated will yield the same type. Without this set, types that aren’t recognized will be translated to a string-like type.
- :foreign_keys
-
If set to false, don’t dump foreign_keys (they can be added later via
dump_foreign_key_migration) - :indexes
-
If set to false, don’t dump indexes (they can be added later via dump_index_migration).
- :index_names
-
If set to false, don’t record names of indexes. If set to :namespace, prepend the table name to the index name.
Source
# File lib/sequel/extensions/schema_dumper.rb 166 def dump_table_schema(table, options=OPTS) 167 gen = dump_table_generator(table, options) 168 commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n") 169 "create_table(#{table.inspect}#{", #{IGNORE_INDEX_ERRORS_KEY}true" if !options[:same_db] && options[:indexes] != false && !gen.indexes.empty?}) do\n#{commands.gsub(/^/, ' ')}\nend" 170 end
Return a string with a create table block that will recreate the given table’s schema. Takes the same options as dump_schema_migration.
Private Instance Methods
Source
# File lib/sequel/extensions/schema_dumper.rb 176 def _dump_tables(opts) 177 if opts[:schema] 178 _literal_table_sort(tables(opts.merge(:qualify=>true))) 179 else 180 tables(opts).sort 181 end 182 end
Handle schema option to dump tables in a different schema. Such tables must be schema qualified for this to work correctly.
Source
# File lib/sequel/extensions/schema_dumper.rb 185 def _literal_table_sort(tables) 186 tables.sort_by{|s| literal(s)} 187 end
Sort the given table by the literalized value.
Source
# File lib/sequel/extensions/schema_dumper.rb 191 def column_schema_to_ruby_default_fallback(default, options) 192 if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback? 193 default = default.dup 194 def default.inspect 195 "Sequel::LiteralString.new(#{super})" 196 end 197 default 198 end 199 end
If a database default exists and can’t be converted, and we are dumping with :same_db, return a string with the inspect method modified a literal string is created if the code is evaled.
Source
# File lib/sequel/extensions/schema_dumper.rb 273 def dump_add_fk_constraints(table, fks) 274 sfks = String.new 275 sfks << "alter_table(#{table.inspect}) do\n" 276 sfks << create_table_generator do 277 fks.sort_by{|fk| fk[:columns]}.each do |fk| 278 foreign_key fk[:columns], fk 279 end 280 end.dump_constraints.gsub(/^foreign_key /, ' add_foreign_key ') 281 sfks << "\nend" 282 end
For the table and foreign key metadata array, return an alter_table string that would add the foreign keys if run in a migration.
Source
# File lib/sequel/extensions/schema_dumper.rb 286 def dump_table_foreign_keys(table, options=OPTS) 287 if supports_foreign_key_parsing? 288 fks = foreign_key_list(table, options).sort_by{|fk| fk[:columns]} 289 end 290 291 if fks.nil? || fks.empty? 292 '' 293 else 294 dump_add_fk_constraints(table, fks) 295 end 296 end
For the table given, get the list of foreign keys and return an alter_table string that would add the foreign keys if run in a migration.
Source
# File lib/sequel/extensions/schema_dumper.rb 300 def dump_table_generator(table, options=OPTS) 301 s = schema(table, options).dup 302 pks = s.find_all{|x| x.last[:primary_key] == true}.map(&:first) 303 options = options.merge(:single_pk=>true) if pks.length == 1 304 m = method(:recreate_column) 305 im = method(:index_to_generator_opts) 306 307 if options[:indexes] != false && supports_index_parsing? 308 indexes = indexes(table).sort 309 end 310 311 if options[:foreign_keys] != false && supports_foreign_key_parsing? 312 fk_list = foreign_key_list(table) 313 314 if (sfk = options[:skipped_foreign_keys]) && (sfkt = sfk[table]) 315 fk_list.delete_if{|fk| sfkt.has_key?(fk[:columns])} 316 end 317 318 composite_fks, single_fks = fk_list.partition{|h| h[:columns].length > 1} 319 fk_hash = {} 320 321 single_fks.each do |fk| 322 column = fk.delete(:columns).first 323 fk.delete(:name) 324 fk_hash[column] = fk 325 end 326 327 s = s.map do |name, info| 328 if fk_info = fk_hash[name] 329 [name, fk_info.merge(info)] 330 else 331 [name, info] 332 end 333 end 334 end 335 336 create_table_generator do 337 s.each{|name, info| m.call(name, info, self, options)} 338 primary_key(pks) if !@primary_key && pks.length > 0 339 indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} if indexes 340 composite_fks.each{|fk| send(:foreign_key, fk[:columns], fk)} if composite_fks 341 end 342 end
Return a Schema::CreateTableGenerator object that will recreate the table’s schema. Takes the same options as dump_schema_migration.
Source
# File lib/sequel/extensions/schema_dumper.rb 346 def dump_table_indexes(table, meth, options=OPTS) 347 if supports_index_parsing? 348 indexes = indexes(table).sort 349 else 350 return '' 351 end 352 353 im = method(:index_to_generator_opts) 354 gen = create_table_generator do 355 indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} 356 end 357 gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db]) 358 end
Return a string that containing add_index/drop_index method calls for creating the index migration.
Source
# File lib/sequel/extensions/schema_dumper.rb 361 def index_to_generator_opts(table, name, index_opts, options=OPTS) 362 h = {} 363 if options[:index_names] != false && default_index_name(table, index_opts[:columns]) != name.to_s 364 if options[:index_names] == :namespace && !global_index_namespace? 365 h[:name] = "#{table}_#{name}".to_sym 366 else 367 h[:name] = name 368 end 369 end 370 h[:unique] = true if index_opts[:unique] 371 h[:deferrable] = true if index_opts[:deferrable] 372 h 373 end
Convert the parsed index information into options to the CreateTableGenerator’s index method.
Source
# File lib/sequel/extensions/schema_dumper.rb 202 def recreate_column(name, schema, gen, options) 203 db_type = schema[:db_type] 204 db_type = literal(db_type) if options[:same_db] && database_type == :sqlite 205 206 if options[:single_pk] && schema_autoincrementing_primary_key?(schema) 207 type_hash = options[:same_db] ? {:type=>db_type} : column_schema_to_ruby_type(schema) 208 209 [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]} 210 if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"} || type_hash == {:type=>"INTEGER"} 211 type_hash.delete(:type) 212 elsif options[:same_db] && type_hash == {:type=>type_literal_generic_bignum_symbol(type_hash).to_s} 213 type_hash[:type] = :Bignum 214 end 215 216 unless gen.columns.empty? 217 type_hash[:keep_order] = true 218 end 219 220 if type_hash.empty? 221 gen.primary_key(name) 222 else 223 gen.primary_key(name, type_hash) 224 end 225 else 226 col_opts = if options[:same_db] 227 h = {:type=>db_type} 228 if database_type == :mysql && h[:type].start_with?("timestamp") 229 h[:null] = true 230 end 231 if database_type == :mssql && schema[:max_length] 232 h[:size] = schema[:max_length] 233 end 234 h 235 else 236 column_schema_to_ruby_type(schema) 237 end 238 type = col_opts.delete(:type) 239 if col_opts.key?(:size) && col_opts[:size].nil? 240 col_opts.delete(:size) 241 if max_length = schema[:max_length] 242 col_opts[:size] = max_length 243 end 244 end 245 if schema[:generated] 246 if options[:same_db] && database_type == :postgres 247 col_opts[:generated_always_as] = column_schema_to_ruby_default_fallback(schema[:default], options) 248 end 249 else 250 col_opts[:default] = if schema[:ruby_default].nil? 251 column_schema_to_ruby_default_fallback(schema[:default], options) 252 else 253 schema[:ruby_default] 254 end 255 col_opts.delete(:default) if col_opts[:default].nil? 256 end 257 col_opts[:null] = false if schema[:allow_null] == false 258 if table = schema[:table] 259 [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]} 260 col_opts[:type] = type unless type == Integer || type == 'integer' || type == 'INTEGER' 261 gen.foreign_key(name, table, col_opts) 262 else 263 gen.column(name, type, col_opts) 264 if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/i 265 gen.check(Sequel::SQL::Identifier.new(name) >= 0) 266 end 267 end 268 end 269 end
Recreate the column in the passed Schema::CreateTableGenerator from the given name and parsed database schema.
Source
# File lib/sequel/extensions/schema_dumper.rb 377 def sort_dumped_tables(tables, options=OPTS) 378 if options[:foreign_keys] != false && supports_foreign_key_parsing? 379 table_fks = {} 380 tables.each{|t| table_fks[t] = foreign_key_list(t)} 381 # Remove self referential foreign keys, not important when sorting. 382 table_fks.each{|t, fks| fks.delete_if{|fk| fk[:table] == t}} 383 tables, skipped_foreign_keys = sort_dumped_tables_topologically(table_fks, []) 384 options[:skipped_foreign_keys] = skipped_foreign_keys 385 tables 386 else 387 tables 388 end 389 end
Sort the tables so that referenced tables are created before tables that reference them, and then by name. If foreign keys are disabled, just sort by name.
Source
# File lib/sequel/extensions/schema_dumper.rb 395 def sort_dumped_tables_topologically(table_fks, sorted_tables) 396 skipped_foreign_keys = {} 397 398 until table_fks.empty? 399 this_loop = [] 400 401 table_fks.each do |table, fks| 402 fks.delete_if{|fk| !table_fks.has_key?(fk[:table])} 403 this_loop << table if fks.empty? 404 end 405 406 if this_loop.empty? 407 # No tables were changed this round, there must be a circular dependency. 408 # Break circular dependency by picking the table with the least number of 409 # outstanding foreign keys and skipping those foreign keys. 410 # The skipped foreign keys will be added at the end of the 411 # migration. 412 skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, literal(table)]}.first 413 skip_fks_hash = skipped_foreign_keys[skip_table] = {} 414 skip_fks.each{|fk| skip_fks_hash[fk[:columns]] = fk} 415 this_loop << skip_table 416 end 417 418 # Add sorted tables from this loop to the final list 419 sorted_tables.concat(_literal_table_sort(this_loop)) 420 421 # Remove tables that were handled this loop 422 this_loop.each{|t| table_fks.delete(t)} 423 end 424 425 [sorted_tables, skipped_foreign_keys] 426 end
Do a topological sort of tables, so that referenced tables come before referencing tables. Returns an array of sorted tables and a hash of skipped foreign keys. The hash will be empty unless there are circular dependencies.
Source
# File lib/sequel/extensions/schema_dumper.rb 430 def use_column_schema_to_ruby_default_fallback? 431 database_type != :mysql 432 end