ridgepole 1.0.1 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -5,21 +5,41 @@ Ridgepole is a tool to manage DB schema.
5
5
  It defines DB schema using [Rails DSL](http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps), and updates DB schema according to DSL.
6
6
  (like Chef/Puppet)
7
7
 
8
- [![Gem Version](https://badge.fury.io/rb/ridgepole.svg)](http://badge.fury.io/rb/ridgepole)
9
- [![Build Status](https://github.com/ridgepole/ridgepole/workflows/test/badge.svg?branch=1.0)](https://github.com/ridgepole/ridgepole/actions)
10
- [![Coverage Status](https://coveralls.io/repos/github/ridgepole/ridgepole/badge.svg?branch=1.0)](https://coveralls.io/github/ridgepole/ridgepole?branch=1.0)
11
-
12
- **Notice**
13
-
14
- * ActiveRecord 6.1 is supported in ridgepole v0.9, but the ActiveRecord dump has been changed, so there is a difference between ActiveRecord 5.x/6.0 format.
15
- * **If you use ActiveRecord 6.1, please modify Schemafile format**.
16
- * cf. https://github.com/ridgepole/ridgepole/pull/323
17
- * `DROP TABLE` is skipped by default in v1.0 and later versions.
18
- * If you want to `DROP TABLE`, please pass `--drop-table`.
19
- * cf. https://github.com/ridgepole/ridgepole/pull/363
20
- * In Rails 7.0, the output of dumper is different from Rails 6.
21
- * cf. https://github.com/rails/rails/issues/43909
22
- * cf. https://github.com/rails/rails/commit/c2a6f618d22cca4d9b7be7fa7652e7aac509350c#diff-55f41513f027a3d219629f475f03c2d1105ca55c5093d691e1b3dc4710c6cc0b
8
+ [![Gem Version](https://badge.fury.io/rb/ridgepole.svg)](https://badge.fury.io/rb/ridgepole)
9
+ [![test](https://github.com/ridgepole/ridgepole/actions/workflows/test.yml/badge.svg)](https://github.com/ridgepole/ridgepole/actions/workflows/test.yml)
10
+ [![codecov](https://codecov.io/gh/ridgepole/ridgepole/graph/badge.svg)](https://codecov.io/gh/ridgepole/ridgepole)
11
+
12
+ > [!warning]
13
+ > The order of columns when exporting has changed in Rails 8.1. https://github.com/rails/rails/pull/53281
14
+ >
15
+ > If you do not want to sort the columns, use `--disable-sort-columns` option.
16
+
17
+ > [!note]
18
+ > * ridgepole v3.1.0
19
+ > * Support Rails 8.1 (cf. https://github.com/ridgepole/ridgepole/pull/589)
20
+ > * ridgepole v3.0.0
21
+ > * Support Rails 8.0 (cf. https://github.com/ridgepole/ridgepole/pull/504)
22
+ > * ridgepole v2.0.0
23
+ > * Support Trilogy (cf. https://github.com/ridgepole/ridgepole/pull/447)
24
+ > * Support Rails 7.1 (cf. https://github.com/ridgepole/ridgepole/pull/441)
25
+ > * Drop support AcriveRecord 6.0 (cf. https://github.com/ridgepole/ridgepole/pull/440)
26
+ > * Drop support ActiveRecord 5.x in ridgepole v1.2.0.
27
+ > * Partitioning is no longer supported in ridgepole v1.1.0.
28
+ > * ActiveRecord 7.x has some incompatible changes. If you get unintended differences in `datetime` columns consider changing `precision`:
29
+ > * Add `precision: nil` for columns that previously had no `precision` specified (cf. https://github.com/ridgepole/ridgepole/issues/381)
30
+ > * Remove `precision: 6` from columns that previously had `precision: 6` specified explicitly (cf. https://github.com/ridgepole/ridgepole/issues/406)
31
+ > * For ActiveRecord 7.x series, please use AcriveRecord 7.0.2 or higher / Ridgepole 1.0.3 or higher.
32
+ > * cf. https://github.com/ridgepole/ridgepole/pull/380
33
+ > * ActiveRecord 6.1 is supported in ridgepole v0.9, but the ActiveRecord dump has been changed, so there is a difference between ActiveRecord 5.x/6.0 format.
34
+ > * **If you use ActiveRecord 6.1, please modify Schemafile format**.
35
+ > * cf. https://github.com/ridgepole/ridgepole/pull/323
36
+ > * `DROP TABLE` is skipped by default in v1.0 and later versions.
37
+ > * If you want to `DROP TABLE`, please pass `--drop-table`.
38
+ > * cf. https://github.com/ridgepole/ridgepole/pull/363
39
+ > * In Rails 7.0, the output of dumper is different from Rails 6.
40
+ > * cf. https://github.com/rails/rails/issues/43909
41
+ > * cf. https://github.com/rails/rails/commit/c2a6f618d22cca4d9b7be7fa7652e7aac509350c#diff-55f41513f027a3d219629f475f03c2d1105ca55c5093d691e1b3dc4710c6cc0b
42
+ > * SQLite does not support.
23
43
 
24
44
  ## Installation
25
45
 
@@ -29,7 +49,7 @@ Add this line to your application's Gemfile:
29
49
 
30
50
  And then execute:
31
51
 
32
- $ bundle
52
+ $ bundle install
33
53
 
34
54
  Or install it yourself as:
35
55
 
@@ -71,13 +91,15 @@ Usage: ridgepole [options]
71
91
  --dump-with-default-fk-name
72
92
  --index-removed-drop-column
73
93
  --drop-table
94
+ --drop-table-only
74
95
  --mysql-change-table-options
75
96
  --mysql-change-table-comment
76
97
  --check-relation-type DEF_PK
77
98
  --ignore-table-comment
78
99
  --skip-column-comment-change
79
- --create-table-with-index
80
100
  --allow-pk-change
101
+ --create-table-with-index
102
+ --disable-sort-columns
81
103
  --mysql-dump-auto-increment
82
104
  -r, --require LIBS
83
105
  --log-file LOG_FILE
@@ -85,6 +107,7 @@ Usage: ridgepole [options]
85
107
  --debug
86
108
  --[no-]color
87
109
  -v, --version
110
+ -h, --help
88
111
  ```
89
112
 
90
113
  ## Usage
@@ -166,6 +189,13 @@ create_table "user_comments", force: :cascade, renamed_from: "comments" do |t|
166
189
  end
167
190
  ```
168
191
 
192
+ > [!note]
193
+ > When using `renamed_from` on a table, Ridgepole will only perform the rename operation. All other changes to that table (columns, indexes, foreign keys, etc.) will not be detected during the same migration.
194
+ >
195
+ > If you need to rename AND modify a table, do it in two separate steps:
196
+ > 1. First migration: Add `renamed_from` to rename the table
197
+ > 2. Second migration: Remove `renamed_from` and apply your desired changes
198
+
169
199
  ## Foreign Key
170
200
  ```ruby
171
201
  create_table "parent", force: :cascade do |t|
@@ -181,6 +211,31 @@ add_index "child", ["parent_id"], name: "par_ind", using: :btree
181
211
  add_foreign_key "child", "parent", name: "child_ibfk_1"
182
212
  ```
183
213
 
214
+ ## CHECK Constraint
215
+
216
+ ```ruby
217
+ create_table "products", force: :cascade do |t|
218
+ t.string "name", null: false
219
+ t.decimal "price", precision: 10, scale: 2
220
+ t.integer "quantity"
221
+
222
+ t.check_constraint "price > 0", name: "price_check"
223
+ t.check_constraint "quantity >= 0", name: "quantity_check"
224
+ end
225
+ ```
226
+
227
+ > [!important]
228
+ > **Matching Schemafile to Database Output**
229
+ >
230
+ > CHECK constraint expressions may be formatted differently by your database when exported. For example:
231
+ > - DSL: `"(price > 0)"`
232
+ > - Database export: `` "`price` > 0" `` (MySQL) or `"((price > 0))"` (PostgreSQL)
233
+ >
234
+ > To avoid unnecessary migrations where constraints are dropped and recreated:
235
+ > Please modify your Schemafile to match exactly what the SchemaDumper outputs. This approach maintains simplicity and prevents migration issues instead of maintaining complex normalization logic in Ridgepole.
236
+ >
237
+ > Run `ridgepole --export` to see the exact format your database uses, then update your Schemafile accordingly.
238
+
184
239
  ## Ignore Column/Index/FK
185
240
 
186
241
  ```ruby
@@ -210,6 +265,19 @@ activerecord 5.0.0 and activerecord-mysql-awesome dumps a collation rather than
210
265
 
211
266
  See `mysql> show character set;` to find charset / collation pair for your system.
212
267
 
268
+ ## Generated Column (MySQL)
269
+
270
+ There should be NO extra white spaces in the expression (such as after comma).
271
+ Quotes in expression may cause the operations failure with MySQL 8.0.
272
+
273
+ ```ruby
274
+ create_table "users", force: :cascade do |t|
275
+ t.string "last_name"
276
+ t.string "first_name"
277
+ t.virtual "full_name", type: :string, as: "concat(`last_name`,' ',`first_name`)", stored: true
278
+ end
279
+ ```
280
+
213
281
  ## Execute
214
282
  ```ruby
215
283
  create_table "authors", force: :cascade do |t|
@@ -292,7 +360,7 @@ end
292
360
  ```
293
361
 
294
362
  ```sh
295
- $ ridgepole -a -c database.yml --check-relation-type bigint # default primary key type (e.g. `<5.1`: integer, `>=5.1`: bigint for MySQL)
363
+ $ ridgepole -a -c database.yml --check-relation-type bigint # default primary key type (e.g. bigint for MySQL)
296
364
  Apply `Schemafile`
297
365
  ...
298
366
  [WARNING] Relation column type is different.
@@ -301,19 +369,56 @@ Apply `Schemafile`
301
369
  ...
302
370
  ```
303
371
 
372
+ ## Define a partial index in PostgreSQL
373
+
374
+ Partial indexes in PostgreSQL are normalized so differences are always detected.
375
+
376
+ ```ruby
377
+ create_table "users", id: :serial, force: :cascade do |t|
378
+ t.text "email"
379
+ t.text "name"
380
+ t.index ["email"], name: "idx_users_email", unique: true, where: "email is not null"
381
+ end
382
+ ```
383
+
384
+ ```sh
385
+ % ridgepole -a -c database.yml --dry-run --verbose
386
+ Apply `Schemafile` (dry-run)
387
+ # Parse DSL
388
+ # ...
389
+ # Compare definitions
390
+ # users
391
+ :options=>
392
+ {:name=>"idx_users_email",
393
+ :unique=>true,
394
+ - :where=>"(email IS NOT NULL)"}}},
395
+ + :where=>"email is not null"}}},
396
+ :options=>{:id=>:serial}}
397
+ ```
398
+
399
+ Use a normalized WHERE clause to avoid detecting differences.
400
+
401
+ ```ruby
402
+ #t.index ["email"], name: "idx_users_email", unique: true, where: "email is not null"
403
+ t.index ["email"], name: "idx_users_email", unique: true, where: "(email IS NOT NULL)"
404
+ ```
405
+
406
+ see https://github.com/ridgepole/ridgepole/issues/568
407
+
304
408
  ## Run tests
305
409
 
306
410
 
307
411
  ```sh
308
- docker-compose up -d
412
+ docker compose up -d
309
413
  bundle install
310
414
  bundle exec appraisal install
311
- bundle exec appraisal activerecord-7.0 rake
312
- # POSTGRESQL=1 bundle exec appraisal activerecord-7.0 rake
313
- # MYSQL57=1 bundle exec appraisal activerecord-7.0 rake
415
+ bundle exec appraisal activerecord-8.1 rake
416
+ # POSTGRESQL=1 bundle exec appraisal activerecord-8.1 rake
417
+ # MYSQL80=1 bundle exec appraisal activerecord-8.1 rake
314
418
  ```
315
419
 
316
- **Notice:** Ruby 2.6 or above/mysql-client/postgresql-client is required.
420
+ > [!note]
421
+ > mysql-client/postgresql-client is required for testing.
317
422
 
318
423
  ## Demo
319
424
 
@@ -322,6 +427,6 @@ bundle exec appraisal activerecord-7.0 rake
322
427
 
323
428
  ## Example project
324
429
 
325
- * https://github.com/ridgepole/ridgepole-example
326
- * https://github.com/ridgepole/ridgepole-example/pull/1
327
- * https://github.com/ridgepole/ridgepole-example/pull/2
430
+ * https://github.com/winebarrel/ridgepole-example
431
+ * https://github.com/winebarrel/ridgepole-example/pull/1
432
+ * https://github.com/winebarrel/ridgepole-example/pull/2
data/bin/ridgepole CHANGED
@@ -86,8 +86,7 @@ ARGV.options do |opt|
86
86
  opt.on('', '--dry-run') { options[:dry_run] = true }
87
87
  opt.on('', '--table-options OPTIONS') { |v| options[:table_options] = v }
88
88
  opt.on('', '--table-hash-options OPTIONS') do |v|
89
- # NOTE: Ruby2.4 doesn't support `symbolize_names: true`
90
- hash = YAML.safe_load(v).deep_symbolize_keys
89
+ hash = YAML.safe_load(v, symbolize_names: true)
91
90
 
92
91
  case hash[:id]
93
92
  when String
@@ -137,13 +136,16 @@ ARGV.options do |opt|
137
136
  opt.on('', '--dump-with-default-fk-name') { options[:dump_with_default_fk_name] = true }
138
137
  opt.on('', '--index-removed-drop-column') { options[:index_removed_drop_column] = true }
139
138
  opt.on('', '--drop-table') { options[:force_drop_table] = true }
139
+ opt.on('', '--drop-table-only') { options[:drop_table_only] = true }
140
140
  opt.on('', '--mysql-change-table-options') { options[:mysql_change_table_options] = true }
141
141
  opt.on('', '--mysql-change-table-comment') { options[:mysql_change_table_comment] = true }
142
+ opt.on('', '--pg-change-table-comment') { options[:postgresql_change_table_comment] = true }
142
143
  opt.on('', '--check-relation-type DEF_PK') { |v| options[:check_relation_type] = v }
143
144
  opt.on('', '--ignore-table-comment') { options[:ignore_table_comment] = true }
144
145
  opt.on('', '--skip-column-comment-change') { options[:skip_column_comment_change] = true }
145
146
  opt.on('', '--allow-pk-change') { options[:allow_pk_change] = true }
146
147
  opt.on('', '--create-table-with-index') { options[:create_table_with_index] = true }
148
+ opt.on('', '--disable-sort-columns') { options[:disable_sort_columns] = true }
147
149
 
148
150
  opt.on('', '--mysql-dump-auto-increment') do
149
151
  options[:mysql_dump_auto_increment] = true
@@ -155,10 +157,14 @@ ARGV.options do |opt|
155
157
  opt.on('', '--debug') { options[:debug] = true }
156
158
  opt.on('', '--[no-]color') { |v| options[:color] = v }
157
159
 
158
- opt.on('-v', '--version') do
160
+ opt.on_tail('-v', '--version') do
159
161
  puts opt.ver
160
162
  exit
161
163
  end
164
+ opt.on_tail('-h', '--help') do
165
+ puts opt
166
+ exit
167
+ end
162
168
 
163
169
  opt.parse!
164
170
 
@@ -181,7 +187,12 @@ begin
181
187
  client = Ridgepole::Client.new(Ridgepole::Config.load(config, env, spec_name), options) if config
182
188
 
183
189
  ActiveRecord::Base.logger = logger
184
- ActiveSupport::LogSubscriber.colorize_logging = options[:color]
190
+
191
+ if ActiveRecord.gem_version < Gem::Version.new('8.2.0.alpha')
192
+ ActiveSupport::LogSubscriber.colorize_logging = options[:color]
193
+ else
194
+ ActiveSupport.colorize_logging = options[:color]
195
+ end
185
196
 
186
197
  case mode
187
198
  when :export
@@ -261,15 +272,13 @@ begin
261
272
  else
262
273
  File.open(diff_file)
263
274
  end
264
- elsif Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') # Ruby 2.6
275
+ else
265
276
  YAML.safe_load(
266
277
  diff_file,
267
278
  permitted_classes: [],
268
279
  permitted_symbols: [],
269
280
  aliases: true
270
281
  )
271
- else
272
- YAML.safe_load(diff_file, [], [], true)
273
282
  end
274
283
  end
275
284
 
@@ -13,15 +13,13 @@ module Ridgepole
13
13
  parse_config_file(config)
14
14
  elsif (expanded = File.expand_path(config)) && File.exist?(expanded)
15
15
  parse_config_file(expanded)
16
- elsif Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') # Ruby 2.6
16
+ else
17
17
  YAML.safe_load(
18
18
  ERB.new(config).result,
19
19
  permitted_classes: [],
20
20
  permitted_symbols: [],
21
21
  aliases: true
22
22
  )
23
- else
24
- YAML.safe_load(ERB.new(config).result, [], [], true)
25
23
  end
26
24
 
27
25
  parsed_config = parse_database_url(config) unless parsed_config.is_a?(Hash)
@@ -39,17 +37,12 @@ module Ridgepole
39
37
 
40
38
  def parse_config_file(path)
41
39
  yaml = ERB.new(File.read(path)).result
42
-
43
- if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') # Ruby 2.6
44
- YAML.safe_load(
45
- yaml,
46
- permitted_classes: [],
47
- permitted_symbols: [],
48
- aliases: true
49
- )
50
- else
51
- YAML.safe_load(yaml, [], [], true)
52
- end
40
+ YAML.safe_load(
41
+ yaml,
42
+ permitted_classes: [],
43
+ permitted_symbols: [],
44
+ aliases: true
45
+ )
53
46
  end
54
47
 
55
48
  def parse_database_url(config)
@@ -66,7 +59,7 @@ module Ridgepole
66
59
 
67
60
  query_hash =
68
61
  if uri.query
69
- uri.query.split('&').map { |pair| pair.split('=') }.to_h
62
+ uri.query.split('&').to_h { |pair| pair.split('=') }
70
63
  else
71
64
  {}
72
65
  end
@@ -10,12 +10,13 @@ module Ridgepole
10
10
  # XXX: If the required processing in class method?
11
11
  @options[:index_removed_drop_column] = true if !@options.key?(:index_removed_drop_column) && (Ridgepole::DefaultsLimit.adapter == :postgresql)
12
12
 
13
- Ridgepole::ExecuteExpander.expand_execute(ActiveRecord::Base.connection)
13
+ Ridgepole::ExecuteExpander.expand_execute(ActiveRecord::Base.connection, @options)
14
14
  @dumper = Ridgepole::Dumper.new(@options)
15
15
  @parser = Ridgepole::DSLParser.new(@options)
16
16
  @diff = Ridgepole::Diff.new(@options)
17
17
 
18
18
  require 'ridgepole/ext/abstract_mysql_adapter/dump_auto_increment' if @options[:mysql_dump_auto_increment]
19
+ require 'ridgepole/ext/schema_dumper/disable_sort_columns' if @options[:disable_sort_columns]
19
20
  end
20
21
 
21
22
  def dump(&block)
@@ -29,7 +30,7 @@ module Ridgepole
29
30
 
30
31
  logger.verbose_info('# Parse DSL')
31
32
  expected_definition, expected_execute = @parser.parse(dsl, opts)
32
- expected_definition.each do |_table, definition|
33
+ expected_definition.each_value do |definition|
33
34
  merge_table_options(definition)
34
35
  end
35
36
  logger.verbose_info('# Load tables')
@@ -2,16 +2,20 @@
2
2
 
3
3
  module Ridgepole
4
4
  class DefaultsLimit
5
+ DEFAULT_LIMIT_FOR_MYSQL = {
6
+ integer: 4,
7
+ bigint: 8,
8
+ float: 24,
9
+ string: 255,
10
+ text: 65_535,
11
+ binary: 65_535,
12
+ }.tap do |limits|
13
+ limits[:boolean] = 1 if ActiveRecord.gem_version < Gem::Version.new('8.1.0.beta1')
14
+ end.freeze
15
+
5
16
  DEFAULTS_LIMITS = {
6
- mysql2: {
7
- boolean: 1,
8
- integer: 4,
9
- bigint: 8,
10
- float: 24,
11
- string: 255,
12
- text: 65_535,
13
- binary: 65_535,
14
- },
17
+ mysql2: DEFAULT_LIMIT_FOR_MYSQL,
18
+ trilogy: DEFAULT_LIMIT_FOR_MYSQL,
15
19
  }.freeze
16
20
 
17
21
  class << self