DoDiODev opened a new pull request, #9015:
URL: https://github.com/apache/devlake/pull/9015

   > Starting point is the Jira Sprint Report bug below. The regression guard 
added
   > for it turned out to catch the same class of bug in three more plugins
   > (taiga, teambition, testmo), which are fixed here as well — see
   > [§3](#3-additional-schema-drifts-found-by-the-new-cross-plugin-guard).
   
   ## Problem
   
   Collecting Jira data fails in the `extractSprintReport` subtask with:
   
   ```
   subtask extractSprintReport ended unexpectedly
     error getting batch from result (500)
     Error 1054 (42S22): Unknown column '_raw_data_table' in 'where clause'
   ```
   
   ### Root cause
   
   The Sprint Report feature (PR #8967 / #9010) added the table
   `_tool_jira_sprint_reports`. Its migration
   (`20260722_add_sprint_report_table.go`) creates the table from a struct that
   does **not** embed `common.NoPKModel`:
   
   ```go
   type jiraSprintReport20260722 struct {
       ConnectionId uint64 `gorm:"primaryKey"`
       // ... no common.NoPKModel ...
   }
   ```
   
   The runtime model `models.JiraSprintReport` **does** embed `common.NoPKModel`
   (→ `common.RawDataOrigin`), so it expects the columns `_raw_data_params`,
   `_raw_data_table`, `_raw_data_id`, `_raw_data_remark` (plus `created_at`,
   `updated_at`).
   
   During extraction, `api.NewApiExtractor` deletes outdated rows via
   `WHERE _raw_data_table = ? AND _raw_data_params = ?`
   (`helpers/pluginhelper/api/batch_save_divider.go`). Because the migration 
never
   created those columns, MySQL rejects the query with error 1054.
   
   ## What changed
   
   ### 1. Fix for the reported bug (jira)
   - **New migration** 
`plugins/jira/models/migrationscripts/20260727_add_raw_data_columns_to_sprint_report.go`
     re-runs `AutoMigrateTables` on a struct that embeds the raw-data columns,
     adding them to existing `_tool_jira_sprint_reports` tables **without data
     loss** (GORM `AutoMigrate` only adds missing columns). Registered in
     `models/migrationscripts/register.go`.
   - The original `20260722_add_sprint_report_table.go` is left **unchanged**:
     migration scripts are append-only, and editing it would not repair 
databases
     that already ran it (its version is already recorded in
     `_devlake_migration_history`).
   
   ### 2. Regression tests (schema-drift guards)
   - **`plugins/jira/e2e/migration_schema_test.go`** — runs the real Jira 
migration
     chain (framework + jira) and asserts every column each Jira model declares
     exists in the migrated table. Directly reproduces and guards the bug above.
   - **`plugins/schema_e2e/migration_schema_test.go`** — cross-plugin 
generalization:
     applies framework + **all** plugin migrations and validates model-vs-table
     column parity for every built-in Go plugin. Includes 
`TestAllGoPluginsListed`,
     which fails if a new plugin directory with an `impl` package is added but 
not
     registered, so the guard stays complete automatically.
   
   Both deliberately run the **real migration scripts** instead of 
`AutoMigrate`-ing
   the runtime model — an `AutoMigrate`-based check could never detect this 
class of
   drift. Both live in `e2e` packages, so they run under `make 
e2e-test-go-plugins`
   (require `E2E_DB_URL`) and are excluded from the DB-less unit-test run.
   
   Implementation details worth noting:
   - Both tests call `dalgorm.Init(...)` to register the `encdec` GORM 
serializer.
     `runner.CreateBasicRes` does **not** do this (only `CreateAppBasicRes` 
does),
     so without it the migrations abort with `invalid serializer type encdec`.
   - Both fall back to a deterministic `ENCRYPTION_SECRET` if none is 
configured;
     some migrations (e.g. jira `20220716`) refuse to run without one
     (`jira v0.11 invalid encKey`), and CI does not provide a value.
   - Models that no migration materializes (pure API-response models such as
     `_tool_jira_server_infos`) are skipped — the check targets *drift* between 
an
     existing table and its model.
   
   ### 3. Additional schema drifts found by the new cross-plugin guard
   The guard immediately uncovered three pre-existing bugs of exactly the same
   class. Each is fixed with a new, additive migration (registered in the
   respective `register.go`):
   
   | Table | Missing column(s) |
   | --- | --- |
   | `_tool_taiga_scope_configs` | `type_mappings` |
   | `_tool_teambition_scope_configs` | `id`, `created_at`, `updated_at` |
   | `_tool_testmo_scope_configs` | `connection_id`, `name` |
   
   Files: 
`plugins/{taiga,teambition,testmo}/models/migrationscripts/20260727_add_missing_scope_config_columns.go`.
   
   All new migration scripts use `core/models/migrationscripts/archived` — 
importing
   `core/models/common` from a migration script is rejected by
   `make migration-script-lint`.
   
   #### Why these repairs are safe on populated tables
   
   The scope-config repairs re-add columns that carry constraints in
   `common.ScopeConfig` (`name` has a `uniqueIndex`) and, for teambition, an
   AUTO_INCREMENT primary key. Both were verified against live engines rather 
than
   assumed:
   
   - **`uniqueIndex` on `name`** — GORM adds new columns as *nullable*
     (`ALTER TABLE ... ADD COLUMN name VARCHAR(255)`, no `NOT NULL DEFAULT 
''`), so
     pre-existing rows are backfilled with `NULL`, and both MySQL and PostgreSQL
     permit duplicate `NULL`s in a unique index. Creating the index on a table 
with
     two pre-existing rows succeeded on **MySQL 8.4.10** and **PostgreSQL 
17.2**.
     The counter-test confirms the distinction: with an explicit
     `NOT NULL DEFAULT ''` column the same index fails
     (`Error 1062` / `pq: … (23505)`) — which is exactly the case that does 
*not*
     occur here.
   - **AUTO_INCREMENT `id` on the primary-key-less 
`_tool_teambition_scope_configs`** —
     GORM issues a plain `ADD COLUMN`, and MySQL backfills consecutive ids for 
the
     existing rows (verified: two pre-existing rows received `id` 1 and 2). No
     `Error 1075` ("there can be only one auto column and it must be defined as 
a
     key").
   
   ### 4. Build script
   `scripts/build-plugins.sh` builds every directory under `plugins/` with
   `-buildmode=plugin`. `plugins/schema_e2e/` is not a plugin (it contains only 
the
   cross-plugin test and has no `main` package), which made `make build-plugin` 
fail
   with *"-buildmode=plugin requires exactly one main package"*. The directory 
is now
   excluded, alongside the existing `core` / `helper` / `logs` exclusions.
   
   ## Why a new migration (not editing the old one)
   
   - The buggy migration `20260722` is already on `main`/`upstream/main` and has
     already been applied to real databases; its version is recorded, so it will
     never re-run. Only a **new** migration can repair those databases.
   - Append-only migrations keep the migration history reproducible across
     environments.
   
   ## Testing
   
   - `make migration-script-lint` — OK.
   - `gofmt -l` on all added/changed files — clean.
   - `go vet ./plugins/{jira,taiga,teambition,testmo,schema_e2e}/...` — OK.
   - **MySQL 8.4.10, fresh database:**
     - `go test ./plugins/schema_e2e/` — OK, **42/42 plugins PASS**
       (`TestAllGoPluginsListed` included).
     - `go test -run TestMigrationSchema ./plugins/jira/e2e/` — OK.
   - **PostgreSQL 17.2, fresh database:** both tests OK; the repaired 
scope-config
     tables contain the added columns.
   - **Upgrade path on a populated database** (not just a fresh one): all four
     migrations were applied to a real, long-running DevLake instance. They are
     recorded in `_devlake_migration_history` and the target tables carry the
     expected columns afterwards — e.g. `_tool_teambition_scope_configs` gained
     `id`, `created_at`, `updated_at`, and `_tool_jira_sprint_reports` (661 
rows)
     gained the `_raw_data_*` columns without data loss.
   - **Constraint safety** on populated tables (unique index / AUTO_INCREMENT 
PK)
     verified separately against MySQL 8.4.10 and PostgreSQL 17.2 — see
     [§3 "Why these repairs are safe on populated 
tables"](#why-these-repairs-are-safe-on-populated-tables).
   - **Negative test:** dropping `_raw_data_table` from 
`_tool_jira_sprint_reports`
     makes the cross-plugin guard fail with
     `[jira] table "_tool_jira_sprint_reports" is missing column 
"_raw_data_table"`
     — i.e. the guard genuinely detects the original regression.
   
   ## Known limitations of the guard (intentional)
   
   - It checks **column presence only** — not column type, length, nullability,
     primary keys or indexes.
   - It runs against the shared E2E database. Other plugin E2E tests use
     `FlushTabler` (drop + `AutoMigrate` of the runtime model), so a table 
touched by
     an earlier test can appear "repaired". On a fresh database (as in CI) this 
does
     not apply.
   - Tables that no migration creates are skipped, so "model listed in
     `GetTablesInfo()` but no table at all" is not flagged.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to