OSSCA Chromium

기여 목록 / 8521677

[iOS] Use ColumnTime() and BindTime() in DownloadRecordDatabase

BrowserrefactorMerged

iOS 다운로드 기록 DB(DownloadRecordDatabase)가 시간 값을 읽고 쓸 때 손으로 하던 변환을 sql::Statement::ColumnTime()·BindTime()으로 바꿨습니다. sql::Statement 시간 접근자 이행 시리즈(crbug.com/40176243, OSSCA #416)의 여섯 번째 CL입니다. 1파일 +10/−27, 저장 형식은 바뀌지 않습니다. ios/chrome/browser/download/model OWNER sdefresne@가 먼저 승인한 뒤 같은 OWNERS의 gambard@를 더해 두 번째 승인을 받았고, gambard@가 CQ를 돌려 tryjob 42개를 한 번에 통과해 2026-10-06에 머지됐습니다(cb8028e6d1253).

문제 설명

  • issue url: https://crbug.com/40176243
  • sql/statement.h는 시간 컬럼을 ColumnTime()·BindTime()으로 다루고 손 변환을 옮기라는 TODO를 남겨 두었습니다. CL 1~5(8382856, 8410045, 8474691, 8479611, 8505857)에 이어 iOS 쪽에 남은 곳을 옮깁니다.
  • DownloadRecordDatabase는 페이징 커서 시각을 ToDeltaSinceWindowsEpoch().InMicroseconds()로 두 번 바인딩하고, created_time·completed_time을 같은 방식으로 쓰고, 읽을 때는 base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(...))로 되돌리고 있었습니다.
  • completed_time이 비어 있으면 0을 쓰고 읽을 때는 0보다 큰 값만 시각으로 되돌리는 분기가 insert·update·read 세 곳에 반복돼 있었습니다.

해결 내용

  • 커서 바인딩 2곳과 created_time 쓰기 1곳, completed_time 쓰기 2곳(insert·update)을 BindTime()으로, 읽기 2곳을 ColumnTime()으로 바꿨습니다.
  • completed_time의 «없음 = 0» 처리는 분기 없이 그대로 유지됩니다. BindTime()은 null base::Time을 0으로 쓰고 ColumnTime()은 0을 null로 읽기 때문입니다. 옛 읽기 코드는 음수도 버렸지만 이 클래스는 음수를 쓰지 않으므로 저장된 모든 행이 같은 값으로 읽힙니다.
-  int64_t completed_time_microseconds = 0;
-  if (!record.completed_time.is_null()) {
-    completed_time_microseconds =
-        record.completed_time.ToDeltaSinceWindowsEpoch().InMicroseconds();
-  }
-  statement.BindInt64(16, completed_time_microseconds);
+  // A null `completed_time` is stored as 0.
+  statement.BindTime(16, record.completed_time);
 ...
-  int64_t completed_time_microseconds = statement.ColumnInt64(16);
-  if (completed_time_microseconds > 0) {
-    record.completed_time = base::Time::FromDeltaSinceWindowsEpoch(
-        base::Microseconds(completed_time_microseconds));
-  }
+  // A stored 0 reads back as a null `completed_time`.
+  record.completed_time = statement.ColumnTime(16);

발굴 과정

CL 5 착수 전 전수 재검색에서 iOS 전용 파일에 7곳이 더 나와 별도 CL로 나눴습니다. iOS 코드는 Mac에서만 빌드할 수 있어 이 CL을 위해 처음으로 iOS 시뮬레이터 빌드 환경(.gclient의 target_os에 ios 추가, out/ios-sim)을 만들었습니다.

리뷰어는 ios/chrome/browser/download/model/OWNERS 중 이 파일의 최신 변경을 승인했고 최근에도 활동 중인 sdefresne@에게 먼저 받고, 이어서 gambard@에게 받았습니다. 처음 후보로 적어 둔 사람은 이 파일을 리뷰한 적은 있지만 OWNERS가 아니었고, OWNERS 한 명은 80일째 활동이 없어 제외했습니다.

테스트 방법

동작 불변 리팩토링이라 새 테스트는 없고 기존 테스트 통과가 검증입니다.

  • 등가성: sql/statement.cc에서 BindTime()은 time.ToDeltaSinceWindowsEpoch().InMicroseconds()를 바인딩하고(null은 0), ColumnTime()은 base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(value))를 돌려줍니다(0은 null). 손으로 쓴 변환·분기와 같은 값입니다.
  • 첫 iOS 시뮬레이터 빌드 ios_chrome_unittests 27,964스텝 48분 46초
  • iossim으로 DownloadRecordDatabaseTest.*(29) · DownloadRecordServiceImpl*(31) · DownloadRecordStore*(17) 77/77 통과 — 경계값 테스트 DataTypeBoundaryValues와 커서 페이징 테스트 포함
  • presubmit 0 경고

배운 점

  • BindTime(null) = 0, ColumnTime(0) = null이라는 접근자 규약을 확인하고 나서야 «없음 = 0» 분기 세 곳을 지워도 된다고 판단할 수 있었습니다. 접근자의 null 처리까지 원문에서 읽어야 합니다.
  • iOS 전용 코드는 별도 빌드 환경이 필요하지만 한 번 만들면 증분으로 돌릴 수 있어, 시리즈에서 플랫폼별 파일을 따로 CL로 나누는 편이 리뷰어와 빌드 양쪽에서 낫습니다.
  • 리뷰어 후보가 «최근에 이 파일을 리뷰한 사람»이어도 OWNERS 파일에서 등재 여부를 반드시 확인합니다. 자주 보이는 리뷰어가 OWNER가 아닐 수 있습니다.
  • 파일 하나짜리 CL은 같은 OWNERS 파일에서 두 명을 차례로 받으면 됩니다. 첫 승인이 오자 바로 두 번째를 붙이고 승인이 모이자 바로 제출을 요청해, 업로드부터 머지까지 15시간이 걸렸습니다.

참고 자료