dbbridgekit 0.1.0__tar.gz

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 (51) hide show
  1. dbbridgekit-0.1.0/LICENSE +21 -0
  2. dbbridgekit-0.1.0/PKG-INFO +203 -0
  3. dbbridgekit-0.1.0/README.md +167 -0
  4. dbbridgekit-0.1.0/dbbridge/__init__.py +22 -0
  5. dbbridgekit-0.1.0/dbbridge/cli.py +177 -0
  6. dbbridgekit-0.1.0/dbbridge/codemod/__init__.py +3 -0
  7. dbbridgekit-0.1.0/dbbridge/codemod/codemod.py +194 -0
  8. dbbridgekit-0.1.0/dbbridge/compat/__init__.py +3 -0
  9. dbbridgekit-0.1.0/dbbridge/compat/runtime.py +193 -0
  10. dbbridgekit-0.1.0/dbbridge/core/__init__.py +2 -0
  11. dbbridgekit-0.1.0/dbbridge/core/ir.py +106 -0
  12. dbbridgekit-0.1.0/dbbridge/core/parser.py +60 -0
  13. dbbridgekit-0.1.0/dbbridge/core/planner.py +44 -0
  14. dbbridgekit-0.1.0/dbbridge/core/renderer.py +57 -0
  15. dbbridgekit-0.1.0/dbbridge/core/sql_lex.py +61 -0
  16. dbbridgekit-0.1.0/dbbridge/core/types.py +47 -0
  17. dbbridgekit-0.1.0/dbbridge/dialects/__init__.py +6 -0
  18. dbbridgekit-0.1.0/dbbridge/dialects/_stub.py +31 -0
  19. dbbridgekit-0.1.0/dbbridge/dialects/mysql.py +16 -0
  20. dbbridgekit-0.1.0/dbbridge/dialects/postgres.py +292 -0
  21. dbbridgekit-0.1.0/dbbridge/dialects/sqlite.py +279 -0
  22. dbbridgekit-0.1.0/dbbridge/dialects/sqlserver.py +15 -0
  23. dbbridgekit-0.1.0/dbbridge/migration/__init__.py +11 -0
  24. dbbridgekit-0.1.0/dbbridge/migration/data_migration.py +76 -0
  25. dbbridgekit-0.1.0/dbbridge/migration/rollback.py +57 -0
  26. dbbridgekit-0.1.0/dbbridge/migration/schema_migration.py +56 -0
  27. dbbridgekit-0.1.0/dbbridge/migration/validator.py +85 -0
  28. dbbridgekit-0.1.0/dbbridge/reports/__init__.py +3 -0
  29. dbbridgekit-0.1.0/dbbridge/reports/report.py +58 -0
  30. dbbridgekit-0.1.0/dbbridge/scanner/__init__.py +3 -0
  31. dbbridgekit-0.1.0/dbbridge/scanner/scanner.py +176 -0
  32. dbbridgekit-0.1.0/dbbridgekit.egg-info/PKG-INFO +203 -0
  33. dbbridgekit-0.1.0/dbbridgekit.egg-info/SOURCES.txt +49 -0
  34. dbbridgekit-0.1.0/dbbridgekit.egg-info/dependency_links.txt +1 -0
  35. dbbridgekit-0.1.0/dbbridgekit.egg-info/entry_points.txt +2 -0
  36. dbbridgekit-0.1.0/dbbridgekit.egg-info/requires.txt +8 -0
  37. dbbridgekit-0.1.0/dbbridgekit.egg-info/top_level.txt +1 -0
  38. dbbridgekit-0.1.0/pyproject.toml +55 -0
  39. dbbridgekit-0.1.0/setup.cfg +4 -0
  40. dbbridgekit-0.1.0/tests/test_cli.py +215 -0
  41. dbbridgekit-0.1.0/tests/test_codemod.py +234 -0
  42. dbbridgekit-0.1.0/tests/test_compat_runtime.py +168 -0
  43. dbbridgekit-0.1.0/tests/test_ir.py +37 -0
  44. dbbridgekit-0.1.0/tests/test_migration.py +169 -0
  45. dbbridgekit-0.1.0/tests/test_package.py +16 -0
  46. dbbridgekit-0.1.0/tests/test_planner.py +76 -0
  47. dbbridgekit-0.1.0/tests/test_postgres_dialect.py +246 -0
  48. dbbridgekit-0.1.0/tests/test_report.py +56 -0
  49. dbbridgekit-0.1.0/tests/test_scanner.py +192 -0
  50. dbbridgekit-0.1.0/tests/test_sql_lex.py +56 -0
  51. dbbridgekit-0.1.0/tests/test_sqlite_dialect.py +228 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Otavio R Santana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbbridgekit
