@@ -5816,3 +5816,186 @@ DB docker 이미지는 사용자 요청대로 **항상 한 번에 하나만**
58165816- ` src/ui/sql_editor/format_sweep_tests.rs ` : 의미 구문/JSON query-array 깊이 일반 감사,
58175817 내장 회귀와 감사 자체 테스트.
58185818- ` change.md ` : 본 AS-IS/TO-BE, 전수 판독, live 실행, 최종 검증 기록.
5819+
5820+ # 41. 자동 포맷 전수 재감사 · Oracle 연산자/JSON 값 프레임 교정 (2026-07-30)
5821+
5822+ ## 41-1. AS-IS / TO-BE
5823+
5824+ ` cargo test --lib formatting_sweep_all_files_generate_out_report -- --ignored --nocapture `
5825+ 첫 실행은 138개 산출물 모두 PASS였지만, 141,471줄을 처음부터 끝까지 직접 판독해
5826+ 기존 감사가 놓친 실제 결함 2종을 확인했다.
5827+
5828+ ### Oracle ` ^= ` 연산자 보존
5829+
5830+ AS-IS:
5831+
5832+ ``` sql
5833+ WHERE p .pos_id ^ = 999
5834+ ```
5835+
5836+ TO-BE:
5837+
5838+ ``` sql
5839+ WHERE p .pos_id ^= 999
5840+ ```
5841+
5842+ Oracle의 caret not-equal 연산자를 토크나이저가 ` ^ ` , ` = ` 두 symbol로 나누면서
5843+ 포매터가 공백을 삽입했다. 최적화/기준 토크나이저가 모두 ` ^= ` 를 단일 symbol로
5844+ 인식하게 수정했다.
5845+
5846+ source와 formatted를 같은 tokenizer로 지문 처리하는 기존 token fingerprint만으로는
5847+ tokenizer 자체가 두 symbol로 회귀하는 경우를 놓칠 수 있다. 따라서 sweep에 tokenizer의
5848+ compound-symbol 묶음 여부와 독립적인 compact-operator boundary 감사를 추가했다.
5849+ 원문에서 붙어 있던
5850+ ` <=> ` , ` <-> ` , ` <#> ` , ` ->> ` , ` << ` , ` >> ` , ` <= ` , ` >= ` , ` <> ` , ` != ` , ` ^= ` , ` || ` ,
5851+ ` ** ` , ` := ` , ` => ` , ` -> ` 가 출력에서도 불가분 경계를 유지하는지 순서대로 비교한다.
5852+
5853+ ### SQL/JSON ` VALUE CASE ` 의 값 소유 깊이
5854+
5855+ AS-IS:
5856+
5857+ ``` sql
5858+ ' flags' VALUE JSON_OBJECT (' remote' VALUE b .remote_flag ,
5859+ ' active' VALUE
5860+ CASE
5861+ WHEN b .status = ' ACTIVE' THEN ' true'
5862+ ELSE ' false'
5863+ END
5864+ )
5865+ ```
5866+
5867+ TO-BE:
5868+
5869+ ``` sql
5870+ ' flags' VALUE JSON_OBJECT (' remote' VALUE b .remote_flag ,
5871+ ' active' VALUE
5872+ CASE
5873+ WHEN b .status = ' ACTIVE' THEN ' true'
5874+ ELSE ' false'
5875+ END
5876+ )
5877+ ```
5878+
5879+ ` JSON_OBJECT ` /` JSON_OBJECTAGG ` 의 ` VALUE ` 가 값 표현식을 소유하지만 기존에는
5880+ ` AssignmentValue ` frame을 열지 않아 ` CASE ` 와 ` VALUE ` owner가 같은 깊이로
5881+ 렌더링됐다. Oracle SQL/JSON 괄호를 semantic flag로 식별하고 ` VALUE ` 에서 활성
5882+ 괄호의 자식 깊이를 owner로 하는 ` AssignmentValue ` frame을 연다. 물리 행의 선행
5883+ 공백은 사용하지 않으므로 첫 JSON 항목이 ` SELECT ` 행에 inline이어도 논리 깊이는
5884+ 흔들리지 않는다.
5885+
5886+ 값 전체가 괄호식인 경우에는 semantic value frame과 delimiter frame이 같은 문법
5887+ 간선을 표현하므로 하나의 깊이를 공유한다. 이 규칙으로 scalar subquery를
5888+ 불필요하게 두 단계 더 들여쓰는 첫 재스윕의 단일 감사 실패도 일반적으로 해소했다.
5889+
5890+ 검토 중 후보였던 ` MERGE ... VALUES ( ` 의 여러 줄 row 닫는 ` ) ` 는 수정하지 않았다.
5891+ ` VALUES ` 목록의 첫 row가 owner 행에 inline이어도 논리적으로는 목록 자식 깊이에
5892+ 있고, row 괄호의 닫힘은 자신이 닫는 row owner 깊이로 돌아온다. 이는
5893+ ` docs/auto_format_rule.md ` 의 첫 자식 inline 및 closing-boundary 규칙에 맞는
5894+ 의도된 출력이다.
5895+
5896+ ## 41-2. 자동 감사와 회귀 고정
5897+
5898+ - ` tokenize_sql_treats_oracle_caret_not_equal_as_single_symbol ` : 최적화 토크나이저와
5899+ 기준 토크나이저가 모두 ` ^= ` 를 단일 token으로 보존하는지 확인한다.
5900+ - ` format_for_auto_formatting_preserves_oracle_caret_not_equal_operator ` : 실제 formatter
5901+ 출력, frame 감사, 멱등성에서 ` ^= ` 보존을 고정한다.
5902+ - ` format_for_auto_formatting_nests_json_object_value_case_under_value_owner ` : ` VALUE ` ,
5903+ ` CASE ` , ` WHEN ` /` ELSE ` , ` END ` 의 owner→child 깊이와 frame 감사, 멱등성을 고정한다.
5904+ - ` formatting_sweep_detects_split_compact_operator_boundary ` : tokenizer 자체 회귀가
5905+ 있더라도 원문의 ` ^= ` 가 ` ^ = ` 로 분리되면 compact-operator boundary 감사가
5906+ ` ItemOrTokenChanged ` 를 보고하는지 확인한다.
5907+ - 기존
5908+ ` format_for_auto_formatting_keeps_json_object_value_scalar_subquery_on_paren_frame_depth ` 에
5909+ frame 감사 assertion을 추가해 value frame과 괄호 frame의 단일 간선 공유를
5910+ 고정한다.
5911+ - 새 ` AssignmentValue ` frame은 기존 ` FormatFrameAlignmentAudit ` 에 그대로 등록되므로
5912+ parent→child가 정확히 ` +1 ` 인지, 첫 body token이 frame 깊이에 있는지, frame이
5913+ 유효 scope 밖으로 누수되지 않는지를 전체 sweep에서 자동 검사한다.
5914+ - built-in inline-query 회귀도 ` JSON_OBJECT VALUE CASE ` 와 ` VALUE (SELECT ...) ` 를 한
5915+ 문장에 포함하도록 확장해 두 value 형태가 매 exact sweep에서 같은 typed-frame
5916+ 감사를 반드시 통과한다. Oracle ` ^= ` built-in 회귀도 함께 추가했다.
5917+
5918+ ## 41-3. 생성 결과 전수 판독과 재검토
5919+
5920+ 수정 후 생성된 138개 ` .format.out ` 의 141,471줄을 모두 처음부터 끝까지 직접
5921+ 판독했다. footer의 PASS 표시는 판정 근거로 사용하지 않고 owner→child 깊이,
5922+ 형제 정렬, close 복귀, 토큰 순서, 주석/리터럴 격리를 행 단위로 확인했다.
5923+
5924+ - 임시 detached worktree에서 수정 전 ` HEAD ` exact sweep을 다시 실행해 baseline
5925+ 138개를 재현했다.
5926+ - 현재 산출물과 상대 경로별 SHA-256을 비교한 결과 124개는 byte-identical이고,
5927+ Oracle 7개 fixture × 2 layout의 14개가 변경됐으며 누락 파일은 0개였다.
5928+ - compact-operator/built-in 감사 보강 후 exact sweep을 다시 실행한 최종 138개는
5929+ 전수 판독본과 138/138 byte-identical이었다. 전체 manifest SHA-256은
5930+ ` 2bbde88b6df4a9f1f7408b366db0252f79c4220c849666ea302cf73eb833eb15 ` 다.
5931+ - 실제 ` [[FMT:E숫자...]] ` marker, ` status: FAIL ` , 0이 아닌 issue 합계는 모두 0건.
5932+
5933+ 최종 산출물:
5934+
5935+ | layout / dialect | 파일 수 | 줄 수 |
5936+ | --- | ---: | ---: |
5937+ | wrapped / Oracle | 43 | 30,851 |
5938+ | wrapped / MySQL | 12 | 12,700 |
5939+ | wrapped / MariaDB | 14 | 14,907 |
5940+ | stacked / Oracle | 43 | 41,548 |
5941+ | stacked / MySQL | 12 | 19,083 |
5942+ | stacked / MariaDB | 14 | 22,382 |
5943+ | 합계 | 138 | 141,471 |
5944+
5945+ 최종 exact sweep은 88.71초에 PASS했다.
5946+
5947+ | 감사 항목 | wrapped | stacked |
5948+ | --- | ---: | ---: |
5949+ | checked files | 69 | 69 |
5950+ | checked regressions | 34 | 34 |
5951+ | identifier-case words | 152,339 | 152,339 |
5952+ | frames / frame boundaries | 42,027 / 88,740 | 42,027 / 88,740 |
5953+ | frame-depth symmetries | 4,889 | 5,570 |
5954+ | frame body items | 15,873 | 40,209 |
5955+ | frame closes | 19,682 | 19,682 |
5956+ | failures | 0 | 0 |
5957+
5958+ ## 41-4. 포맷 이후 Space Query live 실행
5959+
5960+ DB 컨테이너는 Oracle → 중지 → MySQL → 중지 → MariaDB → 중지 순으로 실행해
5961+ 항상 하나만 실행했다. 최종 확인 시 모든 DB 컨테이너가 정지 상태였다.
5962+
5963+ 각 fixture 원문을 Wrapped와 Stacked로 포맷한 후 dialect별 SPACE Query production
5964+ 배치 경로로 실제 DB에 실행했다. 모든 파일이 성공 statement를 하나 이상 냈고
5965+ 실패 statement/event는 없었다.
5966+
5967+ | DB | fixture × layout | successful statements | 결과 |
5968+ | --- | ---: | ---: | --- |
5969+ | Oracle Free | 43 × 2 | 6,994 | PASS |
5970+ | MySQL 8.0.46 | 12 × 2 | 2,532 | PASS |
5971+ | MariaDB 12.2.2 | 14 × 2 | 3,086 | PASS |
5972+ | 합계 | 69개 원본 / 138회 포맷 실행 | 12,612 | 실패 0 |
5973+
5974+ 사용한 ignored live harness:
5975+
5976+ - ` oracle_thin_query_tool_runs_all_formatted_fixture_scripts_without_errors `
5977+ - ` execute_all_formatted_mysql_fixture_scripts_without_errors `
5978+ - ` execute_all_formatted_mariadb_fixture_scripts_without_errors `
5979+
5980+ ## 41-5. 최종 품질 게이트
5981+
5982+ | 검증 | 결과 |
5983+ | --- | --- |
5984+ | ` cargo test --lib formatting_sweep_all_files_generate_out_report -- --ignored --nocapture ` | PASS — 88.71초, 138 산출물, 141,471줄, failures 0 |
5985+ | 수정 전 baseline byte 비교 | PASS — 124개 동일, Oracle 14개 변경, 누락 0 |
5986+ | 최종 재생성 byte 비교 | PASS — 전수 판독본과 138/138 동일, manifest SHA-256 ` 2bbde88b… ` |
5987+ | 포맷 후 Space Query live 실행 | PASS — 138회, 12,612 successful statements, 실패 0 |
5988+ | ` cargo test ` | PASS — 7,107 passed, 0 failed, 208 ignored |
5989+ | ` cargo clippy --locked --all-targets -- -D warnings -W clippy::perf -W clippy::complexity ` | PASS — 경고 0 |
5990+ | ` cargo fmt --all -- --check ` / ` git diff --check ` | PASS |
5991+
5992+ ## 41-6. 변경 파일
5993+
5994+ - ` src/ui/sql_editor/query_text.rs ` : Oracle ` ^= ` compound symbol의 두 토크나이저 경로와
5995+ 회귀 테스트.
5996+ - ` src/ui/sql_editor/format_sweep_tests.rs ` : compound-token grouping 독립
5997+ compact-operator boundary 감사/자체 테스트, Oracle ` ^= ` built-in 회귀,
5998+ JSON ` VALUE CASE ` frame 회귀 확장.
5999+ - ` src/ui/sql_editor/formatter.rs ` : SQL/JSON ` VALUE ` 의 ` AssignmentValue ` frame,
6000+ parenthesized whole-value depth 공유, CASE/스칼라 서브쿼리 회귀 테스트.
6001+ - ` change.md ` : 본 AS-IS/TO-BE, 전수 판독, live 실행, 최종 검증 기록.
0 commit comments