using SheetMe.Core.Catalog;
namespace SheetMe.Core.Tests;
///
/// SQL 조각내기 테스트.
///
/// 색칠이 어긋나면 편집기가 거짓말을 한다 — 특히 치환 변수와 '깨진 조각' 판정이 그렇다.
/// 이 쿼리는 EMR 런타임에서 돌고 실패해도 빈 catch 가 삼키므로,
/// 편집기에서 눈에 띄지 않으면 배포된 뒤에야 드러난다.
///
[TestClass]
public sealed class SqlTokenizerTests
{
private static SqlTokenKind KindAt(string text, int index)
{
foreach (var token in SqlTokenizer.Tokenize(text))
{
if (index >= token.Start && index < token.End)
{
return token.Kind;
}
}
return SqlTokenKind.Plain;
}
private static string PieceOf(string text, SqlTokenKind kind)
{
foreach (var token in SqlTokenizer.Tokenize(text))
{
if (token.Kind == kind)
{
return text.Substring(token.Start, token.Length);
}
}
return string.Empty;
}
/// 조각은 겹치지 않고 전 구간을 덮어야 한다 — 빈틈이 있으면 그 글자가 안 그려진다
[TestMethod]
public void Tokenize_CoversEveryCharacterExactlyOnce()
{
const string sql = "SELECT NVL(a,0) /* c */ FROM t WHERE x='y' -- 끝\nAND z=1";
var tokens = SqlTokenizer.Tokenize(sql);
var position = 0;
foreach (var token in tokens)
{
Assert.AreEqual(position, token.Start, $"빈틈 또는 겹침: {token.Start} 에서");
position = token.End;
}
Assert.AreEqual(sql.Length, position, "마지막까지 덮지 못했습니다");
}
[TestMethod]
public void Tokenize_MarksKeywordsAndFunctions()
{
const string sql = "SELECT NVL(x,0) FROM DUAL";
Assert.AreEqual(SqlTokenKind.Keyword, KindAt(sql, 0), "SELECT");
Assert.AreEqual(SqlTokenKind.Function, KindAt(sql, 7), "NVL");
Assert.AreEqual(SqlTokenKind.Keyword, KindAt(sql, 16), "FROM");
}
/// 예약어는 대소문자를 가리지 않는다 — 실서식에 소문자 쿼리가 섞여 있다
[TestMethod]
public void Tokenize_KeywordsAreCaseInsensitive()
=> Assert.AreEqual(SqlTokenKind.Keyword, KindAt("select 1 from dual", 0));
/// 식별자 안에 든 예약어 조각은 예약어가 아니다(SELECTED 를 SELECT 로 칠하면 안 된다)
[TestMethod]
public void Tokenize_DoesNotMatchKeywordInsideIdentifier()
=> Assert.AreEqual(SqlTokenKind.Plain, KindAt("SELECTED_FLAG", 0));
[TestMethod]
public void Tokenize_FindsStringsAndComments()
{
Assert.AreEqual("'가나'", PieceOf("WHERE nam='가나'", SqlTokenKind.Text));
Assert.AreEqual("-- 설명", PieceOf("SELECT 1 -- 설명\nFROM t", SqlTokenKind.Comment));
Assert.AreEqual("/* 블록 */", PieceOf("SELECT /* 블록 */ 1", SqlTokenKind.Comment));
}
/// Oracle 은 '' 로 작은따옴표를 이스케이프한다 — 거기서 문자열이 끊기면 안 된다
[TestMethod]
public void Tokenize_HandlesDoubledQuoteEscape()
=> Assert.AreEqual("'it''s'", PieceOf("WHERE x='it''s'", SqlTokenKind.Text));
///
/// 치환 변수는 이 편집기의 핵심 조각이다 — 통째로 하나로 잡혀야 한다.
/// 안쪽 점·대문자 때문에 여러 조각으로 쪼개지면 색이 얼룩진다.
///
[TestMethod]
public void Tokenize_TreatsSubstitutionVariableAsOnePiece()
{
const string sql = "WHERE ChtNum = <>";
Assert.AreEqual("<>",
PieceOf(sql, SqlTokenKind.Variable));
}
[TestMethod]
public void VariablesIn_ListsEveryToken()
{
const string sql = "SELECT <> , <> FROM t";
CollectionAssert.AreEqual(new[] { "<>", "<>" }, SqlTokenizer.VariablesIn(sql).ToArray());
}
///
/// 닫히지 않은 조각은 Broken 이다. 그대로 저장하면 런타임이 조용히 실패하므로
/// 편집기에서 즉시 눈에 띄어야 한다.
///
[DataTestMethod]
[DataRow("WHERE x = < t.Kind == SqlTokenKind.Broken));
// 다음 줄의 AND 는 정상 예약어로 남아야 한다
Assert.AreEqual(SqlTokenKind.Keyword, KindAt(sql, sql.IndexOf("AND", StringComparison.Ordinal)));
}
[TestMethod]
public void Tokenize_EmptyTextYieldsNothing()
=> Assert.AreEqual(0, SqlTokenizer.Tokenize(string.Empty).Count);
}