001package gudusoft.gsqlparser.ir.semantic; 002 003/** 004 * One end of a window frame (slice 22). Represents either {@code start} or 005 * {@code end} of a {@link WindowFrame}. 006 * 007 * <p>{@link Kind#PRECEDING} and {@link Kind#FOLLOWING} optionally carry an 008 * {@code offsetLiteral} string ("1", "INTERVAL '7' DAY", etc.) — the literal 009 * offset spelled by the SQL author. 010 * 011 * <p><b>{@code offsetLiteral} is presentation text, not canonical text.</b> 012 * It is captured directly from {@code TExpression.toString()} on the parser 013 * side, which preserves SQL whitespace and casing. Two SQLs that spell the 014 * same offset differently (e.g. {@code 1} vs {@code 01} vs {@code 1.0}) MUST 015 * NOT be expected to produce equal {@code offsetLiteral}; JSON byte-stability 016 * is only a contract for the same SQL parsed twice. 017 * 018 * <p>The slice-22 builder rejects offsets whose AST type is not 019 * {@code simple_constant_t} — column refs ({@code simple_object_name_t}) and 020 * parenthesised expressions ({@code parenthesis_t}) are reachable in 021 * PostgreSQL / ANSI grammars but rejected at build time. 022 * 023 * <p>{@link Kind#UNBOUNDED_PRECEDING} / {@link Kind#UNBOUNDED_FOLLOWING} / 024 * {@link Kind#CURRENT_ROW} forbid an {@code offsetLiteral}; the constructor 025 * fails fast if one is supplied. 026 * 027 * <p>Bare {@link Kind#PRECEDING} / {@link Kind#FOLLOWING} without an 028 * explicit offset is permitted by this constructor for forward-compat / a 029 * closed domain, but the slice-22 probe across nine vendor grammars showed 030 * no parser surface emits this shape today. 031 */ 032public final class FrameBound { 033 034 public enum Kind { 035 UNBOUNDED_PRECEDING, 036 UNBOUNDED_FOLLOWING, 037 CURRENT_ROW, 038 PRECEDING, 039 FOLLOWING 040 } 041 042 private final Kind kind; 043 private final String offsetLiteral; 044 045 public FrameBound(Kind kind, String offsetLiteral) { 046 if (kind == null) { 047 throw new IllegalArgumentException("kind must not be null"); 048 } 049 boolean offsetAllowed = (kind == Kind.PRECEDING || kind == Kind.FOLLOWING); 050 if (offsetLiteral != null && !offsetAllowed) { 051 throw new IllegalArgumentException( 052 "offsetLiteral allowed only with PRECEDING/FOLLOWING, got " + kind); 053 } 054 this.kind = kind; 055 this.offsetLiteral = offsetLiteral; 056 } 057 058 public Kind getKind() { 059 return kind; 060 } 061 062 public String getOffsetLiteral() { 063 return offsetLiteral; 064 } 065}