convergence 1.0.6 → 1.2.0

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -4
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +57 -0
  5. data/CONTRIBUTING.md +80 -0
  6. data/Gemfile.lock +25 -9
  7. data/README.md +160 -1
  8. data/Rakefile +45 -2
  9. data/convergence.gemspec +5 -1
  10. data/lib/convergence/cli.rb +10 -2
  11. data/lib/convergence/column.rb +3 -2
  12. data/lib/convergence/command/apply.rb +38 -9
  13. data/lib/convergence/command/dryrun.rb +5 -5
  14. data/lib/convergence/command/export.rb +19 -1
  15. data/lib/convergence/command.rb +6 -0
  16. data/lib/convergence/database_connector/postgres_connector.rb +25 -0
  17. data/lib/convergence/database_connector/sqlite_connector.rb +22 -0
  18. data/lib/convergence/database_connector.rb +6 -0
  19. data/lib/convergence/default_parameter/mysql_default_parameter.rb +1 -1
  20. data/lib/convergence/default_parameter/postgres_default_parameter.rb +57 -0
  21. data/lib/convergence/default_parameter/sqlite_default_parameter.rb +57 -0
  22. data/lib/convergence/default_parameter.rb +6 -0
  23. data/lib/convergence/diff.rb +46 -3
  24. data/lib/convergence/dsl.rb +18 -3
  25. data/lib/convergence/dumper/mysql_schema_dumper.rb +37 -9
  26. data/lib/convergence/dumper/postgres_schema_dumper.rb +193 -0
  27. data/lib/convergence/dumper/sqlite_schema_dumper.rb +132 -0
  28. data/lib/convergence/dumper.rb +113 -0
  29. data/lib/convergence/sql_generator/mysql_generator.rb +32 -3
  30. data/lib/convergence/sql_generator/postgres_generator.rb +270 -0
  31. data/lib/convergence/sql_generator/sqlite_generator.rb +192 -0
  32. data/lib/convergence/table.rb +3 -2
  33. data/lib/convergence/version.rb +1 -1
  34. data/spec/config/spec_database.yml +12 -0
  35. data/spec/convergence/config_spec.rb +75 -0
  36. data/spec/convergence/diff_spec.rb +291 -44
  37. data/spec/convergence/dsl_spec.rb +26 -0
  38. data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +25 -2
  39. data/spec/convergence/dumper/postgres_schema_dumper_spec.rb +103 -0
  40. data/spec/convergence/dumper/sqlite_schema_dumper_spec.rb +85 -0
  41. data/spec/convergence/dumper_spec.rb +110 -16
  42. data/spec/convergence/foreign_key_spec.rb +33 -0
  43. data/spec/convergence/index_spec.rb +52 -0
  44. data/spec/convergence/pretty_diff_spec.rb +85 -0
  45. data/spec/convergence/table_spec.rb +46 -0
  46. data/spec/fixtures/add_table_with_enum_set.schema +5 -0
  47. data/spec/fixtures/change_table_comment_to_paper.schema +2 -0
  48. data/spec/fixtures/execute_raw_sql.schema +28 -0
  49. data/spec/fixtures/postgres/add_columns_to_paper.schema +29 -0
  50. data/spec/fixtures/postgres/add_table.schema +32 -0
  51. data/spec/fixtures/postgres/change_comment_columns_to_paper.schema +28 -0
  52. data/spec/fixtures/postgres/change_table_comment_to_paper.schema +28 -0
  53. data/spec/fixtures/postgres/drop_foreign_key.schema +25 -0
  54. data/spec/fixtures/postgres/drop_table.schema +19 -0
  55. data/spec/fixtures/postgres/remove_columns_to_paper.schema +27 -0
  56. data/spec/fixtures/postgres/rename_column_on_paper.schema +28 -0
  57. data/spec/fixtures/postgres/rename_table.schema +28 -0
  58. data/spec/fixtures/postgres_test_db.sql +41 -0
  59. data/spec/fixtures/rename_column_to_author.schema +26 -0
  60. data/spec/fixtures/rename_table.schema +26 -0
  61. data/spec/fixtures/sqlite/add_columns_to_paper.schema +29 -0
  62. data/spec/fixtures/sqlite/add_table.schema +32 -0
  63. data/spec/fixtures/sqlite/change_columns_to_paper.schema +28 -0
  64. data/spec/fixtures/sqlite/drop_foreign_key.schema +25 -0
  65. data/spec/fixtures/sqlite/drop_table.schema +19 -0
  66. data/spec/fixtures/sqlite/remove_columns_to_paper.schema +27 -0
  67. data/spec/fixtures/sqlite/rename_column_on_paper.schema +28 -0
  68. data/spec/fixtures/sqlite/rename_table.schema +27 -0
  69. data/spec/fixtures/sqlite_test_db.sql +31 -0
  70. data/spec/fixtures/test_db.sql +10 -0
  71. data/spec/integrations/command_diff.rb +35 -0
  72. data/spec/integrations/command_dryrun.rb +59 -2
  73. data/spec/integrations/command_export.rb +28 -0
  74. data/spec/postgres_integrations/command_dryrun.rb +101 -0
  75. data/spec/spec_helper.rb +13 -0
  76. data/spec/sqlite_integrations/command_dryrun.rb +92 -0
  77. metadata +135 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c5b9f06840ed6ef0686f00bfb8d677fc0ad0e389398ae30a5e20b86ca2cdf15
