검증 실행이 치환 변수를 지우던 것을 고친다 — 그 쿼리들이 통과할 수 없었다
select * from P_PATINF where PATCHTNUM = <<…bzPatientInfo.ChtNum>> → ORA-00936: 누락된 표현식 원인은 오라클이 아니라 우리 쪽이었다. 검증 실행이 <<…>> 를 전부 빈 문자열로 지운 뒤 보냈다 — "WHERE PATCHTNUM = " 가 된다. 런타임이 값 없을 때 그렇게 한다는 이유였지만(clsMDataTable.vb:67-71), 그 결과 <b>치환 변수를 값 자리에 쓴 쿼리는 검증 실행을 통과할 수가 없었다.</b> 정작 이 기능이 존재하는 이유가 그런 쿼리들이다. ## 두 갈래로 나눴다 환자를 골랐으면 <b>실제 값</b>으로 치환한다 — 구문뿐 아니라 값까지 확인된다. 안 골랐으면 빈 자리를 NULL 로 채운다. 따옴표 안이든 밖이든 문법이 성립하고 0행이 나오므로 테이블·컬럼 오타와 구문 오류는 그대로 걸린다. 자리표시는 QuerySubstitution.Apply 의 선택 인자다(기본은 레거시와 같이 빈 문자열). 실행 경로(미리보기)는 쓰지 않는다 — 거기서는 레거시와 같은 SQL 이어야 한다. 내역(Hits)에는 실제 판정을 남긴다. 자리표시로 채운 것을 "값이 있었다"로 적으면 화면이 "값이 나왔다"고 말하게 되고, 그게 이 기능에서 가장 위험한 거짓말이다. ## 환자를 세션으로 올렸다 환자는 미리보기 창에서 고르는데 검증 실행도 같은 환자로 돌아야 한다. 창마다 따로 들고 있으면 같은 서식을 두 화면에서 서로 다른 환자로 보게 되고 그건 화면으로 구분할 수 없다. PatientSession 하나만 둔다(디스크에 쓰지 않는다). ## 무엇으로 돌렸는지 말한다 요약 첫 줄이 "환자 …의 실제 값으로 실행" 또는 "환자를 고르지 않아 변수를 NULL 로 두고 구문만 검사"다. 이 줄이 없으면 0행을 보고 "이 환자에게 자료가 없다"로 잘못 읽는다. 따옴표 밖에 놓인 변수도 짚는다 — 값이 원문 그대로 박히므로(clsMDataTable.vb:113) 문자 컬럼과 비교하려면 서식 쪽에서 '<<…>>' 로 감싸야 한다. 이 배선의 가장 흔한 실수다. 질문에 딸려 온 쿼리가 정확히 그 경우였다. ## 대조군이 함정을 잡았다 '따옴표로 감싸면 경고가 없다'가 처음에 통과했는데, 경고가 <b>아예 안 떠서</b> 양쪽이 다 "없음"이었다. 원인은 내 가드다 — 토큰이 문장 끝에 오면 건너뛰게 써 놨고, WHERE 절 마지막이 가장 흔한 자리다. 경계를 '따옴표 없음'으로 보도록 고쳤다. 대조군 없이 긍정 판정만 있었으면 이 기능은 조용히 아무것도 안 하고 있었을 것이다. ## 게이트 - dotnet test 323/323 - --query-popup 실패 0 (⑨-b 5건 신규: 구문 검사가 돌고 · 자리가 비지 않고 · 근거를 밝히고 · 따옴표 밖을 짚고 · 감싸면 안 짚는다) - --edit-smoke 실패 0 · --dialog-shots FAIL 0 - --db-patient ①~⑱ 전건 통과 - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3ef1937902
commit
8964c662f3
@@ -0,0 +1,55 @@
|
||||
using SheetMe.Core.Catalog;
|
||||
using SheetMe.Data.Stores;
|
||||
|
||||
namespace SheetMe.Designer.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 지금 고른 환자 — 앱 전체가 하나를 공유한다.
|
||||
///
|
||||
/// <b>왜 전역인가.</b> 환자는 미리보기 창에서 고르는데, 그 값이 필요한 곳은 거기만이 아니다 —
|
||||
/// 쿼리 편집기의 '검증 실행'도 같은 환자로 돌려야 의미가 있다.
|
||||
/// 창마다 따로 들고 있으면 <b>같은 서식을 두 화면에서 서로 다른 환자로</b> 보게 되고,
|
||||
/// 그건 화면으로 구분할 수 없다.
|
||||
///
|
||||
/// <b>하나만 둔다.</b> 여러 개를 허용하면 어느 것이 지금 것인지 알 수 없고,
|
||||
/// 환자 정보가 걸린 문제에서 그건 개인정보 사고가 된다.
|
||||
///
|
||||
/// 앱이 닫힐 때까지 메모리에 남는다. 디스크에 쓰지 않는다 —
|
||||
/// 환자 문맥은 로그에도 안 남기기로 한 값이다(<see cref="PatientPreviewPolicy.AuditLine"/>).
|
||||
/// </summary>
|
||||
public static class PatientSession
|
||||
{
|
||||
#region Member Fields
|
||||
/// <summary>환자가 바뀌었다 — 열려 있는 화면들이 다시 그려야 한다</summary>
|
||||
public static event EventHandler? Changed;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>지금 고른 환자의 문맥 — 안 골랐으면 null</summary>
|
||||
public static PatientContext? Current { get; private set; }
|
||||
|
||||
/// <summary>사람에게 보여 줄 한 줄 — 창 머리에 그대로 쓴다</summary>
|
||||
public static string Label { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>고른 환자가 있는가</summary>
|
||||
public static bool HasPatient => Current is not null;
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>환자를 붙이거나(문맥 전달) 뗀다(null 전달)</summary>
|
||||
public static void Set(PatientContext? context, string label)
|
||||
{
|
||||
Current = context;
|
||||
Label = context is null ? string.Empty : label;
|
||||
Changed?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 치환 변수원 — 환자가 없으면 null 이고, 호출부는 그때 자리표시로 구문만 검사해야 한다.
|
||||
/// </summary>
|
||||
public static IQueryVariableSource? Variables()
|
||||
=> Current is { } context
|
||||
? new PatientQueryVariableSource(context, UserSession.Current.UidCod)
|
||||
: null;
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user