쿼리 편집기 — Enter 가 새던 것과 지나간 검증 결과

## Enter 한 번에 변수를 못 넣은 채 저장되고 닫혔다

치환 변수 검색칸에 KeyDown 이 없었고 확인 버튼은 IsDefault 다.
그래서 '차트' 를 치고 Enter 를 누르면 검색도 삽입도 아니라
<b>SQL 이 저장되고 창이 닫힌다</b>. 변수를 하나도 못 넣은 채 편집이 끝나는데,
무엇이 일어났는지 알기도 어렵다 — 창이 사라졌으니 되짚을 화면이 없다.

Enter 는 지금 걸러진 목록의 선택 항목을 넣는다(검색으로 하나까지 좁혀진 경우가 대부분이다).
↓ 는 목록으로 이동한다 — 손을 마우스로 옮기지 않고 훑을 수 있어야 한다.
둘 다 e.Handled 로 기본 버튼에 닿지 않게 막는다.

## 검증한 뒤 SQL 을 고쳐도 초록 요약이 그대로 남았다

"검증했다"고 믿고 저장하는데 저장되는 것은 <b>검증하지 않은 문장</b>이다.
검증 시점의 원문을 기억해 두고, 지금 텍스트와 다르면 결과 칸을 흐리게 낮추고
요약 앞에 '(수정됨 — 다시 실행하세요)'를 붙인다.

<b>결과를 지우지는 않는다.</b> 방금 본 컬럼명이 사라지면 그것대로 불편하다 —
배선 필드 칸에 옮겨 적는 중일 수 있다. 유효하지 않다는 사실만 말한다.

## 판정

- 단위 시험 388 · edit-smoke 390건 전건 통과
- --query-popup(자동완성 팝업 진단) 전건 통과
- --dialog-shots 69장 · --modal-check · --maxrect 전건 통과
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변

## 남은 것

