convergence 1.0.6 → 1.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +35 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +46 -0
- data/CONTRIBUTING.md +80 -0
- data/Gemfile.lock +23 -8
- data/README.md +131 -1
- data/Rakefile +45 -2
- data/convergence.gemspec +3 -1
- data/lib/convergence/cli.rb +10 -2
- data/lib/convergence/command/apply.rb +38 -9
- data/lib/convergence/command/dryrun.rb +5 -5
- data/lib/convergence/command/export.rb +19 -1
- data/lib/convergence/command.rb +6 -0
- data/lib/convergence/database_connector/postgres_connector.rb +25 -0
- data/lib/convergence/database_connector/sqlite_connector.rb +22 -0
- data/lib/convergence/database_connector.rb +6 -0
- data/lib/convergence/default_parameter/mysql_default_parameter.rb +1 -1
- data/lib/convergence/default_parameter/postgres_default_parameter.rb +57 -0
- data/lib/convergence/default_parameter/sqlite_default_parameter.rb +57 -0
- data/lib/convergence/default_parameter.rb +6 -0
- data/lib/convergence/diff.rb +9 -2
- data/lib/convergence/dsl.rb +18 -3
- data/lib/convergence/dumper/mysql_schema_dumper.rb +37 -9
- data/lib/convergence/dumper/postgres_schema_dumper.rb +193 -0
- data/lib/convergence/dumper/sqlite_schema_dumper.rb +132 -0
- data/lib/convergence/dumper.rb +113 -0
- data/lib/convergence/sql_generator/mysql_generator.rb +18 -3
- data/lib/convergence/sql_generator/postgres_generator.rb +256 -0
- data/lib/convergence/sql_generator/sqlite_generator.rb +178 -0
- data/lib/convergence/version.rb +1 -1
- data/spec/config/spec_database.yml +12 -0
- data/spec/convergence/config_spec.rb +75 -0
- data/spec/convergence/diff_spec.rb +125 -45
- data/spec/convergence/dsl_spec.rb +26 -0
- data/spec/convergence/dumper/mysql_schema_dumper_spec.rb +25 -2
- data/spec/convergence/dumper/postgres_schema_dumper_spec.rb +103 -0
- data/spec/convergence/dumper/sqlite_schema_dumper_spec.rb +85 -0
- data/spec/convergence/dumper_spec.rb +110 -16
- data/spec/convergence/foreign_key_spec.rb +33 -0
- data/spec/convergence/index_spec.rb +52 -0
- data/spec/convergence/pretty_diff_spec.rb +85 -0
- data/spec/convergence/table_spec.rb +23 -0
- data/spec/fixtures/add_table_with_enum_set.schema +5 -0
- data/spec/fixtures/change_table_comment_to_paper.schema +2 -0
- data/spec/fixtures/execute_raw_sql.schema +28 -0
- data/spec/fixtures/postgres/add_columns_to_paper.schema +29 -0
- data/spec/fixtures/postgres/add_table.schema +32 -0
- data/spec/fixtures/postgres/change_comment_columns_to_paper.schema +28 -0
- data/spec/fixtures/postgres/change_table_comment_to_paper.schema +28 -0
- data/spec/fixtures/postgres/drop_foreign_key.schema +25 -0
- data/spec/fixtures/postgres/drop_table.schema +19 -0
- data/spec/fixtures/postgres/remove_columns_to_paper.schema +27 -0
- data/spec/fixtures/postgres_test_db.sql +41 -0
- data/spec/fixtures/sqlite/add_columns_to_paper.schema +29 -0
- data/spec/fixtures/sqlite/add_table.schema +32 -0
- data/spec/fixtures/sqlite/change_columns_to_paper.schema +28 -0
- data/spec/fixtures/sqlite/drop_foreign_key.schema +25 -0
- data/spec/fixtures/sqlite/drop_table.schema +19 -0
- data/spec/fixtures/sqlite/remove_columns_to_paper.schema +27 -0
- data/spec/fixtures/sqlite_test_db.sql +31 -0
- data/spec/fixtures/test_db.sql +10 -0
- data/spec/integrations/command_diff.rb +35 -0
- data/spec/integrations/command_dryrun.rb +37 -2
- data/spec/integrations/command_export.rb +28 -0
- data/spec/postgres_integrations/command_dryrun.rb +79 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/sqlite_integrations/command_dryrun.rb +70 -0
- metadata +96 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75c8ae12a9ee346dd376ff2dfe6f03451a96aa4dc58acf414f9c0447c3e79bab
|
|
4
|
+
data.tar.gz: 57e2096d724239594acd415001e801f89f22bba706e3382ccca46dd3309cd5b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca8171ecd7a63c898401fb316be440a1cee97b4cd44d52bda5ac449c608dd54432574aa5ce6e2222fe0d65acac0e89d4ec092eecd6e83cb6a3e9874ffb382cbc
|
|
7
|
+
data.tar.gz: 3b7ca04775d9e8800a69f6b70034d9d906b8ba679a575603b8d7d5283db2dd031215d50c42e4d16993f2e686c38ba4bb04ffa4d1028eac2f896f52df05c99f31
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -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
|
-
|
|
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@
|
|
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
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
## Convergence 1.1.0 (August 25, 2026) ##
|
|
2
|
+
|
|
3
|
+
* Add PostgreSQL support (PR: #93)
|
|
4
|
+
|
|
5
|
+
*nishio-dens*
|
|
6
|
+
|
|
7
|
+
* Add SQLite support (PR: #95)
|
|
8
|
+
|
|
9
|
+
*nishio-dens*
|
|
10
|
+
|
|
11
|
+
* Support ENUM/SET column types (PR: #92)
|
|
12
|
+
|
|
13
|
+
*nishio-dens*
|
|
14
|
+
|
|
15
|
+
* Add `execute()` DSL method to run raw SQL (PR: #94)
|
|
16
|
+
|
|
17
|
+
*nishio-dens*
|
|
18
|
+
|
|
19
|
+
* Add `--dump-rails-migration` option to export DB schema as a Rails migration file (PR: #96)
|
|
20
|
+
|
|
21
|
+
*nishio-dens*
|
|
22
|
+
|
|
23
|
+
* Add `--safe-migration` option to skip DROP TABLE queries (PR: #91)
|
|
24
|
+
|
|
25
|
+
*nishio-dens*
|
|
26
|
+
|
|
27
|
+
* Add `--ignore-auto-increment` option (PR: #90)
|
|
28
|
+
|
|
29
|
+
*nishio-dens*
|
|
30
|
+
|
|
31
|
+
* Fix MySQL 8.0 test failure and modernize CI Ruby matrix (PR: #89)
|
|
32
|
+
|
|
33
|
+
*nishio-dens*
|
|
34
|
+
|
|
35
|
+
* Bump diffy to >= 3.4.2, fixing CVE-2022-33127 (PR: #100)
|
|
36
|
+
|
|
37
|
+
*nishio-dens*
|
|
38
|
+
|
|
39
|
+
* Add specs for previously untested classes/commands (PR: #97)
|
|
40
|
+
|
|
41
|
+
*nishio-dens*
|
|
42
|
+
|
|
43
|
+
* Add CONTRIBUTING.md (PR: #98)
|
|
44
|
+
|
|
45
|
+
*nishio-dens*
|
|
46
|
+
|
|
1
47
|
## Convergence 1.0.6 (April 28, 2022) ##
|
|
2
48
|
|
|
3
49
|
* 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,22 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
convergence (1.0
|
|
4
|
+
convergence (1.1.0)
|
|
5
5
|
diff-lcs
|
|
6
|
-
diffy
|
|
6
|
+
diffy (>= 3.4.2)
|
|
7
7
|
mysql2
|
|
8
|
+
pg
|
|
9
|
+
sqlite3
|
|
8
10
|
thor
|
|
9
11
|
|
|
10
12
|
GEM
|
|
11
13
|
remote: https://rubygems.org/
|
|
12
14
|
specs:
|
|
13
15
|
ast (2.4.1)
|
|
16
|
+
bigdecimal (4.1.2)
|
|
14
17
|
coderay (1.1.3)
|
|
15
18
|
diff-lcs (1.4.4)
|
|
16
|
-
diffy (3.4.
|
|
19
|
+
diffy (3.4.4)
|
|
17
20
|
ffi (1.14.2)
|
|
18
21
|
formatador (0.2.5)
|
|
19
22
|
guard (2.16.2)
|
|
@@ -30,12 +33,15 @@ GEM
|
|
|
30
33
|
guard (~> 2.1)
|
|
31
34
|
guard-compat (~> 1.1)
|
|
32
35
|
rspec (>= 2.99.0, < 4.0)
|
|
36
|
+
io-console (0.9.2)
|
|
33
37
|
listen (3.3.3)
|
|
34
38
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
35
39
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
36
40
|
lumberjack (1.2.8)
|
|
37
|
-
method_source (1.
|
|
38
|
-
|
|
41
|
+
method_source (1.1.0)
|
|
42
|
+
mini_portile2 (2.8.9)
|
|
43
|
+
mysql2 (0.5.7)
|
|
44
|
+
bigdecimal
|
|
39
45
|
nenv (0.3.0)
|
|
40
46
|
notiffany (0.1.3)
|
|
41
47
|
nenv (~> 0.1)
|
|
@@ -43,15 +49,20 @@ GEM
|
|
|
43
49
|
parallel (1.20.1)
|
|
44
50
|
parser (3.0.0.0)
|
|
45
51
|
ast (~> 2.4.1)
|
|
46
|
-
|
|
52
|
+
pg (1.6.3)
|
|
53
|
+
pg (1.6.3-x86_64-linux)
|
|
54
|
+
pry (0.16.0)
|
|
47
55
|
coderay (~> 1.1)
|
|
48
56
|
method_source (~> 1.0)
|
|
57
|
+
reline (>= 0.6.0)
|
|
49
58
|
rainbow (3.0.0)
|
|
50
|
-
rake (13.
|
|
59
|
+
rake (13.4.2)
|
|
51
60
|
rb-fsevent (0.10.4)
|
|
52
61
|
rb-inotify (0.10.1)
|
|
53
62
|
ffi (~> 1.0)
|
|
54
63
|
regexp_parser (2.0.3)
|
|
64
|
+
reline (0.7.0)
|
|
65
|
+
io-console (~> 0.5)
|
|
55
66
|
rexml (3.2.5)
|
|
56
67
|
rspec (3.10.0)
|
|
57
68
|
rspec-core (~> 3.10.0)
|
|
@@ -79,11 +90,15 @@ GEM
|
|
|
79
90
|
parser (>= 2.7.1.5)
|
|
80
91
|
ruby-progressbar (1.11.0)
|
|
81
92
|
shellany (0.0.1)
|
|
93
|
+
sqlite3 (2.8.1)
|
|
94
|
+
mini_portile2 (~> 2.8.0)
|
|
95
|
+
sqlite3 (2.8.1-x86_64-linux-gnu)
|
|
82
96
|
thor (1.0.1)
|
|
83
97
|
unicode-display_width (1.7.0)
|
|
84
98
|
|
|
85
99
|
PLATFORMS
|
|
86
100
|
ruby
|
|
101
|
+
x86_64-linux
|
|
87
102
|
|
|
88
103
|
DEPENDENCIES
|
|
89
104
|
convergence!
|
|
@@ -95,4 +110,4 @@ DEPENDENCIES
|
|
|
95
110
|
rubocop
|
|
96
111
|
|
|
97
112
|
BUNDLED WITH
|
|
98
|
-
|
|
113
|
+
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
|
|
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,49 @@ 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.
|
|
195
|
+
|
|
196
|
+
Like PostgreSQL, MySQL-only table options (`engine`, `row_format`, `default_charset`/`character_set`, `collate`,
|
|
197
|
+
table/column `comment`) have no SQLite equivalent and are ignored. `extra: 'auto_increment'` maps to SQLite's
|
|
198
|
+
`INTEGER PRIMARY KEY AUTOINCREMENT`.
|
|
199
|
+
|
|
155
200
|
#### Use SSL connection
|
|
156
201
|
|
|
157
202
|
If you would like to use SSL connection, you can specify SSL options in database.yml
|
|
@@ -216,6 +261,43 @@ create_table "paper_authors", collate: "utf8_general_ci", comment: "Paper Author
|
|
|
216
261
|
end
|
|
217
262
|
```
|
|
218
263
|
|
|
264
|
+
### Export as a Rails migration file
|
|
265
|
+
|
|
266
|
+
If you're migrating to (or working alongside) a Rails app, pass `--dump-rails-migration` to export the schema as an
|
|
267
|
+
ActiveRecord migration instead of a Convergence DSL file. `--filename` sets the base filename/class name (Rails'
|
|
268
|
+
usual `snake_case` filename / `CamelCase` class name convention applies).
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
$ convergence export -c database.yml --dump-rails-migration --filename create_initial_tables
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# Filename: 20240101000000_create_initial_tables.rb
|
|
276
|
+
class CreateInitialTables < ActiveRecord::Migration[7.0]
|
|
277
|
+
def change
|
|
278
|
+
create_table :authors do |t|
|
|
279
|
+
t.string :name, limit: 110
|
|
280
|
+
t.timestamps
|
|
281
|
+
|
|
282
|
+
t.index :created_at, name: "index_authors_on_created_at"
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The output is written to stdout with a `# Filename: ...` comment on top (including a timestamp prefix in the usual
|
|
289
|
+
Rails migration filename format) — redirect it to that file yourself, e.g.
|
|
290
|
+
`convergence export ... > db/migrate/$(date +%Y%m%d%H%M%S)_create_initial_tables.rb`.
|
|
291
|
+
|
|
292
|
+
A few notes on the conversion:
|
|
293
|
+
* An `id` column that matches Rails' implicit auto-incrementing primary key (`int`/`bigint`, `primary_key: true`,
|
|
294
|
+
`extra: 'auto_increment'`) is omitted, since `create_table` adds it automatically.
|
|
295
|
+
* `created_at`/`updated_at` columns with matching `datetime`/`null` options are collapsed into `t.timestamps`.
|
|
296
|
+
* Column types use Rails' type names (e.g. `varchar` → `string`, `datetime`/`timestamp` → `datetime`); `enum`/`set`
|
|
297
|
+
have no ActiveRecord equivalent and fall back to `string`.
|
|
298
|
+
* MySQL-only concepts with no `create_table` DSL equivalent (`character_set`, `collate`, `extra`, `after`) are
|
|
299
|
+
dropped from column definitions.
|
|
300
|
+
|
|
219
301
|
### Dry run
|
|
220
302
|
|
|
221
303
|
```
|
|
@@ -235,6 +317,29 @@ $ convergence apply example.schema -c database.yml --rollback-dry-run
|
|
|
235
317
|
$ convergence apply example.schema -c database.yml
|
|
236
318
|
```
|
|
237
319
|
|
|
320
|
+
### Ignore AUTO_INCREMENT changes
|
|
321
|
+
|
|
322
|
+
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)
|
|
323
|
+
and apply it to another database, convergence will normally generate a query that bumps AUTO_INCREMENT to match, which
|
|
324
|
+
can jump/skip a large range of ids. Pass `--ignore-auto-increment` to skip generating AUTO_INCREMENT change queries entirely.
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
$ convergence apply example.schema -c database.yml --ignore-auto-increment
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
This also applies to `--dry-run` and `--rollback-dry-run`.
|
|
331
|
+
|
|
332
|
+
### Safe migration
|
|
333
|
+
|
|
334
|
+
If you want to prevent convergence from ever dropping a table (e.g. when the DSL file removed a table that still
|
|
335
|
+
has important data), pass `--safe-migration`. It skips generating `DROP TABLE` queries; every other change is still applied.
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
$ convergence apply example.schema -c database.yml --safe-migration
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
This also applies to `--dry-run` and `--rollback-dry-run`.
|
|
342
|
+
|
|
238
343
|
### Include Other Schema files
|
|
239
344
|
|
|
240
345
|
```
|
|
@@ -242,6 +347,20 @@ include 'first_schema.schema'
|
|
|
242
347
|
include 'other_file.schema'
|
|
243
348
|
```
|
|
244
349
|
|
|
350
|
+
### Execute raw SQL
|
|
351
|
+
|
|
352
|
+
For things the DSL has no dedicated syntax for (views, triggers, stored procedures, data backfills, grants, ...),
|
|
353
|
+
use `execute` to run an arbitrary SQL statement. Unlike `create_table`, statements passed to `execute` are **not**
|
|
354
|
+
diffed against the current schema: they run every time the schema file is applied, so make sure the SQL itself is
|
|
355
|
+
idempotent (e.g. `CREATE OR REPLACE VIEW ...`, `CREATE TABLE IF NOT EXISTS ...`).
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
execute "CREATE OR REPLACE VIEW active_users AS SELECT * FROM users WHERE active = 1"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`execute` statements always run after the generated schema changes, and are skipped by `--rollback-dry-run` (there's
|
|
362
|
+
no way to know how to reverse an arbitrary SQL statement).
|
|
363
|
+
|
|
245
364
|
|
|
246
365
|
## Detail About Convergence DSL
|
|
247
366
|
|
|
@@ -273,6 +392,8 @@ Convergence is currently support column types below.
|
|
|
273
392
|
- timestamp
|
|
274
393
|
- year
|
|
275
394
|
- json
|
|
395
|
+
- enum
|
|
396
|
+
- set
|
|
276
397
|
|
|
277
398
|
```
|
|
278
399
|
create_table "tests", comment: 'Column type example' do |t|
|
|
@@ -282,6 +403,8 @@ create_table "tests", comment: 'Column type example' do |t|
|
|
|
282
403
|
t.varchar 'test_string', null: true, default: 'hello', limit: 300
|
|
283
404
|
t.text 'text_col'
|
|
284
405
|
t.datetime 'created_at'
|
|
406
|
+
t.enum 'status', values: %w(active inactive pending), default: 'active'
|
|
407
|
+
t.set 'flags', values: %w(a b c)
|
|
285
408
|
end
|
|
286
409
|
```
|
|
287
410
|
|
|
@@ -344,9 +467,16 @@ end
|
|
|
344
467
|
|
|
345
468
|
```
|
|
346
469
|
$ bundle exec rake db:convergence:prepare
|
|
470
|
+
$ bundle exec rake db:convergence:postgres:prepare
|
|
471
|
+
$ bundle exec rake db:convergence:sqlite:prepare
|
|
347
472
|
$ bundle exec rspec
|
|
348
473
|
```
|
|
349
474
|
|
|
475
|
+
## Contributing
|
|
476
|
+
|
|
477
|
+
Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a
|
|
478
|
+
development environment, run the test suite, and what to include in a pull request.
|
|
479
|
+
|
|
350
480
|
## Copyright
|
|
351
481
|
|
|
352
482
|
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
|
-
|
|
9
|
-
mysql_settings = Hash[
|
|
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,8 +19,10 @@ 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'
|
|
25
27
|
|
|
26
28
|
spec.required_ruby_version = ">= 2.4.0"
|
data/lib/convergence/cli.rb
CHANGED
|
@@ -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 = {
|
|
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
|
|
|
@@ -8,40 +8,69 @@ require 'convergence/diff'
|
|
|
8
8
|
class Convergence::Command::Apply < Convergence::Command
|
|
9
9
|
def execute
|
|
10
10
|
current_dir_path = Pathname.new(@opts[:input]).realpath.dirname
|
|
11
|
-
|
|
11
|
+
dsl = Convergence::DSL.parse_dsl(File.open(@opts[:input]).read, current_dir_path)
|
|
12
12
|
current_tables = dumper.dump
|
|
13
|
-
execute_sql(
|
|
13
|
+
execute_sql(dsl.tables, current_tables, dsl.raw_sqls)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def generate_sql(input_tables, current_tables)
|
|
16
|
+
def generate_sql(input_tables, current_tables, raw_sqls = [])
|
|
17
17
|
current_tables_with_full_option =
|
|
18
18
|
Convergence::DefaultParameter.append_database_default_parameter(current_tables, database_adapter)
|
|
19
19
|
input_tables_with_full_option =
|
|
20
20
|
Convergence::DefaultParameter.append_database_default_parameter(input_tables, database_adapter)
|
|
21
|
-
delta = Convergence::Diff
|
|
22
|
-
|
|
21
|
+
delta = Convergence::Diff
|
|
22
|
+
.new(ignore_auto_increment: @opts[:ignore_auto_increment])
|
|
23
|
+
.diff(current_tables_with_full_option, input_tables_with_full_option)
|
|
24
|
+
sql = sql_generator.generate(
|
|
25
|
+
input_tables_with_full_option,
|
|
26
|
+
delta,
|
|
27
|
+
current_tables_with_full_option,
|
|
28
|
+
safe_migration: @opts[:safe_migration]
|
|
29
|
+
)
|
|
30
|
+
append_raw_sqls(sql, raw_sqls)
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
private
|
|
26
34
|
|
|
35
|
+
def append_raw_sqls(sql, raw_sqls)
|
|
36
|
+
return sql if raw_sqls.empty?
|
|
37
|
+
raw_sql_block = raw_sqls.map { |q| q.strip.end_with?(';') ? q.strip : "#{q.strip};" }.join("\n")
|
|
38
|
+
[sql, raw_sql_block].reject(&:empty?).join("\n")
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
def sql_generator
|
|
28
42
|
@sql_generator ||= case database_adapter
|
|
29
43
|
when 'mysql', 'mysql2'
|
|
30
44
|
require 'convergence/sql_generator/mysql_generator'
|
|
31
45
|
SQLGenerator::MysqlGenerator.new
|
|
46
|
+
when 'postgresql', 'postgres', 'pg'
|
|
47
|
+
require 'convergence/sql_generator/postgres_generator'
|
|
48
|
+
SQLGenerator::PostgresGenerator.new
|
|
49
|
+
when 'sqlite3', 'sqlite'
|
|
50
|
+
require 'convergence/sql_generator/sqlite_generator'
|
|
51
|
+
SQLGenerator::SqliteGenerator.new
|
|
32
52
|
else
|
|
33
53
|
fail NotImplementedError.new('unknown database adapter')
|
|
34
54
|
end
|
|
35
55
|
end
|
|
36
56
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
def wrap_with_constraint_pragma(sql)
|
|
58
|
+
case database_adapter
|
|
59
|
+
when 'mysql', 'mysql2'
|
|
60
|
+
<<-SQL
|
|
41
61
|
SET FOREIGN_KEY_CHECKS=0;
|
|
42
62
|
#{sql}
|
|
43
63
|
SET FOREIGN_KEY_CHECKS=1;
|
|
44
64
|
SQL
|
|
65
|
+
else
|
|
66
|
+
sql
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def execute_sql(input_tables, current_tables, raw_sqls = [])
|
|
71
|
+
sql = generate_sql(input_tables, current_tables, raw_sqls)
|
|
72
|
+
unless sql.strip.empty?
|
|
73
|
+
sql = wrap_with_constraint_pragma(sql)
|
|
45
74
|
end
|
|
46
75
|
sql.split(';').each do |q2|
|
|
47
76
|
q = q2.strip
|