Skip to content

fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility#8745

Merged
klesh merged 1 commit intoapache:mainfrom
yamoyamoto:fix/q_dev-mysql8-incompatible-migration-syntax
Mar 5, 2026
Merged

fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility#8745
klesh merged 1 commit intoapache:mainfrom
yamoyamoto:fix/q_dev-mysql8-incompatible-migration-syntax

Conversation

@yamoyamoto
Copy link
Copy Markdown
Contributor

⚠️ Pre Checklist

Please complete ALL items in this checklist, and remove before submitting

  • I have read through the Contributing Documentation.
  • I have added relevant tests.
  • I have added relevant documentation.
  • I will add labels to the PR, such as pr-type/bug-fix, pr-type/feature-development, etc.

Summary

Replace MariaDB-specific ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS syntax with database-agnostic DAL methods (db.HasColumn() + db.AddColumn()) in q_dev migration scripts. This fixes MySQL 8.x compatibility where these syntaxes cause Error 1064 (syntax error).

Does this close any open issues?

Closes #8744

Screenshots

N/A

@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. component/plugins This issue or PR relates to plugins pr-type/bug-fix This PR fixes a bug priority/medium This issue is medium important severity/p1 This bug affects functionality or significantly affect ux labels Mar 2, 2026
Copy link
Copy Markdown
Contributor

@klesh klesh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job. Thanks for your contribution.

@klesh klesh merged commit 04795c2 into apache:main Mar 5, 2026
15 checks passed
lrf-nitro pushed a commit to AkerBP/nitro-devlake that referenced this pull request Mar 9, 2026
klesh pushed a commit that referenced this pull request Mar 12, 2026
* feat(github): auto-refresh GitHub App installation tokens

Add transport-level token refresh for GitHub App (AppKey) connections.
GitHub App installation tokens expire after ~1 hour; this adds proactive
refresh (before expiry) and reactive refresh (on 401) using the existing
TokenProvider/RefreshRoundTripper infrastructure.

New files:
- app_installation_refresh.go: refresh logic + DB persistence
- refresh_api_client.go: minimal ApiClient for token refresh POST
- cmd/test_refresh/main.go: manual test script for real GitHub Apps

Modified:
- connection.go: export GetInstallationAccessToken, parse ExpiresAt
- token_provider.go: add refreshFn for pluggable refresh strategies
- round_tripper.go: document dual Authorization header interaction
- api_client.go: wire AppKey connections into refresh infrastructure
- Tests updated for new constructors and AppKey refresh flow

* feat(github): add diagnostic logging to GitHub App token refresh

Add structured logging at key decision points for token refresh:
- Token provider creation (connection ID, installation ID, expiry)
- Round tripper installation (connection ID, auth method)
- Proactive refresh trigger (near-expiry detection)
- Refresh start/success/failure (old/new token prefixes, expiry times)
- DB persistence success/failure
- Reactive 401 refresh and skip-due-to-concurrent-refresh

All logs route through the DevLake logger to pipeline log files.

* fix(github): prevent deadlock and fix token persistence in App token refresh

Deadlock fix: NewAppInstallationTokenProvider now captures client.Transport
(the base transport) before wrapping with RefreshRoundTripper. The refresh
function uses newRefreshApiClientWithTransport(baseTransport) to POST for
new installation tokens, bypassing the RefreshRoundTripper entirely.

Token persistence fix: PersistEncryptedTokenColumns() manually encrypts
tokens via plugin.Encrypt() then writes ciphertext via dal.UpdateColumns
with conn.TableName() (a string) as the first argument. Passing the table
name string makes GORM use Table() instead of Model(), preventing the
encdec serializer from corrupting the in-memory token value. The encryption
secret is threaded from taskCtx.GetConfig(ENCRYPTION_SECRET) through
CreateApiClient to TokenProvider to persist functions.

Also persists the initial App token at startup for DB consistency, and
adds TestProactiveRefreshNoDeadlock with a real RSA key to verify the
deadlock scenario is resolved.

* fix(grafana): update dashboard descriptions to list all supported data sources (#8741)

Several dashboard introduction panels hardcoded "GitHub and Jira" as
required data sources, even though the underlying queries use generic
domain layer tables that work with any supported Git tool or issue
tracker. Updated to list all supported sources following the pattern
already used by DORA and WorkLogs dashboards.

Closes #8740

Co-authored-by: Spiff Azeta <spiffazeta@gmail.com>

* fix: modify cicd_deployments name from varchar to text (#8724)

* fix: modify cicd_deployments name from varchar to text

* fix: update the year

* fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility (#8745)

* fix(azuredevops): default empty entities and add CROSS to repo scope in makeScopeV200 (#8751)

When scopeConfig.Entities is empty (common when no entities are
explicitly selected in the UI), makeScopeV200 produced zero scopes,
leaving project_mapping with no rows. Additionally, the repo scope
condition did not check for DOMAIN_TYPE_CROSS, so selecting only
CROSS would not create a repo scope, breaking DORA metrics.

This adds the same fixes applied to GitLab in #8743.

Closes #8749

* fix(bitbucket): default empty entities to all domain types in makeScopesV200 (#8750)

When scopeConfig.Entities is empty (common when no entities are
explicitly selected in the UI), makeScopesV200 produced zero scopes,
leaving project_mapping with no repo rows. This adds the same
empty-entities default applied to GitLab in #8743.

Closes #8748

* fix(github): remove unused refresh client constructor and update tests

---------

Co-authored-by: Spiff Azeta <35563797+spiffaz@users.noreply.github.com>
Co-authored-by: Spiff Azeta <spiffazeta@gmail.com>
Co-authored-by: Dan Crews <crewsd@gmail.com>
Co-authored-by: Tomoya Kawaguchi <68677002+yamoyamoto@users.noreply.github.com>
la-tamas pushed a commit to archfz/incubator-devlake that referenced this pull request Mar 26, 2026
la-tamas pushed a commit to archfz/incubator-devlake that referenced this pull request Mar 26, 2026
* feat(github): auto-refresh GitHub App installation tokens

Add transport-level token refresh for GitHub App (AppKey) connections.
GitHub App installation tokens expire after ~1 hour; this adds proactive
refresh (before expiry) and reactive refresh (on 401) using the existing
TokenProvider/RefreshRoundTripper infrastructure.

New files:
- app_installation_refresh.go: refresh logic + DB persistence
- refresh_api_client.go: minimal ApiClient for token refresh POST
- cmd/test_refresh/main.go: manual test script for real GitHub Apps

Modified:
- connection.go: export GetInstallationAccessToken, parse ExpiresAt
- token_provider.go: add refreshFn for pluggable refresh strategies
- round_tripper.go: document dual Authorization header interaction
- api_client.go: wire AppKey connections into refresh infrastructure
- Tests updated for new constructors and AppKey refresh flow

* feat(github): add diagnostic logging to GitHub App token refresh

Add structured logging at key decision points for token refresh:
- Token provider creation (connection ID, installation ID, expiry)
- Round tripper installation (connection ID, auth method)
- Proactive refresh trigger (near-expiry detection)
- Refresh start/success/failure (old/new token prefixes, expiry times)
- DB persistence success/failure
- Reactive 401 refresh and skip-due-to-concurrent-refresh

All logs route through the DevLake logger to pipeline log files.

* fix(github): prevent deadlock and fix token persistence in App token refresh

Deadlock fix: NewAppInstallationTokenProvider now captures client.Transport
(the base transport) before wrapping with RefreshRoundTripper. The refresh
function uses newRefreshApiClientWithTransport(baseTransport) to POST for
new installation tokens, bypassing the RefreshRoundTripper entirely.

Token persistence fix: PersistEncryptedTokenColumns() manually encrypts
tokens via plugin.Encrypt() then writes ciphertext via dal.UpdateColumns
with conn.TableName() (a string) as the first argument. Passing the table
name string makes GORM use Table() instead of Model(), preventing the
encdec serializer from corrupting the in-memory token value. The encryption
secret is threaded from taskCtx.GetConfig(ENCRYPTION_SECRET) through
CreateApiClient to TokenProvider to persist functions.

Also persists the initial App token at startup for DB consistency, and
adds TestProactiveRefreshNoDeadlock with a real RSA key to verify the
deadlock scenario is resolved.

* fix(grafana): update dashboard descriptions to list all supported data sources (apache#8741)

Several dashboard introduction panels hardcoded "GitHub and Jira" as
required data sources, even though the underlying queries use generic
domain layer tables that work with any supported Git tool or issue
tracker. Updated to list all supported sources following the pattern
already used by DORA and WorkLogs dashboards.

Closes apache#8740

Co-authored-by: Spiff Azeta <spiffazeta@gmail.com>

* fix: modify cicd_deployments name from varchar to text (apache#8724)

* fix: modify cicd_deployments name from varchar to text

* fix: update the year

* fix(q_dev): replace MariaDB-specific IF NOT EXISTS syntax with DAL methods for MySQL 8.x compatibility (apache#8745)

* fix(azuredevops): default empty entities and add CROSS to repo scope in makeScopeV200 (apache#8751)

When scopeConfig.Entities is empty (common when no entities are
explicitly selected in the UI), makeScopeV200 produced zero scopes,
leaving project_mapping with no rows. Additionally, the repo scope
condition did not check for DOMAIN_TYPE_CROSS, so selecting only
CROSS would not create a repo scope, breaking DORA metrics.

This adds the same fixes applied to GitLab in apache#8743.

Closes apache#8749

* fix(bitbucket): default empty entities to all domain types in makeScopesV200 (apache#8750)

When scopeConfig.Entities is empty (common when no entities are
explicitly selected in the UI), makeScopesV200 produced zero scopes,
leaving project_mapping with no repo rows. This adds the same
empty-entities default applied to GitLab in apache#8743.

Closes apache#8748

* fix(github): remove unused refresh client constructor and update tests

---------

Co-authored-by: Spiff Azeta <35563797+spiffaz@users.noreply.github.com>
Co-authored-by: Spiff Azeta <spiffazeta@gmail.com>
Co-authored-by: Dan Crews <crewsd@gmail.com>
Co-authored-by: Tomoya Kawaguchi <68677002+yamoyamoto@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/plugins This issue or PR relates to plugins pr-type/bug-fix This PR fixes a bug priority/medium This issue is medium important severity/p1 This bug affects functionality or significantly affect ux size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug][q_dev] MySQL 8.x incompatibility: ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS in migration scripts

2 participants