@@ -4424,3 +4424,201 @@ unknown 상태의 의미를 좁혔다. `SET`의 우변에 등장한 `@var`는 as
44244424| ` cargo test ` | library 6,623 통과 · 234 ignored · 통합/가드/doc-test 실패 0 |
44254425| ` cargo clippy --locked --all-targets -- -D warnings -W clippy::perf -W clippy::complexity ` | 통과, 경고 0 |
44264426| ` cargo fmt --all -- --check ` / ` git diff --check ` | 통과 |
4427+
4428+ ## 31-1. 포맷 sweep 전수 재검토 (2026-07-17)
4429+
4430+ ` formatting_sweep_all_files_generate_out_report ` 1차 실행은 PASS(66개 파일,
4431+ 30,120 frame, failures=0)였다. 자동 결과를 신뢰하지 않고 66개 ` .format.out `
4432+ 53,361줄을 다시 전수 검토했다.
4433+
4434+ - Oracle 43개 파일: 처음부터 끝까지 정독.
4435+ - MySQL 11개·MariaDB 13개: 대표 파일 정독 + 전 파일 소스↔출력 토큰 diff,
4436+ 공백/괄호/들여쓰기 기계 스캔, 플래그된 문맥 전부 정독.
4437+ - 프레임 깊이(rule 1~ 6), 보존 텍스트(rule 7), 고정구(rule 8), 정규 출력(rule 10)
4438+ 기준으로 대조.
4439+
4440+ 휴리스틱이 플래그했으나 규칙에 부합해 정상 판정한 항목: 여러 줄 주석 내부
4441+ 후행 공백/비정렬(보존 텍스트), q-quote 동적 SQL 내부의 별도 정렬, 주석 뒤로
4442+ 밀린 선행 콤마(` -- comment ` 다음 ` , ghij = ( ` ), CREATE TABLE 컬럼 정렬의 이중
4443+ 공백, ` COUNT(*) ` 압축 표기와 ` 이름 ( ` 공백 표기의 공존.
4444+
4445+ ## 31-2. [ 수정] 키워드 대문자화가 식별자 위치를 침범
4446+
4447+ Oracle 경로의 키워드 케이스 정규화(` token_text ` / 메인 루프)는
4448+ ` ORACLE_SQL_KEYWORDS ` 에 있는 단어를 문법 위치와 무관하게 대문자화했다.
4449+ 컨텍스트 판정(` formatter_keyword_should_render_as_identifier_from_context ` )은
4450+ 8개 단어(MODEL/WINDOW/…)만 허용해, NAME·DEPTH·SEED·PATH·REMARK·GRAPH·
4451+ SETTINGS·LABEL·SOURCE·ERRORS·EVENTS·CUME_DIST 같은 비예약 키워드가 식별자
4452+ 자리에서도 대문자로 바뀌었다.
4453+
4454+ AS-IS (formatted 출력):
4455+
4456+ ``` sql
4457+ -- test/test5.txt: 소스는 모두 소문자 name/depth
4458+ INSERT INTO oqt_t_depth (id, grp, NAME)
4459+ ' grp=0 id=' || r .id || ' name=' || r .NAME
4460+ MIN (DEPTH) min_depth
4461+
4462+ -- test/mega_torture.txt: <<top>> 라벨과 불일치
4463+ PROCEDURE SEED (p_rows IN NUMBER ) IS
4464+ GOTO TOP;
4465+ oqt_mega_pkg .SEED (30 );
4466+
4467+ -- boss/final: CTE·별칭·스키마 한정자
4468+ WITH GRAPH (node_id, ...) FROM GRAPH g
4469+ WITH SEED AS (...) FROM SEED s
4470+ EVENTS (event_id, ...) AS (...) FROM EVENTS e
4471+ ) SOURCE ON (target .category = SOURCE .category )
4472+ FROM SYSTEM .help ;
4473+ ' ERRORS' AS ERRORS 2 ERRORS
4474+ ) AS CUME_DIST, ' /' ) AS PATH
4475+ PATTERN (SEED rise* ) -- FIRST (SEED.day_value)와 함께 패턴 변수까지 대문자화
4476+ WITH RECURSIVE SETTINGS AS ( -- MySQL
4477+ COLUMNS (LABEL VARCHAR (40 ) PATH ' $' -- MariaDB JSON_TABLE 컬럼명
4478+ ```
4479+
4480+ TO-BE:
4481+
4482+ ``` sql
4483+ INSERT INTO oqt_t_depth (id, grp, name)
4484+ ' grp=0 id=' || r .id || ' name=' || r .name
4485+ MIN (depth) min_depth
4486+ PROCEDURE seed (p_rows IN NUMBER ) IS
4487+ GOTO top;
4488+ oqt_mega_pkg .seed (30 );
4489+ WITH graph (node_id, ...) FROM graph g
4490+ WITH seed AS (...) FROM seed s
4491+ events (event_id, ...) AS (...) FROM events e
4492+ ) source ON (target .category = source .category )
4493+ FROM system .help ;
4494+ ' ERRORS' AS errors 2 errors
4495+ ) AS cume_dist, ' /' ) AS path
4496+ PATTERN (seed rise* ) FIRST (seed .day_value )
4497+ WITH RECURSIVE settings AS (
4498+ COLUMNS (label VARCHAR (40 ) PATH ' $'
4499+ ```
4500+
4501+ 컨텍스트 판정을 문법 기반으로 일반화했다
4502+ (` formatter_keyword_should_render_as_identifier_from_context ` ):
4503+
4504+ 1 . ` . ` 앞뒤에 붙은 단어는 항상 한정 식별자 세그먼트 (모든 키워드에 적용).
4505+ 2 . ` GOTO ` 다음 단어는 항상 라벨 식별자 (모든 키워드에 적용).
4506+ 3 . 비예약 명사 키워드 12개를 컨텍스트 판정 대상에 추가하고 새 슬롯 규칙 도입:
4507+ ` PROCEDURE ` /` FUNCTION ` 뒤의 객체명, ` WITH ` /` RECURSIVE ` 뒤의 CTE명,
4508+ 리스트 구분자 사이(` (id, name) ` )와 선언 머리(` (label VARCHAR(40) ` ,
4509+ ` PATTERN (seed rise*) ` ), 구분자 뒤 ` AS ` 별칭, 절 머리 바로 뒤 테이블명
4510+ (` FROM seed s ` ), 파생 테이블 뒤 별칭(` ) source ON ... ` ).
4511+ 4 . 소스가 대문자인 경우(` REMARK ` 등)는 그대로 보존된다 — 케이스는 항상
4512+ 소스를 따른다.
4513+
4514+ 의도적으로 제외(잔여 known-limitation): ` INNER ` (조인 수식어와 충돌 위험),
4515+ 연산자 피연산자 슬롯(` 'x' || inner || 'y' ` ), 절 밖 SELECT-리스트가 아닌 일반
4516+ 표현식 내부의 명사 키워드. 모두 대문자 유지가 안전한 방향이다.
4517+
4518+ ## 31-3. sweep 일반 검출 추가: IdentifierCase 감사
4519+
4520+ goal: 이런 부류를 사람이 아닌 sweep이 잡도록 일반 규칙으로 내재화.
4521+
4522+ ` format_sweep_audit_identifier_case ` 를 추가했다. 소스와 포맷 결과의 Word
4523+ 토큰을 1:1로 짝지어, 케이스가 바뀐 토큰이 다음의 "문법적으로 식별자일 수밖에
4524+ 없는 슬롯"에 있으면 ` IdentifierCase ` 이슈로 보고한다.
4525+
4526+ - 앞 또는 뒤 유의미 토큰이 ` . ` (한정 식별자 세그먼트)
4527+ - 직전 유의미 단어가 ` GOTO ` /` PROCEDURE ` /` FUNCTION ` (명명 대상 위치)
4528+
4529+ 리포트의 ` checked: ` 줄에 ` identifier_case_words= ` 카운터가 추가되었고,
4530+ 집계에 ` checked_identifier_case_words=90,145 ` (66개 파일 + 30개 회귀)로
4531+ 반영된다. 수정 전 포맷터로 실행하면 ` r.NAME ` , ` oqt_mega_pkg.SEED ` ,
4532+ ` SYSTEM.help ` , ` GOTO TOP ` , ` PROCEDURE SEED ` 가 모두 FAIL로 검출된다.
4533+
4534+ 고정 회귀 테스트 9개 추가:
4535+
4536+ - formatter: ` oracle_dot_qualified_identifiers_preserve_source_case ` ,
4537+ ` oracle_goto_label_target_preserves_source_case ` ,
4538+ ` oracle_cte_and_table_names_matching_keywords_preserve_case ` ,
4539+ ` oracle_procedure_named_like_keyword_preserves_case ` ,
4540+ ` oracle_merge_source_alias_preserves_case ` ,
4541+ ` oracle_column_list_members_matching_keywords_preserve_case `
4542+ - sweep: ` formatting_sweep_detects_identifier_case_change_next_to_dot ` ,
4543+ ` formatting_sweep_detects_identifier_case_change_after_goto_and_procedure ` ,
4544+ ` formatting_sweep_accepts_keyword_case_normalization_outside_identifier_slots `
4545+
4546+ ## 31-4. 검토 결과 의도된 설계로 판정한 항목 (변경 없음)
4547+
4548+ 1 . ` CALL proc(arg,\n arg\n); ` — 닫는 괄호 단독 줄. CALL 인자 목록은
4549+ ` paren_opens_call_argument_list ` → ` WrappedLayout ` 프레임으로 명시 분류되어
4550+ 있고(OVER/WINDOW/MODEL RULES/ON DUPLICATE 다중행 계열과 동일), 해당 배치를
4551+ 고정하는 회귀 테스트가 이미 존재한다. 함수 호출식 ` ');' ` 부착과 다른 것은
4552+ 구조 프레임 계열의 일관된 규약.
4553+ 2 . MySQL ` DESCRIBE t ` / ` SHOW PROCESSLIST ` 등 5종의 문말 ` ; ` 제거와
4554+ SQL* Plus ` VARIABLE ` →` VAR ` 정규화 — 실행기(QueryExecutor)의 tool-command
4555+ 에뮬레이션과 공유되는 canonical 렌더링(` format_tool_command ` )이다.
4556+ 포맷터 단독으로 바꾸면 실행 모델과 어긋나므로 유지. 단, 같은 SHOW 계열
4557+ 안에서 서버 전송형(` SHOW DATABASES; ` )과 도구 명령형(` SHOW PROCESSLIST ` )의
4558+ 표기가 갈리는 점은 추후 tool-command 목록 차원의 정리 후보로 기록한다.
4559+
4560+ ## 31-5. [ 수정] 케이스 보존의 일관성 보강 (후속)
4561+
4562+ 31-2 적용 후 전체 테스트에서 드러난 두 가지 비일관성을 함께 정리했다.
4563+
4564+ ### 제어 키워드 별칭의 혼합 케이스
4565+
4566+ AS-IS (31-2 직후):
4567+
4568+ ``` sql
4569+ -- 소스: delete from sales if where if.id = 1
4570+ DELETE FROM sales IF -- 별칭 선언부는 대문자
4571+ WHERE if .id = 1 ; -- dot 참조부는 소문자 (혼합)
4572+ ```
4573+
4574+ TO-BE:
4575+
4576+ ``` sql
4577+ DELETE FROM sales if
4578+ WHERE if .id = 1 ; -- 같은 별칭은 어디서나 소스 케이스
4579+ ```
4580+
4581+ ` treat_control_keyword_as_identifier ` 는 지금까지 구조 처리만 억제하고 케이스는
4582+ 대문자화했다. 새 플래그 ` control_keyword_alias_preserves_case ` 를 도입해, 별칭
4583+ 슬롯 판정을 받은 제어 키워드 중 직전 유의미 토큰이 별칭 자리를 증명하는 경우
4584+ (비키워드 단어 = 테이블/컬럼명, 또는 파생 테이블의 ` ) ` )에만 케이스도 소스를
4585+ 따르게 했다. ` PROCEDURE p IS begin ` (IS 뒤)이나 ` WHEN NOT MATCHED then `
4586+ (MATCHED 뒤)처럼 이웃이 키워드인 경우는 키워드 케이싱을 유지한다 — 첫 시도에서
4587+ 이 두 슬롯이 소문자로 남는 회귀가 전체 테스트 8건으로 드러나 조건을 좁혔다.
4588+ (test12/test13 픽스처는 소스가 애초에 대문자 ` IF ` /` RANK ` /` COUNT ` 라 출력 변화
4589+ 없음 — 소문자 ` trim ` 별칭이 보존되던 기존 동작과 이제 대칭이 된다.)
4590+
4591+ ### ORDER/GROUP BY 머리 뒤 식별자
4592+
4593+ AS-IS: ` SELECT remark, ... ORDER BY REMARK; ` — 같은 컬럼이 SELECT 리스트에선
4594+ 소문자, ORDER BY에선 대문자.
4595+
4596+ TO-BE: ` ORDER BY remark; ` — ` BY ` (ORDER/GROUP/PARTITION 절)를 절 머리로
4597+ 인정해 그 뒤 단어도 동일한 식별자 슬롯 판정을 받는다.
4598+
4599+ ### 기대값 갱신 (구 동작을 고정하던 테스트 13개)
4600+
4601+ - ` comma_list_layout_tests ` 3개: ` NAME ` → ` name ` (SELECT 리스트 멤버 보존)
4602+ - ` format_sql_basic_indents_forall_{insert,update,delete}_body ` +
4603+ ` format_sql_basic_nested_forall_update_body_uses_forall_body_depth ` :
4604+ ` v_arr.COUNT ` → ` v_arr.count ` (dot 뒤 컬렉션 메서드 케이스 보존)
4605+ - ` format_sql_basic_oracle_remark_identifier_does_not_become_script_command ` :
4606+ ` REMARK ` → ` remark ` 3곳 (테스트 제목 그대로 "identifier" 판정)
4607+ - IF-별칭 테스트 5개: ` sales IF ` /` IF.id ` → ` sales if ` /` if.id `
4608+ - ` format_sql_basic_keeps_keyword_like_implicit_select_aliases_inline_lowercase ` :
4609+ ` amount IF, ` /` total END ` → ` amount if, ` /` total end `
4610+
4611+ 이 테스트들의 원래 목적(인라인 유지·블록 키워드 오인 방지·FORALL 본문 들여쓰기)
4612+ 은 그대로 검증되며, 케이스 기대값만 새 규칙에 맞춰 바뀌었다.
4613+
4614+ ## 31-6. 최종 품질 게이트
4615+
4616+ | 검증 | 결과 |
4617+ | --- | --- |
4618+ | ` formatting_sweep_all_files_generate_out_report ` | 통과 — 66개 파일, 30,120 frames, 60,919 boundaries, 15,154 closes, failures 0 |
4619+ | 신규 IdentifierCase 감사 | 90,145 단어쌍 검사, 위반 0 (수정 전 포맷터 기준이면 r.NAME 등 다수 FAIL) |
4620+ | 전수 상세 검토 | 66개 파일 · 53,361줄 — Oracle 43개 전량 정독, MySQL/MariaDB 토큰 diff + 기계 스캔 + 표본 정독, 잔여 미해결 오류 0 |
4621+ | 수정 항목 | 식별자 케이스 침범(31-2) + 일관성 보강(31-5), 산출물 상 대문자화 오류 약 150여 토큰 해소 |
4622+ | 회귀 고정 | 신규 테스트 9개 + 기존 테스트 14개 기대값 갱신 |
4623+ | ` cargo test ` | 통과 — 전 타깃 합계 6,781 passed · 0 failed · 238 ignored |
4624+ | ` cargo clippy --locked --all-targets -- -D warnings -W clippy::perf -W clippy::complexity ` | 통과, 경고 0 |
0 commit comments