ora2pg-gap-report 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 (40) hide show
  1. ora2pg_gap_report-0.1.0/LICENSE +21 -0
  2. ora2pg_gap_report-0.1.0/PKG-INFO +261 -0
  3. ora2pg_gap_report-0.1.0/README.md +237 -0
  4. ora2pg_gap_report-0.1.0/ora2pg_gap_report/__init__.py +0 -0
  5. ora2pg_gap_report-0.1.0/ora2pg_gap_report/cli.py +188 -0
  6. ora2pg_gap_report-0.1.0/ora2pg_gap_report/detectors/__init__.py +0 -0
  7. ora2pg_gap_report-0.1.0/ora2pg_gap_report/detectors/autonomous_tx.py +94 -0
  8. ora2pg_gap_report-0.1.0/ora2pg_gap_report/detectors/compound_triggers.py +68 -0
  9. ora2pg_gap_report-0.1.0/ora2pg_gap_report/detectors/connect_by.py +110 -0
  10. ora2pg_gap_report-0.1.0/ora2pg_gap_report/detectors/dbms_utl_calls.py +67 -0
  11. ora2pg_gap_report-0.1.0/ora2pg_gap_report/effort_estimator.py +54 -0
  12. ora2pg_gap_report-0.1.0/ora2pg_gap_report/models.py +12 -0
  13. ora2pg_gap_report-0.1.0/ora2pg_gap_report/ora2pg_wrapper.py +140 -0
  14. ora2pg_gap_report-0.1.0/ora2pg_gap_report/oracle_connector.py +166 -0
  15. ora2pg_gap_report-0.1.0/ora2pg_gap_report/oracle_export.py +82 -0
  16. ora2pg_gap_report-0.1.0/ora2pg_gap_report/plsql_lex.py +213 -0
  17. ora2pg_gap_report-0.1.0/ora2pg_gap_report/report_generator.py +26 -0
  18. ora2pg_gap_report-0.1.0/ora2pg_gap_report/terminal_report.py +106 -0
  19. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/PKG-INFO +261 -0
  20. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/SOURCES.txt +38 -0
  21. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/dependency_links.txt +1 -0
  22. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/entry_points.txt +3 -0
  23. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/requires.txt +7 -0
  24. ora2pg_gap_report-0.1.0/ora2pg_gap_report.egg-info/top_level.txt +1 -0
  25. ora2pg_gap_report-0.1.0/pyproject.toml +42 -0
  26. ora2pg_gap_report-0.1.0/setup.cfg +4 -0
  27. ora2pg_gap_report-0.1.0/tests/test_autonomous_tx.py +78 -0
  28. ora2pg_gap_report-0.1.0/tests/test_autonomous_tx_edge_cases.py +130 -0
  29. ora2pg_gap_report-0.1.0/tests/test_cli.py +293 -0
  30. ora2pg_gap_report-0.1.0/tests/test_compound_triggers.py +64 -0
  31. ora2pg_gap_report-0.1.0/tests/test_connect_by.py +105 -0
  32. ora2pg_gap_report-0.1.0/tests/test_dbms_utl_calls.py +122 -0
  33. ora2pg_gap_report-0.1.0/tests/test_effort_estimator.py +41 -0
  34. ora2pg_gap_report-0.1.0/tests/test_ora2pg_wrapper.py +93 -0
  35. ora2pg_gap_report-0.1.0/tests/test_oracle_connector.py +195 -0
  36. ora2pg_gap_report-0.1.0/tests/test_oracle_export.py +127 -0
  37. ora2pg_gap_report-0.1.0/tests/test_plsql_lex.py +59 -0
  38. ora2pg_gap_report-0.1.0/tests/test_report_generator.py +30 -0
  39. ora2pg_gap_report-0.1.0/tests/test_terminal_report.py +94 -0
  40. ora2pg_gap_report-0.1.0/tests/test_verify_against_live_oracle_helpers.py +128 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ora2pg-gap-report contributors
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,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: ora2pg-gap-report
3
+ Version: 0.1.0
4
+ Summary: Сканер Oracle-схемы, дополняющий ora2pg: находит объекты, которые он не перенесёт или перенесёт некорректно, до начала миграции на Postgres Pro.
5
+ Author: Lunch418
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/Lunch418/ora2pg-gap-report
8
+ Keywords: oracle,postgresql,ora2pg,migration,plsql
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Database
14
+ Classifier: Environment :: Console
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: rich>=13
19
+ Provides-Extra: oracle
20
+ Requires-Dist: oracledb>=2.0; extra == "oracle"
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # ora2pg-gap-report
26
+
27
+ [![tests](https://github.com/Lunch418/ora2pg-gap-report/actions/workflows/tests.yml/badge.svg)](https://github.com/Lunch418/ora2pg-gap-report/actions/workflows/tests.yml)
28
+
29
+ Инструмент для оценки миграции Oracle → PostgreSQL Pro (Standard/Certified) **до** её начала.
30
+
31
+ ## Проблема
32
+
33
+ При миграции с Oracle на Postgres Pro в сегменте Standard/Certified (то есть без
34
+ лицензии на Postgres Pro Enterprise и без проприетарной утилиты `ora2pgpro`)
35
+ единственный доступный автоматический конвертер — открытый
36
+ [`ora2pg`](https://github.com/darold/ora2pg). По независимым оценкам он закрывает
37
+ в среднем ~80% задачи перевода PL/SQL → PL/pgSQL. Оставшиеся ~20% (пакеты,
38
+ автономные транзакции, `CONNECT BY`, вызовы `DBMS_*`/`UTL_*`, составные триггеры)
39
+ сейчас разбираются вручную и, как правило, обнаруживаются постфактум — когда
40
+ что-то уже сломалось в проде.
41
+
42
+ ## Что делает этот инструмент
43
+
44
+ Сканирует схему Oracle **до** миграции и говорит: какие конкретно объекты
45
+ `ora2pg` пропустит без предупреждения, недооценит по трудоёмкости или
46
+ сконвертирует потенциально некорректно — и почему. Не замена `ora2pg`, а
47
+ надстройка над ним: список того, что он реально не переносит, проверен
48
+ эмпирически на открытом PL/SQL-коде (`docs/research/step0-show-report-baseline.md`),
49
+ а не взят на веру.
50
+
51
+ ## Детекторы
52
+
53
+ | Детектор | Что ловит |
54
+ |---|---|
55
+ | `autonomous_tx` | `PRAGMA AUTONOMOUS_TRANSACTION` внутри `PACKAGE BODY` — ora2pg конвертирует через dblink, но занижает/теряет стоимость в `SHOW_REPORT`/`--estimate_cost` |
56
+ | `compound_triggers` | `COMPOUND TRIGGER` — файловый парсер ora2pg тихо возвращает 0 триггеров, без единой ошибки |
57
+ | `dbms_utl_calls` | Классификатор конкретных вызовов `DBMS_*`/`UTL_*` — что из них ora2pg реально конвертирует, а что остаётся как есть |
58
+ | `connect_by` | Линтинг сгенерированного ora2pg `WITH RECURSIVE` на баг с `LEVEL`. Включается флагом `--check-connect-by` и, в отличие от остальных, требует установленный `ora2pg` |
59
+
60
+ Плюс `ora2pg_wrapper.py` — запуск `ora2pg` по типам объектов на выгруженном
61
+ DDL с парсингом `--estimate_cost`, и `oracle_connector.py`/`oracle_export.py`
62
+ — живая выгрузка `PACKAGE BODY`/`TRIGGER` прямо из Oracle-схемы через
63
+ `DBMS_METADATA.GET_DDL`.
64
+
65
+ ## Установка и использование
66
+
67
+ ```sh
68
+ pip install ora2pg-gap-report # (или: pip install . из клона репозитория)
69
+ ```
70
+
71
+ Сама детекторная библиотека (`detectors/`, `models.py`,
72
+ `report_generator.py`) — чистый Python без единой внешней зависимости, её
73
+ можно импортировать отдельно (например, в своих скриптах) вообще без
74
+ установки чего-либо ещё. У CLI есть одна обязательная зависимость —
75
+ [`rich`](https://github.com/Textualize/rich), только ради приятного
76
+ терминального вывода; ставится сама через `pip install`.
77
+
78
+ Сразу после установки доступна команда:
79
+
80
+ ```sh
81
+ ora2pg-gap-report path/to/schema_dump.pkb another_file.sql
82
+ ```
83
+
84
+ В интерактивном терминале по умолчанию — цветной отчёт: сводная панель
85
+ (сколько найдено, разбивка по severity, грубая оценка часов), компактная
86
+ таблица находок и пояснения под каждым сработавшим детектором. Для
87
+ скриптов/redirect — `--format markdown` или `--format json` (тоже
88
+ работают как формат по умолчанию, если stdout не терминал):
89
+
90
+ ```sh
91
+ ora2pg-gap-report path/to/schema_dump.pkb --format json --output report.json
92
+ ora2pg-gap-report path/to/schema_dump.pkb --format markdown > report.md
93
+
94
+ # Опционально: линтинг сгенерированного ora2pg кода для CONNECT BY.
95
+ # Требует установленный ora2pg (см. https://github.com/darold/ora2pg) —
96
+ # единственная внешняя (не-Python) зависимость во всём проекте, и только
97
+ # для этой конкретной проверки.
98
+ ora2pg-gap-report path/to/schema_dump.pkb --check-connect-by
99
+ ```
100
+
101
+ Файлы с DDL можно передавать как есть — один файл может содержать сразу
102
+ несколько пакетов/триггеров, детекторы разбирают границы объектов сами.
103
+
104
+ Пример реального вывода на открытом пакете —
105
+ [`docs/examples/logger-autonomous_tx-report.md`](docs/examples/logger-autonomous_tx-report.md).
106
+
107
+ Оценка трудозатрат в отчёте — грубая эвристика по severity (диапазон
108
+ часов, не точечное число). Это ориентир для планирования, а не
109
+ откалиброванная на реальных миграциях оценка — не стоит выдавать её
110
+ клиенту как обязательство.
111
+
112
+ ## Выгрузка DDL прямо из Oracle (опционально)
113
+
114
+ Если под рукой живая Oracle-схема, а не уже готовый DDL-дамп:
115
+
116
+ ```sh
117
+ pip install "ora2pg-gap-report[oracle]" # добавляет python-oracledb, thin-режим, без Instant Client
118
+
119
+ ora2pg-gap-export --dsn host:1521/ORCLPDB1 --user hr --output-dir dumps/
120
+ # пароль — из переменной окружения ORACLE_PASSWORD, либо будет запрошен интерактивно
121
+
122
+ ora2pg-gap-report dumps/*.sql
123
+ ```
124
+
125
+ `ora2pg-gap-export` — отдельная команда, не флаг у `ora2pg-gap-report`,
126
+ специально: выгрузка требует сетевого доступа к Oracle, анализ — никогда.
127
+ В закрытом контуре это часто две разные машины (jump host с доступом к БД
128
+ и изолированная рабочая станция для анализа) — единственное, что должно
129
+ пересечь границу между ними, это уже выгруженные `.sql` файлы.
130
+
131
+ ## Установка без интернета (закрытый контур)
132
+
133
+ Целевая аудитория этого инструмента — как раз изолированные сети без
134
+ выхода наружу, поэтому `pip install` там обычно не вариант. Решение —
135
+ собрать самодостаточный архив на машине с интернетом, перенести его
136
+ любым доступным способом (`scp`/`sftp`/через jump host/на флешке) и
137
+ поставить на целевой машине уже совсем без сети:
138
+
139
+ ```sh
140
+ # На машине с интернетом, из клона репозитория:
141
+ python scripts/build_offline_bundle.py --oracle # --oracle опционально, --dev для pytest
142
+ # → ora2pg-gap-report-offline.tar.gz (пакет + rich + всё транзитивно,
143
+ # включая oracledb и его зависимости, если указан --oracle)
144
+
145
+ scp ora2pg-gap-report-offline.tar.gz user@jump-host:/tmp/
146
+ # ...дальше как получится добраться до целевой машины в контуре —
147
+ # sftp, ещё один jump host, физический перенос
148
+
149
+ # На целевой машине БЕЗ интернета:
150
+ tar xzf ora2pg-gap-report-offline.tar.gz
151
+ cd ora2pg-gap-report-offline
152
+ ./install.sh oracle # или: python3 install.py oracle
153
+ ```
154
+
155
+ `install.sh`/`install.py` вызывают `pip install --no-index --find-links=./wheels
156
+ ...` — pip ставит целиком из положенных рядом `.whl`-файлов, ни одного
157
+ обращения в сеть.
158
+
159
+ `rich` и его зависимости (`markdown-it-py`, `pygments`, `mdurl`) —
160
+ чистый Python, один набор wheel-файлов работает везде. `oracledb`
161
+ (только при `--oracle`) собирает платформозависимые wheel — если
162
+ машина сборки отличается от целевой по ОС/архитектуре/версии Python,
163
+ передайте `--platform`/`--python-version`/`--abi` в
164
+ `build_offline_bundle.py` (см. `--help`), чтобы скачать wheel именно
165
+ под целевую платформу, а не под ту, где запущен скрипт.
166
+
167
+ ## Архитектура
168
+
169
+ `ora2pg SHOW_REPORT` целиком не имеет офлайн-режима — он требует живого
170
+ подключения к Oracle (`ORACLE_DSN`). Офлайн от DDL-дампа работает только
171
+ анализ *отдельных типов объектов* (`-t PACKAGE`, `-t TRIGGER`, `-t FUNCTION`,
172
+ …) — именно так работает `ora2pg_wrapper.py`, а не через `SHOW_REPORT`. Это
173
+ принципиально для целевой аудитории — закрытые контуры, air-gapped среды,
174
+ госсектор.
175
+
176
+ Три из четырёх детекторов (`autonomous_tx`, `compound_triggers`,
177
+ `dbms_utl_calls`) анализируют Oracle-исходник напрямую и не требуют
178
+ установленного `ora2pg` — чистый Python, без внешних зависимостей.
179
+ Четвёртый (`connect_by`) устроен иначе: он линтит *сгенерированный*
180
+ ora2pg-код, а не исходник (ora2pg сам неплохо считает CONNECT BY —
181
+ ценность не в обнаружении, а в проверке качества конвертации), поэтому
182
+ ему нужен реальный `ora2pg` и он подключается только через
183
+ `--check-connect-by`.
184
+
185
+ ```
186
+ pyproject.toml # единственный источник правды по зависимостям/точкам входа
187
+ ora2pg_gap_report/
188
+ ├── models.py # Finding — общая структура находки для всех детекторов
189
+ ├── plsql_lex.py # общая инфраструктура: маскирование строк/комментариев
190
+ │ # (включая q-quote), сопоставление блоков BEGIN/CASE/IF/LOOP...END,
191
+ │ # разбор идентификаторов — используется всеми детекторами
192
+ ├── oracle_connector.py # живая выгрузка PACKAGE BODY/TRIGGER через DBMS_METADATA.GET_DDL
193
+ ├── oracle_export.py # консольная команда ora2pg-gap-export
194
+ ├── detectors/
195
+ │ ├── autonomous_tx.py # PRAGMA AUTONOMOUS_TRANSACTION в PACKAGE BODY
196
+ │ ├── compound_triggers.py # COMPOUND TRIGGER — тихий провал парсинга у ora2pg
197
+ │ ├── dbms_utl_calls.py # классификатор конкретных DBMS_*/UTL_* функций
198
+ │ └── connect_by.py # линтинг сгенерированного WITH RECURSIVE (нужен ora2pg)
199
+ ├── ora2pg_wrapper.py # запуск ora2pg по типам объектов, парсинг --estimate_cost
200
+ ├── cli.py # консольная команда ora2pg-gap-report
201
+ ├── effort_estimator.py # грубая эвристика по severity, диапазон часов
202
+ ├── report_generator.py # JSON + Markdown (машиночитаемые форматы)
203
+ └── terminal_report.py # цветной вывод через rich (единственная зависимость;
204
+ # библиотеки-детекторов не касается, только CLI)
205
+ tests/
206
+ ├── fixtures/ # реальные захваченные прогоны ora2pg — тесты парсера не требуют
207
+ │ # установленного ora2pg, кроме нескольких live-тестов
208
+ │ # (пропускаются автоматически, если ora2pg не найден в PATH)
209
+ docs/research/ # эмпирическая проверка предпосылок, реальные PL/SQL примеры
210
+ docs/examples/ # примеры вывода детекторов на реальных данных
211
+ scripts/
212
+ ├── build_offline_bundle.py # сборка автономного архива для установки без интернета
213
+ ├── oracle-test-compose.yml # Oracle Free 23ai в Docker для живой проверки
214
+ ├── setup_oracle_test_schema.sql
215
+ └── verify_against_live_oracle.py
216
+ .github/workflows/tests.yml # CI: pytest на 3.10-3.13 + сборка и smoke-test пакета
217
+ ```
218
+
219
+ ## Тестирование
220
+
221
+ ```sh
222
+ pip install -e ".[dev]" # editable-режим + pytest
223
+ pytest
224
+ ```
225
+
226
+ Детекторы и лексер проверены на реальном открытом PL/SQL-коде (Logger,
227
+ alexandria-plsql-utils, составной триггер из Apress), а не только на
228
+ синтетических примерах.
229
+
230
+ ### Проверка на живой Oracle
231
+
232
+ Юнит-тесты `oracle_connector.py` идут на fake-соединении
233
+ (`tests/fakes/fake_oracle.py`) — быстро, детерминированно, не требует
234
+ Oracle. Живой путь ("подключился к настоящей Oracle → выгрузил через
235
+ `DBMS_METADATA.GET_DDL` → проанализировал") ими не покрыт — для него
236
+ нужна настоящая база:
237
+
238
+ ```sh
239
+ docker compose -f scripts/oracle-test-compose.yml up -d
240
+ docker compose -f scripts/oracle-test-compose.yml logs -f # ждать "DATABASE IS READY TO USE"
241
+
242
+ pip install -e ".[oracle]"
243
+ ORACLE_DSN=localhost:1521/FREEPDB1 ORACLE_USER=testuser ORACLE_PASSWORD=testpass1 \
244
+ python scripts/verify_against_live_oracle.py
245
+ ```
246
+
247
+ Скрипт создаёт пару служебных таблиц (`scripts/setup_oracle_test_schema.sql`
248
+ — триггерам, в отличие от пакетов, нужна реально существующая целевая
249
+ таблица), заливает реальные фикстуры из `docs/research/samples/` как
250
+ есть, выгружает их обратно живым `DBMS_METADATA.GET_DDL`, прогоняет
251
+ детекторы и сверяет счётчики с уже независимо проверенными на этих же
252
+ файлах как на тексте (`tests/`). Если в `PATH` есть `ora2pg` — заодно
253
+ прогоняет `SHOW_REPORT` против живого подключения.
254
+
255
+ `gvenzl/oracle-free:23-slim` — контейнерный пакет официального
256
+ бесплатного дистрибутива Oracle (тот же движок), просто с более удобной
257
+ для CI/тестов оберткой, чем прямой образ Oracle Container Registry.
258
+
259
+ ## Лицензия
260
+
261
+ MIT, см. [LICENSE](LICENSE).
@@ -0,0 +1,237 @@
1
+ # ora2pg-gap-report
2
+
3
+ [![tests](https://github.com/Lunch418/ora2pg-gap-report/actions/workflows/tests.yml/badge.svg)](https://github.com/Lunch418/ora2pg-gap-report/actions/workflows/tests.yml)
4
+
5
+ Инструмент для оценки миграции Oracle → PostgreSQL Pro (Standard/Certified) **до** её начала.
6
+
7
+ ## Проблема
8
+
9
+ При миграции с Oracle на Postgres Pro в сегменте Standard/Certified (то есть без
10
+ лицензии на Postgres Pro Enterprise и без проприетарной утилиты `ora2pgpro`)
11
+ единственный доступный автоматический конвертер — открытый
12
+ [`ora2pg`](https://github.com/darold/ora2pg). По независимым оценкам он закрывает
13
+ в среднем ~80% задачи перевода PL/SQL → PL/pgSQL. Оставшиеся ~20% (пакеты,
14
+ автономные транзакции, `CONNECT BY`, вызовы `DBMS_*`/`UTL_*`, составные триггеры)
15
+ сейчас разбираются вручную и, как правило, обнаруживаются постфактум — когда
16
+ что-то уже сломалось в проде.
17
+
18
+ ## Что делает этот инструмент
19
+
20
+ Сканирует схему Oracle **до** миграции и говорит: какие конкретно объекты
21
+ `ora2pg` пропустит без предупреждения, недооценит по трудоёмкости или
22
+ сконвертирует потенциально некорректно — и почему. Не замена `ora2pg`, а
23
+ надстройка над ним: список того, что он реально не переносит, проверен
24
+ эмпирически на открытом PL/SQL-коде (`docs/research/step0-show-report-baseline.md`),
25
+ а не взят на веру.
26
+
27
+ ## Детекторы
28
+
29
+ | Детектор | Что ловит |
30
+ |---|---|
31
+ | `autonomous_tx` | `PRAGMA AUTONOMOUS_TRANSACTION` внутри `PACKAGE BODY` — ora2pg конвертирует через dblink, но занижает/теряет стоимость в `SHOW_REPORT`/`--estimate_cost` |
32
+ | `compound_triggers` | `COMPOUND TRIGGER` — файловый парсер ora2pg тихо возвращает 0 триггеров, без единой ошибки |
33
+ | `dbms_utl_calls` | Классификатор конкретных вызовов `DBMS_*`/`UTL_*` — что из них ora2pg реально конвертирует, а что остаётся как есть |
34
+ | `connect_by` | Линтинг сгенерированного ora2pg `WITH RECURSIVE` на баг с `LEVEL`. Включается флагом `--check-connect-by` и, в отличие от остальных, требует установленный `ora2pg` |
35
+
36
+ Плюс `ora2pg_wrapper.py` — запуск `ora2pg` по типам объектов на выгруженном
37
+ DDL с парсингом `--estimate_cost`, и `oracle_connector.py`/`oracle_export.py`
38
+ — живая выгрузка `PACKAGE BODY`/`TRIGGER` прямо из Oracle-схемы через
39
+ `DBMS_METADATA.GET_DDL`.
40
+
41
+ ## Установка и использование
42
+
43
+ ```sh
44
+ pip install ora2pg-gap-report # (или: pip install . из клона репозитория)
45
+ ```
46
+
47
+ Сама детекторная библиотека (`detectors/`, `models.py`,
48
+ `report_generator.py`) — чистый Python без единой внешней зависимости, её
49
+ можно импортировать отдельно (например, в своих скриптах) вообще без
50
+ установки чего-либо ещё. У CLI есть одна обязательная зависимость —
51
+ [`rich`](https://github.com/Textualize/rich), только ради приятного
52
+ терминального вывода; ставится сама через `pip install`.
53
+
54
+ Сразу после установки доступна команда:
55
+
56
+ ```sh
57
+ ora2pg-gap-report path/to/schema_dump.pkb another_file.sql
58
+ ```
59
+
60
+ В интерактивном терминале по умолчанию — цветной отчёт: сводная панель
61
+ (сколько найдено, разбивка по severity, грубая оценка часов), компактная
62
+ таблица находок и пояснения под каждым сработавшим детектором. Для
63
+ скриптов/redirect — `--format markdown` или `--format json` (тоже
64
+ работают как формат по умолчанию, если stdout не терминал):
65
+
66
+ ```sh
67
+ ora2pg-gap-report path/to/schema_dump.pkb --format json --output report.json
68
+ ora2pg-gap-report path/to/schema_dump.pkb --format markdown > report.md
69
+
70
+ # Опционально: линтинг сгенерированного ora2pg кода для CONNECT BY.
71
+ # Требует установленный ora2pg (см. https://github.com/darold/ora2pg) —
72
+ # единственная внешняя (не-Python) зависимость во всём проекте, и только
73
+ # для этой конкретной проверки.
74
+ ora2pg-gap-report path/to/schema_dump.pkb --check-connect-by
75
+ ```
76
+
77
+ Файлы с DDL можно передавать как есть — один файл может содержать сразу
78
+ несколько пакетов/триггеров, детекторы разбирают границы объектов сами.
79
+
80
+ Пример реального вывода на открытом пакете —
81
+ [`docs/examples/logger-autonomous_tx-report.md`](docs/examples/logger-autonomous_tx-report.md).
82
+
83
+ Оценка трудозатрат в отчёте — грубая эвристика по severity (диапазон
84
+ часов, не точечное число). Это ориентир для планирования, а не
85
+ откалиброванная на реальных миграциях оценка — не стоит выдавать её
86
+ клиенту как обязательство.
87
+
88
+ ## Выгрузка DDL прямо из Oracle (опционально)
89
+
90
+ Если под рукой живая Oracle-схема, а не уже готовый DDL-дамп:
91
+
92
+ ```sh
93
+ pip install "ora2pg-gap-report[oracle]" # добавляет python-oracledb, thin-режим, без Instant Client
94
+
95
+ ora2pg-gap-export --dsn host:1521/ORCLPDB1 --user hr --output-dir dumps/
96
+ # пароль — из переменной окружения ORACLE_PASSWORD, либо будет запрошен интерактивно
97
+
98
+ ora2pg-gap-report dumps/*.sql
99
+ ```
100
+
101
+ `ora2pg-gap-export` — отдельная команда, не флаг у `ora2pg-gap-report`,
102
+ специально: выгрузка требует сетевого доступа к Oracle, анализ — никогда.
103
+ В закрытом контуре это часто две разные машины (jump host с доступом к БД
104
+ и изолированная рабочая станция для анализа) — единственное, что должно
105
+ пересечь границу между ними, это уже выгруженные `.sql` файлы.
106
+
107
+ ## Установка без интернета (закрытый контур)
108
+
109
+ Целевая аудитория этого инструмента — как раз изолированные сети без
110
+ выхода наружу, поэтому `pip install` там обычно не вариант. Решение —
111
+ собрать самодостаточный архив на машине с интернетом, перенести его
112
+ любым доступным способом (`scp`/`sftp`/через jump host/на флешке) и
113
+ поставить на целевой машине уже совсем без сети:
114
+
115
+ ```sh
116
+ # На машине с интернетом, из клона репозитория:
117
+ python scripts/build_offline_bundle.py --oracle # --oracle опционально, --dev для pytest
118
+ # → ora2pg-gap-report-offline.tar.gz (пакет + rich + всё транзитивно,
119
+ # включая oracledb и его зависимости, если указан --oracle)
120
+
121
+ scp ora2pg-gap-report-offline.tar.gz user@jump-host:/tmp/
122
+ # ...дальше как получится добраться до целевой машины в контуре —
123
+ # sftp, ещё один jump host, физический перенос
124
+
125
+ # На целевой машине БЕЗ интернета:
126
+ tar xzf ora2pg-gap-report-offline.tar.gz
127
+ cd ora2pg-gap-report-offline
128
+ ./install.sh oracle # или: python3 install.py oracle
129
+ ```
130
+
131
+ `install.sh`/`install.py` вызывают `pip install --no-index --find-links=./wheels
132
+ ...` — pip ставит целиком из положенных рядом `.whl`-файлов, ни одного
133
+ обращения в сеть.
134
+
135
+ `rich` и его зависимости (`markdown-it-py`, `pygments`, `mdurl`) —
136
+ чистый Python, один набор wheel-файлов работает везде. `oracledb`
137
+ (только при `--oracle`) собирает платформозависимые wheel — если
138
+ машина сборки отличается от целевой по ОС/архитектуре/версии Python,
139
+ передайте `--platform`/`--python-version`/`--abi` в
140
+ `build_offline_bundle.py` (см. `--help`), чтобы скачать wheel именно
141
+ под целевую платформу, а не под ту, где запущен скрипт.
142
+
143
+ ## Архитектура
144
+
145
+ `ora2pg SHOW_REPORT` целиком не имеет офлайн-режима — он требует живого
146
+ подключения к Oracle (`ORACLE_DSN`). Офлайн от DDL-дампа работает только
147
+ анализ *отдельных типов объектов* (`-t PACKAGE`, `-t TRIGGER`, `-t FUNCTION`,
148
+ …) — именно так работает `ora2pg_wrapper.py`, а не через `SHOW_REPORT`. Это
149
+ принципиально для целевой аудитории — закрытые контуры, air-gapped среды,
150
+ госсектор.
151
+
152
+ Три из четырёх детекторов (`autonomous_tx`, `compound_triggers`,
153
+ `dbms_utl_calls`) анализируют Oracle-исходник напрямую и не требуют
154
+ установленного `ora2pg` — чистый Python, без внешних зависимостей.
155
+ Четвёртый (`connect_by`) устроен иначе: он линтит *сгенерированный*
156
+ ora2pg-код, а не исходник (ora2pg сам неплохо считает CONNECT BY —
157
+ ценность не в обнаружении, а в проверке качества конвертации), поэтому
158
+ ему нужен реальный `ora2pg` и он подключается только через
159
+ `--check-connect-by`.
160
+
161
+ ```
162
+ pyproject.toml # единственный источник правды по зависимостям/точкам входа
163
+ ora2pg_gap_report/
164
+ ├── models.py # Finding — общая структура находки для всех детекторов
165
+ ├── plsql_lex.py # общая инфраструктура: маскирование строк/комментариев
166
+ │ # (включая q-quote), сопоставление блоков BEGIN/CASE/IF/LOOP...END,
167
+ │ # разбор идентификаторов — используется всеми детекторами
168
+ ├── oracle_connector.py # живая выгрузка PACKAGE BODY/TRIGGER через DBMS_METADATA.GET_DDL
169
+ ├── oracle_export.py # консольная команда ora2pg-gap-export
170
+ ├── detectors/
171
+ │ ├── autonomous_tx.py # PRAGMA AUTONOMOUS_TRANSACTION в PACKAGE BODY
172
+ │ ├── compound_triggers.py # COMPOUND TRIGGER — тихий провал парсинга у ora2pg
173
+ │ ├── dbms_utl_calls.py # классификатор конкретных DBMS_*/UTL_* функций
174
+ │ └── connect_by.py # линтинг сгенерированного WITH RECURSIVE (нужен ora2pg)
175
+ ├── ora2pg_wrapper.py # запуск ora2pg по типам объектов, парсинг --estimate_cost
176
+ ├── cli.py # консольная команда ora2pg-gap-report
177
+ ├── effort_estimator.py # грубая эвристика по severity, диапазон часов
178
+ ├── report_generator.py # JSON + Markdown (машиночитаемые форматы)
179
+ └── terminal_report.py # цветной вывод через rich (единственная зависимость;
180
+ # библиотеки-детекторов не касается, только CLI)
181
+ tests/
182
+ ├── fixtures/ # реальные захваченные прогоны ora2pg — тесты парсера не требуют
183
+ │ # установленного ora2pg, кроме нескольких live-тестов
184
+ │ # (пропускаются автоматически, если ora2pg не найден в PATH)
185
+ docs/research/ # эмпирическая проверка предпосылок, реальные PL/SQL примеры
186
+ docs/examples/ # примеры вывода детекторов на реальных данных
187
+ scripts/
188
+ ├── build_offline_bundle.py # сборка автономного архива для установки без интернета
189
+ ├── oracle-test-compose.yml # Oracle Free 23ai в Docker для живой проверки
190
+ ├── setup_oracle_test_schema.sql
191
+ └── verify_against_live_oracle.py
192
+ .github/workflows/tests.yml # CI: pytest на 3.10-3.13 + сборка и smoke-test пакета
193
+ ```
194
+
195
+ ## Тестирование
196
+
197
+ ```sh
198
+ pip install -e ".[dev]" # editable-режим + pytest
199
+ pytest
200
+ ```
201
+
202
+ Детекторы и лексер проверены на реальном открытом PL/SQL-коде (Logger,
203
+ alexandria-plsql-utils, составной триггер из Apress), а не только на
204
+ синтетических примерах.
205
+
206
+ ### Проверка на живой Oracle
207
+
208
+ Юнит-тесты `oracle_connector.py` идут на fake-соединении
209
+ (`tests/fakes/fake_oracle.py`) — быстро, детерминированно, не требует
210
+ Oracle. Живой путь ("подключился к настоящей Oracle → выгрузил через
211
+ `DBMS_METADATA.GET_DDL` → проанализировал") ими не покрыт — для него
212
+ нужна настоящая база:
213
+
214
+ ```sh
215
+ docker compose -f scripts/oracle-test-compose.yml up -d
216
+ docker compose -f scripts/oracle-test-compose.yml logs -f # ждать "DATABASE IS READY TO USE"
217
+
218
+ pip install -e ".[oracle]"
219
+ ORACLE_DSN=localhost:1521/FREEPDB1 ORACLE_USER=testuser ORACLE_PASSWORD=testpass1 \
220
+ python scripts/verify_against_live_oracle.py
221
+ ```
222
+
223
+ Скрипт создаёт пару служебных таблиц (`scripts/setup_oracle_test_schema.sql`
224
+ — триггерам, в отличие от пакетов, нужна реально существующая целевая
225
+ таблица), заливает реальные фикстуры из `docs/research/samples/` как
226
+ есть, выгружает их обратно живым `DBMS_METADATA.GET_DDL`, прогоняет
227
+ детекторы и сверяет счётчики с уже независимо проверенными на этих же
228
+ файлах как на тексте (`tests/`). Если в `PATH` есть `ora2pg` — заодно
229
+ прогоняет `SHOW_REPORT` против живого подключения.
230
+
231
+ `gvenzl/oracle-free:23-slim` — контейнерный пакет официального
232
+ бесплатного дистрибутива Oracle (тот же движок), просто с более удобной
233
+ для CI/тестов оберткой, чем прямой образ Oracle Container Registry.
234
+
235
+ ## Лицензия
236
+
237
+ MIT, см. [LICENSE](LICENSE).
File without changes