조사에서 함께 나온 것 중 이번에 안 한 것들 — '돌린 SQL 보기'(치환을 마친 문장을 창 안에서 확인),
0행일 때도 컬럼 머리글 남기기, 치환 변수를 안 쓰는 쿼리에 늘 붙는 '구문만 검사' 꼬리표 제거,
인스펙터로 연 편집기에서 컨트롤 변수 칩이 사라지는 것.
전부 근거가 잡혀 있고 다음 회차에 이어서 할 수 있다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-21 08:44:53 +09:00
co-authored by Claude Fable 5
parent e162d0fb6b
commit d83478619e
2 changed files with 67 additions and 0 deletions
@@ -422,8 +422,11 @@
<!-- 검색: 돋보기를 겹쳐 둔다 — 빈 상자만 있으면 무엇을 하는 칸인지 안 읽힌다 --> <!-- 검색: 돋보기를 겹쳐 둔다 — 빈 상자만 있으면 무엇을 하는 칸인지 안 읽힌다 -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,8"> <Grid DockPanel.Dock="Top" Margin="0,0,0,8">
<!-- KeyDown 필수 — 확인 버튼이 IsDefault 라 이 칸의 Enter 가
'저장하고 닫기'로 새어 나간다(변수를 못 넣은 채 편집이 끝난다) -->
<TextBox x:Name="VariableSearch" Padding="28,4,8,4" <TextBox x:Name="VariableSearch" Padding="28,4,8,4"
VerticalContentAlignment="Center" TextChanged="OnVariableSearchChanged" VerticalContentAlignment="Center" TextChanged="OnVariableSearchChanged"
KeyDown="OnVariableSearchKeyDown"
AutomationProperties.Name="치환 변수 검색" AutomationProperties.Name="치환 변수 검색"
ToolTip="변수명·설명·초성으로 찾습니다 (예: ㅊㅌㅂㅎ → 차트번호)"/> ToolTip="변수명·설명·초성으로 찾습니다 (예: ㅊㅌㅂㅎ → 차트번호)"/>
<TextBlock Text="🔍" FontSize="11" Margin="9,0,0,0" IsHitTestVisible="False" <TextBlock Text="🔍" FontSize="11" Margin="9,0,0,0" IsHitTestVisible="False"
@@ -41,6 +41,9 @@ public partial class QueryEditorWindow : Window
/// <summary>마지막으로 돌린 SQL — 진단이 "무엇을 돌렸는가"를 확인할 때 쓴다</summary> /// <summary>마지막으로 돌린 SQL — 진단이 "무엇을 돌렸는가"를 확인할 때 쓴다</summary>
private string lastTrialSql = string.Empty; private string lastTrialSql = string.Empty;
/// <summary>검증을 돌린 시점의 SQL 원문 — 그 뒤 고쳤는지 판정한다</summary>
private string trialBaseline = string.Empty;
/// <summary>진단 전용 — 검증 실행이 실제로 만든 SQL</summary> /// <summary>진단 전용 — 검증 실행이 실제로 만든 SQL</summary>
internal string LastTrialSql => lastTrialSql; internal string LastTrialSql => lastTrialSql;
@@ -552,6 +555,8 @@ public partial class QueryEditorWindow : Window
emptyPlaceholder: "NULL"); emptyPlaceholder: "NULL");
var probe = converted.Sql; var probe = converted.Sql;
lastTrialSql = probe; lastTrialSql = probe;
// 치환 <b>전</b> 원문을 기준으로 삼는다 — 사용자가 고치는 것은 이쪽이다
trialBaseline = SqlBox.Text;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
SheetMe.Data.Stores.QueryTrialResult result; SheetMe.Data.Stores.QueryTrialResult result;
@@ -736,8 +741,38 @@ public partial class QueryEditorWindow : Window
LengthText.Text = $"{text.Length:N0}자 · {CountLines(text)}줄"; LengthText.Text = $"{text.Length:N0}자 · {CountLines(text)}줄";
UpdateCaretText(); UpdateCaretText();
UpdateProblems(text); UpdateProblems(text);
RefreshTrialFreshness();
} }
/// <summary>
/// 검증 결과가 <b>지금 SQL 의 것인지</b> 표시한다.
///
/// 전에는 검증을 돌린 뒤 SQL 을 고쳐도 초록 요약과 결과 표가 그대로 남았다.
/// "검증했다"고 믿고 저장하는데, 저장되는 것은 <b>검증하지 않은 문장</b>이다.
/// 결과를 지우지는 않는다 — 방금 본 컬럼명이 사라지면 그것대로 불편하다.
/// 흐리게 낮추고 다시 돌리라고 말하는 것으로 충분하다.
/// </summary>
private void RefreshTrialFreshness()
{
if (TrialPane is null || TrialPane.Visibility != Visibility.Visible || trialBaseline.Length == 0)
{
return;
}
var stale = !string.Equals(trialBaseline, SqlBox.Text, StringComparison.Ordinal);
TrialPane.Opacity = stale ? 0.5 : 1.0;
if (stale && !TrialSummary.Text.StartsWith(StaleMark, StringComparison.Ordinal))
{
TrialSummary.Text = StaleMark + TrialSummary.Text;
}
else if (!stale && TrialSummary.Text.StartsWith(StaleMark, StringComparison.Ordinal))
{
TrialSummary.Text = TrialSummary.Text[StaleMark.Length..];
}
}
/// <summary>검증 이후 SQL 이 바뀌었을 때 요약 앞에 붙는 표시</summary>
private const string StaleMark = "(수정됨 — 다시 실행하세요) ";
/// <summary>줄·열 표시 — 오류 메시지의 위치를 찾아갈 때 쓴다</summary> /// <summary>줄·열 표시 — 오류 메시지의 위치를 찾아갈 때 쓴다</summary>
private void UpdateCaretText() private void UpdateCaretText()
{ {
@@ -1047,6 +1082,35 @@ public partial class QueryEditorWindow : Window
#endregion #endregion
#region Methods - Variables #region Methods - Variables
/// <summary>
/// 변수 검색칸의 Enter·↓ — <b>확인 버튼으로 새어 나가는 것을 막는다</b>.
///
/// 확인 버튼이 <c>IsDefault</c> 라, 이 칸에서 Enter 를 누르면 검색이나 삽입이 아니라
/// <b>SQL 이 저장되고 창이 닫혔다</b>. '차트' 를 치고 Enter 를 눌렀을 뿐인데
/// 변수를 하나도 못 넣은 채 편집이 끝난다 — 무엇이 일어났는지도 알기 어렵다.
///
/// Enter 는 지금 걸러진 목록의 선택 항목을 넣는다(필터가 하나로 좁혀졌을 때가 대부분이다).
/// ↓ 는 목록으로 이동한다 — 손을 마우스로 옮기지 않고 훑을 수 있어야 한다.
/// </summary>
private void OnVariableSearchKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
InsertSelectedVariable();
e.Handled = true; // IsDefault 버튼에 도달시키지 않는다
return;
}
if (e.Key == Key.Down && VariableList.Items.Count > 0)
{
VariableList.Focus();
if (VariableList.SelectedIndex < 0)
{
VariableList.SelectedIndex = 0;
}
e.Handled = true;
}
}
private void OnVariableSearchChanged(object sender, TextChangedEventArgs e) private void OnVariableSearchChanged(object sender, TextChangedEventArgs e)
{ {
SearchPlaceholder.Visibility = VariableSearch.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; SearchPlaceholder.Visibility = VariableSearch.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed;