Skip to content

Fix: MySQL年代別集計でBIGINT UNSIGNEDオーバーフローエラーを修正#1350

Merged
nanasess merged 3 commits intomasterfrom
fix/mysql-bigint-unsigned-age-calculation
Feb 25, 2026
Merged

Fix: MySQL年代別集計でBIGINT UNSIGNEDオーバーフローエラーを修正#1350
nanasess merged 3 commits intomasterfrom
fix/mysql-bigint-unsigned-age-calculation

Conversation

@nobuhiko
Copy link
Contributor

@nobuhiko nobuhiko commented Feb 24, 2026

Summary

  • MySQL の売上集計 > 年代別集計で BIGINT UNSIGNED value is out of range エラーが発生するバグを修正
  • YEAR() 関数が unsigned integer を返すため、order_birth の年が create_date の年以上の場合に引き算が負数となりオーバーフローしていた
  • CAST(... AS SIGNED) で符号付き演算に変更

原因

SC_DB_DBFactory_MYSQL::getOrderTotalAgeColSql() の SQL:

-- Before
TRUNC((YEAR(create_date) - YEAR(order_birth)) - (RIGHT(create_date, 5) < RIGHT(order_birth, 5)), -1)

-- After
TRUNC((CAST(YEAR(create_date) AS SIGNED) - CAST(YEAR(order_birth) AS SIGNED)) - (RIGHT(create_date, 5) < RIGHT(order_birth, 5)), -1)

YEAR(create_date) - YEAR(order_birth)0 で、かつ誕生日がまだ来ていない場合に 0 - 1 = -1 となり、MySQL 8+ の unsigned 演算でオーバーフローする。

PostgreSQL 版は AGE() 関数で符号付き演算のため問題なし。

Test plan

  • PHPUnit全テスト通過(MySQL & PostgreSQL)
  • E2Eテスト全テスト通過(MySQL & PostgreSQL)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • バグ修正
    • 年齢ベースのグルーピング計算での桁あふれや誤差を防ぎ、同年で誕生日が未到来の場合でも正しく集計されるよう改善しました。
  • テスト
    • MySQL環境向けの回帰テストを追加し、上記のあふれ問題が再発しないことを検証します。

YEAR()関数がunsigned integerを返すため、order_birthの年がcreate_dateの年以上の場合に
引き算が負数となりBIGINT UNSIGNEDオーバーフローが発生していた。
CAST(... AS SIGNED)で符号付き演算に変更することで修正。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Warning

Rate limit exceeded

@nobuhiko has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9e599c1 and c555a9e.

📒 Files selected for processing (2)
  • data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php
  • tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php
📝 Walkthrough

Walkthrough

YEAR()で取得した年の差算を行うSQLで、年値を減算前に明示的にSIGNEDキャストするよう修正。加えて、MySQL専用の単体テストが同ファイル内で同一内容のテストを重複追加している。

変更内容

コホート / ファイル 概要
年齢計算SQL修正
data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php
YEAR(create_date) / YEAR(order_birth) の結果を減算前に CAST(... AS SIGNED) で明示的に符号付き整数化する変更(TRUNCおよび月日比較ロジックは維持)。
MySQL テスト追加(重複あり)
tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php
MySQL専用の PHPUnit テスト testGetOrderTotalAgeColSqlは同年で誕生日未到来でもオーバーフローしない() を追加(同一内容のテストがファイル内に重複して追加されている)。

推定コード審査所要時間

🎯 3 (Moderate) | ⏱️ ~20 分

ポエム

🐰✨
YEARを抱えて跳ねる夜、
キャストひとつで安心する耳、
数が整えば心も弾む、
小さなテストも一緒に踊る。

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、主な変更内容(MySQLの年代別集計でのBIGINT UNSIGNEDオーバーフローエラーの修正)を明確かつ簡潔に要約しており、変更セットの主要な目的と完全に関連している。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/mysql-bigint-unsigned-age-calculation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 54.42%. Comparing base (0295256) to head (c555a9e).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1350   +/-   ##
=======================================
  Coverage   54.42%   54.42%           
=======================================
  Files          84       84           
  Lines       10817    10817           
=======================================
  Hits         5887     5887           
  Misses       4930     4930           
Flag Coverage Δ
tests 54.42% <0.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php`:
- Line 219: The current return expression in SC_DB_DBFactory_MYSQL.php uses
RIGHT(create_date, 5) and RIGHT(order_birth, 5), which extract minutes:seconds
for TIMESTAMP/DATETIME and break the month-day comparison; replace those
RIGHT(...) calls with DATE_FORMAT(create_date, '%m-%d') and
DATE_FORMAT(order_birth, '%m-%d') respectively so the month-day is correctly
compared while keeping the CAST(... AS SIGNED) changes and the TRUNC->TRUNCATE
behavior intact in the same return expression.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0295256 and ccba7bc.

📒 Files selected for processing (1)
  • data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php

同年のorder_birthでRIGHT(col,5)の比較結果が1になるケースで
修正前のSQLがBIGINT UNSIGNEDオーバーフローを起こすことを検証するテスト。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nobuhiko added a commit to nobuhiko/eccube2-fixture-generator that referenced this pull request Feb 24, 2026
dateTimeThisDecade() は直近10年の日付を生成するため、
0〜6歳の生年月日が生成されていた。
これにより売上集計の年代別集計で MySQL の BIGINT UNSIGNED
オーバーフローが発生する場合があった。

dateTimeBetween('-80 years', '-18 years') に変更し、
18〜80歳の現実的な生年月日を生成するようにした。

See: EC-CUBE/ec-cube2#1350

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php (1)

152-197: 回帰テストとして適切です。BIGINT UNSIGNED オーバーフローのシナリオを正しく再現しています。

テストデータの設定とクエリの実行ロジックは正しく、修正前のSQLでオーバーフローが発生するケースを正確に再現しています。

1点だけ提案:現在のアサーションはクエリがエラーなく完了することのみを検証していますが、age カラムの計算結果も検証すると、回帰テストとしてより堅牢になります。この修正後のSQLでは、YEAR差=0 から 1 を引いて -1、TRUNC(-1, -1) = -10 が返るはずです。

💡 計算結果の検証を追加する提案
         $this->assertIsArray($result);
         $this->assertCount(1, $result);
+        $this->assertEquals(-10, (int) $result[0]['age'], 'YEAR差0で誕生日未到来の場合、年齢は-10となること');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php` around lines 152 -
197, The test currently only asserts the query succeeded; add an assertion that
validates the computed age value from getOrderTotalAgeColSql to make the
regression check stronger: after retrieving $result (from the test method
testGetOrderTotalAgeColSqlは同年で誕生日未到来でもオーバーフローしない), assert that the 'age' field
in $result[0] equals the expected value produced by the fixed SQL (TRUNC(-1, -1)
=> -10), e.g. use assertEquals(-10, (int)$result[0]['age']) or equivalent to
tolerate string numeric results; keep the existing group/order/count assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php`:
- Around line 152-197: The test currently only asserts the query succeeded; add
an assertion that validates the computed age value from getOrderTotalAgeColSql
to make the regression check stronger: after retrieving $result (from the test
method testGetOrderTotalAgeColSqlは同年で誕生日未到来でもオーバーフローしない), assert that the 'age'
field in $result[0] equals the expected value produced by the fixed SQL
(TRUNC(-1, -1) => -10), e.g. use assertEquals(-10, (int)$result[0]['age']) or
equivalent to tolerate string numeric results; keep the existing
group/order/count assertions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccba7bc and 9e599c1.

📒 Files selected for processing (1)
  • tests/class/db/dbfactory/SC_DB_DBFactory_MYSQLTest.php

RIGHT(datetime, 5)はMM:SS(分秒)を返すため月日比較が正しくなかった。
DATE_FORMAT(col, '%m-%d')に変更し正しく月日を比較するようにした。
テストでもage計算結果の値を検証するよう強化。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@nanasess nanasess left a comment

Choose a reason for hiding this comment

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

レビュー結果: Approve

修正内容を確認しました。適切な修正です。

1. CAST(YEAR(...) AS SIGNED) — 適切

MySQL 8+ では YEAR() が UNSIGNED INT を返すため、同年で誕生日未到来の場合に 0 - 1 = -1 でオーバーフローします。CAST(... AS SIGNED) で符号付き演算に変換するのは正しいアプローチです。

2. RIGHT()DATE_FORMAT() — 適切(既存バグの修正を含む)

RIGHT(datetime, 5) は DATETIME 値 '2026-02-01 00:00:00' の末尾5文字 00:00(分秒)を返してしまい、月日比較として元から壊れていました。DATE_FORMAT('%m-%d') に変更することで正しく月日を比較できるようになります。SQLite3版の strftime('%m-%d', ...) とも一貫性があります。

3. TRUNC の扱い — 問題なし

sfChangeTrunc() で MySQL の TRUNCATE に自動変換される仕組みは引き続き正常に機能します。TRUNCATE(-1, -1) = 0 となるため、同年で誕生日未到来の場合は年代 0 となり妥当です。

4. テスト — 適切

  • オーバーフローを再現するエッジケースを正確にカバーしている
  • age の値まで検証しており、回帰テストとして有効
  • DB_TYPE チェックで MySQL 環境のみ実行されるのも適切

🤖 Generated with Claude Code

@nanasess nanasess merged commit 33a4688 into master Feb 25, 2026
207 of 209 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants