Files
document-haness/docs/TechLog/tech-log-studio/seams-no-test-crosses/case/case-the-sql-had-never-been-executed.md
T
DongHyeonkaandClaude Opus 5 fd221353a3 docs(TechLog): 얇은 Case 열 편과 Question 셋을 저장소 실물로 채운다
SSOT 를 저장소에서 확인해 더 보강하고 그것으로 다시 썼다.

  §7.2   참조 검사 SQL 을 문자열로 조립하는 실제 코드 — 컴파일러가 표 이름도
         컬럼 이름도 보지 않는다는 것이 그 모양에서 드러난다
  §11.2  section-heading-rank 가 미디어 쿼리 값을 먼저 걷어내는 이유(테스트 주석)
  §16.1  질문 삭제는 참조가 둘뿐이라 같은 문제가 덜하다는 대조

Case 열 편과 Question 셋을 다시 썼다. Question 은 사실·가정·미지수·제약을 갈라
채우고 선택지마다 무엇을 감수하는지 적었다 — 오류 코드를 나누면 계약과 반입한 두
저장소가 함께 움직인다는 것처럼.

SSOT 62,643 → 68,319 자. 검사 넷 전부 통과한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:18:37 +09:00

5.3 KiB

kind, slug, title, topic, topicName, project, status, lastVerifiedOn, sourceRevision, source
kind slug title topic topicName project status lastVerifiedOn sourceRevision source
CASE the-sql-had-never-been-executed 그 SQL 은 한 번도 실행된 적이 없었다 seams-no-test-crosses 테스트가 지나지 않는 이음매 TechLog 게시 전 2026-09-04 tech-log@2026-09-02
final/document.md#§7.2

그 SQL 은 한 번도 실행된 적이 없었다

작업본 삭제가 500 을 돌려줬다. 참조 검사가 없는 컬럼을 조회하고 있었다. 진짜 문제는 그 SQL 이 한 번도 실행된 적이 없다는 것이었다 — 표준 check 는 Testcontainers 를 띄우지 않으므로 persistence SQL 을 한 줄도 돌리지 않고 빌드가 통과한다.

관계

  • 이음매마다 그 이음매를 실제로 지나는 검사를 하나씩 둔다 persistence SQL 이 그 목록의 한 줄이다.
  • 파드가 두 번 CrashLoopBackOff 로 들어갔다 같은 「지나지 않은 이음매」의 다른 예다.
  • 삭제를 막는 이유 다섯 가지가 전부 같은 한 문장으로 나온다 이 참조 검사가 지금도 남긴 문제다.

문제

작업본을 지우려 하면 500 이 났다. 참조 검사가 public_resource_projection.document_id 를 조회하는데 그런 컬럼이 없다.

이 테이블은 하나로 case·question·project·release 를 모두 담기 때문에 종류와 식별자 두 컬럼으로 기록을 가리킨다.

결론

컬럼 이름 하나가 틀렸고, 그 쿼리가 한 번도 실행된 적이 없었다.

그 쿼리의 여섯 컬럼 중 다섯은 마이그레이션과 대조했다. 이 하나만 가정했고, 그것이 틀렸다.

표준 check 는 Testcontainers 를 띄우지 않는다. persistence SQL 은 한 번도 실행되지 않은 채 빌드가 통과하고, 컴파일도 단위 테스트도 컬럼 이름을 검증하지 못한다.

삭제 경로 전용 통합 테스트 태스크를 만들고 실패했던 그 쿼리를 포함해 여덟 시나리오를 실제 PostgreSQL 에서 돌린다.

검증 환경

tech-log-backend : 37f474a 이후 DB : 실제 PostgreSQL (Testcontainers) 빌드 : 표준 check 는 Testcontainers 를 띄우지 않음 확인 방식 : 삭제 경로 전용 통합 테스트 태스크에서 여덟 시나리오 실행

재현 조건

  1. 어댑터의 SQL 에서 컬럼 이름을 하나 틀리게 적는다
  2. ./gradlew check 를 돌린다 — 통과한다
  3. 삭제 경로 통합 테스트 태스크를 돌린다 — 그 쿼리에서 멈춘다

본문

없는 컬럼을 조회했다

작업본을 지우려 하면 500 이 났다. 참조 검사가 공개 투영에서 문서 식별자 컬럼을 조회하는데 그런 컬럼이 없다.

이 테이블은 하나로 case·question·project·release 를 모두 담는다. 그래서 기록을 가리킬 때 종류와 식별자 두 컬럼을 함께 쓰고, 종류별 식별자 컬럼은 두지 않는다.

그 쿼리의 여섯 컬럼 중 다섯은 마이그레이션과 대조했다. 이 하나만 가정했고, 그것이 틀렸다.

같은 어댑터의 다른 참조 검사들은 문서 식별자 컬럼을 가진 표를 조회하므로 그 이름이 맞는다. 한 함수 안에서 표마다 컬럼 이름이 다른데, 다섯 개가 맞으니 여섯 번째도 맞을 것으로 읽었다.

그 SQL 은 한 번도 실행되지 않았다

컬럼 이름보다 더 드러난 것은 검사 구조였다.

무엇이 도는가 SQL 을 실행하는가
컴파일 x — 문자열 안을 보지 않는다
단위 테스트 x — 어댑터를 스텁으로 바꾼다
표준 check x — Testcontainers 를 띄우지 않는다
삭제 경로 통합 테스트 o — 실제 PostgreSQL

표준 검사가 컨테이너를 띄우지 않으므로 어댑터의 SQL 은 한 줄도 실행되지 않은 채 빌드가 통과한다. 컬럼 이름이 맞는지 묻는 검사가 어디에도 없었다.

문자열로 조립한 SQL 은 컴파일러가 보지 않는다

이 어댑터는 SQL 을 문자열로 이어 붙여 만든다.

"SELECT EXISTS ("
    + "  SELECT 1 FROM document_relation WHERE target_document_id = :id"
    + "  UNION ALL SELECT 1 FROM question_document_link WHERE document_id = :id"
    + "  UNION ALL SELECT 1 FROM project_document_link WHERE document_id = :id"
    + "  UNION ALL SELECT 1 FROM topic_featured_document WHERE document_id = :id"
    + "  UNION ALL SELECT 1 FROM project_decision WHERE source_case_id = :id"
    + ")"

컴파일러가 확인하는 것은 이 식이 문자열이라는 것까지다. 표 이름도 컬럼 이름도 실행해야 검증된다.

전용 태스크로 여덟 시나리오를 돌린다

삭제 경로 전용 통합 테스트 태스크를 만들고, 실패했던 그 쿼리를 포함해 여덟 시나리오를 실제 PostgreSQL 에서 돌린다.

전용 태스크로 뺀 이유는 컨테이너를 띄우는 데 시간이 들어 표준 검사에 넣으면 모든 빌드가 느려지기 때문이다. 대신 그 태스크를 돌리는 것은 사람이 기억해야 한다.

확인하지 못한 것

이 태스크가 덮는 것은 삭제 경로다. 표준 검사는 여전히 컨테이너를 띄우지 않고, 새 어댑터 SQL 이 이 태스크에 등록되는지 보는 검사도 없다.

같은 참조 검사가 지금도 다섯 표를 하나로 묶어 확인하므로, 무엇이 막았는지를 응답이 말하지 못한다.