001package gudusoft.gsqlparser.stmt.powerquery;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.ESqlStatementType;
005import gudusoft.gsqlparser.TCustomSqlStatement;
006import gudusoft.gsqlparser.TSourceToken;
007import gudusoft.gsqlparser.TSourceTokenList;
008import gudusoft.gsqlparser.nodes.TParseTreeVisitor;
009import gudusoft.gsqlparser.nodes.powerquery.TPowerQueryLetExpr;
010import gudusoft.gsqlparser.parser.powerquery.PowerQueryDocumentParser;
011
012import java.util.ArrayList;
013import java.util.List;
014
015/**
016 * A parsed Power Query M document.
017 *
018 * <p>After parsing, {@link #getLetExpression()} returns the root step graph.
019 * Lineage extraction (inner SQL dispatch for {@code Value.NativeQuery},
020 * navigation chain resolution) happens later in
021 * {@code TPowerQueryAnalyzer}, not here.  This class is deliberately
022 * lightweight — it runs the M-language recognizer over the token list and
023 * stores the result.
024 *
025 * <p>Diagnostics for unsupported constructs land in {@link #getWarnings()}.
026 * Following the plan's "never silent wrong lineage" rule, parse errors on
027 * individual steps are recorded but do not fail the whole document.
028 */
029public class TPowerQueryDocumentStmt extends TPowerQueryStmt {
030
031    private TPowerQueryLetExpr letExpression;
032    private final List<String> warnings = new ArrayList<>();
033
034    public TPowerQueryDocumentStmt(EDbVendor dbvendor) {
035        super(dbvendor);
036        sqlstatementtype = ESqlStatementType.sstpowerquerydocument;
037    }
038
039    public TPowerQueryLetExpr getLetExpression() {
040        return letExpression;
041    }
042
043    public void setLetExpression(TPowerQueryLetExpr letExpression) {
044        this.letExpression = letExpression;
045    }
046
047    public List<String> getWarnings() {
048        return warnings;
049    }
050
051    public void addWarning(String warning) {
052        if (warning != null) warnings.add(warning);
053    }
054
055    @Override
056    public int doParseStatement(TCustomSqlStatement psql) {
057        super.doParseStatement(psql);
058
059        TSourceTokenList allTokens = this.sourcetokenlist;
060        if (allTokens == null || allTokens.size() == 0) {
061            return 0;
062        }
063
064        List<TSourceToken> stmtTokens = collectStatementTokens();
065        if (stmtTokens.isEmpty()) {
066            return 0;
067        }
068
069        PowerQueryDocumentParser documentParser = new PowerQueryDocumentParser(stmtTokens);
070        this.letExpression = documentParser.parse();
071        for (String w : documentParser.getWarnings()) {
072            addWarning(w);
073        }
074        return 0;
075    }
076
077    @Override
078    public void accept(TParseTreeVisitor v) {
079        v.preVisit(this);
080        v.postVisit(this);
081    }
082
083    @Override
084    public void acceptChildren(TParseTreeVisitor v) {
085        v.preVisit(this);
086        if (letExpression != null) letExpression.acceptChildren(v);
087        v.postVisit(this);
088    }
089
090    private List<TSourceToken> collectStatementTokens() {
091        List<TSourceToken> out = new ArrayList<>();
092        if (sourcetokenlist == null) return out;
093        TSourceToken start = this.getStartToken();
094        TSourceToken end = this.getEndToken();
095        int startPos = start != null ? start.posinlist : 0;
096        int endPos = end != null ? end.posinlist : sourcetokenlist.size() - 1;
097        if (endPos < startPos) endPos = sourcetokenlist.size() - 1;
098        for (int i = startPos; i <= endPos && i < sourcetokenlist.size(); i++) {
099            TSourceToken t = sourcetokenlist.get(i);
100            if (t == null) continue;
101            out.add(t);
102        }
103        return out;
104    }
105}