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}