001package gudusoft.gsqlparser.ir.semantic; 002 003import java.util.Objects; 004 005/** 006 * Per-statement row-limit metadata. Emitted by the builder when a 007 * single-SELECT {@code LIMIT N}, {@code FETCH FIRST N ROWS ONLY}, 008 * {@code TOP N}, or {@code OFFSET m [FETCH NEXT n ROWS ONLY]} clause 009 * is present and the count/offset expressions are resolvable from the 010 * parser AST. Column lineage (ORDER BY refs, output sources, etc.) is 011 * <i>unchanged</i> by row-limit metadata. 012 * 013 * <p>{@link #getCount() count} is the verbatim text of the count 014 * expression — what the SQL author wrote, e.g. {@code "10"}, 015 * {@code "100"}, {@code "ALL"}, {@code "NULL"}, or even {@code "-10"} 016 * if the parser admits a signed literal. This mirrors the slice-22 017 * {@code FrameBound.offsetLiteral} convention: capture presentation 018 * accurately, do not impose a numeric interpretation that would be 019 * dialect-dependent or lossy for parameterized counts ({@code LIMIT ?}, 020 * {@code LIMIT :n}, {@code TOP (@v)}). Downstream governance code is 021 * responsible for parsing the count into a number when it needs one. 022 * 023 * <p>{@link #getCount() count} is normally non-null. The single 024 * exception is {@link RowLimitKind#OFFSET_FETCH} when the SQL author 025 * wrote only an offset and no fetch count 026 * (PG {@code OFFSET 5 [ROWS]}, Oracle/MSSQL {@code OFFSET 5 ROWS} 027 * without {@code FETCH NEXT}). In that case {@code count} is 028 * {@code null} and {@code offset} is non-null. 029 * 030 * <p>{@link #getOffset() offset} is non-null for any row-limit clause 031 * that carries an offset slot in the source SQL: 032 * <ul> 033 * <li>PG / MySQL / SQLite / BigQuery / Snowflake / Redshift inline 034 * {@code LIMIT N OFFSET M} (kind {@link RowLimitKind#LIMIT}).</li> 035 * <li>MySQL old-style {@code LIMIT M, N} (kind 036 * {@link RowLimitKind#LIMIT}; offset = M).</li> 037 * <li>PG {@code OFFSET m FETCH FIRST n} (kind 038 * {@link RowLimitKind#FETCH_FIRST}).</li> 039 * <li>Informix {@code SKIP m FIRST n} (kind 040 * {@link RowLimitKind#FETCH_FIRST}) and 041 * {@code SKIP m LIMIT n} (kind {@link RowLimitKind#LIMIT}).</li> 042 * <li>Oracle / MSSQL {@code OFFSET m ROWS [FETCH NEXT n ROWS ONLY]} 043 * (kind {@link RowLimitKind#OFFSET_FETCH}).</li> 044 * </ul> 045 * 046 * <p>Set-op outer row-limit (e.g. {@code UNION ALL ... LIMIT 5}) 047 * continues to be rejected by {@code rejectSetOpRowLimit} with 048 * {@code SET_OP_ROW_LIMIT_NOT_SUPPORTED} (slice 72 lifts). 049 * 050 * <p>Immutable. Value equality on (kind, count, offset). 051 */ 052public final class RowLimit { 053 054 private final RowLimitKind kind; 055 private final String count; 056 private final String offset; 057 058 public RowLimit(RowLimitKind kind, String count, String offset) { 059 if (kind == null) { 060 throw new IllegalArgumentException("kind must not be null"); 061 } 062 // Null count is legal only for OFFSET_FETCH offset-only form, 063 // where the SQL author wrote "OFFSET m" with no FETCH NEXT. 064 if (count == null) { 065 if (kind != RowLimitKind.OFFSET_FETCH) { 066 throw new IllegalArgumentException( 067 "count must not be null for kind " + kind 068 + "; null count is reserved for " 069 + "OFFSET_FETCH offset-only form"); 070 } 071 if (offset == null) { 072 throw new IllegalArgumentException( 073 "OFFSET_FETCH with null count requires a " 074 + "non-null offset"); 075 } 076 } 077 this.kind = kind; 078 this.count = count; 079 this.offset = offset; 080 } 081 082 /** 083 * Non-null. One of {@link RowLimitKind}'s four values; identifies 084 * the SQL surface form the user wrote so downstream consumers can 085 * distinguish e.g. {@code TOP N} from {@code LIMIT N}. 086 */ 087 public RowLimitKind getKind() { 088 return kind; 089 } 090 091 /** 092 * Verbatim text of the count expression 093 * ({@code TExpression.toString()}). Non-null for {@link RowLimitKind#LIMIT}, 094 * {@link RowLimitKind#FETCH_FIRST}, and {@link RowLimitKind#TOP}. 095 * May be {@code null} only for {@link RowLimitKind#OFFSET_FETCH} 096 * when the SQL author wrote only an offset and no fetch count 097 * ({@code OFFSET 5 ROWS} without {@code FETCH NEXT}). 098 * Downstream code is responsible for any numeric validation of 099 * the count text. 100 */ 101 public String getCount() { 102 return count; 103 } 104 105 /** 106 * Verbatim text of the offset expression 107 * ({@code TExpression.toString()}). Non-null whenever the source 108 * SQL carries an offset slot; {@code null} otherwise. See the 109 * class-level javadoc for the full enumeration of offset-bearing 110 * surfaces. 111 */ 112 public String getOffset() { 113 return offset; 114 } 115 116 @Override 117 public boolean equals(Object o) { 118 if (this == o) return true; 119 if (!(o instanceof RowLimit)) return false; 120 RowLimit other = (RowLimit) o; 121 return kind == other.kind 122 && Objects.equals(count, other.count) 123 && Objects.equals(offset, other.offset); 124 } 125 126 @Override 127 public int hashCode() { 128 return Objects.hash(kind, count, offset); 129 } 130 131 @Override 132 public String toString() { 133 return "RowLimit{" + kind + ", count=" + count 134 + ", offset=" + offset + "}"; 135 } 136}