001package gudusoft.gsqlparser.dlineage.dataflow.model; 002 003import gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment; 004 005/** 006 * One unresolved fragment ("hole") of a dynamic-SQL site's argument: a value the 007 * abstract evaluator could not fold to exact text. Exposed by 008 * {@link DynamicSqlSite#getUnresolvedFragments()} so a consumer can say WHICH 009 * value stopped the rebuild and where that value comes from, rather than only 010 * that the site was not rebuilt. 011 * 012 * <p>{@link #getName()} is the variable / parameter / function the hole stands 013 * for ({@code @Where}, {@code @HuidigOutboundRunID}, {@code CAST}); it is null 014 * only when the producer had no name. {@link #getText()} is how the hole is 015 * rendered inside {@link DynamicSqlSite#getMaterializedText()}. 016 * {@link #getValueOrigin()} describes the data source the runtime value would be 017 * read from when the evaluator saw it — {@code dbo.SSF_Entity.Entity} for a 018 * variable assigned by {@code SELECT @entity = Entity FROM dbo.SSF_Entity}. 019 * 020 * <p>The span is the fragment's <em>origin</em> in the analyzed text — the 021 * assignment target of a query-assigned or flow-dependent variable, the 022 * declaration of an unbound parameter or of a declared-but-unassigned 023 * variable, otherwise the reference that produced the hole — so a consumer can 024 * highlight it with the same locate-in-source control it uses for the site. 025 * Positions are 1-based with an end-exclusive end column, the convention of 026 * {@link DynamicSqlSite#getStartPosition()}; {@code -1} when the origin is not 027 * in the analyzed text. 028 */ 029public final class UnresolvedFragment { 030 031 private final SqlFragment.HoleOrigin kind; 032 private final String name; 033 private final String text; 034 private final String reason; 035 private final boolean valueUnknown; 036 private final String valueOrigin; 037 private final int startLine; 038 private final int startColumn; 039 private final int endLine; 040 private final int endColumn; 041 042 public UnresolvedFragment(SqlFragment.HoleOrigin kind, String name, String text, String reason, 043 boolean valueUnknown, String valueOrigin, int startLine, int startColumn, 044 int endLine, int endColumn) { 045 this.kind = kind == null ? SqlFragment.HoleOrigin.UNSUPPORTED_EXPR : kind; 046 this.name = name; 047 this.text = text == null ? "" : text; 048 this.reason = reason; 049 this.valueUnknown = valueUnknown; 050 this.valueOrigin = valueOrigin; 051 this.startLine = startLine; 052 this.startColumn = startColumn; 053 this.endLine = endLine; 054 this.endColumn = endColumn; 055 } 056 057 /** The fragment view of a non-literal {@link SqlFragment}; {@code null} for a literal. */ 058 public static UnresolvedFragment of(SqlFragment fragment) { 059 if (fragment == null || fragment.isLiteral()) { 060 return null; 061 } 062 return new UnresolvedFragment(fragment.origin, fragment.originName, fragment.text, 063 fragment.reason, fragment.valueUnknown, fragment.valueOrigin, 064 fragment.sourceStartLine, fragment.sourceStartColumn, 065 fragment.sourceEndLine, fragment.sourceEndColumn); 066 } 067 068 /** Why the hole exists: which producer limitation created it. */ 069 public SqlFragment.HoleOrigin getKind() { 070 return kind; 071 } 072 073 /** The variable / parameter / function / expression the hole stands for; may be null. */ 074 public String getName() { 075 return name; 076 } 077 078 /** The hole's rendering inside {@link DynamicSqlSite#getMaterializedText()}. Never null. */ 079 public String getText() { 080 return text; 081 } 082 083 /** Human-readable cause: why the value was not folded. */ 084 public String getReason() { 085 return reason; 086 } 087 088 /** 089 * True when the runtime VALUE is unknown; false for an inexact transform computed 090 * entirely from literals, whose value is known and only its byte-exact rendering is 091 * unproven (see {@link SqlFragment#valueUnknown}). 092 */ 093 public boolean isValueUnknown() { 094 return valueUnknown; 095 } 096 097 /** Where the runtime value would come from, when known; otherwise null. */ 098 public String getValueOrigin() { 099 return valueOrigin; 100 } 101 102 public int getStartLine() { 103 return startLine; 104 } 105 106 public int getStartColumn() { 107 return startColumn; 108 } 109 110 public int getEndLine() { 111 return endLine; 112 } 113 114 /** End-exclusive. */ 115 public int getEndColumn() { 116 return endColumn; 117 } 118 119 /** True when the origin span is known. */ 120 public boolean hasSpan() { 121 return startLine > 0 && startColumn > 0; 122 } 123 124 @Override 125 public String toString() { 126 StringBuilder sb = new StringBuilder(); 127 sb.append(name != null ? name : (text.isEmpty() ? "<" + kind + ">" : text)) 128 .append(" kind=").append(kind); 129 if (reason != null) { 130 sb.append(" reason=").append(reason); 131 } 132 if (valueOrigin != null) { 133 sb.append(" origin=").append(valueOrigin); 134 } 135 if (hasSpan()) { 136 sb.append(" span=[").append(startLine).append(',').append(startColumn).append("]-[") 137 .append(endLine).append(',').append(endColumn).append(']'); 138 } 139 return sb.toString(); 140 } 141}