3
+ Version: 0.1.0
4
+ Summary: Plataforma genérica de conversão/análise/migração entre bancos de dados via Intermediate Representation (IR) canônica — SQLite, PostgreSQL, MySQL (em progresso), SQL Server (planejado).
5
+ Author-email: Otavio R Santana <tvostrodrigues8@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/tvost2/dbbridge
8
+ Project-URL: Repository, https://github.com/tvost2/dbbridge
9
+ Project-URL: Issues, https://github.com/tvost2/dbbridge/issues
10
+ Project-URL: Documentation, https://github.com/tvost2/dbbridge/blob/main/docs/guide.md
11
+ Project-URL: Changelog, https://github.com/tvost2/dbbridge/blob/main/CHANGELOG.md
12
+ Keywords: sql,sqlite,postgresql,mysql,migration,ir,database
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: psycopg[binary]>=3.1
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.6; extra == "dev"
33
+ Requires-Dist: build>=1.0; extra == "dev"
34
+ Requires-Dist: twine>=5.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # DBBridge
38
+
39
+ Plataforma genérica de conversão, análise e migração entre bancos de dados, baseada numa
40
+ **Intermediate Representation (IR)** canônica — não um conversor pareado por combinação de bancos.
41
+
42
+ ```txt
43
+ Banco origem → Parser do dialeto origem → IR canônica → Renderer do dialeto destino → Banco destino
44
+ ```
45
+
46
+ Evolução do [PostgresModel](../postgresmodel) generalizada: em vez de assumir SQLite→PostgreSQL
47
+ sempre, qualquer par de dialetos suportados passa pelo mesmo pipeline.
48
+
49
+ ## Status (Fase 1)
50
+
51
+ | Dialeto | Parser | Renderer |
52
+ |---|---|---|
53
+ | SQLite | ✅ | ✅ |
54
+ | PostgreSQL | ✅ | ✅ |
55
+ | MySQL | estrutura registrada (Fase 2) | estrutura registrada (Fase 2) |
56
+ | SQL Server | estrutura registrada (Fase 5) | estrutura registrada (Fase 5) |
57
+
58
+ `dbbridge.dialects.mysql`/`sqlserver` já registram um dialeto "conhecido, ainda não implementado" —
59
+ `dbbridge dialects` lista os dois, e chamar `.parse()`/`.render_schema()` neles levanta
60
+ `NotImplementedError` com a fase planejada, em vez de um erro genérico de dialeto desconhecido.
61
+
62
+ 167 testes, 99% de cobertura (`pytest --cov=dbbridge`). Os poucos pontos não cobertos são código
63
+ genuinamente inalcançável (stubs de método abstrato, guard `if __name__ == "__main__"`) — ver
64
+ [docs/guide.md](docs/guide.md#cobertura-de-testes).
65
+
66
+ ## Instalação
67
+
68
+ ```bash
69
+ pip install -e .
70
+ # ou, pra rodar contra PostgreSQL de verdade:
71
+ pip install -e ".[dev]"
72
+ ```
73
+
74
+ ## Uso rápido
75
+
76
+ ### Traduzir um schema
77
+
78
+ ```bash
79
+ dbbridge translate --from sqlite --to postgres schema.sql
80
+ ```
81
+
82
+ Casos que o parser/renderer não reconhecem com segurança viram `REVIEW_REQUIRED` no stderr — nunca
83
+ uma tradução inventada.
84
+
85
+ ### Escanear um projeto Python
86
+
87
+ ```bash
88
+ dbbridge scan --from sqlite --to postgres ./meu_projeto
89
+ ```
90
+
91
+ Encontra `execute()`/`executemany()` com SQL potencialmente incompatível entre os dois dialetos:
92
+ placeholders (`?` vs `%s`), `sqlite_master`, `PRAGMA`, `INSERT OR IGNORE`/`OR REPLACE`,
93
+ case-sensitivity de `LIKE`, autoincrement, e mais.
94
+
95
+ ### Migrar código-fonte automaticamente
96
+
97
+ ```bash
98
+ dbbridge patch --from sqlite --to postgres ./meu_projeto # dry-run, mostra o plano
99
+ dbbridge apply --from sqlite --to postgres ./meu_projeto # aplica com backup automático
100
+ dbbridge rollback ./meu_projeto # desfaz, restaura do backup
101
+ ```
102
+
103
+ Reescreve só literais de string simples sem ambiguidade (troca de placeholder). `f-strings`,
104
+ concatenação e casos ambíguos (aspas duplas, `INSERT OR REPLACE`, etc.) nunca são alterados
105
+ automaticamente — viram `REVIEW_REQUIRED`/`SKIPPED_COMPLEX_EXPRESSION` no plano.
106
+
107
+ ### Migrar dados entre bancos de verdade
108
+
109
+ ```bash
110
+ dbbridge migrate-data --from sqlite --to postgres \
111
+ --source-dsn ./app.db --target-dsn "host=localhost dbname=app" --tables users leads
112
+ dbbridge validate --from sqlite --to postgres \
113
+ --source-dsn ./app.db --target-dsn "host=localhost dbname=app" --tables users leads
114
+ ```
115
+
116
+ Copia em lotes, nunca faz `DROP`/`TRUNCATE`, sempre acrescenta. `validate` compara contagens e
117
+ checksum (SHA-256) linha a linha quando a tabela é pequena o bastante.
118
+
119
+ ### Compatibility Mode (código antigo continua rodando)
120
+
121
+ ```python
122
+ from dbbridge import connect
123
+
124
+ db = connect(source_dialect="sqlite", target_dialect="postgres", dsn="host=localhost dbname=app")
125
+ db.execute("SELECT * FROM users WHERE id=?", (1,)) # vira %s por baixo, roda no Postgres de verdade
126
+ ```
127
+
128
+ Permite migrar a aplicação por partes: o código continua escrito na sintaxe de origem enquanto o
129
+ banco de verdade já é o destino.
130
+
131
+ ## Exemplos executáveis
132
+
133
+ ```bash
134
+ python examples/translate_schema.py # traduz um schema via API Python
135
+ python examples/scan_and_patch_project.py # scan -> patch -> apply -> rollback num mini-app
136
+ python examples/compat_mode.py # Compatibility Mode de ponta a ponta
137
+ DBBRIDGE_EXAMPLE_PG_DSN="host=localhost dbname=..." python examples/full_data_migration.py
138
+ ```
139
+
140
+ Todos os quatro rodam de verdade (não são pseudocódigo) — os três primeiros só precisam do SQLite
141
+ da stdlib; o último precisa de um PostgreSQL acessível via a variável de ambiente indicada.
142
+
143
+ ## Arquitetura
144
+
145
+ ```txt
146
+ dbbridge/
147
+ cli.py # scan|report|translate|patch|apply|migrate-data|validate|rollback|doctor|dialects
148
+ core/
149
+ ir.py # Schema, Table, Column, ForeignKey, UniqueConstraint, CheckConstraint, Index, View, Trigger, Enum
150
+ types.py # CanonicalType, Ambiguity
151
+ parser.py / renderer.py # contratos + registry (register_parser/get_parser/available_parsers, idem renderer)
152
+ planner.py # translate(sql, source, target) — o pipeline completo numa função
153
+ dialects/
154
+ sqlite.py, postgres.py # implementados
155
+ mysql.py, sqlserver.py # stubs registrados (NotImplementedError com a fase planejada)
156
+ scanner/scanner.py # AST — encontra SQL arriscado por par de dialetos
157
+ codemod/codemod.py # AST — reescreve só literais simples sem ambiguidade
158
+ migration/
159
+ schema_migration.py # traduz + aplica DDL (aditivo, nunca DROP/TRUNCATE)
160
+ data_migration.py # copia dados em lotes
161
+ validator.py # contagens + checksum
162
+ rollback.py # restaura código a partir de backup
163
+ compat/runtime.py # connect() — Compatibility Mode
164
+ reports/report.py # normaliza qualquer resultado pra texto/JSON
165
+ ```
166
+
167
+ Por que IR em vez de conversores pareados: com N dialetos, um conversor direto por par cresce O(N²)
168
+ e duplica a mesma lógica de tipos/constraints em cada combinação. Com um modelo canônico no meio,
169
+ cada dialeto novo precisa de só 1 parser + 1 renderer (O(N)) pra já converter de/para todos os
170
+ outros já implementados.
171
+
172
+ ## Segurança
173
+
174
+ - Toda alteração de código gera backup antes de escrever, e pode ser revertida via `dbbridge rollback`.
175
+ - Migração de schema/dados é sempre aditiva — nunca `DROP`/`TRUNCATE`/`DELETE`.
176
+ - Casos ambíguos (aspas duplas em literal, `INSERT OR REPLACE`, f-strings, expressões dinâmicas)
177
+ nunca são "adivinhados" — viram `REVIEW_REQUIRED` pra revisão manual.
178
+ - `apply_changes` verifica `ast.parse()` do arquivo resultante antes de considerar sucesso; se o
179
+ resultado tiver `SyntaxError`, reverte sozinho a partir do backup.
180
+
181
+ ## Fases
182
+
183
+ 1. **IR + SQLite ↔ PostgreSQL** — atual, completo: parser/renderer dos dois dialetos, scanner,
184
+ codemod, migração de schema/dados, validação por checksum, rollback, CLI, Compatibility Mode.
185
+ 2. MySQL (parser + renderer reais).
186
+ 3. Codemod genérico validado contra os 3 dialetos.
187
+ 4. Data migration validada nos 4 pares centrais (SQLite/Postgres/MySQL cruzados).
188
+ 5. SQL Server.
189
+ 6. **Fases futuras (levantadas, ainda não priorizadas):** shadow migration (rodar origem e destino
190
+ em paralelo comparando resultados antes do cutover final), query replay (reproduzir tráfego real
191
+ de produção contra o destino como teste de carga/compatibilidade), dashboard web, relatórios em
192
+ HTML/PDF (hoje só texto/JSON via `reports/report.py`), mecanismo de plugin formal pra dialetos
193
+ externos (hoje a extensão já é possível via `register_parser`/`register_renderer` — falta só
194
+ empacotar como plugin instalável separadamente, ex. entry points do Python), e suporte a bancos
195
+ não-relacionais como MongoDB (exigiria uma IR paralela pra documentos/coleções — ver
196
+ [docs/compatibility-matrix.md](docs/compatibility-matrix.md#roadmap-além-do-sql-relacional)).
197
+
198
+ Cada fase só avança depois da anterior validada — ver [docs/guide.md](docs/guide.md) para detalhes
199
+ de arquitetura e extensão pra novos dialetos, [docs/cli-reference.md](docs/cli-reference.md) pra
200
+ todos os comandos, [docs/compatibility-matrix.md](docs/compatibility-matrix.md) pro que já
201
+ funciona vs. planejado, [docs/migration-tutorial.md](docs/migration-tutorial.md) pro passo a passo
202
+ completo, e [docs/production-checklist.md](docs/production-checklist.md) antes de rodar contra
203
+ dados reais.
@@ -0,0 +1,167 @@
1
+ # DBBridge
2
+
3
+ Plataforma genérica de conversão, análise e migração entre bancos de dados, baseada numa
4
+ **Intermediate Representation (IR)** canônica — não um conversor pareado por combinação de bancos.
5
+
6
+ ```txt
7
+ Banco origem → Parser do dialeto origem → IR canônica → Renderer do dialeto destino → Banco destino
8
+ ```
9
+
10
+ Evolução do [PostgresModel](../postgresmodel) generalizada: em vez de assumir SQLite→PostgreSQL
11
+ sempre, qualquer par de dialetos suportados passa pelo mesmo pipeline.
12
+
13
+ ## Status (Fase 1)
14
+
15
+ | Dialeto | Parser | Renderer |
16
+ |---|---|---|
17
+ | SQLite | ✅ | ✅ |
18
+ | PostgreSQL | ✅ | ✅ |
19
+ | MySQL | estrutura registrada (Fase 2) | estrutura registrada (Fase 2) |
20
+ | SQL Server | estrutura registrada (Fase 5) | estrutura registrada (Fase 5) |
21
+
22
+ `dbbridge.dialects.mysql`/`sqlserver` já registram um dialeto "conhecido, ainda não implementado" —
23
+ `dbbridge dialects` lista os dois, e chamar `.parse()`/`.render_schema()` neles levanta
24
+ `NotImplementedError` com a fase planejada, em vez de um erro genérico de dialeto desconhecido.
25
+
26
+ 167 testes, 99% de cobertura (`pytest --cov=dbbridge`). Os poucos pontos não cobertos são código
27
+ genuinamente inalcançável (stubs de método abstrato, guard `if __name__ == "__main__"`) — ver
28
+ [docs/guide.md](docs/guide.md#cobertura-de-testes).
29
+
30
+ ## Instalação
31
+
32
+ ```bash
33
+ pip install -e .
34
+ # ou, pra rodar contra PostgreSQL de verdade:
35
+ pip install -e ".[dev]"
36
+ ```
37
+
38
+ ## Uso rápido
39
+
40
+ ### Traduzir um schema
41
+
42
+ ```bash
43
+ dbbridge translate --from sqlite --to postgres schema.sql
44
+ ```
45
+
46
+ Casos que o parser/renderer não reconhecem com segurança viram `REVIEW_REQUIRED` no stderr — nunca
47
+ uma tradução inventada.
48
+
49
+ ### Escanear um projeto Python
50
+
51
+ ```bash
52
+ dbbridge scan --from sqlite --to postgres ./meu_projeto
53
+ ```
54
+
55
+ Encontra `execute()`/`executemany()` com SQL potencialmente incompatível entre os dois dialetos:
56
+ placeholders (`?` vs `%s`), `sqlite_master`, `PRAGMA`, `INSERT OR IGNORE`/`OR REPLACE`,
57
+ case-sensitivity de `LIKE`, autoincrement, e mais.
58
+
59
+ ### Migrar código-fonte automaticamente
60
+
61
+ ```bash
62
+ dbbridge patch --from sqlite --to postgres ./meu_projeto # dry-run, mostra o plano
63
+ dbbridge apply --from sqlite --to postgres ./meu_projeto # aplica com backup automático
64
+ dbbridge rollback ./meu_projeto # desfaz, restaura do backup
65
+ ```
66
+
67
+ Reescreve só literais de string simples sem ambiguidade (troca de placeholder). `f-strings`,
68
+ concatenação e casos ambíguos (aspas duplas, `INSERT OR REPLACE`, etc.) nunca são alterados
69
+ automaticamente — viram `REVIEW_REQUIRED`/`SKIPPED_COMPLEX_EXPRESSION` no plano.
70
+
71
+ ### Migrar dados entre bancos de verdade
72
+
73
+ ```bash
74
+ dbbridge migrate-data --from sqlite --to postgres \
75
+ --source-dsn ./app.db --target-dsn "host=localhost dbname=app" --tables users leads
76
+ dbbridge validate --from sqlite --to postgres \
77
+ --source-dsn ./app.db --target-dsn "host=localhost dbname=app" --tables users leads
78
+ ```
79
+
80
+ Copia em lotes, nunca faz `DROP`/`TRUNCATE`, sempre acrescenta. `validate` compara contagens e
81
+ checksum (SHA-256) linha a linha quando a tabela é pequena o bastante.
82
+
83
+ ### Compatibility Mode (código antigo continua rodando)
84
+
85
+ ```python
86
+ from dbbridge import connect
87
+
88
+ db = connect(source_dialect="sqlite", target_dialect="postgres", dsn="host=localhost dbname=app")
89
+ db.execute("SELECT * FROM users WHERE id=?", (1,)) # vira %s por baixo, roda no Postgres de verdade
90
+ ```
91
+
92
+ Permite migrar a aplicação por partes: o código continua escrito na sintaxe de origem enquanto o
93
+ banco de verdade já é o destino.
94
+
95
+ ## Exemplos executáveis
96
+
97
+ ```bash
98
+ python examples/translate_schema.py # traduz um schema via API Python
99
+ python examples/scan_and_patch_project.py # scan -> patch -> apply -> rollback num mini-app
100
+ python examples/compat_mode.py # Compatibility Mode de ponta a ponta
101
+ DBBRIDGE_EXAMPLE_PG_DSN="host=localhost dbname=..." python examples/full_data_migration.py
102
+ ```
103
+
104
+ Todos os quatro rodam de verdade (não são pseudocódigo) — os três primeiros só precisam do SQLite
105
+ da stdlib; o último precisa de um PostgreSQL acessível via a variável de ambiente indicada.
106
+
107
+ ## Arquitetura
108
+
109
+ ```txt
110
+ dbbridge/
111
+ cli.py # scan|report|translate|patch|apply|migrate-data|validate|rollback|doctor|dialects
112
+ core/
113
+ ir.py # Schema, Table, Column, ForeignKey, UniqueConstraint, CheckConstraint, Index, View, Trigger, Enum
114
+ types.py # CanonicalType, Ambiguity
115
+ parser.py / renderer.py # contratos + registry (register_parser/get_parser/available_parsers, idem renderer)
116
+ planner.py # translate(sql, source, target) — o pipeline completo numa função
117
+ dialects/
118
+ sqlite.py, postgres.py # implementados
119
+ mysql.py, sqlserver.py # stubs registrados (NotImplementedError com a fase planejada)
120
+ scanner/scanner.py # AST — encontra SQL arriscado por par de dialetos
121
+ codemod/codemod.py # AST — reescreve só literais simples sem ambiguidade
122
+ migration/
123
+ schema_migration.py # traduz + aplica DDL (aditivo, nunca DROP/TRUNCATE)
124
+ data_migration.py # copia dados em lotes
125
+ validator.py # contagens + checksum
126
+ rollback.py # restaura código a partir de backup
127
+ compat/runtime.py # connect() — Compatibility Mode
128
+ reports/report.py # normaliza qualquer resultado pra texto/JSON
129
+ ```
130
+
131
+ Por que IR em vez de conversores pareados: com N dialetos, um conversor direto por par cresce O(N²)
132
+ e duplica a mesma lógica de tipos/constraints em cada combinação. Com um modelo canônico no meio,
133
+ cada dialeto novo precisa de só 1 parser + 1 renderer (O(N)) pra já converter de/para todos os
134
+ outros já implementados.
135
+
136
+ ## Segurança
137
+
138
+ - Toda alteração de código gera backup antes de escrever, e pode ser revertida via `dbbridge rollback`.
139
+ - Migração de schema/dados é sempre aditiva — nunca `DROP`/`TRUNCATE`/`DELETE`.
140
+ - Casos ambíguos (aspas duplas em literal, `INSERT OR REPLACE`, f-strings, expressões dinâmicas)
141
+ nunca são "adivinhados" — viram `REVIEW_REQUIRED` pra revisão manual.
142
+ - `apply_changes` verifica `ast.parse()` do arquivo resultante antes de considerar sucesso; se o
143
+ resultado tiver `SyntaxError`, reverte sozinho a partir do backup.
144
+
145
+ ## Fases
146
+
147
+ 1. **IR + SQLite ↔ PostgreSQL** — atual, completo: parser/renderer dos dois dialetos, scanner,
148
+ codemod, migração de schema/dados, validação por checksum, rollback, CLI, Compatibility Mode.
149
+ 2. MySQL (parser + renderer reais).
150
+ 3. Codemod genérico validado contra os 3 dialetos.
151
+ 4. Data migration validada nos 4 pares centrais (SQLite/Postgres/MySQL cruzados).
152
+ 5. SQL Server.
153
+ 6. **Fases futuras (levantadas, ainda não priorizadas):** shadow migration (rodar origem e destino
154
+ em paralelo comparando resultados antes do cutover final), query replay (reproduzir tráfego real
155
+ de produção contra o destino como teste de carga/compatibilidade), dashboard web, relatórios em
156
+ HTML/PDF (hoje só texto/JSON via `reports/report.py`), mecanismo de plugin formal pra dialetos
157
+ externos (hoje a extensão já é possível via `register_parser`/`register_renderer` — falta só
158
+ empacotar como plugin instalável separadamente, ex. entry points do Python), e suporte a bancos
159
+ não-relacionais como MongoDB (exigiria uma IR paralela pra documentos/coleções — ver
160
+ [docs/compatibility-matrix.md](docs/compatibility-matrix.md#roadmap-além-do-sql-relacional)).
161
+
162
+ Cada fase só avança depois da anterior validada — ver [docs/guide.md](docs/guide.md) para detalhes
163
+ de arquitetura e extensão pra novos dialetos, [docs/cli-reference.md](docs/cli-reference.md) pra
164
+ todos os comandos, [docs/compatibility-matrix.md](docs/compatibility-matrix.md) pro que já
165
+ funciona vs. planejado, [docs/migration-tutorial.md](docs/migration-tutorial.md) pro passo a passo
166
+ completo, e [docs/production-checklist.md](docs/production-checklist.md) antes de rodar contra
167
+ dados reais.
@@ -0,0 +1,22 @@
1
+ """DBBridge — plataforma genérica de conversão/análise/migração entre bancos de dados, baseada numa
2
+ Intermediate Representation (IR) canônica.
3
+
4
+ Banco origem -> Parser do dialeto origem -> IR -> Renderer do dialeto destino -> Banco destino
5
+
6
+ Não é uma evolução ad-hoc pareada (SQLite->Postgres, SQLite->MySQL, ...) — é uma arquitetura N-para-N
7
+ através de UM modelo comum. Ver core/ir.py pra IR, core/planner.py pro pipeline completo.
8
+
9
+ from dbbridge.core.planner import translate
10
+ result = translate(sql, source_dialect="sqlite", target_dialect="postgres")
11
+ print(result.sql)
12
+
13
+ Fase 1 (atual): IR completa + SQLite <-> PostgreSQL, scanner, codemod, CLI, rollback.
14
+ Dialetos MySQL/SQL Server: estrutura registrada, implementação nas Fases 2/5 (ver README.md).
15
+ """
16
+ from . import dialects # noqa: F401 — registra sqlite/postgres/mysql(stub)/sqlserver(stub)
17
+ from .compat.runtime import connect
18
+ from .core.planner import TranslationResult, translate
19
+
20
+ __version__ = "0.1.0"
21
+
22
+ __all__ = ["translate", "TranslationResult", "connect"]
@@ -0,0 +1,177 @@
1
+ """CLI: dbbridge scan|report|translate|patch|apply|migrate-data|validate|rollback|doctor."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from . import dialects # noqa: F401 — garante que sqlite/postgres/mysql/sqlserver estão registrados
9
+ from .codemod.codemod import apply_changes, plan_file
10
+ from .core.parser import available_parsers
11
+ from .core.planner import translate as translate_ir
12
+ from .core.renderer import available_renderers
13
+ from .migration.rollback import rollback_code_changes
14
+ from .reports.report import render_report
15
+ from .scanner.scanner import scan_project
16
+
17
+
18
+ def _add_common(p: argparse.ArgumentParser):
19
+ p.add_argument("--from", dest="source", required=True, choices=available_parsers(), help="Dialeto de origem")
20
+ p.add_argument("--to", dest="target", required=True, choices=available_renderers(), help="Dialeto de destino")
21
+
22
+
23
+ def main(argv=None):
24
+ parser = argparse.ArgumentParser(prog="dbbridge", description="Conversão/análise/migração entre bancos de dados via IR canônica")
25
+ sub = parser.add_subparsers(dest="command", required=True)
26
+
27
+ p_scan = sub.add_parser("scan", help="Varre um projeto Python procurando SQL incompatível entre os dois dialetos")
28
+ _add_common(p_scan)
29
+ p_scan.add_argument("path")
30
+ p_scan.add_argument("--json", action="store_true")
31
+
32
+ p_report = sub.add_parser("report", help="Gera relatório do último scan (texto ou JSON)")
33
+ p_report.add_argument("path")
34
+ p_report.add_argument("--from", dest="source", choices=available_parsers())
35
+ p_report.add_argument("--to", dest="target", choices=available_renderers())
36
+ p_report.add_argument("--json", action="store_true")
37
+ p_report.add_argument("--out", default=None)
38
+
39
+ p_translate = sub.add_parser("translate", help="Traduz um arquivo .sql (schema DDL) de um dialeto pro outro")
40
+ _add_common(p_translate)
41
+ p_translate.add_argument("sql_file")
42
+ p_translate.add_argument("--out", default=None)
43
+
44
+ p_patch = sub.add_parser("patch", help="Gera o plano de mudanças de código SEM escrever nada (dry-run)")
45
+ _add_common(p_patch)
46
+ p_patch.add_argument("path")
47
+
48
+ p_apply = sub.add_parser("apply", help="Aplica as mudanças de código seguras (com backup automático)")
49
+ _add_common(p_apply)
50
+ p_apply.add_argument("path")
51
+
52
+ p_migrate = sub.add_parser("migrate-data", help="Copia dados entre uma conexão origem e uma destino")
53
+ _add_common(p_migrate)
54
+ p_migrate.add_argument("--source-dsn", required=True)
55
+ p_migrate.add_argument("--target-dsn", required=True)
56
+ p_migrate.add_argument("--tables", nargs="+", required=True)
57
+
58
+ p_validate = sub.add_parser("validate", help="Valida contagens/checksums entre origem e destino após migrate-data")
59
+ _add_common(p_validate)
60
+ p_validate.add_argument("--source-dsn", required=True)
61
+ p_validate.add_argument("--target-dsn", required=True)
62
+ p_validate.add_argument("--tables", nargs="+", required=True)
63
+
64
+ p_rollback = sub.add_parser("rollback", help="Restaura arquivos de código a partir do backup mais recente")
65
+ p_rollback.add_argument("path")
66
+
67
+ sub.add_parser("doctor", help="Diagnóstico rápido: dialetos disponíveis, drivers instalados")
68
+ sub.add_parser("dialects", help="Lista os dialetos com parser/renderer disponíveis")
69
+
70
+ args = parser.parse_args(argv)
71
+
72
+ if args.command == "scan":
73
+ report = scan_project(Path(args.path), args.source, args.target)
74
+ print(render_report(f"DBBridge scan ({args.source} -> {args.target})", report, fmt="json" if args.json else "text"))
75
+
76
+ elif args.command == "report":
77
+ report = scan_project(Path(args.path), args.source or "sqlite", args.target or "postgres")
78
+ text = render_report(f"DBBridge report ({args.source} -> {args.target})", report, fmt="json" if args.json else "text")
79
+ if args.out:
80
+ Path(args.out).write_text(text, encoding="utf-8")
81
+ print(f"Relatório salvo em {args.out}")
82
+ else:
83
+ print(text)
84
+
85
+ elif args.command == "translate":
86
+ sql = Path(args.sql_file).read_text(encoding="utf-8")
87
+ result = translate_ir(sql, args.source, args.target)
88
+ if args.out:
89
+ Path(args.out).write_text(result.sql, encoding="utf-8")
90
+ print(f"SQL traduzido salvo em {args.out}")
91
+ else:
92
+ print(result.sql)
93
+ if result.review_required:
94
+ print("\n--- REVIEW_REQUIRED ---", file=sys.stderr)
95
+ for f in result.parse_findings:
96
+ print(f" [parse] {f.statement[:80]!r}: {f.reason}", file=sys.stderr)
97
+ # Nenhum parser atual produz Trigger/View no IR que gere RenderFinding (CREATE TRIGGER já
98
+ # vira ParseFinding antes de chegar aqui) — mantido para dialetos futuros que possam parsear
99
+ # esses casos com sucesso e só falhar na hora de renderizar pro destino.
100
+ for f in result.render_findings:
101
+ print(f" [render] {f.target}: {f.reason}", file=sys.stderr)
102
+
103
+ elif args.command == "patch":
104
+ total = 0
105
+ for path in sorted(Path(args.path).rglob("*.py")):
106
+ if any(part in ("venv", ".venv", "__pycache__") for part in path.parts):
107
+ continue
108
+ changes = plan_file(path, args.source, args.target)
109
+ if changes:
110
+ print(f"\n{path}:")
111
+ for c in changes:
112
+ print(f" [{c.status}] linha {c.line}: {c.reason}")
113
+ total += len(changes)
114
+ print(f"\nTotal planejado: {total} mudança(s). Nada foi escrito — rode 'apply' pra gravar.")
115
+
116
+ elif args.command == "apply":
117
+ applied_total = 0
118
+ touched = []
119
+ backup_dir = Path(args.path) / ".dbbridge_backups"
120
+ for path in sorted(Path(args.path).rglob("*.py")):
121
+ if any(part in ("venv", ".venv", "__pycache__", ".dbbridge_backups") for part in path.parts):
122
+ continue
123
+ changes = plan_file(path, args.source, args.target)
124
+ if not changes:
125
+ continue
126
+ ok = apply_changes(path, changes, backup_dir)
127
+ if ok:
128
+ n = sum(1 for c in changes if c.status == "APPLIED")
129
+ if n:
130
+ applied_total += n
131
+ touched.append(path)
132
+ print(f"{applied_total} mudança(s) aplicada(s) em {len(touched)} arquivo(s).")
133
+
134
+ elif args.command == "migrate-data":
135
+ from .compat.runtime import connect as db_connect
136
+ from .migration.data_migration import migrate_data
137
+
138
+ source_conn = db_connect(args.source, args.source, args.source_dsn)
139
+ target_conn = db_connect(args.source, args.target, args.target_dsn)
140
+ result = migrate_data(source_conn, target_conn, args.tables)
141
+ print(render_report(f"DBBridge migrate-data ({args.source} -> {args.target})", result))
142
+ if not result.all_counts_match:
143
+ sys.exit(1)
144
+
145
+ elif args.command == "validate":
146
+ from .compat.runtime import connect as db_connect
147
+ from .migration.validator import validate
148
+
149
+ source_conn = db_connect(args.source, args.source, args.source_dsn)
150
+ target_conn = db_connect(args.source, args.target, args.target_dsn)
151
+ report = validate(source_conn, target_conn, {t: None for t in args.tables})
152
+ print(render_report(f"DBBridge validate ({args.source} -> {args.target})", report))
153
+ if not report.all_ok:
154
+ sys.exit(1)
155
+
156
+ elif args.command == "rollback":
157
+ n = rollback_code_changes(Path(args.path))
158
+ print(f"{n} arquivo(s) restaurado(s) a partir do backup.")
159
+
160
+ elif args.command == "doctor":
161
+ print("Dialetos com parser:", available_parsers())
162
+ print("Dialetos com renderer:", available_renderers())
163
+ try:
164
+ import psycopg # noqa: F401
165
+ print("psycopg (Postgres driver): instalado")
166
+ except ImportError:
167
+ print("psycopg (Postgres driver): NÃO instalado")
168
+ import sqlite3 # noqa: F401
169
+ print("sqlite3 (stdlib): disponível")
170
+
171
+ elif args.command == "dialects":
172
+ print("Parsers disponíveis:", available_parsers())
173
+ print("Renderers disponíveis:", available_renderers())
174
+
175
+
176
+ if __name__ == "__main__":
177
+ main()
@@ -0,0 +1,3 @@
1
+ from .codemod import APPLIED, REVIEW_REQUIRED, SKIPPED_COMPLEX, CodemodChange, apply_changes, plan_file
2
+
3
+ __all__ = ["APPLIED", "REVIEW_REQUIRED", "SKIPPED_COMPLEX", "CodemodChange", "apply_changes", "plan_file"]