4
- data.tar.gz: 5f9f77898685d66e68d2c110ba53e4709fd45fffba9f5a13f77c8aa30b4605a0
3
+ metadata.gz: 633f45bac912daa7950afa5535bfd87d13a7b33b25ff72489fb7fa125d0b6bdd
4
+ data.tar.gz: 392d293827a735cfeb515e878eb6f565d593259d0ff9f36e7aae8c0b4076b1ec
5
5
  SHA512:
6
- metadata.gz: 587acb6716a2358a4babb9468473c65fcd0f61f5f81ead163aefe615bc81b547b0e487d9dbc558600a922d5c5bb4356c2ae7e153d837bcb1b865c1258c23b109
7
- data.tar.gz: 7c87a0849716fce53dea625c05532357bc9c92076f7658cdad1c29c0ee44a7eb48a1d3c32233c519e36d1f592e1d9c13c4cecde7ee586e16f307e6c9fda54d6e
6
+ metadata.gz: f4255769af38418d8d336b8db4ef7e1ec2844ed3fbcf99cfd4e4bafa055e9765e5d30aaa92d2962ea8152c0b0688821e632ab71e2700158bcbe2f6a212f06270
7
+ data.tar.gz: 481f9bbebb0423275c0695445ab065ca6d54771f7dc25f94b005ac2918a1d7ad94730766a0d8a7c869fb76e49d7b6cc8e8da7cfea177d77c6fd2c049dbf0051b
@@ -4,21 +4,52 @@ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  test:
7
+ name: Test with MySQL ${{ matrix.mysql }} / PostgreSQL 16, Ruby ${{ matrix.ruby }}
7
8
  runs-on: ubuntu-latest
8
9
  strategy:
10
+ fail-fast: false
9
11
  matrix:
10
- ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
12
+ mysql: ['5.7', '8.0']
13
+ ruby: ['3.1', '3.2', '3.3', '3.4']
14
+ services:
15
+ mysql:
16
+ image: mysql:${{ matrix.mysql }}
17
+ env:
18
+ MYSQL_ROOT_PASSWORD: root
19
+ ports:
20
+ - 3306:3306
21
+ options: >-
22
+ --health-cmd "mysqladmin ping"
23
+ --health-interval 10s
24
+ --health-timeout 5s
25
+ --health-retries 5
26
+ postgres:
27
+ image: postgres:16
28
+ env:
29
+ POSTGRES_PASSWORD: postgres
30
+ ports:
31
+ - 5432:5432
32
+ options: >-
33
+ --health-cmd pg_isready
34
+ --health-interval 10s
35
+ --health-timeout 5s
36
+ --health-retries 5
11
37
  steps:
12
- - uses: actions/checkout@v2
38
+ - uses: actions/checkout@v4
39
+ - name: Install PostgreSQL and SQLite client libraries
40
+ run: sudo apt-get update && sudo apt-get install -y libpq-dev libsqlite3-dev sqlite3
13
41
  - name: Set up Ruby
14
42
  uses: ruby/setup-ruby@v1
15
43
  with:
16
44
  ruby-version: ${{ matrix.ruby }}
45
+ bundler-cache: true
17
46
  - name: Install dependencies
18
47
  run: bundle install
19
- - name: Start MySQL
20
- run: sudo systemctl start mysql.service
21
48
  - name: Prepare database
22
49
  run: bundle exec rake db:convergence:prepare
50
+ - name: Prepare PostgreSQL database
51
+ run: bundle exec rake db:convergence:postgres:prepare
52
+ - name: Prepare SQLite database
53
+ run: bundle exec rake db:convergence:sqlite:prepare
23
54
  - name: Run tests
24
55
  run: bundle exec rake spec
data/.gitignore CHANGED
@@ -2,6 +2,7 @@ tmp/
2
2
  tags
3
3
  database.yml
4
4
  pkg/
5
+ spec/fixtures/sqlite_test_db.sqlite3
5
6
 
6
7
  .bundle
7
8
  .ruby-version
