miguel 0.1.0.pre2 → 0.1.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.gemfile +1 -0
- data/README.md +35 -17
- data/examples/schema.rb +1 -1
- data/lib/miguel/command.rb +14 -6
- data/lib/miguel/importer.rb +34 -15
- data/lib/miguel/schema.rb +119 -30
- data/miguel.gemspec +1 -1
- data/test/data/schema.rb +100 -0
- data/test/data/schema.txt +100 -0
- data/test/data/simple.rb +16 -0
- data/test/data/simple.txt +10 -0
- data/test/data/simple_mysql.txt +10 -0
- data/test/test_schema.rb +47 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be756726f39332cdc296e5a236f934748da5676d
|
4
|
+
data.tar.gz: bac00b2ce0a29b8e020f78c5bb76bf41b4185ac7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7ff9bb4c91af68d0902e63f3bf4616ffece4be5015ed3517cf5915f86632f1dd60a8d3f37b7c1271803795c79f4de9d9ab70d1ff5194002562bdd06897b6aa4
|
7
|
+
data.tar.gz: 2d1ca527b7bf924d5fb571c1a320d86030e9a004c4bd13c106c41fd795f571e079c1359ead9f4dd11e7e301a6ba534244a7b8a9f53a0fc680a7036e53533d258
|
data/.travis.gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Miguel
|
2
2
|
|
3
|
-
[![Gem Version](https://img.shields.io/gem/v/miguel.svg)](http://rubygems.org/gems/miguel) [![Build Status](https://
|
3
|
+
[![Gem Version](https://img.shields.io/gem/v/miguel.svg)](http://rubygems.org/gems/miguel) [![Build Status](https://travis-ci.org/raxoft/miguel.svg?branch=master)](http://travis-ci.org/raxoft/miguel) [![Dependency Status](https://img.shields.io/gemnasium/raxoft/miguel.svg)](https://gemnasium.com/raxoft/miguel) [![Code Climate](https://img.shields.io/codeclimate/github/raxoft/miguel.svg)](https://codeclimate.com/github/raxoft/miguel) [![Coverage](https://img.shields.io/codeclimate/coverage/github/raxoft/miguel.svg)](https://codeclimate.com/github/raxoft/miguel) [![Donate](https://img.shields.io/badge/support-donate-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=paypal%40raxoft%2ecz&item_name=Miguel%20%2d%20Database%20migration%20tool&no_shipping=1&return=https%3a%2f%2fgithub%2ecom%2fraxoft%2fmiguel&cancel_return=https%3a%2f%2fgithub%2ecom%2fraxoft%2fmiguel&cn=Optional%20Feedback&tax=0¤cy_code=EUR&bn=PP%2dDonationsBF&charset=UTF%2d8)
|
4
4
|
|
5
5
|
Miguel is a tool for sane management of database schemas. It aims to help with these goals:
|
6
6
|
|
@@ -10,7 +10,7 @@ Miguel is a tool for sane management of database schemas. It aims to help with t
|
|
10
10
|
|
11
11
|
To achieve this, it provides the following features:
|
12
12
|
|
13
|
-
* [Sequel]
|
13
|
+
* [Sequel][]-like DSL for schema description with some enhancements.
|
14
14
|
* Load schema from given description file or from given database.
|
15
15
|
* Show changes necessary to turn one schema into another.
|
16
16
|
* Render those changes as Sequel's change or up/down migrations.
|
@@ -110,22 +110,30 @@ See documentation of the `set_defaults` method for details.
|
|
110
110
|
The preset defaults are like this:
|
111
111
|
|
112
112
|
``` ruby
|
113
|
-
set_defaults :global, :
|
114
|
-
|
115
|
-
set_defaults :foreign_key, :key => :id, :type => :integer, :unsigned => true
|
116
|
-
set_defaults :unique, :index, :unique => true
|
113
|
+
set_defaults :global, null: false
|
114
|
+
|
117
115
|
set_defaults :Bool, :TrueClass
|
118
|
-
set_defaults :True, :TrueClass, :
|
119
|
-
set_defaults :False, :TrueClass, :
|
120
|
-
set_defaults :Signed, :integer, :
|
121
|
-
set_defaults :Unsigned, :integer, :
|
122
|
-
set_defaults :Text, :String, :
|
123
|
-
set_defaults :Time, :timestamp, :
|
124
|
-
set_defaults :Time?, :timestamp, :
|
116
|
+
set_defaults :True, :TrueClass, default: true
|
117
|
+
set_defaults :False, :TrueClass, default: false
|
118
|
+
set_defaults :Signed, :integer, unsigned: false
|
119
|
+
set_defaults :Unsigned, :integer, unsigned: true
|
120
|
+
set_defaults :Text, :String, text: true
|
121
|
+
set_defaults :Time, :timestamp, default: '0000-00-00 00:00:00'
|
122
|
+
set_defaults :Time?, :timestamp, default: nil
|
123
|
+
|
124
|
+
set_defaults :unique, :index, unique: true
|
125
|
+
|
126
|
+
set_defaults :primary_key, type: :integer, unsigned: false
|
127
|
+
set_defaults :foreign_key, key: :id, type: :integer, unsigned: false
|
125
128
|
```
|
126
129
|
|
127
|
-
|
130
|
+
If you prefer unsigned keys instead and your database engine supports it,
|
131
|
+
you can pass the `unsigned_keys: true` option to `define` to make it happen.
|
132
|
+
|
133
|
+
Finally, the `timestamps` helper can be used to create the
|
128
134
|
`create_time` and `update_time` timestamps for you.
|
135
|
+
If you pass the `mysql_timestamps: true` option to `define`,
|
136
|
+
the `update_time` timestamp will have the MySQL auto-update feature enabled.
|
129
137
|
|
130
138
|
## Using the command
|
131
139
|
|
@@ -145,10 +153,13 @@ the command will always ask for a confirmation before changing anything in the d
|
|
145
153
|
(unless you use the `--force` option).
|
146
154
|
|
147
155
|
Databases can be specified either by their Sequel URL like
|
156
|
+
`sqlite://test.db`
|
157
|
+
or
|
148
158
|
`mysql2://user:password@localhost/main`,
|
149
159
|
or by the common database `.yml` config file:
|
150
160
|
|
151
161
|
``` yaml
|
162
|
+
# Example db.yml.
|
152
163
|
adapter: mysql2
|
153
164
|
user: jim
|
154
165
|
password: sup3rsecr3t
|
@@ -176,12 +187,15 @@ leaving dozens of piecewise migration files finally behind.
|
|
176
187
|
|
177
188
|
## Limitations
|
178
189
|
|
179
|
-
The type support is geared towards
|
190
|
+
The database specific type support is geared towards [MySQL][] and [SQLite][].
|
180
191
|
Generic types should work with any database, but your mileage may vary.
|
181
192
|
|
182
|
-
|
193
|
+
Changing the type of primary keys can be as problematic as with normal Sequel migrations,
|
194
|
+
so it's best to set them once and stick with them.
|
195
|
+
|
196
|
+
It is currently not possible to describe renaming of columns or tables.
|
183
197
|
If you need that,
|
184
|
-
simply rename
|
198
|
+
simply rename them directly in the database or by using standard Sequel migration,
|
185
199
|
and adjust the schema description accordingly.
|
186
200
|
|
187
201
|
## Credits
|
@@ -189,3 +203,7 @@ and adjust the schema description accordingly.
|
|
189
203
|
Copyright © 2015 Patrik Rak
|
190
204
|
|
191
205
|
Miguel is released under the MIT license.
|
206
|
+
|
207
|
+
[Sequel]: http://sequel.jeremyevans.net/
|
208
|
+
[MySQL]: https://www.mysql.com/
|
209
|
+
[SQLite]: https://www.sqlite.org/
|
data/examples/schema.rb
CHANGED
data/lib/miguel/command.rb
CHANGED
@@ -163,19 +163,27 @@ module Miguel
|
|
163
163
|
db = if name.nil? or name.empty?
|
164
164
|
fail "Missing database name."
|
165
165
|
elsif File.exist?( name )
|
166
|
-
|
167
|
-
config = YAML.load_file( name )
|
168
|
-
env = self.env || "development"
|
169
|
-
config = config[ env ] || config[ env.to_sym ] || config
|
170
|
-
config.keys.each{ |k| config[ k.to_sym ] = config.delete( k ) }
|
166
|
+
config = load_db_config( name )
|
171
167
|
Sequel.connect( config )
|
172
|
-
|
168
|
+
elsif name =~ /:\/\//
|
173
169
|
Sequel.connect( name )
|
170
|
+
else
|
171
|
+
fail "Database config #{name} not found."
|
174
172
|
end
|
175
173
|
db.loggers = loggers
|
176
174
|
db
|
177
175
|
end
|
178
176
|
|
177
|
+
# Load database config from given file.
|
178
|
+
def load_db_config( name )
|
179
|
+
require 'yaml'
|
180
|
+
config = YAML.load_file( name )
|
181
|
+
env = self.env || "development"
|
182
|
+
config = config[ env ] || config[ env.to_sym ] || config
|
183
|
+
config.keys.each{ |k| config[ k.to_sym ] = config.delete( k ) }
|
184
|
+
config
|
185
|
+
end
|
186
|
+
|
179
187
|
# Show changes between the two schemas.
|
180
188
|
def show_changes( from, to )
|
181
189
|
m = Migrator.new
|
data/lib/miguel/importer.rb
CHANGED
@@ -17,7 +17,7 @@ module Miguel
|
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
-
# Which
|
20
|
+
# Which characters we convert when parsing enum values.
|
21
21
|
# Quite likely not exhaustive, but sufficient for our purposes.
|
22
22
|
ESCAPED_CHARS = {
|
23
23
|
"''" => "'",
|
@@ -56,19 +56,24 @@ module Miguel
|
|
56
56
|
when /\A(enum|set)\((.*)\)\z/
|
57
57
|
return $1.to_sym, :elements => parse_elements( $2 )
|
58
58
|
end
|
59
|
+
when :sqlite
|
60
|
+
case type
|
61
|
+
when /\Ainteger UNSIGNED\z/
|
62
|
+
return :integer, :unsigned => true
|
63
|
+
end
|
59
64
|
end
|
60
65
|
|
61
66
|
case type
|
62
67
|
when /\Avarchar/
|
63
|
-
return :
|
68
|
+
return :String, :default_size => 255
|
64
69
|
when /\Achar/
|
65
|
-
return :
|
70
|
+
return :String, :fixed => true, :default_size => 255
|
66
71
|
when /\Atext\z/
|
67
|
-
return :
|
72
|
+
return :String, :text => true
|
68
73
|
when /\A(\w+)\([\s\d,]+\)\z/
|
69
74
|
return $1.to_sym
|
70
75
|
when /\A\w+\z/
|
71
|
-
return type
|
76
|
+
return type.to_sym
|
72
77
|
end
|
73
78
|
|
74
79
|
ruby_type
|
@@ -98,6 +103,28 @@ module Miguel
|
|
98
103
|
[ type, opts ]
|
99
104
|
end
|
100
105
|
|
106
|
+
# Types we support for default values. Anything else is converted to String.
|
107
|
+
DEFAULT_TYPES = [ String, Numeric, TrueClass, FalseClass ]
|
108
|
+
|
109
|
+
# Convert given database default of given type to default used by our schema definitions.
|
110
|
+
def revert_default( type, default, ruby_default )
|
111
|
+
default = ruby_default unless ruby_default.nil?
|
112
|
+
return if default.nil?
|
113
|
+
|
114
|
+
default = default.to_s unless DEFAULT_TYPES.any?{ |x| default.is_a?( x ) }
|
115
|
+
|
116
|
+
if type.to_s =~ /date|time/
|
117
|
+
case default
|
118
|
+
when /\A'([-: \d]+)'\z/
|
119
|
+
default = $1
|
120
|
+
when 'CURRENT_TIMESTAMP'
|
121
|
+
# This matches our use of MySQL timestamps in schema definitions.
|
122
|
+
default = Sequel.lit('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
default
|
126
|
+
end
|
127
|
+
|
101
128
|
# Import indexes of given table.
|
102
129
|
def import_indexes( table )
|
103
130
|
# Foreign keys also automatically create indexes, which we must exclude when importing.
|
@@ -122,12 +149,6 @@ module Miguel
|
|
122
149
|
end
|
123
150
|
end
|
124
151
|
|
125
|
-
# Our custom mapping of some database defaults into the values we use in our schemas.
|
126
|
-
DEFAULT_CONSTANTS = {
|
127
|
-
"0000-00-00 00:00:00" => 0,
|
128
|
-
"CURRENT_TIMESTAMP" => Sequel.lit("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
129
|
-
}
|
130
|
-
|
131
152
|
# Options which are ignored for columns.
|
132
153
|
# These are usually just schema hints which the user normally doesn't specify.
|
133
154
|
IGNORED_OPTS = [ :max_length ]
|
@@ -169,11 +190,9 @@ module Miguel
|
|
169
190
|
default = opts.delete( :default )
|
170
191
|
ruby_default = opts.delete( :ruby_default )
|
171
192
|
|
172
|
-
default =
|
193
|
+
default = revert_default( type, default, ruby_default )
|
173
194
|
|
174
|
-
unless default.nil?
|
175
|
-
opts[ :default ] = default
|
176
|
-
end
|
195
|
+
opts[ :default ] = default unless default.nil?
|
177
196
|
|
178
197
|
# Deal with primary keys, which is a bit obscure because of the auto-increment handling.
|
179
198
|
|
data/lib/miguel/schema.rb
CHANGED
@@ -17,9 +17,9 @@ module Miguel
|
|
17
17
|
def out_value( value )
|
18
18
|
case value
|
19
19
|
when Hash
|
20
|
-
"{
|
20
|
+
"{#{ value.map{ |k,v| "#{out_value( k )} => #{out_value( v )}" }.join( ', ' ) }}"
|
21
21
|
when Array
|
22
|
-
"[
|
22
|
+
"[#{ value.map{ |v| out_value( v ) }.join( ', ' ) }]"
|
23
23
|
when Sequel::LiteralString
|
24
24
|
"Sequel.lit(#{value.to_s.inspect})"
|
25
25
|
else
|
@@ -131,12 +131,22 @@ module Miguel
|
|
131
131
|
CANONIC_TYPES[ t ] || t
|
132
132
|
end
|
133
133
|
|
134
|
+
# Convert given size into its canonic form.
|
135
|
+
def canonic_size( size )
|
136
|
+
if canonic_type == :decimal && size.is_a?( Integer )
|
137
|
+
[ size, 0 ]
|
138
|
+
else
|
139
|
+
size
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
134
143
|
# Default options implied for certain types.
|
135
144
|
DEFAULT_OPTS = {
|
136
145
|
:string => { :size => 255 },
|
137
146
|
:bigint => { :size => 20 },
|
138
147
|
:decimal => { :size => [ 10, 0 ] },
|
139
148
|
:integer => { :unsigned => false },
|
149
|
+
:primary_key => { :unsigned => false, :type => :integer },
|
140
150
|
}
|
141
151
|
|
142
152
|
# Options which are ignored for columns.
|
@@ -149,6 +159,7 @@ module Miguel
|
|
149
159
|
o = { :type => canonic_type, :default => default }
|
150
160
|
o.merge!( DEFAULT_OPTS[ canonic_type ] || {} )
|
151
161
|
o.merge!( opts )
|
162
|
+
o[ :size ] = canonic_size( o[ :size ] )
|
152
163
|
o.delete_if{ |key, value| IGNORED_OPTS.include? key }
|
153
164
|
end
|
154
165
|
|
@@ -229,7 +240,9 @@ module Miguel
|
|
229
240
|
|
230
241
|
# Get the foreign key options, in a canonic way.
|
231
242
|
def canonic_opts
|
232
|
-
|
243
|
+
o = { :on_update => :no_action, :on_delete => :no_action }
|
244
|
+
o.merge!( opts )
|
245
|
+
o.delete_if{ |key, value| IGNORED_OPTS.include? key }
|
233
246
|
end
|
234
247
|
|
235
248
|
# Compare one foreign key with another one.
|
@@ -274,15 +287,21 @@ module Miguel
|
|
274
287
|
|
275
288
|
# Create the default timestamp fields.
|
276
289
|
def timestamps
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
290
|
+
opts = @table.schema.opts
|
291
|
+
if opts[ :mysql_timestamps ]
|
292
|
+
# Unfortunately, MySQL allows only either automatic create timestamp
|
293
|
+
# (DEFAULT CURRENT_TIMESTAMP) or automatic update timestamp (DEFAULT
|
294
|
+
# CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP), but not both - one
|
295
|
+
# has to be updated manually anyway. So we choose to have the update timestamp
|
296
|
+
# automatically updated, and let the create one to be set manually.
|
297
|
+
# Also, Sequel doesn't currently honor :on_update for column definitions,
|
298
|
+
# so we have to use default literal to make it work. Sigh.
|
299
|
+
timestamp :create_time, :null => false, :default => '0000-00-00 00:00:00'
|
300
|
+
timestamp :update_time, :null => false, :default => Sequel.lit( 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' )
|
301
|
+
else
|
302
|
+
Time :create_time
|
303
|
+
Time :update_time
|
304
|
+
end
|
286
305
|
end
|
287
306
|
end
|
288
307
|
|
@@ -373,12 +392,16 @@ module Miguel
|
|
373
392
|
|
374
393
|
end
|
375
394
|
|
395
|
+
# Schema options.
|
396
|
+
attr_reader :opts
|
397
|
+
|
376
398
|
# Create new schema.
|
377
|
-
def initialize
|
399
|
+
def initialize( opts = {} )
|
378
400
|
@tables = {}
|
379
401
|
@aliases = {}
|
380
402
|
@defaults = {}
|
381
403
|
@callbacks = {}
|
404
|
+
@opts = self.class.default_options.merge(opts)
|
382
405
|
end
|
383
406
|
|
384
407
|
# Get all tables.
|
@@ -452,6 +475,15 @@ module Miguel
|
|
452
475
|
@defaults[ name ] = args.pop if args.last.is_a? Hash
|
453
476
|
@callbacks[ name ] = block
|
454
477
|
fail( ArgumentError, "invalid defaults for #{name}" ) unless args.empty?
|
478
|
+
self
|
479
|
+
end
|
480
|
+
|
481
|
+
# Clear defaults and aliases for given statement.
|
482
|
+
def clear_defaults( name )
|
483
|
+
@aliases.delete( name )
|
484
|
+
@defaults.delete( name )
|
485
|
+
@callbacks.delete( name )
|
486
|
+
self
|
455
487
|
end
|
456
488
|
|
457
489
|
# Get default options for given statement.
|
@@ -464,31 +496,41 @@ module Miguel
|
|
464
496
|
# The current set of defaults is as follows:
|
465
497
|
#
|
466
498
|
# :global, :null => false
|
467
|
-
#
|
468
|
-
# :foreign_key, :key => :id, :type => :integer, :unsigned => true
|
469
|
-
# :unique, :index, :unique => true
|
499
|
+
#
|
470
500
|
# :Bool, :TrueClass
|
471
501
|
# :True, :TrueClass, :default => true
|
472
502
|
# :False, :TrueClass, :default => false
|
473
503
|
# :Signed, :integer, :unsigned => false
|
474
504
|
# :Unsigned, :integer, :unsigned => true
|
475
505
|
# :Text, :String, :text => true
|
476
|
-
# :Time, :timestamp, :default =>
|
506
|
+
# :Time, :timestamp, :default => '0000-00-00 00:00:00'
|
477
507
|
# :Time?, :timestamp, :default => nil
|
478
|
-
|
508
|
+
#
|
509
|
+
# :unique, :index, :unique => true
|
510
|
+
#
|
511
|
+
# :primary_key, :type => :integer, :unsigned => false
|
512
|
+
# :foreign_key, :key => :id, :type => :integer, :unsigned => false
|
513
|
+
#
|
514
|
+
# If the +unsigned_keys+ option is set to true, the keys
|
515
|
+
# are set up to use unsigned integers instead.
|
516
|
+
def set_standard_defaults( opts = self.opts )
|
479
517
|
|
480
518
|
# We set NOT NULL on everything by default, but note the ?
|
481
519
|
# syntax (like Text?) which declares the column as NULL.
|
482
|
-
|
520
|
+
|
521
|
+
set_defaults :global, :null => false
|
522
|
+
|
523
|
+
# We also like our keys unsigned, so we allow setting that, too.
|
483
524
|
# Unfortunately, :unsigned currently works only with :integer,
|
484
525
|
# not the default :Integer, and :integer can't be specified for compound keys,
|
485
526
|
# so we have to use the callback to set the type only at correct times.
|
486
527
|
|
487
|
-
|
488
|
-
|
528
|
+
unsigned_keys = !! opts[ :unsigned_keys ]
|
529
|
+
|
530
|
+
set_defaults :primary_key, :unsigned => unsigned_keys do |opts,args,table|
|
489
531
|
opts[ :type ] ||= :integer unless args.first.is_a? Array
|
490
532
|
end
|
491
|
-
set_defaults :foreign_key, :key => :id, :unsigned =>
|
533
|
+
set_defaults :foreign_key, :key => :id, :unsigned => unsigned_keys do |opts,args,table|
|
492
534
|
opts[ :type ] ||= :integer unless args.first.is_a? Array
|
493
535
|
end
|
494
536
|
|
@@ -511,7 +553,7 @@ module Miguel
|
|
511
553
|
# we have to be careful to turn off the MySQL autoupdate behavior.
|
512
554
|
# That's why we have to set defaults explicitly.
|
513
555
|
|
514
|
-
set_defaults :Time, :timestamp, :default =>
|
556
|
+
set_defaults :Time, :timestamp, :default => '0000-00-00 00:00:00'
|
515
557
|
set_defaults :Time?, :timestamp, :default => nil
|
516
558
|
|
517
559
|
self
|
@@ -551,7 +593,7 @@ module Miguel
|
|
551
593
|
end
|
552
594
|
|
553
595
|
# Define schema with the provided block.
|
554
|
-
def define(
|
596
|
+
def define( &block )
|
555
597
|
fail( ArgumentError, "missing schema block" ) unless block
|
556
598
|
set_standard_defaults unless opts[ :use_defaults ] == false
|
557
599
|
instance_eval &block
|
@@ -560,22 +602,69 @@ module Miguel
|
|
560
602
|
|
561
603
|
class << self
|
562
604
|
|
563
|
-
#
|
564
|
-
|
605
|
+
# Mutex protecting access to thread sensitive variables.
|
606
|
+
LOCK = Mutex.new
|
607
|
+
|
608
|
+
# Get default schema options.
|
609
|
+
def default_options
|
610
|
+
sync{ @opts || {} }
|
611
|
+
end
|
612
|
+
|
613
|
+
# Set default schema options.
|
614
|
+
def default_options=( opts )
|
615
|
+
sync{ @opts = opts.nil? ? nil : opts.dup }
|
616
|
+
end
|
617
|
+
alias set_default_options default_options=
|
565
618
|
|
566
619
|
# Define schema with provided block.
|
567
620
|
def define( opts = {}, &block )
|
568
|
-
|
621
|
+
set_schema( new( opts ).define( &block ) )
|
569
622
|
end
|
570
623
|
|
571
624
|
# Load schema from given file.
|
572
|
-
def load( name )
|
573
|
-
@schema = nil
|
625
|
+
def load( name, opts = {} )
|
574
626
|
name = File.expand_path( name )
|
575
|
-
|
627
|
+
sync do
|
628
|
+
get_schema do
|
629
|
+
with_options( opts ) do
|
630
|
+
Kernel.load( name )
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
private
|
637
|
+
|
638
|
+
# Serialize access to thread sensitive variables.
|
639
|
+
def sync( &block )
|
640
|
+
LOCK.synchronize &block
|
641
|
+
rescue ThreadError
|
642
|
+
yield
|
643
|
+
end
|
644
|
+
|
645
|
+
# Store given schema for later if requested.
|
646
|
+
def set_schema( schema )
|
647
|
+
sync{ @schema = schema if @schema == self }
|
576
648
|
schema
|
577
649
|
end
|
578
650
|
|
651
|
+
# Execute given block and return stored schema.
|
652
|
+
def get_schema
|
653
|
+
@schema = self
|
654
|
+
yield
|
655
|
+
@schema unless @schema == self
|
656
|
+
ensure
|
657
|
+
@schema = nil
|
658
|
+
end
|
659
|
+
|
660
|
+
# Execute block with given options.
|
661
|
+
def with_options( opts )
|
662
|
+
saved, @opts = @opts, default_options.merge(opts)
|
663
|
+
yield
|
664
|
+
ensure
|
665
|
+
@opts = saved
|
666
|
+
end
|
667
|
+
|
579
668
|
end
|
580
669
|
|
581
670
|
end
|
data/miguel.gemspec
CHANGED
@@ -4,7 +4,7 @@ require File.expand_path( '../lib/miguel/version', __FILE__ )
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'miguel'
|
7
|
-
s.version = Miguel::VERSION + '.
|
7
|
+
s.version = Miguel::VERSION + '.pre3'
|
8
8
|
s.summary = 'Database migrator and migration generator for Sequel.'
|
9
9
|
s.description = <<EOT
|
10
10
|
This gem makes it easy to create and maintain an up-to-date database schema
|
data/test/data/schema.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# Test schema.
|
2
|
+
|
3
|
+
Miguel::Schema.define do
|
4
|
+
|
5
|
+
table :sequel_types do
|
6
|
+
Integer :a0 # integer
|
7
|
+
String :a1 # varchar(255)
|
8
|
+
String :a2, :size=>50 # varchar(50)
|
9
|
+
String :a3, :fixed=>true # char(255)
|
10
|
+
String :a4, :fixed=>true, :size=>50 # char(50)
|
11
|
+
String :a5, :text=>true # text
|
12
|
+
File :b # blob
|
13
|
+
Fixnum :c # integer
|
14
|
+
Bignum :d # bigint
|
15
|
+
Float :e # double precision
|
16
|
+
BigDecimal :f # numeric
|
17
|
+
BigDecimal :f2, :size=>10 # numeric(10)
|
18
|
+
BigDecimal :f3, :size=>[10, 2] # numeric(10, 2)
|
19
|
+
Date :g # date
|
20
|
+
DateTime :h # timestamp
|
21
|
+
Time :i # timestamp
|
22
|
+
Numeric :j # numeric
|
23
|
+
TrueClass :k # boolean
|
24
|
+
FalseClass :l # boolean
|
25
|
+
end
|
26
|
+
|
27
|
+
table :miguel_types do
|
28
|
+
String :string
|
29
|
+
Text :text
|
30
|
+
File :blob
|
31
|
+
Integer :int
|
32
|
+
Signed :signed
|
33
|
+
Unsigned :unsigned
|
34
|
+
Float :float
|
35
|
+
Bool :bool
|
36
|
+
True :true
|
37
|
+
False :false
|
38
|
+
Time :t
|
39
|
+
end
|
40
|
+
|
41
|
+
table :timestamps do
|
42
|
+
Time :t1
|
43
|
+
Time? :t2
|
44
|
+
timestamps
|
45
|
+
end
|
46
|
+
|
47
|
+
table :users do
|
48
|
+
primary_key :id
|
49
|
+
String :name
|
50
|
+
unique :name
|
51
|
+
end
|
52
|
+
|
53
|
+
table :simple do
|
54
|
+
primary_key :id
|
55
|
+
foreign_key :user_id, :users
|
56
|
+
index :user_id
|
57
|
+
end
|
58
|
+
|
59
|
+
table :reuse do
|
60
|
+
primary_key :user_id
|
61
|
+
foreign_key [:user_id], :users
|
62
|
+
end
|
63
|
+
|
64
|
+
table :compound do
|
65
|
+
Unsigned :a
|
66
|
+
Unsigned :b
|
67
|
+
primary_key [:a, :b]
|
68
|
+
foreign_key [:b, :a], :compound, key: [:a, :b]
|
69
|
+
String :c
|
70
|
+
Signed :d
|
71
|
+
unique [:a, :c]
|
72
|
+
index [:b, :c, :d]
|
73
|
+
index [:b, :a]
|
74
|
+
end
|
75
|
+
|
76
|
+
table :null do
|
77
|
+
String? :string
|
78
|
+
Text? :text
|
79
|
+
File? :blob
|
80
|
+
Integer? :int
|
81
|
+
Signed? :signed
|
82
|
+
Unsigned? :unsigned
|
83
|
+
Float? :float
|
84
|
+
Bool? :bool
|
85
|
+
True? :true
|
86
|
+
False? :false
|
87
|
+
Time? :t
|
88
|
+
foreign_key? :user_id, :users
|
89
|
+
end
|
90
|
+
|
91
|
+
join_table :user_id, :users, :simple_id, :simple
|
92
|
+
|
93
|
+
join_table :left_id, :users, :right_id, :users, :self_join do
|
94
|
+
timestamps
|
95
|
+
index :create_time
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# EOF #
|
@@ -0,0 +1,100 @@
|
|
1
|
+
table :sequel_types do
|
2
|
+
Integer :a0, :null => false
|
3
|
+
String :a1, :null => false
|
4
|
+
String :a2, :null => false, :size => 50
|
5
|
+
String :a3, :null => false, :fixed => true
|
6
|
+
String :a4, :null => false, :fixed => true, :size => 50
|
7
|
+
String :a5, :null => false, :text => true
|
8
|
+
File :b, :null => false
|
9
|
+
Fixnum :c, :null => false
|
10
|
+
Bignum :d, :null => false
|
11
|
+
Float :e, :null => false
|
12
|
+
BigDecimal :f, :null => false
|
13
|
+
BigDecimal :f2, :null => false, :size => 10
|
14
|
+
BigDecimal :f3, :null => false, :size => [10, 2]
|
15
|
+
Date :g, :null => false
|
16
|
+
DateTime :h, :null => false
|
17
|
+
timestamp :i, :null => false, :default => "0000-00-00 00:00:00"
|
18
|
+
Numeric :j, :null => false
|
19
|
+
TrueClass :k, :null => false
|
20
|
+
FalseClass :l, :null => false
|
21
|
+
end
|
22
|
+
table :miguel_types do
|
23
|
+
String :string, :null => false
|
24
|
+
String :text, :null => false, :text => true
|
25
|
+
File :blob, :null => false
|
26
|
+
Integer :int, :null => false
|
27
|
+
integer :signed, :null => false, :unsigned => false
|
28
|
+
integer :unsigned, :null => false, :unsigned => true
|
29
|
+
Float :float, :null => false
|
30
|
+
TrueClass :bool, :null => false
|
31
|
+
TrueClass :true, :null => false, :default => true
|
32
|
+
TrueClass :false, :null => false, :default => false
|
33
|
+
timestamp :t, :null => false, :default => "0000-00-00 00:00:00"
|
34
|
+
end
|
35
|
+
table :timestamps do
|
36
|
+
timestamp :t1, :null => false, :default => "0000-00-00 00:00:00"
|
37
|
+
timestamp :t2, :null => true, :default => nil
|
38
|
+
timestamp :create_time, :null => false, :default => "0000-00-00 00:00:00"
|
39
|
+
timestamp :update_time, :null => false, :default => "0000-00-00 00:00:00"
|
40
|
+
end
|
41
|
+
table :users do
|
42
|
+
primary_key :id, :null => false, :unsigned => false, :type => :integer
|
43
|
+
String :name, :null => false
|
44
|
+
index [:name], :null => false, :unique => true
|
45
|
+
end
|
46
|
+
table :simple do
|
47
|
+
primary_key :id, :null => false, :unsigned => false, :type => :integer
|
48
|
+
integer :user_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
49
|
+
index [:user_id], :null => false
|
50
|
+
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
51
|
+
end
|
52
|
+
table :reuse do
|
53
|
+
primary_key :user_id, :null => false, :unsigned => false, :type => :integer
|
54
|
+
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
55
|
+
end
|
56
|
+
table :compound do
|
57
|
+
integer :a, :null => false, :unsigned => true
|
58
|
+
integer :b, :null => false, :unsigned => true
|
59
|
+
primary_key [:a, :b], :null => false, :unsigned => false
|
60
|
+
String :c, :null => false
|
61
|
+
integer :d, :null => false, :unsigned => false
|
62
|
+
index [:a, :c], :null => false, :unique => true
|
63
|
+
index [:b, :c, :d], :null => false
|
64
|
+
index [:b, :a], :null => false
|
65
|
+
foreign_key [:b, :a], :compound, :null => false, :key => [:a, :b], :unsigned => false
|
66
|
+
end
|
67
|
+
table :null do
|
68
|
+
String :string, :null => true
|
69
|
+
String :text, :null => true, :text => true
|
70
|
+
File :blob, :null => true
|
71
|
+
Integer :int, :null => true
|
72
|
+
integer :signed, :null => true, :unsigned => false
|
73
|
+
integer :unsigned, :null => true, :unsigned => true
|
74
|
+
Float :float, :null => true
|
75
|
+
TrueClass :bool, :null => true
|
76
|
+
TrueClass :true, :null => true, :default => true
|
77
|
+
TrueClass :false, :null => true, :default => false
|
78
|
+
timestamp :t, :null => true, :default => nil
|
79
|
+
integer :user_id, :null => true, :key => [:id], :unsigned => false, :type => :integer
|
80
|
+
foreign_key [:user_id], :users, :null => true, :key => [:id], :unsigned => false, :type => :integer
|
81
|
+
end
|
82
|
+
table :simple_users do
|
83
|
+
integer :user_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
84
|
+
integer :simple_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
85
|
+
primary_key [:user_id, :simple_id], :null => false, :unsigned => false
|
86
|
+
index [:simple_id, :user_id], :null => false, :unique => true
|
87
|
+
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
88
|
+
foreign_key [:simple_id], :simple, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
89
|
+
end
|
90
|
+
table :self_join do
|
91
|
+
integer :left_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
92
|
+
integer :right_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
93
|
+
primary_key [:left_id, :right_id], :null => false, :unsigned => false
|
94
|
+
timestamp :create_time, :null => false, :default => "0000-00-00 00:00:00"
|
95
|
+
timestamp :update_time, :null => false, :default => "0000-00-00 00:00:00"
|
96
|
+
index [:right_id, :left_id], :null => false, :unique => true
|
97
|
+
index [:create_time], :null => false
|
98
|
+
foreign_key [:left_id], :users, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
99
|
+
foreign_key [:right_id], :users, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
100
|
+
end
|
data/test/data/simple.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
table :items do
|
2
|
+
primary_key :id, :null => false, :unsigned => false, :type => :integer
|
3
|
+
String :name, :null => false
|
4
|
+
integer :parent_id, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
5
|
+
timestamp :create_time, :null => false, :default => "0000-00-00 00:00:00"
|
6
|
+
timestamp :update_time, :null => false, :default => "0000-00-00 00:00:00"
|
7
|
+
index [:name], :null => false, :unique => true
|
8
|
+
index [:parent_id], :null => false
|
9
|
+
foreign_key [:parent_id], :items, :null => false, :key => [:id], :unsigned => false, :type => :integer
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
table :items do
|
2
|
+
primary_key :id, :null => false, :unsigned => true, :type => :integer
|
3
|
+
String :name, :null => false
|
4
|
+
integer :parent_id, :null => false, :key => [:id], :unsigned => true, :type => :integer
|
5
|
+
timestamp :create_time, :null => false, :default => "0000-00-00 00:00:00"
|
6
|
+
timestamp :update_time, :null => false, :default => Sequel.lit("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
|
7
|
+
index [:name], :null => false, :unique => true
|
8
|
+
index [:parent_id], :null => false
|
9
|
+
foreign_key [:parent_id], :items, :null => false, :key => [:id], :unsigned => true, :type => :integer
|
10
|
+
end
|
data/test/test_schema.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Test Schema.
|
2
|
+
|
3
|
+
require 'miguel/schema'
|
4
|
+
|
5
|
+
describe Miguel::Schema do
|
6
|
+
|
7
|
+
DATA_DIR = File.expand_path( "#{__FILE__}/../data" )
|
8
|
+
|
9
|
+
def data( name )
|
10
|
+
"#{DATA_DIR}/#{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'load and dump schema properly' do
|
14
|
+
schema = Miguel::Schema.load( data( 'schema.rb' ) )
|
15
|
+
schema.dump.to_s.should == File.read( data( 'schema.txt' ) )
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'allow changing default schema options temporarily' do
|
19
|
+
schema = Miguel::Schema.load( data( 'simple.rb' ), unsigned_keys: true, mysql_timestamps: true )
|
20
|
+
schema.dump.to_s.should == File.read( data( 'simple_mysql.txt' ) )
|
21
|
+
Miguel::Schema.new.opts.should.be.empty
|
22
|
+
|
23
|
+
schema = Miguel::Schema.load( data( 'simple.rb' ) )
|
24
|
+
schema.dump.to_s.should == File.read( data( 'simple.txt' ) )
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'allow changing default schema options permanently' do
|
28
|
+
Miguel::Schema.default_options.should == {}
|
29
|
+
|
30
|
+
Miguel::Schema.set_default_options( unsigned_keys: true, mysql_timestamps: true )
|
31
|
+
Miguel::Schema.new( test: true ).opts.should == { unsigned_keys: true, mysql_timestamps: true, test: true }
|
32
|
+
Miguel::Schema.default_options.should == { unsigned_keys: true, mysql_timestamps: true }
|
33
|
+
|
34
|
+
schema = Miguel::Schema.load( data( 'simple.rb' ) )
|
35
|
+
schema.dump.to_s.should == File.read( data( 'simple_mysql.txt' ) )
|
36
|
+
|
37
|
+
Miguel::Schema.default_options = nil
|
38
|
+
Miguel::Schema.default_options.should == {}
|
39
|
+
Miguel::Schema.new.opts.should.be.empty
|
40
|
+
|
41
|
+
schema = Miguel::Schema.load( data( 'simple.rb' ) )
|
42
|
+
schema.dump.to_s.should == File.read( data( 'simple.txt' ) )
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# EOF #
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: miguel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.pre3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrik Rak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -64,7 +64,13 @@ files:
|
|
64
64
|
- lib/miguel/schema.rb
|
65
65
|
- lib/miguel/version.rb
|
66
66
|
- miguel.gemspec
|
67
|
+
- test/data/schema.rb
|
68
|
+
- test/data/schema.txt
|
69
|
+
- test/data/simple.rb
|
70
|
+
- test/data/simple.txt
|
71
|
+
- test/data/simple_mysql.txt
|
67
72
|
- test/test_dumper.rb
|
73
|
+
- test/test_schema.rb
|
68
74
|
homepage: http://rubygems.org/gems/miguel
|
69
75
|
licenses:
|
70
76
|
- MIT
|