data/CHANGELOG.md CHANGED
@@ -1,3 +1,60 @@
1
+ ## Convergence 1.2.0 (August 26, 2026) ##
2
+
3
+ * Add `renamed_from:` support to detect table/column renames instead of a
4
+ destructive drop+add, for MySQL, PostgreSQL, and SQLite (PR: #101)
5
+
6
+ *nishio-dens*
7
+
8
+ * Bump rexml to fix CVE-2024-49761 (PR: #103)
9
+
10
+ *nishio-dens*
11
+
12
+ ## Convergence 1.1.0 (August 25, 2026) ##
13
+
14
+ * Add PostgreSQL support (PR: #93)
15
+
16
+ *nishio-dens*
17
+
18
+ * Add SQLite support (PR: #95)
19
+
20
+ *nishio-dens*
21
+
22
+ * Support ENUM/SET column types (PR: #92)
23
+
24
+ *nishio-dens*
25
+
26
+ * Add `execute()` DSL method to run raw SQL (PR: #94)
27
+
28
+ *nishio-dens*
29
+
30
+ * Add `--dump-rails-migration` option to export DB schema as a Rails migration file (PR: #96)
31
+
32
+ *nishio-dens*
33
+
34
+ * Add `--safe-migration` option to skip DROP TABLE queries (PR: #91)
35
+
36
+ *nishio-dens*
37
+
38
+ * Add `--ignore-auto-increment` option (PR: #90)
39
+
40
+ *nishio-dens*
41
+
42
+ * Fix MySQL 8.0 test failure and modernize CI Ruby matrix (PR: #89)
43
+
44
+ *nishio-dens*
45
+
46
+ * Bump diffy to >= 3.4.2, fixing CVE-2022-33127 (PR: #100)
47
+
48
+ *nishio-dens*
49
+
50
+ * Add specs for previously untested classes/commands (PR: #97)
51
+
52
+ *nishio-dens*
53
+
54
+ * Add CONTRIBUTING.md (PR: #98)
55
+
56
+ *nishio-dens*
57
+
1
58
  ## Convergence 1.0.6 (April 28, 2022) ##
2
59
 
3
60
  * Fixes YAML.safe_load arguments (PR: #84)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,80 @@
1
+ # Contributing to Convergence
2
+
3
+ Thanks for considering a contribution! This document covers everything you need to get a development
4
+ environment running, test your change, and open a pull request.
5
+
6
+ ## Development setup
7
+
8
+ Convergence supports MySQL, PostgreSQL, and SQLite, so the test suite exercises all three. You don't need
9
+ every adapter running to work on a change that's scoped to one of them, but `rake spec` runs the whole suite
10
+ regardless of which adapter you touched.
11
+
12
+ 1. Install Ruby (see `required_ruby_version` in `convergence.gemspec` for the minimum version) and Bundler.
13
+ 2. Install dependencies:
14
+
15
+ ```
16
+ $ bundle install
17
+ ```
18
+
19
+ `sqlite3`, `mysql2`, and `pg` are native extensions; if a gem fails to build, make sure the corresponding
20
+ client library/headers are installed for your OS (e.g. `libmysqlclient-dev`, `libpq-dev`, `libsqlite3-dev`
21
+ on Debian/Ubuntu, or the Homebrew equivalents on macOS).
22
+ 3. Start MySQL and PostgreSQL (locally, in Docker, however you prefer) and set connection details for all
23
+ three adapters in `spec/config/spec_database.yml` if the defaults don't match your setup.
24
+ 4. Prepare the test databases:
25
+
26
+ ```
27
+ $ bundle exec rake db:convergence:prepare # MySQL
28
+ $ bundle exec rake db:convergence:postgres:prepare # PostgreSQL
29
+ $ bundle exec rake db:convergence:sqlite:prepare # SQLite (no server needed, just a file)
30
+ ```
31
+
32
+ ## Running the tests
33
+
34
+ ```
35
+ $ bundle exec rspec
36
+ ```
37
+
38
+ or, equivalently:
39
+
40
+ ```
41
+ $ bundle exec rake spec
42
+ ```
43
+
44
+ If you change the schema of the shared test fixtures (`spec/fixtures/test_db.sql`,
45
+ `spec/fixtures/postgres_test_db.sql`, `spec/fixtures/sqlite_test_db.sql`), rebuild the affected database with
46
+ the corresponding `overhaul` task (e.g. `rake db:convergence:overhaul`) before rerunning the suite.
47
+
48
+ ## Code style
49
+
50
+ The project uses RuboCop (see `.rubocop.yml` for the enabled/disabled cops); there's no dedicated Rake task
51
+ wired up, so run it directly if you want to check your changes:
52
+
53
+ ```
54
+ $ bundle exec rubocop
55
+ ```
56
+
57
+ ## Adding support for a new adapter capability
58
+
59
+ Convergence's DSL uses MySQL terminology for column types so that schema files stay close to portable across
60
+ adapters. If you're adding a feature to an existing adapter (or a new adapter entirely), the adapter-specific
61
+ logic lives in four places per adapter:
62
+
63
+ - `lib/convergence/database_connector/*_connector.rb` — opens the DB connection.
64
+ - `lib/convergence/dumper/*_schema_dumper.rb` — reads the current schema from the database.
65
+ - `lib/convergence/sql_generator/*_generator.rb` — turns a diff into `CREATE`/`ALTER`/`DROP` SQL.
66
+ - `lib/convergence/default_parameter/*_default_parameter.rb` — fills in / strips adapter-specific defaults
67
+ (e.g. MySQL's default charset) so diffing doesn't produce spurious noise.
68
+
69
+ All four are dispatched by adapter name in `lib/convergence/database_connector.rb`, `lib/convergence/command.rb`,
70
+ `lib/convergence/command/apply.rb`, and `lib/convergence/default_parameter.rb` respectively — a new adapter
71
+ needs a `when` branch added to each. Add a parallel fixture database/schema files under `spec/fixtures/` and a
72
+ matching test suite (see the existing `spec/postgres_integrations/` or `spec/sqlite_integrations/` for the
73
+ pattern) so the new adapter is covered end-to-end.
74
+
75
+ ## Opening a pull request
76
+
77
+ - Keep pull requests focused on a single change; it makes review (and `CHANGELOG.md`/release notes) easier.
78
+ - Add or update specs for any behavior change.
79
+ - Update `README.md` if you're adding a user-facing option or adapter capability.
80
+ - Reference the issue you're addressing (e.g. `Closes #123`) in the pull request description.
data/Gemfile.lock CHANGED
@@ -1,19 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- convergence (1.0.6)
4
+ convergence (1.2.0)
5
+ benchmark
5
6
  diff-lcs
6
- diffy
7
+ diffy (>= 3.4.2)
8
+ logger
7
9
  mysql2
10
+ pg
11
+ sqlite3
8
12
  thor
9
13
 
10
14
  GEM
11
15
  remote: https://rubygems.org/
12
16
  specs:
13
17
  ast (2.4.1)
18
+ benchmark (0.5.0)
19
+ bigdecimal (4.1.2)
14
20
  coderay (1.1.3)
15
21
  diff-lcs (1.4.4)
16
- diffy (3.4.0)
22
+ diffy (3.4.4)
17
23
  ffi (1.14.2)
18
24
  formatador (0.2.5)
19
25
  guard (2.16.2)
@@ -30,12 +36,16 @@ GEM
30
36
  guard (~> 2.1)
31
37
  guard-compat (~> 1.1)
32
38
  rspec (>= 2.99.0, < 4.0)
39
+ io-console (0.9.2)
33
40
  listen (3.3.3)
34
41
  rb-fsevent (~> 0.10, >= 0.10.3)
35
42
  rb-inotify (~> 0.9, >= 0.9.10)
43
+ logger (1.7.0)
36
44
  lumberjack (1.2.8)
37
- method_source (1.0.0)
38
- mysql2 (0.5.3)
45
+ method_source (1.1.0)
46
+ mini_portile2 (2.8.9)
47
+ mysql2 (0.5.7)
48
+ bigdecimal
39
49
  nenv (0.3.0)
40
50
  notiffany (0.1.3)
41
51
  nenv (~> 0.1)
@@ -43,16 +53,20 @@ GEM
43
53
  parallel (1.20.1)
44
54
  parser (3.0.0.0)
45
55
  ast (~> 2.4.1)
46
- pry (0.13.1)
56
+ pg (1.6.3)
57
+ pry (0.16.0)
47
58
  coderay (~> 1.1)
48
59
  method_source (~> 1.0)
60
+ reline (>= 0.6.0)
49
61
  rainbow (3.0.0)
50
- rake (13.0.3)
62
+ rake (13.4.2)
51
63
  rb-fsevent (0.10.4)
52
64
  rb-inotify (0.10.1)
53
65
  ffi (~> 1.0)
54
66
  regexp_parser (2.0.3)
55
- rexml (3.2.5)
67
+ reline (0.7.0)
68
+ io-console (~> 0.5)
69
+ rexml (3.4.4)
56
70
  rspec (3.10.0)
57
71
  rspec-core (~> 3.10.0)
58
72
  rspec-expectations (~> 3.10.0)
@@ -79,6 +93,8 @@ GEM
79
93
  parser (>= 2.7.1.5)
80
94
  ruby-progressbar (1.11.0)
81
95
  shellany (0.0.1)
96
+ sqlite3 (2.8.1)
97
+ mini_portile2 (~> 2.8.0)
82
98
  thor (1.0.1)
83
99
  unicode-display_width (1.7.0)
84
100
 
@@ -95,4 +111,4 @@ DEPENDENCIES
95
111
  rubocop
96
112
 
97
113
  BUNDLED WITH
98
- 2.2.32
114
+ 4.0.19
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Convergence
2
2
 
3
3
  Convergence is a pure-Ruby database schema migration tool.
4
- Currently, This tool is support only MySQL.
4
+ Currently, This tool supports MySQL, PostgreSQL, and SQLite.
5
5
 
6
6
  It defines DB Schema using Convergence DSL(like Rails DSL).
7
7
  For more information about Convergence DSL, See below ['Detail About Convergence DSL'](#detail-about-convergence-dsl)
@@ -133,8 +133,10 @@ Create Table: CREATE TABLE `test_tables` (
133
133
  ```
134
134
  Commands:
135
135
  convergence apply FILE -c, --config=CONFIG # execute sql to your database
136
+ # [--dry-run], [--rollback-dry-run], [--safe-migration], [--ignore-auto-increment]
136
137
  convergence diff FILE1 FILE2 # print diff of DSLs
137
138
  convergence export -c, --config=CONFIG # export db schema to dsl
139
+ # [--dump-rails-migration], [--filename=FILENAME]
138
140
  convergence help [COMMAND] # Describe available commands or one specific command
139
141
  convergence version # print the version
140
142
  ```
@@ -152,6 +154,51 @@ username: root
152
154
  password:
153
155
  ```
154
156
 
157
+ To use PostgreSQL instead, set `adapter: postgresql` (`postgres`/`pg` also work):
158
+
159
+ ```
160
+ $ cat database.yml
161
+ adapter: postgresql
162
+ database: convergence_test
163
+ host: 127.0.0.1
164
+ username: postgres
165
+ password:
166
+ ```
167
+
168
+ #### PostgreSQL notes
169
+
170
+ The Convergence DSL uses MySQL terminology for column types (`tinyint`, `mediumint`, `datetime`, ...) so the
171
+ same schema files stay close to portable across adapters; the PostgreSQL adapter maps them to their closest
172
+ native PostgreSQL type (e.g. `mediumint`/`int` → `integer`, `datetime` → `timestamp`, `json` → `jsonb`,
173
+ `extra: 'auto_increment'` → `GENERATED BY DEFAULT AS IDENTITY`, requiring PostgreSQL 10+).
174
+
175
+ A few MySQL-specific concepts have no PostgreSQL equivalent and are ignored on that adapter: `engine`,
176
+ `row_format`, `default_charset`/`character_set`, and `collate`. `enum`/`set` column types are not supported yet
177
+ on PostgreSQL.
178
+
179
+ To use SQLite instead, set `adapter: sqlite3` (`sqlite` also works) and point `database` at a file path:
180
+
181
+ ```
182
+ $ cat database.yml
183
+ adapter: sqlite3
184
+ database: /path/to/development.sqlite3
185
+ ```
186
+
187
+ #### SQLite notes
188
+
189
+ SQLite's `ALTER TABLE` support is intentionally limited by SQLite itself: it can only add/drop columns and
190
+ create/drop indexes on an existing table. Changing a column's type/null/default, and adding or removing a
191
+ foreign key or primary key on an existing table, all require SQLite's own "rebuild the table" procedure
192
+ (create a new table, copy the data over, drop the old one, rename it), which this adapter does not implement
193
+ yet -- it raises `NotImplementedError` instead of generating SQL that SQLite would reject. Creating a brand new
194
+ table with any of the above (including foreign keys) works fine. Renaming a table/column via `renamed_from:`
195
+ (see [Rename tables and columns](#rename-tables-and-columns)) is an exception to this limitation -- SQLite has
196
+ natively supported `RENAME TABLE`/`RENAME COLUMN` since 3.25.0, so it doesn't need the rebuild procedure.
197
+
198
+ Like PostgreSQL, MySQL-only table options (`engine`, `row_format`, `default_charset`/`character_set`, `collate`,
199
+ table/column `comment`) have no SQLite equivalent and are ignored. `extra: 'auto_increment'` maps to SQLite's
200
+ `INTEGER PRIMARY KEY AUTOINCREMENT`.
201
+
155
202
  #### Use SSL connection
156
203
 
157
204
  If you would like to use SSL connection, you can specify SSL options in database.yml
@@ -216,6 +263,43 @@ create_table "paper_authors", collate: "utf8_general_ci", comment: "Paper Author
216
263
  end
217
264
  ```
218
265
 
266
+ ### Export as a Rails migration file
267
+
268
+ If you're migrating to (or working alongside) a Rails app, pass `--dump-rails-migration` to export the schema as an
269
+ ActiveRecord migration instead of a Convergence DSL file. `--filename` sets the base filename/class name (Rails'
270
+ usual `snake_case` filename / `CamelCase` class name convention applies).
271
+
272
+ ```
273
+ $ convergence export -c database.yml --dump-rails-migration --filename create_initial_tables
274
+ ```
275
+
276
+ ```ruby
277
+ # Filename: 20240101000000_create_initial_tables.rb
278
+ class CreateInitialTables < ActiveRecord::Migration[7.0]
279
+ def change
280
+ create_table :authors do |t|
281
+ t.string :name, limit: 110
282
+ t.timestamps
283
+
284
+ t.index :created_at, name: "index_authors_on_created_at"
285
+ end
286
+ end
287
+ end
288
+ ```
289
+
290
+ The output is written to stdout with a `# Filename: ...` comment on top (including a timestamp prefix in the usual
291
+ Rails migration filename format) — redirect it to that file yourself, e.g.
292
+ `convergence export ... > db/migrate/$(date +%Y%m%d%H%M%S)_create_initial_tables.rb`.
293
+
294
+ A few notes on the conversion:
295
+ * An `id` column that matches Rails' implicit auto-incrementing primary key (`int`/`bigint`, `primary_key: true`,
296
+ `extra: 'auto_increment'`) is omitted, since `create_table` adds it automatically.
297
+ * `created_at`/`updated_at` columns with matching `datetime`/`null` options are collapsed into `t.timestamps`.
298
+ * Column types use Rails' type names (e.g. `varchar` → `string`, `datetime`/`timestamp` → `datetime`); `enum`/`set`
299
+ have no ActiveRecord equivalent and fall back to `string`.
300
+ * MySQL-only concepts with no `create_table` DSL equivalent (`character_set`, `collate`, `extra`, `after`) are
301
+ dropped from column definitions.
302
+
219
303
  ### Dry run
220
304
 
221
305
  ```
@@ -235,6 +319,56 @@ $ convergence apply example.schema -c database.yml --rollback-dry-run
235
319
  $ convergence apply example.schema -c database.yml
236
320
  ```
237
321
 
322
+ ### Ignore AUTO_INCREMENT changes
323
+
324
+ If you dump your schema from a database with a high AUTO_INCREMENT value (e.g. a dev/staging box you've been testing on)
325
+ and apply it to another database, convergence will normally generate a query that bumps AUTO_INCREMENT to match, which
326
+ can jump/skip a large range of ids. Pass `--ignore-auto-increment` to skip generating AUTO_INCREMENT change queries entirely.
327
+
328
+ ```
329
+ $ convergence apply example.schema -c database.yml --ignore-auto-increment
330
+ ```
331
+
332
+ This also applies to `--dry-run` and `--rollback-dry-run`.
333
+
334
+ ### Safe migration
335
+
336
+ If you want to prevent convergence from ever dropping a table (e.g. when the DSL file removed a table that still
337
+ has important data), pass `--safe-migration`. It skips generating `DROP TABLE` queries; every other change is still applied.
338
+
339
+ ```
340
+ $ convergence apply example.schema -c database.yml --safe-migration
341
+ ```
342
+
343
+ This also applies to `--dry-run` and `--rollback-dry-run`.
344
+
345
+ ### Rename tables and columns
346
+
347
+ Without `renamed_from:`, convergence detects a renamed table/column as a drop followed by an add -- which loses
348
+ the data in that table/column. Add `renamed_from:` to the new name so convergence generates `RENAME TABLE`/`RENAME
349
+ COLUMN` instead:
350
+
351
+ ```ruby
352
+ create_table "user_accounts", renamed_from: "users" do |t|
353
+ t.int "id", primary_key: true, extra: "auto_increment"
354
+ t.varchar "full_name", limit: 200, renamed_from: "name"
355
+ end
356
+ ```
357
+
358
+ A few things to know:
359
+
360
+ * When a table or column has `renamed_from:`, **only the rename is applied on that pass** -- any other change to
361
+ the same table/column (a type change, a new `null`/`default`, ...) is ignored until you run `apply` again. Run
362
+ `apply` a second time (with `renamed_from:` still in place, or removed) to pick up the rest.
363
+ * `renamed_from:` is safe to leave in the schema file after the rename has been applied -- convergence detects
364
+ that the target name already exists and treats it as a no-op.
365
+ * Swapping two names (A ↔ B) or chaining renames (A → B → C) in a single `apply` is not supported. Use an
366
+ intermediate temporary name and apply in multiple steps instead.
367
+ * MySQL's `RENAME COLUMN` requires MySQL 8.0+; there's no fallback for 5.7.
368
+ * `convergence diff`'s pretty-printed output does not yet understand renames -- it will describe a rename as "no
369
+ change" for the renamed table/column. This is a known limitation, unrelated to the SQL that `apply`/`dry-run`
370
+ actually generate.
371
+
238
372
  ### Include Other Schema files
239
373
 
240
374
  ```
@@ -242,6 +376,20 @@ include 'first_schema.schema'
242
376
  include 'other_file.schema'
243
377
  ```
244
378
 
379
+ ### Execute raw SQL
380
+
381
+ For things the DSL has no dedicated syntax for (views, triggers, stored procedures, data backfills, grants, ...),
382
+ use `execute` to run an arbitrary SQL statement. Unlike `create_table`, statements passed to `execute` are **not**
383
+ diffed against the current schema: they run every time the schema file is applied, so make sure the SQL itself is
384
+ idempotent (e.g. `CREATE OR REPLACE VIEW ...`, `CREATE TABLE IF NOT EXISTS ...`).
385
+
386
+ ```
387
+ execute "CREATE OR REPLACE VIEW active_users AS SELECT * FROM users WHERE active = 1"
388
+ ```
389
+
390
+ `execute` statements always run after the generated schema changes, and are skipped by `--rollback-dry-run` (there's
391
+ no way to know how to reverse an arbitrary SQL statement).
392
+
245
393
 
246
394
  ## Detail About Convergence DSL
247
395
 
@@ -273,6 +421,8 @@ Convergence is currently support column types below.
273
421
  - timestamp
274
422
  - year
275
423
  - json
424
+ - enum
425
+ - set
276
426
 
277
427
  ```
278
428
  create_table "tests", comment: 'Column type example' do |t|
@@ -282,6 +432,8 @@ create_table "tests", comment: 'Column type example' do |t|
282
432
  t.varchar 'test_string', null: true, default: 'hello', limit: 300
283
433
  t.text 'text_col'
284
434
  t.datetime 'created_at'
435
+ t.enum 'status', values: %w(active inactive pending), default: 'active'
436
+ t.set 'flags', values: %w(a b c)
285
437
  end
286
438
  ```
287
439
 
@@ -344,9 +496,16 @@ end
344
496
 
345
497
  ```
346
498
  $ bundle exec rake db:convergence:prepare
499
+ $ bundle exec rake db:convergence:postgres:prepare
500
+ $ bundle exec rake db:convergence:sqlite:prepare
347
501
  $ bundle exec rspec
348
502
  ```
349
503
 
504
+ ## Contributing
505
+
506
+ Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a
507
+ development environment, run the test suite, and what to include in a pull request.
508
+
350
509
  ## Copyright
351
510
 
352
511
  Copyright © 2014-2018 S.nishio. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -5,8 +5,10 @@ require 'yaml'
5
5
  RSpec::Core::RakeTask.new('spec')
6
6
  task default: :spec
7
7
 
8
- mysql_settings = YAML.load_file("#{File.dirname(__FILE__)}/spec/config/spec_database.yml")['mysql']
9
- mysql_settings = Hash[mysql_settings.map { |k, v| [k.to_sym, v] }]
8
+ spec_database_settings = YAML.load_file("#{File.dirname(__FILE__)}/spec/config/spec_database.yml")
9
+ mysql_settings = Hash[spec_database_settings['mysql'].map { |k, v| [k.to_sym, v] }]
10
+ postgres_settings = Hash[spec_database_settings['postgresql'].map { |k, v| [k.to_sym, v] }]
11
+ sqlite_db_path = File.expand_path("#{File.dirname(__FILE__)}/spec/fixtures/#{spec_database_settings['sqlite3']['database']}")
10
12
 
11
13
  namespace :db do
12
14
  namespace :convergence do
@@ -30,5 +32,46 @@ namespace :db do
30
32
  desc 'Prepare the test databases'
31
33
  task prepare: [:build_databases, :create_tables]
32
34
  task overhaul: [:drop_databases, :build_databases, :create_tables]
35
+
36
+ namespace :postgres do
37
+ desc 'Build the PostgreSQL database for tests'
38
+ task :build_databases do
39
+ ENV['PGPASSWORD'] = postgres_settings[:password].to_s
40
+ system("psql -U #{postgres_settings[:username]} -h #{postgres_settings[:host]} -p #{postgres_settings[:port]} -d postgres -c 'create database #{postgres_settings[:database]};'")
41
+ end
42
+
43
+ task :drop_databases do
44
+ ENV['PGPASSWORD'] = postgres_settings[:password].to_s
45
+ system("psql -U #{postgres_settings[:username]} -h #{postgres_settings[:host]} -p #{postgres_settings[:port]} -d postgres -c 'drop database if exists #{postgres_settings[:database]};'")
46
+ end
47
+
48
+ desc 'Create tables on the PostgreSQL test database'
49
+ task :create_tables do
50
+ ENV['PGPASSWORD'] = postgres_settings[:password].to_s
51
+ query_path = "#{File.dirname(__FILE__)}/spec/fixtures/postgres_test_db.sql"
52
+ system("psql -U #{postgres_settings[:username]} -h #{postgres_settings[:host]} -p #{postgres_settings[:port]} -d #{postgres_settings[:database]} -f #{query_path}")
53
+ end
54
+
55
+ desc 'Prepare the PostgreSQL test database'
56
+ task prepare: [:build_databases, :create_tables]
57
+ task overhaul: [:drop_databases, :build_databases, :create_tables]
58
+ end
59
+
60
+ namespace :sqlite do
61
+ desc 'Remove the SQLite test database file'
62
+ task :drop_databases do
63
+ File.delete(sqlite_db_path) if File.exist?(sqlite_db_path)
64
+ end
65
+
66
+ desc 'Create tables on the SQLite test database'
67
+ task :create_tables do
68
+ query_path = "#{File.dirname(__FILE__)}/spec/fixtures/sqlite_test_db.sql"
69
+ system("sqlite3 #{sqlite_db_path} < #{query_path}")
70
+ end
71
+
72
+ desc 'Prepare the SQLite test database'
73
+ task prepare: [:drop_databases, :create_tables]
74
+ task overhaul: [:drop_databases, :create_tables]
75
+ end
33
76
  end
34
77
  end
data/convergence.gemspec CHANGED
@@ -19,9 +19,13 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_dependency 'mysql2'
22
+ spec.add_dependency 'pg'
23
+ spec.add_dependency 'sqlite3'
22
24
  spec.add_dependency 'diff-lcs'
23
- spec.add_dependency 'diffy'
25
+ spec.add_dependency 'diffy', '>= 3.4.2'
24
26
  spec.add_dependency 'thor'
27
+ spec.add_dependency 'logger'
28
+ spec.add_dependency 'benchmark'
25
29
 
26
30
  spec.required_ruby_version = ">= 2.4.0"
27
31
 
@@ -11,13 +11,19 @@ class Convergence::CLI < Thor
11
11
  method_option :config, aliases: '-c', type: :string, required: true, desc: 'Database Yaml Setting'
12
12
  method_option :dry_run, type: :boolean
13
13
  method_option :rollback_dry_run, type: :boolean
14
+ method_option :ignore_auto_increment, type: :boolean, desc: 'Do not generate AUTO_INCREMENT change queries'
15
+ method_option :safe_migration, type: :boolean, desc: 'Do not generate DROP TABLE queries'
14
16
 
15
17
  def self.exit_on_failure?
16
18
  true
17
19
  end
18
20
 
19
21
  def apply(file)
20
- opts = { input: file }
22
+ opts = {
23
+ input: file,
24
+ ignore_auto_increment: options[:ignore_auto_increment],
25
+ safe_migration: options[:safe_migration]
26
+ }
21
27
  if options[:dry_run]
22
28
  require 'convergence/command/dryrun'
23
29
  Convergence::Command::Dryrun.new(opts, config: config).execute
@@ -39,9 +45,11 @@ class Convergence::CLI < Thor
39
45
 
40
46
  desc 'export', 'export db schema to dsl'
41
47
  method_option :config, aliases: '-c', type: :string, required: true, desc: 'Database Yaml Setting'
48
+ method_option :dump_rails_migration, type: :boolean, desc: 'Output an ActiveRecord migration file instead of a Convergence DSL file'
49
+ method_option :filename, type: :string, desc: 'Base filename (without timestamp/extension) used for --dump-rails-migration'
42
50
  def export
43
51
  require 'convergence/command/export'
44
- opts = {}
52
+ opts = { dump_rails_migration: options[:dump_rails_migration], filename: options[:filename] }
45
53
  Convergence::Command::Export.new(opts, config: config).execute
46
54
  end
47
55
 
@@ -1,5 +1,5 @@
1
1
  class Convergence::Column
2
- attr_accessor :type, :column_name, :options
2
+ attr_accessor :type, :column_name, :options, :renamed_from
3
3
 
4
4
  COLUMN_TYPE = %i(
5
5
  tinyint
@@ -38,6 +38,7 @@ class Convergence::Column
38
38
  def initialize(type, column_name, options = {})
39
39
  @type = type
40
40
  @column_name = column_name
41
- @options = options
41
+ @renamed_from = options[:renamed_from]&.to_s
42
+ @options = options.reject { |k| k == :renamed_from }
42
43
  end
43
44
  end