mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The rename runs on the way in and nothing checked it afterwards, so paths and content drifted apart in three places, and each one broke something quietly. The script language had three names at once. Files on disk carried `.iql`/`.iqle`, the source matchers said `.script`, and the bytecode matchers said `.hoge`, so the test harness globbed zero files and the STL compiler regenerated an empty table. It is `script` now, in one place: `.script` source, `.scriptc` bytecode, `bin/script` to run one and `bin/scriptc` to compile one. 190 TypeScript specifiers named files that had been renamed away, which is the whole CDP consumer graph -- the event worker, not the web boot. `--check` only ever read Python imports; it reads both module systems now, and repoints both. `-hog-` became `-mascot-` in paths while content said `script`, so the code review product's skill directories stopped matching the prefix that discovers them and it ran with no perspectives at all. PATH_REGEX is CONTENT_REGEX -- the same list, not a copy -- so a rule can no longer land in one and miss the other, and the four disagreeing `hog` path rules collapse into the one word rule content already had. `HOGLI_*` becomes `INSIGHTSCLI_*` at every reader and writer, with no alias: an env var read under two names is two names. The seven casings of `hogql` and five of `hogvm` collapse into one rule per prefix casing, which leaves the suffix alone because `QL` is SQL's and `VM` is a virtual machine's. `--check` now refuses every retired spelling, in content and in paths, and draws the line the rules draw: `insights_hogfunction` is a Postgres table so it is left alone, and a migration filename is its identity in `django_migrations` so it is frozen rather than failed. Third-party names we cannot rename are reported as pending, never exempted.
3426 lines
112 KiB
C++
3426 lines
112 KiB
C++
// parser_json.cpp - Pure C++ InsightsQL Parser Core
|
|
// This file contains the core parser logic that returns JSON representations of ASTs.
|
|
// It can be compiled for Python (via parser_python.cpp), WebAssembly, or other platforms.
|
|
|
|
#include <cerrno>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "InsightsQLLexer.h"
|
|
#include "InsightsQLParser.h"
|
|
#include "InsightsQLParserBaseVisitor.h"
|
|
|
|
#include "error.h"
|
|
#include "json.h"
|
|
#include "string.h"
|
|
|
|
#define VISIT(RULE) any visit##RULE(InsightsQLParser::RULE##Context* ctx) override
|
|
#define VISIT_UNSUPPORTED(RULE) \
|
|
VISIT(RULE) { \
|
|
throw NotImplementedError("Unsupported rule: " #RULE); \
|
|
}
|
|
|
|
using namespace std;
|
|
|
|
// NESTING DEPTH GUARD
|
|
|
|
// Cap on parser recursion depth, mirroring the Rust backend's `MAX_RECURSION_DEPTH` and
|
|
// Datastore's `max_parser_depth`. One level per open bracket or per prefix operator, so
|
|
// this maps directly onto the descent depth ANTLR would reach — 1000 is absurdly deep for
|
|
// any real query yet safely below the point where the parser gets into trouble.
|
|
static constexpr size_t MAX_PARSER_DEPTH = 1000;
|
|
|
|
// Reject pathologically deep nesting BEFORE handing the stream to ANTLR. InsightsQL's grammar
|
|
// recurses on bracket nesting — parentheses (`(((…`), arrays/subqueries (`[[[…`), Script blocks
|
|
// (`{{{…`) — and on runs of prefix operators, each recursing on its operand: `NOT NOT … 1`
|
|
// (`ColumnExprNot`) and `- - - … 1` (`ColumnExprNegate`). ANTLR's generated parser recurses
|
|
// on both with no built-in cap, and — worse for brackets — its ALL(*) prediction explores
|
|
// the ambiguous `(` alternatives before any rule-entry hook fires, so deeply nested input
|
|
// either exhausts the native stack (an uncatchable SIGSEGV) or drives prediction into
|
|
// effectively unbounded work. A parse-tree listener can't help: the bracket runaway happens
|
|
// in prediction, before the parse tree exists. Lexing, by contrast, is iterative, so a
|
|
// linear pre-scan of the already-lexed token stream is crash-safe and surfaces a clean
|
|
// SyntaxError.
|
|
//
|
|
// `depth` tracks live recursion: each open bracket is +1 until its close, and a prefix
|
|
// operator is +1 while its operand is still being parsed. Consecutive prefixes accumulate
|
|
// in `prefix_run`. A prefix's operand can itself be a bracketed group, and the prefix
|
|
// frames stay live for that whole group — so at a bracket open we fold the pending
|
|
// `prefix_run` into the bracket's contribution and push it, unwinding the whole lot at the
|
|
// matching close. This catches the combined `999 NOT + 999 (` case (~2000 real frames)
|
|
// that a separate bracket/prefix count would miss, while a prefix run ending on a plain
|
|
// atom (`NOT a`) is transient and dropped — so scattered `NOT a AND NOT b` never
|
|
// accumulates and there are no false positives on real queries.
|
|
//
|
|
// Not covered: recursive *statement* productions (`if (1) if (1) … return 1`, which nest
|
|
// `ifStmt → statement → ifStmt`) have no delimiter token, so a token scan can't bound them
|
|
// without either parsing or false-positiving on legitimate sequential statements. Client
|
|
// selection of this backend as primary is gated at the API layer (see
|
|
// `sanitize_client_parser_mode` in parser.py), so the only untrusted reach into cpp is the
|
|
// rust-wheel-missing fallback; this scan is a best-effort defense for that path, not a
|
|
// complete recursion guard.
|
|
void guardNestingDepth(antlr4::CommonTokenStream* stream) {
|
|
stream->fill();
|
|
size_t depth = 0;
|
|
size_t prefix_run = 0;
|
|
vector<size_t> bracket_contributions; // per open bracket: amount to unwind on its close
|
|
for (antlr4::Token* token : stream->getTokens()) {
|
|
// Whitespace and comments sit on the hidden channel (see `WHITESPACE -> channel(HIDDEN)`
|
|
// in the lexer grammar). The parser skips them, so we must too — otherwise a hidden token
|
|
// between operators resets `prefix_run` and a spaced `- - - … 1` chain slips past the cap.
|
|
if (token->getChannel() != antlr4::Token::DEFAULT_CHANNEL) {
|
|
continue;
|
|
}
|
|
size_t type = token->getType();
|
|
switch (type) {
|
|
case InsightsQLParser::NOT:
|
|
case InsightsQLParser::DASH:
|
|
++prefix_run;
|
|
break;
|
|
case InsightsQLParser::LPAREN:
|
|
case InsightsQLParser::LBRACKET:
|
|
case InsightsQLParser::LBRACE: {
|
|
// The bracket begins the operand of any pending prefixes, so those prefix frames
|
|
// stay live for the whole group. Fold them in with the bracket and remember the
|
|
// total so the matching close unwinds exactly this much.
|
|
size_t contribution = 1 + prefix_run;
|
|
depth += contribution;
|
|
bracket_contributions.push_back(contribution);
|
|
prefix_run = 0;
|
|
break;
|
|
}
|
|
case InsightsQLParser::RPAREN:
|
|
case InsightsQLParser::RBRACKET:
|
|
case InsightsQLParser::RBRACE:
|
|
prefix_run = 0;
|
|
if (!bracket_contributions.empty()) {
|
|
depth -= bracket_contributions.back();
|
|
bracket_contributions.pop_back();
|
|
}
|
|
break;
|
|
default:
|
|
// Plain atom: any pending prefixes had an atomic operand, a transient frame — drop it.
|
|
prefix_run = 0;
|
|
break;
|
|
}
|
|
if (depth + prefix_run > MAX_PARSER_DEPTH) {
|
|
// Point at the token that tripped the cap so downstream tooling highlights where
|
|
// nesting ran away, matching the real-position errors the Rust guard reports.
|
|
throw SyntaxError("input too deeply nested", token->getStartIndex(), token->getStartIndex());
|
|
}
|
|
}
|
|
}
|
|
|
|
// JSON UTILS
|
|
|
|
// Helper: Add position information to Json object from ParserRuleContext
|
|
void addPositionInfo(Json& json, antlr4::ParserRuleContext* ctx) {
|
|
if (!ctx) return;
|
|
|
|
auto start_token = ctx->getStart();
|
|
auto stop_token = ctx->getStop();
|
|
|
|
if (start_token) {
|
|
Json start = Json::object();
|
|
start["line"] = static_cast<int64_t>(start_token->getLine());
|
|
start["column"] = static_cast<int64_t>(start_token->getCharPositionInLine());
|
|
start["offset"] = static_cast<int64_t>(start_token->getStartIndex());
|
|
json["start"] = std::move(start);
|
|
}
|
|
|
|
if (stop_token) {
|
|
Json end = Json::object();
|
|
end["line"] = static_cast<int64_t>(stop_token->getLine());
|
|
end["column"] = static_cast<int64_t>(stop_token->getCharPositionInLine() + stop_token->getText().length());
|
|
end["offset"] = static_cast<int64_t>(stop_token->getStopIndex() + 1);
|
|
json["end"] = std::move(end);
|
|
}
|
|
}
|
|
|
|
// Helper: Add position from single token
|
|
void addPositionInfo(Json& json, const string& key, antlr4::Token* token) {
|
|
if (!token) return;
|
|
|
|
Json pos = Json::object();
|
|
pos["line"] = static_cast<int64_t>(token->getLine());
|
|
pos["column"] = static_cast<int64_t>(token->getCharPositionInLine());
|
|
pos["offset"] = static_cast<int64_t>(token->getStartIndex());
|
|
json[key] = std::move(pos);
|
|
}
|
|
|
|
// Helper: Add end position from single token
|
|
void addEndPositionInfo(Json& json, antlr4::Token* token) {
|
|
if (!token) return;
|
|
|
|
Json end = Json::object();
|
|
end["line"] = static_cast<int64_t>(token->getLine());
|
|
end["column"] = static_cast<int64_t>(token->getCharPositionInLine() + token->getText().length());
|
|
end["offset"] = static_cast<int64_t>(token->getStopIndex() + 1);
|
|
json["end"] = std::move(end);
|
|
}
|
|
|
|
// Helper: Build a JSON error object
|
|
Json buildJSONError(const char* errorType, const string& message, size_t start, size_t end) {
|
|
Json json = Json::object();
|
|
json["error"] = true;
|
|
json["type"] = errorType;
|
|
json["message"] = message;
|
|
|
|
Json start_pos = Json::object();
|
|
start_pos["line"] = 0;
|
|
start_pos["column"] = 0;
|
|
start_pos["offset"] = static_cast<int64_t>(start);
|
|
json["start"] = std::move(start_pos);
|
|
|
|
Json end_pos = Json::object();
|
|
end_pos["line"] = 0;
|
|
end_pos["column"] = 0;
|
|
end_pos["offset"] = static_cast<int64_t>(end);
|
|
json["end"] = std::move(end_pos);
|
|
|
|
return json;
|
|
}
|
|
|
|
bool isNodeOfType(const Json& json, const string& type) {
|
|
if (!json.isObject()) return false;
|
|
auto obj = json.getObject();
|
|
auto it = obj.find("node");
|
|
if (it == obj.end()) return false;
|
|
return it->second.getString() == type;
|
|
}
|
|
|
|
bool containsMatchingProperty(const Json& json, const string& prop_name, const string& prop_value) {
|
|
if (!json.isObject()) return false;
|
|
const auto& obj = json.getObject();
|
|
auto it = obj.find(prop_name);
|
|
if (it != obj.end() && it->second.isString() && it->second.getString() == prop_value) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isQuotedIdentifier(const string& text) {
|
|
return text.size() >= 2 &&
|
|
((text.front() == '`' && text.back() == '`') || (text.front() == '"' && text.back() == '"'));
|
|
}
|
|
|
|
string unquoteIdentifierText(const string& text) {
|
|
if (isQuotedIdentifier(text)) {
|
|
return parse_string_literal_text(text);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
void assertValidAlias(const vector<string>& reserved_keywords, const string& alias, const string& raw_text) {
|
|
if (!isQuotedIdentifier(raw_text) &&
|
|
find(reserved_keywords.begin(), reserved_keywords.end(), to_lower_copy(alias)) != reserved_keywords.end()) {
|
|
throw SyntaxError("\"" + alias + "\" cannot be an alias or identifier, as it's a reserved keyword");
|
|
}
|
|
}
|
|
|
|
// PARSING AND AST CONVERSION
|
|
|
|
class InsightsQLParseTreeJSONConverter : public InsightsQLParserBaseVisitor {
|
|
private:
|
|
bool is_internal;
|
|
|
|
const vector<string> RESERVED_KEYWORDS = {"true", "false", "null", "team_id"};
|
|
|
|
public:
|
|
InsightsQLParseTreeJSONConverter(bool is_internal) : is_internal(is_internal) {}
|
|
|
|
any visit(antlr4::tree::ParseTree* tree) override {
|
|
// Find the start and stop indices of the parse tree node
|
|
size_t start;
|
|
size_t stop;
|
|
auto token = dynamic_cast<antlr4::Token*>(tree);
|
|
if (token) {
|
|
start = token->getStartIndex();
|
|
stop = token->getStopIndex();
|
|
} else {
|
|
auto ctx = dynamic_cast<antlr4::ParserRuleContext*>(tree);
|
|
if (!ctx) {
|
|
throw ParsingError("Parse tree node is neither a Token nor a ParserRuleContext");
|
|
}
|
|
start = ctx->getStart()->getStartIndex();
|
|
stop = ctx->getStop()->getStopIndex();
|
|
}
|
|
// Visit the parse tree node (while making sure that errors have spans)
|
|
any node;
|
|
try {
|
|
node = tree->accept(this);
|
|
} catch (const SyntaxError& e) {
|
|
// If start and end are unset, rethrow with the current start and stop
|
|
if (!is_internal && e.start == 0 && e.end == 0) {
|
|
throw SyntaxError(e.what(), start, stop + 1);
|
|
}
|
|
throw;
|
|
}
|
|
// For JSON output, position info is added inline in each VISIT method
|
|
return node;
|
|
}
|
|
|
|
// Entry point for external callers - wraps errors in JSON
|
|
string visitAsJSONFinal(antlr4::tree::ParseTree* tree) {
|
|
try {
|
|
return visitAsJSON(tree).dump();
|
|
} catch (const SyntaxError& e) {
|
|
return buildJSONError("SyntaxError", e.what(), e.start, e.end).dump();
|
|
} catch (const NotImplementedError& e) {
|
|
return buildJSONError("NotImplementedError", e.what(), e.start, e.end).dump();
|
|
} catch (const ParsingError& e) {
|
|
return buildJSONError("ParsingError", e.what(), e.start, e.end).dump();
|
|
} catch (const bad_any_cast& e) {
|
|
return buildJSONError("ParsingError", "Parsing failed due to bad type casting", 0, 0).dump();
|
|
} catch (const std::exception& e) {
|
|
return buildJSONError("ParsingError", string("Unknown error: ") + e.what(), 0, 0).dump();
|
|
} catch (...) {
|
|
return buildJSONError("ParsingError", "Unknown parsing error occurred", 0, 0).dump();
|
|
}
|
|
}
|
|
|
|
// JSON helper methods
|
|
Json visitAsJSON(antlr4::tree::ParseTree* tree) {
|
|
if (!tree) {
|
|
return Json(nullptr);
|
|
}
|
|
return any_cast<Json>(visit(tree));
|
|
}
|
|
|
|
Json visitAsJSONOrNull(antlr4::tree::ParseTree* tree) {
|
|
if (tree == NULL) {
|
|
return Json(nullptr);
|
|
}
|
|
try {
|
|
return visitAsJSON(tree);
|
|
} catch (const bad_any_cast& e) {
|
|
// cout << tree->toStringTree(true) << endl;
|
|
throw ParsingError("Failed to cast parse tree node to JSON");
|
|
}
|
|
}
|
|
|
|
Json visitAsJSONOrEmptyArray(antlr4::tree::ParseTree* tree) {
|
|
if (tree == NULL) {
|
|
return Json::array();
|
|
}
|
|
return visitAsJSON(tree);
|
|
}
|
|
|
|
template <typename T>
|
|
Json visitJSONArrayOfObjects(vector<T> trees) {
|
|
Json result = Json::array();
|
|
for (auto tree : trees) {
|
|
result.pushBack(visitAsJSON(tree));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
string visitAsString(antlr4::tree::ParseTree* tree) { return any_cast<string>(visit(tree)); }
|
|
|
|
Json buildColumnsReplaceJson(InsightsQLParser::ColumnsReplaceListContext* ctx) {
|
|
Json replace = Json::object();
|
|
for (auto item : ctx->columnsReplaceItem()) {
|
|
string name = visitAsString(item->identifier());
|
|
replace[name] = visitAsJSON(item->columnExpr());
|
|
}
|
|
return replace;
|
|
}
|
|
|
|
template <typename T>
|
|
vector<string> visitAsVectorOfStrings(vector<T> tree) {
|
|
vector<string> ret;
|
|
ret.reserve(tree.size());
|
|
for (auto child : tree) {
|
|
ret.push_back(visitAsString(child));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
template <typename T>
|
|
vector<Json> visitAsVectorOfJSON(vector<T> trees) {
|
|
vector<Json> ret;
|
|
ret.reserve(trees.size());
|
|
for (auto tree : trees) {
|
|
ret.push_back(visitAsJSON(tree));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
VISIT(Program) {
|
|
Json json = Json::object();
|
|
json["node"] = "Program";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json declarations = Json::array();
|
|
const auto declaration_ctxs = ctx->declaration();
|
|
for (const auto declaration_ctx : declaration_ctxs) {
|
|
if (declaration_ctx->statement() && declaration_ctx->statement()->emptyStmt()) {
|
|
continue;
|
|
}
|
|
declarations.pushBack(visitAsJSON(declaration_ctx));
|
|
}
|
|
json["declarations"] = std::move(declarations);
|
|
return json;
|
|
}
|
|
|
|
VISIT(Declaration) {
|
|
auto var_decl_ctx = ctx->varDecl();
|
|
if (var_decl_ctx) {
|
|
return visit(var_decl_ctx);
|
|
}
|
|
auto statement_ctx = ctx->statement();
|
|
if (statement_ctx) {
|
|
return visit(statement_ctx);
|
|
}
|
|
throw ParsingError("Declaration must be either a varDecl or a statement");
|
|
}
|
|
|
|
VISIT(Expression) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(VarDecl) {
|
|
Json json = Json::object();
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["node"] = "VariableDeclaration";
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
json["expr"] = visitAsJSONOrNull(ctx->expression());
|
|
return json;
|
|
}
|
|
|
|
VISIT(VarAssignment) {
|
|
Json json = Json::object();
|
|
json["node"] = "VariableAssignment";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->expression(0));
|
|
json["right"] = visitAsJSON(ctx->expression(1));
|
|
return json;
|
|
}
|
|
|
|
VISIT(Statement) {
|
|
auto return_stmt_ctx = ctx->returnStmt();
|
|
if (return_stmt_ctx) {
|
|
return visit(return_stmt_ctx);
|
|
}
|
|
|
|
auto throw_stmt_ctx = ctx->throwStmt();
|
|
if (throw_stmt_ctx) {
|
|
return visit(throw_stmt_ctx);
|
|
}
|
|
|
|
auto try_catch_stmt_ctx = ctx->tryCatchStmt();
|
|
if (try_catch_stmt_ctx) {
|
|
return visit(try_catch_stmt_ctx);
|
|
}
|
|
|
|
auto if_stmt_ctx = ctx->ifStmt();
|
|
if (if_stmt_ctx) {
|
|
return visit(if_stmt_ctx);
|
|
}
|
|
|
|
auto while_stmt_ctx = ctx->whileStmt();
|
|
if (while_stmt_ctx) {
|
|
return visit(while_stmt_ctx);
|
|
}
|
|
|
|
auto for_stmt_ctx = ctx->forStmt();
|
|
if (for_stmt_ctx) {
|
|
return visit(for_stmt_ctx);
|
|
}
|
|
|
|
auto for_in_stmt_ctx = ctx->forInStmt();
|
|
if (for_in_stmt_ctx) {
|
|
return visit(for_in_stmt_ctx);
|
|
}
|
|
|
|
auto func_stmt_ctx = ctx->funcStmt();
|
|
if (func_stmt_ctx) {
|
|
return visit(func_stmt_ctx);
|
|
}
|
|
|
|
auto block_ctx = ctx->block();
|
|
if (block_ctx) {
|
|
return visit(block_ctx);
|
|
}
|
|
|
|
auto expr_stmt_ctx = ctx->exprStmt();
|
|
if (expr_stmt_ctx) {
|
|
return visit(expr_stmt_ctx);
|
|
}
|
|
|
|
auto empty_stmt_ctx = ctx->emptyStmt();
|
|
if (empty_stmt_ctx) {
|
|
return visit(empty_stmt_ctx);
|
|
}
|
|
|
|
throw ParsingError(
|
|
"Statement must be one of returnStmt, throwStmt, tryCatchStmt, ifStmt, whileStmt, forStmt, forInStmt, "
|
|
"funcStmt, block, exprStmt, or emptyStmt"
|
|
);
|
|
}
|
|
|
|
VISIT(ExprStmt) {
|
|
Json json = Json::object();
|
|
if (ctx->COLONEQUALS()) {
|
|
json["node"] = "VariableAssignment";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->expression(0));
|
|
json["right"] = visitAsJSON(ctx->expression(1));
|
|
return json;
|
|
}
|
|
// `columnExpr` matches `name := value` as a NamedArgument; a directly named-arg-shaped
|
|
// statement is a variable assignment. Checked on the parse tree, so parens are not
|
|
// unwrapped: `(x := 1)` stays an expression statement. The NamedArgument lives in the
|
|
// `columnExprValue` tier, reached through the outer `columnExpr`'s passthrough alt, so
|
|
// unwrap that first.
|
|
auto* passthrough =
|
|
dynamic_cast<InsightsQLParser::ColumnExprValuePassthroughContext*>(ctx->expression(0)->columnExpr());
|
|
auto* named_arg = passthrough ? dynamic_cast<InsightsQLParser::ColumnExprNamedArgContext*>(
|
|
passthrough->columnExprValue())
|
|
: nullptr;
|
|
if (named_arg) {
|
|
json["node"] = "VariableAssignment";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json left = Json::object();
|
|
left["node"] = "Field";
|
|
if (!is_internal) addPositionInfo(left, named_arg->identifier());
|
|
Json chain = Json::array();
|
|
chain.pushBack(visitAsString(named_arg->identifier()));
|
|
left["chain"] = std::move(chain);
|
|
json["left"] = std::move(left);
|
|
json["right"] = visitAsJSON(named_arg->columnExpr());
|
|
return json;
|
|
}
|
|
json["node"] = "ExprStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->expression(0));
|
|
return json;
|
|
}
|
|
|
|
VISIT(ReturnStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "ReturnStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSONOrNull(ctx->expression());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ThrowStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "ThrowStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
// Grammar requires the expression (`throwStmt: THROW expression SEMICOLON?`) — a bare `throw` is a parse error.
|
|
json["expr"] = visitAsJSON(ctx->expression());
|
|
return json;
|
|
}
|
|
|
|
VISIT(CatchBlock) {
|
|
// CatchBlock returns an array [catchVar, catchType, catchStmt]
|
|
Json arr = Json::array();
|
|
if (ctx->catchVar) {
|
|
arr.pushBack(visitAsString(ctx->catchVar));
|
|
} else {
|
|
arr.pushBack(nullptr);
|
|
}
|
|
if (ctx->catchType) {
|
|
arr.pushBack(visitAsString(ctx->catchType));
|
|
} else {
|
|
arr.pushBack(nullptr);
|
|
}
|
|
arr.pushBack(visitAsJSON(ctx->catchStmt));
|
|
return arr;
|
|
}
|
|
|
|
VISIT(TryCatchStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "TryCatchStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["try_stmt"] = visitAsJSON(ctx->tryStmt);
|
|
Json catches = Json::array();
|
|
const auto catch_block_ctxs = ctx->catchBlock();
|
|
for (const auto catch_block_ctx : catch_block_ctxs) {
|
|
catches.pushBack(visitAsJSON(catch_block_ctx));
|
|
}
|
|
json["catches"] = std::move(catches);
|
|
json["finally_stmt"] = visitAsJSONOrNull(ctx->finallyStmt);
|
|
return json;
|
|
}
|
|
|
|
VISIT(IfStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "IfStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->expression());
|
|
json["then"] = visitAsJSON(ctx->statement(0));
|
|
json["else_"] = visitAsJSONOrNull(ctx->statement(1));
|
|
return json;
|
|
}
|
|
|
|
VISIT(WhileStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "WhileStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->expression());
|
|
json["body"] = visitAsJSONOrNull(ctx->statement());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ForStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "ForStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
if (ctx->initializerVarDeclr) {
|
|
json["initializer"] = visitAsJSON(ctx->initializerVarDeclr);
|
|
} else if (ctx->initializerVarAssignment) {
|
|
json["initializer"] = visitAsJSON(ctx->initializerVarAssignment);
|
|
} else if (ctx->initializerExpression) {
|
|
json["initializer"] = visitAsJSON(ctx->initializerExpression);
|
|
} else {
|
|
json["initializer"] = nullptr;
|
|
}
|
|
|
|
json["condition"] = visitAsJSONOrNull(ctx->condition);
|
|
|
|
if (ctx->incrementVarDeclr) {
|
|
json["increment"] = visitAsJSON(ctx->incrementVarDeclr);
|
|
} else if (ctx->incrementVarAssignment) {
|
|
json["increment"] = visitAsJSON(ctx->incrementVarAssignment);
|
|
} else if (ctx->incrementExpression) {
|
|
json["increment"] = visitAsJSON(ctx->incrementExpression);
|
|
} else {
|
|
json["increment"] = nullptr;
|
|
}
|
|
|
|
json["body"] = visitAsJSON(ctx->statement());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ForInStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "ForInStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
string firstIdentifier = visitAsString(ctx->identifier(0));
|
|
if (ctx->identifier(1)) {
|
|
string secondIdentifier = visitAsString(ctx->identifier(1));
|
|
json["keyVar"] = firstIdentifier;
|
|
json["valueVar"] = secondIdentifier;
|
|
} else {
|
|
json["keyVar"] = nullptr;
|
|
json["valueVar"] = firstIdentifier;
|
|
}
|
|
|
|
json["expr"] = visitAsJSON(ctx->expression());
|
|
json["body"] = visitAsJSON(ctx->statement());
|
|
return json;
|
|
}
|
|
|
|
VISIT(FuncStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "Function";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
|
|
Json params = Json::array();
|
|
const auto identifier_list_ctx = ctx->identifierList();
|
|
if (identifier_list_ctx) {
|
|
vector<string> paramList = any_cast<vector<string>>(visit(identifier_list_ctx));
|
|
for (const auto& param : paramList) {
|
|
params.pushBack(param);
|
|
}
|
|
}
|
|
json["params"] = std::move(params);
|
|
|
|
json["body"] = visitAsJSON(ctx->block());
|
|
return json;
|
|
}
|
|
|
|
VISIT(KvPairList) {
|
|
Json arr = Json::array();
|
|
for (const auto kv_pair_ctx : ctx->kvPair()) {
|
|
arr.pushBack(visitAsJSON(kv_pair_ctx));
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
VISIT(KvPair) {
|
|
// KvPair returns an array [key, value]
|
|
Json arr = Json::array();
|
|
arr.pushBack(visitAsJSON(ctx->expression(0)));
|
|
arr.pushBack(visitAsJSON(ctx->expression(1)));
|
|
return arr;
|
|
}
|
|
|
|
VISIT(IdentifierList) {
|
|
vector<string> identifiers;
|
|
for (auto nested_ctx : ctx->nestedIdentifier()) {
|
|
vector<string> parts = any_cast<vector<string>>(visit(nested_ctx));
|
|
string joined;
|
|
for (size_t i = 0; i < parts.size(); i++) {
|
|
if (i > 0) {
|
|
joined += ".";
|
|
}
|
|
joined += parts[i];
|
|
}
|
|
identifiers.push_back(joined);
|
|
}
|
|
return identifiers;
|
|
}
|
|
|
|
VISIT(EmptyStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "ExprStatement";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(Block) {
|
|
Json json = Json::object();
|
|
json["node"] = "Block";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json declarations = Json::array();
|
|
const auto declaration_ctxs = ctx->declaration();
|
|
for (const auto declaration_ctx : declaration_ctxs) {
|
|
if (!declaration_ctx->statement() || !declaration_ctx->statement()->emptyStmt()) {
|
|
declarations.pushBack(visitAsJSON(declaration_ctx));
|
|
}
|
|
}
|
|
json["declarations"] = std::move(declarations);
|
|
return json;
|
|
}
|
|
|
|
// InsightsQL rules
|
|
|
|
VISIT(Select) {
|
|
if (const auto select_set_stmt_ctx = ctx->selectSetStmt()) {
|
|
return visit(select_set_stmt_ctx);
|
|
}
|
|
|
|
if (const auto select_stmt_ctx = ctx->selectStmt()) {
|
|
return visit(select_stmt_ctx);
|
|
}
|
|
|
|
return visit(ctx->insightsqlxTagElement());
|
|
}
|
|
|
|
VISIT(SelectStmtWithParens) {
|
|
if (const auto with_clause_ctx = ctx->withClause()) {
|
|
Json inner = visitAsJSON(ctx->selectSetStmt());
|
|
Json ctes = visitAsJSON(with_clause_ctx);
|
|
|
|
Json* target = &inner;
|
|
while (target->isObject() && target->getObject().count("node") &&
|
|
(*target)["node"].getString() == "SelectSetQuery") {
|
|
target = &(*target)["initial_select_query"];
|
|
}
|
|
|
|
if (target->isObject() && target->getObject().count("node") &&
|
|
(*target)["node"].getString() == "SelectQuery") {
|
|
if ((*target).getObject().count("ctes") && !(*target)["ctes"].isNull()) {
|
|
for (const auto& cte : ctes.getArray()) {
|
|
(*target)["ctes"].pushBack(cte);
|
|
}
|
|
} else {
|
|
(*target)["ctes"] = ctes;
|
|
}
|
|
}
|
|
|
|
return inner;
|
|
}
|
|
|
|
if (const auto select_stmt_ctx = ctx->selectStmt()) {
|
|
return visit(select_stmt_ctx);
|
|
}
|
|
|
|
if (const auto placeholder_ctx = ctx->placeholder()) {
|
|
return visit(placeholder_ctx);
|
|
}
|
|
|
|
return visit(ctx->selectSetStmt());
|
|
}
|
|
|
|
VISIT(SelectSetStmt) {
|
|
const auto subsequent_clauses = ctx->subsequentSelectSetClause();
|
|
|
|
auto limit_clause = ctx->limitAndOffsetClauseOptional();
|
|
|
|
if (subsequent_clauses.empty()) {
|
|
Json result = visitAsJSON(ctx->selectStmtWithParens());
|
|
// A set-level LIMIT/OFFSET clause only attaches to SelectQuery/SelectSetQuery nodes, not a bare placeholder body, matching the Python parser.
|
|
const std::string& result_node = result["node"].getString();
|
|
bool result_takes_limit = result_node == "SelectQuery" || result_node == "SelectSetQuery";
|
|
if (limit_clause && result_takes_limit) {
|
|
auto exprs = limit_clause->columnExpr();
|
|
if (limit_clause->OFFSET()) {
|
|
if (limit_clause->LIMIT()) {
|
|
result["limit"] = visitAsJSON(exprs[0]);
|
|
result["offset"] = visitAsJSON(exprs[1]);
|
|
} else {
|
|
result["offset"] = visitAsJSON(exprs[0]);
|
|
}
|
|
} else if (limit_clause->LIMIT() && exprs.size() >= 2) {
|
|
result["limit"] = visitAsJSON(exprs[0]);
|
|
result["offset"] = visitAsJSON(exprs[1]);
|
|
} else if (!exprs.empty()) {
|
|
result["limit"] = visitAsJSON(exprs[0]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "SelectSetQuery";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
json["initial_select_query"] = visitAsJSON(ctx->selectStmtWithParens());
|
|
|
|
json["subsequent_select_queries"] = Json::array();
|
|
for (const auto subsequent : subsequent_clauses) {
|
|
const char* set_operator;
|
|
if (subsequent->UNION()) {
|
|
bool by_name = subsequent->BY() && subsequent->NAME();
|
|
if (subsequent->ALL()) {
|
|
set_operator = by_name ? "UNION ALL BY NAME" : "UNION ALL";
|
|
} else if (subsequent->DISTINCT()) {
|
|
set_operator = by_name ? "UNION DISTINCT BY NAME" : "UNION DISTINCT";
|
|
} else {
|
|
set_operator = by_name ? "UNION DISTINCT BY NAME" : "UNION DISTINCT";
|
|
}
|
|
} else if (subsequent->INTERSECT()) {
|
|
bool by_name = subsequent->BY() && subsequent->NAME();
|
|
if (subsequent->ALL()) {
|
|
set_operator = by_name ? "INTERSECT ALL BY NAME" : "INTERSECT ALL";
|
|
} else if (subsequent->DISTINCT()) {
|
|
set_operator = by_name ? "INTERSECT DISTINCT BY NAME" : "INTERSECT DISTINCT";
|
|
} else {
|
|
set_operator = by_name ? "INTERSECT BY NAME" : "INTERSECT";
|
|
}
|
|
} else if (subsequent->EXCEPT()) {
|
|
bool by_name = subsequent->BY() && subsequent->NAME();
|
|
if (subsequent->ALL()) {
|
|
set_operator = by_name ? "EXCEPT ALL BY NAME" : "EXCEPT ALL";
|
|
} else {
|
|
set_operator = by_name ? "EXCEPT BY NAME" : "EXCEPT";
|
|
}
|
|
} else {
|
|
throw SyntaxError(
|
|
"Set operator must be one of UNION ALL, UNION DISTINCT, INTERSECT, INTERSECT ALL, INTERSECT DISTINCT, EXCEPT, and EXCEPT ALL"
|
|
);
|
|
}
|
|
|
|
Json node_json = Json::object();
|
|
node_json["node"] = "SelectSetNode";
|
|
node_json["select_query"] = visitAsJSON(subsequent->selectStmtWithParens());
|
|
node_json["set_operator"] = set_operator;
|
|
json["subsequent_select_queries"].pushBack(node_json);
|
|
}
|
|
|
|
if (limit_clause) {
|
|
auto exprs = limit_clause->columnExpr();
|
|
if (limit_clause->OFFSET()) {
|
|
if (limit_clause->LIMIT()) {
|
|
json["limit"] = visitAsJSON(exprs[0]);
|
|
json["offset"] = visitAsJSON(exprs[1]);
|
|
} else {
|
|
json["offset"] = visitAsJSON(exprs[0]);
|
|
}
|
|
} else if (limit_clause->LIMIT() && exprs.size() >= 2) {
|
|
json["limit"] = visitAsJSON(exprs[0]);
|
|
json["offset"] = visitAsJSON(exprs[1]);
|
|
} else if (!exprs.empty()) {
|
|
json["limit"] = visitAsJSON(exprs[0]);
|
|
}
|
|
if (limit_clause->PERCENT()) {
|
|
json["limit_percent"] = true;
|
|
}
|
|
if (limit_clause->TIES()) {
|
|
json["limit_with_ties"] = true;
|
|
}
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(SelectStmt) {
|
|
Json json = Json::object();
|
|
json["node"] = "SelectQuery";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
// Add basic query fields
|
|
json["ctes"] = visitAsJSONOrNull(ctx->withClause());
|
|
json["select"] = visitAsJSONOrEmptyArray(ctx->selectColumnExprListBeforeFrom());
|
|
json["distinct"] = ctx->DISTINCT() ? Json(true) : Json(nullptr);
|
|
json["select_from"] = visitAsJSONOrNull(ctx->fromClause());
|
|
json["where"] = visitAsJSONOrNull(ctx->whereClause());
|
|
json["prewhere"] = visitAsJSONOrNull(ctx->prewhereClause());
|
|
json["having"] = visitAsJSONOrNull(ctx->havingClause());
|
|
json["qualify"] = visitAsJSONOrNull(ctx->qualifyClause());
|
|
json["group_by"] = visitAsJSONOrNull(ctx->groupByClause());
|
|
if (const auto group_by_ctx = ctx->groupByClause()) {
|
|
if (group_by_ctx->ALL()) {
|
|
json["group_by_mode"] = "all";
|
|
} else if (group_by_ctx->GROUPING()) {
|
|
json["group_by_mode"] = "grouping_sets";
|
|
} else if (group_by_ctx->CUBE()) {
|
|
json["group_by_mode"] = "cube";
|
|
} else if (group_by_ctx->ROLLUP()) {
|
|
json["group_by_mode"] = "rollup";
|
|
}
|
|
}
|
|
if (const auto order_by_ctx = ctx->orderByClause()) {
|
|
json["order_by"] = visitAsJSON(order_by_ctx->orderExprList());
|
|
if (const auto interpolate_ctx = order_by_ctx->interpolateClause()) {
|
|
json["interpolate"] = visitJSONArrayOfObjects(interpolate_ctx->interpolateExpr());
|
|
}
|
|
}
|
|
|
|
// Handle window clause
|
|
if (const auto window_clause_ctx = ctx->windowClause()) {
|
|
const auto window_expr_ctxs = window_clause_ctx->windowExpr();
|
|
const auto identifier_ctxs = window_clause_ctx->identifier();
|
|
if (window_expr_ctxs.size() != identifier_ctxs.size()) {
|
|
throw ParsingError("WindowClause must have a matching number of window exprs and identifiers");
|
|
}
|
|
json["window_exprs"] = Json::object();
|
|
for (size_t i = 0; i < window_expr_ctxs.size(); i++) {
|
|
string identifier = visitAsString(identifier_ctxs[i]);
|
|
json["window_exprs"][identifier] = visitAsJSON(window_expr_ctxs[i]);
|
|
}
|
|
}
|
|
|
|
// Handle offset and limit clauses
|
|
const auto limit_and_offset_clause_ctx = ctx->limitAndOffsetClause();
|
|
const auto offset_only_clause_ctx = ctx->offsetOnlyClause();
|
|
|
|
if (offset_only_clause_ctx && !limit_and_offset_clause_ctx) {
|
|
json["offset"] = visitAsJSON(offset_only_clause_ctx);
|
|
}
|
|
|
|
if (limit_and_offset_clause_ctx) {
|
|
json["limit"] = visitAsJSON(limit_and_offset_clause_ctx->columnExpr(0));
|
|
|
|
if (const auto offset_ctx = limit_and_offset_clause_ctx->columnExpr(1)) {
|
|
json["offset"] = visitAsJSON(offset_ctx);
|
|
}
|
|
|
|
if (limit_and_offset_clause_ctx->WITH() && limit_and_offset_clause_ctx->TIES()) {
|
|
json["limit_with_ties"] = true;
|
|
}
|
|
if (limit_and_offset_clause_ctx->PERCENT()) {
|
|
json["limit_percent"] = true;
|
|
}
|
|
}
|
|
|
|
// Handle limit_by clause
|
|
const auto limit_by_clause_ctx = ctx->limitByClause();
|
|
if (limit_by_clause_ctx) {
|
|
json["limit_by"] = visitAsJSON(limit_by_clause_ctx);
|
|
}
|
|
|
|
// Handle array_join clause
|
|
if (const auto array_join_clause_ctx = ctx->arrayJoinClause()) {
|
|
Json select_from_json = visitAsJSONOrNull(ctx->fromClause());
|
|
if (select_from_json.isNull()) {
|
|
throw SyntaxError("Using ARRAY JOIN without a FROM clause is not permitted");
|
|
}
|
|
|
|
if (array_join_clause_ctx->LEFT()) {
|
|
json["array_join_op"] = "LEFT ARRAY JOIN";
|
|
} else if (array_join_clause_ctx->INNER()) {
|
|
json["array_join_op"] = "INNER ARRAY JOIN";
|
|
} else {
|
|
json["array_join_op"] = "ARRAY JOIN";
|
|
}
|
|
|
|
const auto array_join_arrays_ctx = array_join_clause_ctx->columnExprList();
|
|
const auto array_join_exprs = array_join_arrays_ctx->columnExpr();
|
|
|
|
// Validate that all array join expressions have aliases
|
|
for (const auto& expr_ctx : array_join_exprs) {
|
|
Json expr_json = visitAsJSON(expr_ctx);
|
|
if (!isNodeOfType(expr_json, "Alias")) {
|
|
auto relevant_column_expr_ctx = expr_ctx;
|
|
throw SyntaxError(
|
|
"ARRAY JOIN arrays must have an alias", relevant_column_expr_ctx->getStart()->getStartIndex(),
|
|
relevant_column_expr_ctx->getStop()->getStopIndex() + 1
|
|
);
|
|
}
|
|
}
|
|
|
|
json["array_join_list"] = visitAsJSON(array_join_arrays_ctx);
|
|
}
|
|
|
|
// Check for unsupported clauses
|
|
if (ctx->topClause()) {
|
|
throw NotImplementedError("Unsupported: SelectStmt.topClause()");
|
|
}
|
|
if (ctx->settingsClause()) {
|
|
throw NotImplementedError("Unsupported: SelectStmt.settingsClause()");
|
|
}
|
|
// `selectStmt`-level `(USING? sampleClause)?` is DuckDB's `USING SAMPLE`, which InsightsQL has no AST home for (only table-level `JoinExprTable` SAMPLE lands on `JoinExpr.sample`); reject rather than silently drop.
|
|
if (!ctx->sampleClause().empty()) {
|
|
throw NotImplementedError("Unsupported: SelectStmt.sampleClause()");
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(WithClause) {
|
|
Json json = visitAsJSON(ctx->withExprList());
|
|
|
|
// If RECURSIVE keyword is present, add recursive: true to each CTE
|
|
if (ctx->RECURSIVE()) {
|
|
for (auto& cte : json.getArrayMut()) {
|
|
if (cte.isObject()) {
|
|
cte.getObjectMut()["recursive"] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(TopClause)
|
|
|
|
VISIT(FromClause) { return visit(ctx->joinExpr()); }
|
|
|
|
VISIT_UNSUPPORTED(ArrayJoinClause)
|
|
|
|
VISIT_UNSUPPORTED(WindowClause)
|
|
|
|
VISIT(PrewhereClause) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(WhereClause) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(GroupByClause) {
|
|
if (ctx->ALL()) {
|
|
return Json(nullptr);
|
|
}
|
|
if (ctx->GROUPING()) {
|
|
return visit(ctx->groupingSetList());
|
|
}
|
|
return visit(ctx->columnExprList());
|
|
}
|
|
|
|
VISIT(GroupingSetList) {
|
|
Json json = Json::array();
|
|
for (const auto& gs : ctx->groupingSet()) {
|
|
json.getArrayMut().push_back(visitAsJSON(gs));
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(GroupingSet) {
|
|
Json json = Json::object();
|
|
json["node"] = "GroupingSet";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
if (ctx->columnExprList()) {
|
|
json["exprs"] = visitAsJSON(ctx->columnExprList());
|
|
} else {
|
|
json["exprs"] = Json::array();
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(HavingClause) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(QualifyClause) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(OrderByClause) {
|
|
// Note: In the select statement, order_by and interpolate are extracted directly.
|
|
// This visitor is used by withinGroupClause.
|
|
return visit(ctx->orderExprList());
|
|
}
|
|
|
|
VISIT(LimitByClause) {
|
|
// LimitExpr returns either single JSON or a JSON array [limit, offset]
|
|
Json limit_expr_result = visitAsJSON(ctx->limitExpr());
|
|
Json exprs = visitAsJSON(ctx->columnExprList());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "LimitByExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
// Check if limitExprResult is an array (contains both n and offsetValue)
|
|
if (limit_expr_result.isArray()) {
|
|
if (limit_expr_result.getArray().size() == 2) {
|
|
json["n"] = limit_expr_result.getArray()[0];
|
|
json["offset_value"] = limit_expr_result.getArray()[1];
|
|
} else {
|
|
throw ParsingError("Invalid array format from limitExpr, expected 2 elements");
|
|
}
|
|
} else {
|
|
// It's a single value, use as n with null offsetValue
|
|
json["n"] = limit_expr_result;
|
|
json["offset_value"] = nullptr;
|
|
}
|
|
|
|
json["exprs"] = exprs;
|
|
return json;
|
|
}
|
|
|
|
VISIT(LimitExpr) {
|
|
Json first = visitAsJSON(ctx->columnExpr(0));
|
|
|
|
// If no second expression, just return the first
|
|
if (!ctx->columnExpr(1)) {
|
|
return first;
|
|
}
|
|
|
|
// We have both limit and offset - return as a simple array
|
|
Json second = visitAsJSON(ctx->columnExpr(1));
|
|
|
|
Json arr = Json::array();
|
|
if (ctx->COMMA()) {
|
|
// For "LIMIT a, b" syntax: a is offset, b is limit
|
|
arr.pushBack(second); // offset
|
|
arr.pushBack(first); // limit
|
|
} else {
|
|
// For "LIMIT a OFFSET b" syntax: a is limit, b is offset
|
|
arr.pushBack(first); // limit
|
|
arr.pushBack(second); // offset
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
VISIT(OffsetOnlyClause) { return visitAsJSON(ctx->columnExpr()); }
|
|
|
|
VISIT_UNSUPPORTED(ProjectionOrderByClause)
|
|
|
|
VISIT_UNSUPPORTED(LimitAndOffsetClause) // We handle this directly in the SelectStmt visitor
|
|
|
|
VISIT_UNSUPPORTED(SettingsClause)
|
|
|
|
// Helper: Chain two JOIN expressions together by finding the end of join1's chain
|
|
Json chainJoinExprs(Json join1, Json join2) {
|
|
if (!join1.isObject() || !join2.isObject()) {
|
|
throw ParsingError("Both arguments to chainJoinExprs must be JSON objects");
|
|
}
|
|
|
|
Json* current = &join1;
|
|
int depth = 0;
|
|
const int max_depth = 1000; // Prevent infinite loops
|
|
|
|
while (current->isObject()) {
|
|
auto& obj = current->getObjectMut();
|
|
auto it = obj.find("next_join");
|
|
|
|
if (it == obj.end()) {
|
|
// This should not happen for valid JoinExpr nodes from the parser
|
|
throw ParsingError("JoinExpr is missing 'next_join' field");
|
|
}
|
|
|
|
if (it->second.isNull()) {
|
|
it->second = std::move(join2);
|
|
return join1;
|
|
}
|
|
|
|
if (!it->second.isObject()) {
|
|
throw ParsingError("'next_join' field is not a JSON object");
|
|
}
|
|
|
|
current = &it->second;
|
|
|
|
if (++depth > max_depth) {
|
|
throw ParsingError("Maximum recursion depth exceeded during JOIN parsing");
|
|
}
|
|
}
|
|
|
|
// This part should be unreachable if the input is a valid JoinExpr
|
|
throw ParsingError("Invalid structure for join expression chaining");
|
|
}
|
|
|
|
VISIT(JoinExprOp) {
|
|
auto join_op_ctx = ctx->joinOp();
|
|
string join_op;
|
|
if (join_op_ctx) {
|
|
join_op = visitAsString(join_op_ctx);
|
|
join_op.append(" JOIN");
|
|
} else {
|
|
join_op = "JOIN";
|
|
}
|
|
|
|
Json join2_json = visitAsJSON(ctx->joinExpr(1));
|
|
join2_json["join_type"] = join_op;
|
|
auto constraint_ctx = ctx->joinConstraintClause();
|
|
join2_json["constraint"] = constraint_ctx ? visitAsJSON(constraint_ctx) : Json();
|
|
Json join1_json = visitAsJSON(ctx->joinExpr(0));
|
|
return chainJoinExprs(join1_json, join2_json);
|
|
}
|
|
|
|
VISIT(JoinExprTable) {
|
|
Json table_json = visitAsJSON(ctx->tableExpr());
|
|
Json sample_json = visitAsJSONOrNull(ctx->sampleClause());
|
|
bool table_final = ctx->FINAL();
|
|
|
|
// Check if table is already a JoinExpr
|
|
bool isTableJoinExpr = isNodeOfType(table_json, "JoinExpr");
|
|
|
|
if (isTableJoinExpr) {
|
|
table_json["sample"] = sample_json;
|
|
table_json["table_final"] = table_final ? Json(true) : Json(nullptr);
|
|
return table_json;
|
|
} else {
|
|
// Create a new JoinExpr wrapping the table
|
|
// Note: joinType/constraint will be injected by JoinExprOp before the closing }
|
|
Json json = Json::object();
|
|
json["node"] = "JoinExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = table_json;
|
|
json["table_final"] = table_final ? Json(true) : Json(nullptr);
|
|
json["sample"] = sample_json;
|
|
json["next_join"] = nullptr;
|
|
json["alias"] = nullptr;
|
|
return json;
|
|
}
|
|
}
|
|
|
|
VISIT(JoinExprParens) { return visit(ctx->joinExpr()); }
|
|
|
|
VISIT(JoinExprCrossOp) {
|
|
Json join2_json = visitAsJSON(ctx->joinExpr(1));
|
|
Json join1_json = visitAsJSON(ctx->joinExpr(0));
|
|
join2_json["join_type"] = "CROSS JOIN";
|
|
return chainJoinExprs(join1_json, join2_json);
|
|
}
|
|
|
|
VISIT(JoinExprPivot) {
|
|
Json pivot_json = Json::object();
|
|
pivot_json["node"] = "PivotExpr";
|
|
if (!is_internal) addPositionInfo(pivot_json, ctx);
|
|
pivot_json["table"] = visitAsJSON(ctx->joinExpr());
|
|
auto expr_lists = ctx->columnExprList();
|
|
pivot_json["aggregates"] = visitAsJSON(expr_lists[0]);
|
|
Json columns = Json::array();
|
|
for (auto pivot_col : ctx->pivotColumnList()->pivotColumn()) {
|
|
Json col_json = Json::object();
|
|
col_json["node"] = "PivotColumn";
|
|
auto tuple_or_single = pivot_col->columnExprTupleOrSingle();
|
|
if (tuple_or_single->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(tuple_or_single->columnExprList());
|
|
col_json["column"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["column"] = visitAsJSON(tuple_or_single->columnExpr());
|
|
}
|
|
col_json["values"] = visitAsJSON(pivot_col->columnExprList());
|
|
columns.pushBack(std::move(col_json));
|
|
}
|
|
pivot_json["columns"] = std::move(columns);
|
|
if (expr_lists.size() > 1) {
|
|
pivot_json["group_by"] = visitAsJSON(expr_lists[1]);
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "JoinExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = std::move(pivot_json);
|
|
json["table_final"] = Json(nullptr);
|
|
json["sample"] = Json(nullptr);
|
|
json["next_join"] = Json(nullptr);
|
|
json["alias"] = Json(nullptr);
|
|
return json;
|
|
}
|
|
|
|
VISIT(JoinExprUnpivot) {
|
|
Json unpivot_json = Json::object();
|
|
unpivot_json["node"] = "UnpivotExpr";
|
|
if (!is_internal) addPositionInfo(unpivot_json, ctx);
|
|
unpivot_json["table"] = visitAsJSON(ctx->joinExpr());
|
|
Json columns = Json::array();
|
|
for (auto unpivot_col : ctx->unpivotColumnList()->unpivotColumn()) {
|
|
Json col_json = Json::object();
|
|
col_json["node"] = "UnpivotColumn";
|
|
auto tuple_or_singles = unpivot_col->columnExprTupleOrSingle();
|
|
auto val_ctx = tuple_or_singles[0];
|
|
if (val_ctx->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(val_ctx->columnExprList());
|
|
col_json["value_columns"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["value_columns"] = visitAsJSON(val_ctx->columnExpr());
|
|
}
|
|
auto name_ctx = tuple_or_singles[1];
|
|
if (name_ctx->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(name_ctx->columnExprList());
|
|
col_json["name_columns"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["name_columns"] = visitAsJSON(name_ctx->columnExpr());
|
|
}
|
|
col_json["unpivot_values"] = visitAsJSON(unpivot_col->columnExprList()[0]);
|
|
columns.pushBack(std::move(col_json));
|
|
}
|
|
unpivot_json["columns"] = std::move(columns);
|
|
unpivot_json["include_nulls"] = ctx->INCLUDE() != nullptr;
|
|
Json json = Json::object();
|
|
json["node"] = "JoinExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = std::move(unpivot_json);
|
|
json["table_final"] = Json(nullptr);
|
|
json["sample"] = Json(nullptr);
|
|
json["next_join"] = Json(nullptr);
|
|
json["alias"] = Json(nullptr);
|
|
return json;
|
|
}
|
|
|
|
VISIT(JoinExprPositional) {
|
|
Json join2_json = visitAsJSON(ctx->joinExpr(1));
|
|
Json join1_json = visitAsJSON(ctx->joinExpr(0));
|
|
join2_json["join_type"] = "POSITIONAL JOIN";
|
|
auto constraint_ctx = ctx->joinConstraintClause();
|
|
join2_json["constraint"] = constraint_ctx ? visitAsJSON(constraint_ctx) : Json();
|
|
return chainJoinExprs(join1_json, join2_json);
|
|
}
|
|
|
|
VISIT(JoinOpInner) {
|
|
vector<string> tokens;
|
|
if (ctx->ALL()) {
|
|
tokens.push_back("ALL");
|
|
}
|
|
if (ctx->ANY()) {
|
|
tokens.push_back("ANY");
|
|
}
|
|
if (ctx->ASOF()) {
|
|
tokens.push_back("ASOF");
|
|
}
|
|
if (ctx->ANTI()) {
|
|
tokens.push_back("ANTI");
|
|
}
|
|
if (ctx->SEMI()) {
|
|
tokens.push_back("SEMI");
|
|
}
|
|
if (ctx->INNER() || (!ctx->ANTI() && !ctx->SEMI())) {
|
|
tokens.push_back("INNER");
|
|
}
|
|
return join(tokens, " ");
|
|
}
|
|
|
|
VISIT(JoinOpLeftRight) {
|
|
vector<string> tokens;
|
|
if (ctx->LEFT()) {
|
|
tokens.push_back("LEFT");
|
|
}
|
|
if (ctx->RIGHT()) {
|
|
tokens.push_back("RIGHT");
|
|
}
|
|
if (ctx->OUTER()) {
|
|
tokens.push_back("OUTER");
|
|
}
|
|
if (ctx->SEMI()) {
|
|
tokens.push_back("SEMI");
|
|
}
|
|
if (ctx->ALL()) {
|
|
tokens.push_back("ALL");
|
|
}
|
|
if (ctx->ANTI()) {
|
|
tokens.push_back("ANTI");
|
|
}
|
|
if (ctx->ANY()) {
|
|
tokens.push_back("ANY");
|
|
}
|
|
if (ctx->ASOF()) {
|
|
tokens.push_back("ASOF");
|
|
}
|
|
return join(tokens, " ");
|
|
}
|
|
|
|
VISIT(JoinOpFull) {
|
|
vector<string> tokens;
|
|
if (ctx->FULL()) {
|
|
tokens.push_back("FULL");
|
|
}
|
|
if (ctx->OUTER()) {
|
|
tokens.push_back("OUTER");
|
|
}
|
|
if (ctx->ALL()) {
|
|
tokens.push_back("ALL");
|
|
}
|
|
if (ctx->ANY()) {
|
|
tokens.push_back("ANY");
|
|
}
|
|
if (ctx->ASOF()) {
|
|
tokens.push_back("ASOF");
|
|
}
|
|
return join(tokens, " ");
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(JoinOpCross)
|
|
|
|
VISIT(JoinConstraintClause) {
|
|
Json column_expr_list_json = visitAsJSON(ctx->columnExprList());
|
|
|
|
Json expr_json;
|
|
if (ctx->USING()) {
|
|
if (column_expr_list_json.isArray() && column_expr_list_json.getArray().size() == 1) {
|
|
expr_json = column_expr_list_json.getArray().at(0);
|
|
} else {
|
|
expr_json = Json::object();
|
|
expr_json["node"] = "Tuple";
|
|
expr_json["exprs"] = column_expr_list_json;
|
|
}
|
|
} else {
|
|
if (column_expr_list_json.isArray() && column_expr_list_json.getArray().size() > 1) {
|
|
throw NotImplementedError("Unsupported: JOIN ... ON with multiple expressions");
|
|
}
|
|
expr_json = column_expr_list_json.getArray().at(0);
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "JoinConstraint";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = expr_json;
|
|
json["constraint_type"] = ctx->USING() ? "USING" : "ON";
|
|
return json;
|
|
}
|
|
|
|
VISIT(SampleClause) {
|
|
Json json = Json::object();
|
|
json["node"] = "SampleExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["sample_value"] = visitAsJSON(ctx->ratioExpr(0));
|
|
json["offset_value"] = visitAsJSONOrNull(ctx->ratioExpr(1));
|
|
return json;
|
|
}
|
|
|
|
VISIT(OrderExprList) { return visitJSONArrayOfObjects(ctx->orderExpr()); }
|
|
|
|
VISIT(OrderExpr) {
|
|
const char* order = ctx->DESC() || ctx->DESCENDING() ? "DESC" : "ASC";
|
|
Json json = Json::object();
|
|
json["node"] = "OrderExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["order"] = order;
|
|
if (const auto with_fill_ctx = ctx->withFillClause()) {
|
|
json["with_fill"] = visitAsJSON(with_fill_ctx);
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(WithFillClause) {
|
|
Json json = Json::object();
|
|
json["node"] = "WithFillExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
const auto column_exprs = ctx->columnExpr();
|
|
size_t idx = 0;
|
|
if (ctx->FROM()) {
|
|
json["from_value"] = visitAsJSON(column_exprs[idx++]);
|
|
}
|
|
if (ctx->TO()) {
|
|
json["to_value"] = visitAsJSON(column_exprs[idx++]);
|
|
}
|
|
if (ctx->STEP()) {
|
|
json["step_value"] = visitAsJSON(column_exprs[idx++]);
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(InterpolateExpr) {
|
|
Json json = Json::object();
|
|
json["node"] = "InterpolateExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
const auto column_exprs = ctx->columnExpr();
|
|
json["expr"] = visitAsJSON(column_exprs[0]);
|
|
json["value"] = visitAsJSON(column_exprs[1]);
|
|
return json;
|
|
}
|
|
|
|
VISIT(RatioExpr) {
|
|
if (const auto placeholder_ctx = ctx->placeholder()) {
|
|
return visitAsJSON(placeholder_ctx);
|
|
}
|
|
|
|
const auto number_literal_ctxs = ctx->numberLiteral();
|
|
|
|
if (number_literal_ctxs.size() > 2) {
|
|
throw ParsingError("RatioExpr must have at most two number literals");
|
|
} else if (number_literal_ctxs.size() == 0) {
|
|
throw ParsingError("RatioExpr must have at least one number literal");
|
|
}
|
|
|
|
auto left_ctx = number_literal_ctxs[0];
|
|
auto right_ctx = ctx->SLASH() && number_literal_ctxs.size() > 1 ? number_literal_ctxs[1] : NULL;
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "RatioExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(left_ctx);
|
|
json["right"] = visitAsJSONOrNull(right_ctx);
|
|
return json;
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(SettingExprList)
|
|
|
|
VISIT_UNSUPPORTED(SettingExpr)
|
|
|
|
VISIT(WindowExpr) {
|
|
auto frame_ctx = ctx->winFrameClause();
|
|
Json frame_json = visitAsJSONOrNull(frame_ctx);
|
|
Json frame_start_json = Json(nullptr);
|
|
Json frame_end_json = Json(nullptr);
|
|
|
|
if (!frame_json.isNull()) {
|
|
if (frame_json.isArray()) {
|
|
const auto& frameArray = frame_json.getArray();
|
|
if (frameArray.size() == 2) {
|
|
frame_start_json = frameArray[0];
|
|
frame_end_json = frameArray[1];
|
|
} else {
|
|
throw ParsingError("WindowExpr frame must be an array of size 2");
|
|
}
|
|
} else {
|
|
frame_start_json = frame_json;
|
|
}
|
|
}
|
|
|
|
const char* frame_method = nullptr;
|
|
if (frame_ctx) {
|
|
if (frame_ctx->RANGE()) {
|
|
frame_method = "RANGE";
|
|
} else if (frame_ctx->ROWS()) {
|
|
frame_method = "ROWS";
|
|
}
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "WindowExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["partition_by"] = visitAsJSONOrNull(ctx->winPartitionByClause());
|
|
json["order_by"] = visitAsJSONOrNull(ctx->winOrderByClause());
|
|
if (frame_method) {
|
|
json["frame_method"] = frame_method;
|
|
}
|
|
json["frame_start"] = frame_start_json;
|
|
json["frame_end"] = frame_end_json;
|
|
return json;
|
|
}
|
|
|
|
VISIT(WinPartitionByClause) { return visit(ctx->columnExprList()); }
|
|
|
|
VISIT(WinOrderByClause) { return visit(ctx->orderExprList()); }
|
|
|
|
VISIT(WithinGroupClause) { return visit(ctx->orderByClause()->orderExprList()); }
|
|
|
|
VISIT(WinFrameClause) { return visit(ctx->winFrameExtend()); }
|
|
|
|
VISIT(FrameStart) { return visit(ctx->winFrameBound()); }
|
|
|
|
VISIT(FrameBetween) {
|
|
// Return an array with [start, end]
|
|
Json arr = Json::array();
|
|
arr.pushBack(visitAsJSON(ctx->winFrameBound(0)));
|
|
arr.pushBack(visitAsJSON(ctx->winFrameBound(1)));
|
|
return arr;
|
|
}
|
|
|
|
VISIT(WinFrameBound) {
|
|
Json json = Json::object();
|
|
json["node"] = "WindowFrameExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
if (ctx->PRECEDING() || ctx->FOLLOWING()) {
|
|
json["frame_type"] = ctx->PRECEDING() ? "PRECEDING" : "FOLLOWING";
|
|
if (ctx->columnExpr()) {
|
|
Json value = visitAsJSON(ctx->columnExpr());
|
|
// Unwrap Constant integer/float values to bare numbers to match Python parser behavior
|
|
if (value.isObject()) {
|
|
const auto& obj = value.getObject();
|
|
auto node_it = obj.find("node");
|
|
auto val_it = obj.find("value");
|
|
if (node_it != obj.end() && node_it->second.isString()
|
|
&& node_it->second.getString() == "Constant"
|
|
&& val_it != obj.end() && val_it->second.isInt()) {
|
|
json["frame_value"] = val_it->second.getInt();
|
|
} else {
|
|
json["frame_value"] = std::move(value);
|
|
}
|
|
} else {
|
|
json["frame_value"] = std::move(value);
|
|
}
|
|
} else {
|
|
json["frame_value"] = nullptr;
|
|
}
|
|
} else {
|
|
json["frame_type"] = "CURRENT ROW";
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(Expr) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(ColumnTypeExprArray) {
|
|
string base_type = any_cast<Json>(visit(ctx->columnTypeExpr())).getString();
|
|
auto size_token = ctx->DECIMAL_LITERAL();
|
|
if (size_token) {
|
|
return Json(base_type + "[" + size_token->getText() + "]");
|
|
}
|
|
return Json(base_type + "[]");
|
|
}
|
|
|
|
VISIT(ColumnTypeExprCompound) {
|
|
string result;
|
|
for (auto ident : ctx->identifier()) {
|
|
if (!result.empty()) result += " ";
|
|
result += visitAsString(ident);
|
|
}
|
|
return Json(to_lower_copy(result));
|
|
}
|
|
|
|
VISIT(ColumnTypeExprSimple) {
|
|
return Json(to_lower_copy(visitAsString(ctx->identifier())));
|
|
}
|
|
|
|
VISIT(ColumnTypeExprNested) {
|
|
auto identifiers = ctx->identifier();
|
|
auto type_exprs = ctx->columnTypeExpr();
|
|
string name = to_lower_copy(visitAsString(identifiers[0]));
|
|
string fields;
|
|
for (size_t i = 0; i < type_exprs.size(); i++) {
|
|
if (i > 0) fields += ", ";
|
|
fields += to_lower_copy(visitAsString(identifiers[i + 1]));
|
|
fields += " ";
|
|
fields += any_cast<Json>(visit(type_exprs[i])).getString();
|
|
}
|
|
return Json(name + "(" + fields + ")");
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(ColumnTypeExprEnum)
|
|
|
|
VISIT(ColumnTypeExprComplex) {
|
|
string name = to_lower_copy(visitAsString(ctx->identifier()));
|
|
string inner;
|
|
bool first = true;
|
|
for (auto type_expr : ctx->columnTypeExpr()) {
|
|
if (!first) inner += ", ";
|
|
inner += any_cast<Json>(visit(type_expr)).getString();
|
|
first = false;
|
|
}
|
|
return Json(name + "(" + inner + ")");
|
|
}
|
|
|
|
VISIT(ColumnTypeExprParam) {
|
|
string name = to_lower_copy(visitAsString(ctx->identifier()));
|
|
string params;
|
|
auto expr_list_ctx = ctx->columnExprList();
|
|
if (expr_list_ctx) {
|
|
bool first = true;
|
|
for (auto expr_ctx : expr_list_ctx->columnExpr()) {
|
|
if (!first) params += ", ";
|
|
params += expr_ctx->getText();
|
|
first = false;
|
|
}
|
|
}
|
|
return Json(name + "(" + params + ")");
|
|
}
|
|
|
|
VISIT(ColumnExprList) { return visitJSONArrayOfObjects(ctx->columnExpr()); }
|
|
|
|
VISIT(SelectColumnExprListBeforeFromTrailingComma) { return visitJSONArrayOfObjects(ctx->selectColumnExpr()); }
|
|
|
|
VISIT(SelectColumnExprListBeforeFromPlain) { return visit(ctx->selectColumnExprList()); }
|
|
|
|
VISIT(SelectColumnExprList) { return visitJSONArrayOfObjects(ctx->selectColumnExpr()); }
|
|
|
|
VISIT(ColumnExprAliasBefore) {
|
|
string alias = visitAsString(ctx->identifier());
|
|
assertValidAlias(RESERVED_KEYWORDS, alias, ctx->identifier()->getText());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Alias";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["alias"] = alias;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprSelectValue) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(ColumnExprTernaryOp) {
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = "if";
|
|
Json args = Json::array();
|
|
args.pushBack(visitAsJSON(ctx->columnExpr(0)));
|
|
args.pushBack(visitAsJSON(ctx->columnExpr(1)));
|
|
args.pushBack(visitAsJSON(ctx->columnExpr(2)));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprAlias) {
|
|
string alias;
|
|
string raw_alias_text;
|
|
if (ctx->identifier()) {
|
|
alias = visitAsString(ctx->identifier());
|
|
raw_alias_text = ctx->identifier()->getText();
|
|
} else if (ctx->STRING_LITERAL()) {
|
|
alias = parse_string_literal_ctx(ctx->STRING_LITERAL());
|
|
raw_alias_text = ctx->STRING_LITERAL()->getText();
|
|
} else {
|
|
throw ParsingError("A ColumnExprAlias must have the alias in some form");
|
|
}
|
|
assertValidAlias(RESERVED_KEYWORDS, alias, raw_alias_text);
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Alias";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["alias"] = alias;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprInvalidFromImplicitAlias) {
|
|
throw SyntaxError("Cannot use \"from\" before an implicit alias");
|
|
}
|
|
|
|
VISIT(ColumnExprAliasImplicit) {
|
|
string alias = visitAsString(ctx->implicitAlias());
|
|
assertValidAlias(RESERVED_KEYWORDS, alias, ctx->implicitAlias()->getText());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Alias";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["alias"] = alias;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNegate) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArithmeticOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
// Create a Constant 0 for left side
|
|
Json left_json = Json::object();
|
|
left_json["node"] = "Constant";
|
|
left_json["value"] = 0;
|
|
|
|
json["left"] = std::move(left_json);
|
|
json["right"] = visitAsJSON(ctx->columnExprValue());
|
|
json["op"] = "-";
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprSubquery) { return visit(ctx->selectSetStmt()); }
|
|
|
|
VISIT(ColumnExprArray) {
|
|
Json json = Json::object();
|
|
json["node"] = "Array";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["exprs"] = visitAsJSONOrEmptyArray(ctx->columnExprList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprDict) {
|
|
Json json = Json::object();
|
|
json["node"] = "Dict";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["items"] = visitAsJSONOrEmptyArray(ctx->kvPairList());
|
|
return json;
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(ColumnExprSubstring)
|
|
|
|
VISIT(ColumnExprCast) {
|
|
Json json = Json::object();
|
|
json["node"] = "TypeCast";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["type_name"] = visitAsJSON(ctx->columnTypeExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTryCast) {
|
|
Json json = Json::object();
|
|
json["node"] = "TryCast";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["type_name"] = visitAsJSON(ctx->columnTypeExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprPrecedence1) {
|
|
string op;
|
|
if (ctx->SLASH()) {
|
|
op = "/";
|
|
} else if (ctx->ASTERISK()) {
|
|
op = "*";
|
|
} else if (ctx->PERCENT()) {
|
|
op = "%";
|
|
} else {
|
|
throw ParsingError("Unsupported value of rule ColumnExprPrecedence1");
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "ArithmeticOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->columnExprValue(0));
|
|
json["right"] = visitAsJSON(ctx->right);
|
|
json["op"] = op;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprPrecedence2) {
|
|
Json left_json = visitAsJSON(ctx->left);
|
|
Json right_json = visitAsJSON(ctx->right);
|
|
|
|
if (ctx->PLUS()) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArithmeticOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = left_json;
|
|
json["right"] = right_json;
|
|
json["op"] = "+";
|
|
return json;
|
|
} else if (ctx->DASH()) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArithmeticOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = left_json;
|
|
json["right"] = right_json;
|
|
json["op"] = "-";
|
|
return json;
|
|
} else if (ctx->CONCAT()) {
|
|
// Check if left or right are already concat calls
|
|
bool is_left_concat = isNodeOfType(left_json, "Call") && containsMatchingProperty(left_json, "name", "concat");
|
|
bool is_right_concat = isNodeOfType(right_json, "Call") && containsMatchingProperty(right_json, "name", "concat");
|
|
|
|
// Build args string manually for concat flattening
|
|
Json args = Json::array();
|
|
|
|
// Extract args from left if it's a concat, otherwise use left itself
|
|
if (is_left_concat) {
|
|
args = left_json["args"];
|
|
} else {
|
|
args.pushBack(left_json);
|
|
}
|
|
|
|
// Extract args from right if it's a concat, otherwise use right itself
|
|
if (is_right_concat) {
|
|
for (const auto& item : right_json["args"].getArray()) {
|
|
args.pushBack(item);
|
|
}
|
|
} else {
|
|
args.pushBack(right_json);
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = "concat";
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
} else {
|
|
throw ParsingError("Unsupported value of rule ColumnExprPrecedence2");
|
|
}
|
|
}
|
|
|
|
VISIT(ColumnExprPrecedence3) {
|
|
string op;
|
|
if (ctx->EQ_SINGLE() || ctx->EQ_DOUBLE()) {
|
|
op = "==";
|
|
} else if (ctx->NOT_EQ()) {
|
|
op = "!=";
|
|
} else if (ctx->LT()) {
|
|
op = "<";
|
|
} else if (ctx->LT_EQ()) {
|
|
op = "<=";
|
|
} else if (ctx->GT()) {
|
|
op = ">";
|
|
} else if (ctx->GT_EQ()) {
|
|
op = ">=";
|
|
} else if (ctx->LIKE()) {
|
|
op = ctx->NOT() ? "not like" : "like";
|
|
} else if (ctx->ILIKE()) {
|
|
op = ctx->NOT() ? "not ilike" : "ilike";
|
|
} else if (ctx->REGEX_SINGLE() or ctx->REGEX_DOUBLE()) {
|
|
op = "=~";
|
|
} else if (ctx->NOT_REGEX()) {
|
|
op = "!~";
|
|
} else if (ctx->IREGEX_SINGLE() or ctx->IREGEX_DOUBLE()) {
|
|
op = "=~*";
|
|
} else if (ctx->NOT_IREGEX()) {
|
|
op = "!~*";
|
|
} else if (ctx->IN()) {
|
|
if (ctx->COHORT()) {
|
|
op = ctx->NOT() ? "not in cohort" : "in cohort";
|
|
} else {
|
|
op = ctx->NOT() ? "not in" : "in";
|
|
}
|
|
} else {
|
|
throw ParsingError("Unsupported value of rule ColumnExprPrecedence3");
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "CompareOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->left);
|
|
json["right"] = visitAsJSON(ctx->right);
|
|
json["op"] = op;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprInterval) {
|
|
auto interval_ctx = ctx->interval();
|
|
const char* name;
|
|
if (interval_ctx->SECOND()) {
|
|
name = "toIntervalSecond";
|
|
} else if (interval_ctx->MINUTE()) {
|
|
name = "toIntervalMinute";
|
|
} else if (interval_ctx->HOUR()) {
|
|
name = "toIntervalHour";
|
|
} else if (interval_ctx->DAY()) {
|
|
name = "toIntervalDay";
|
|
} else if (interval_ctx->WEEK()) {
|
|
name = "toIntervalWeek";
|
|
} else if (interval_ctx->MONTH()) {
|
|
name = "toIntervalMonth";
|
|
} else if (interval_ctx->QUARTER()) {
|
|
name = "toIntervalQuarter";
|
|
} else if (interval_ctx->YEAR()) {
|
|
name = "toIntervalYear";
|
|
} else {
|
|
throw ParsingError("Unsupported value of rule ColumnExprInterval");
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
Json args = Json::array();
|
|
args.pushBack(visitAsJSON(ctx->columnExpr()));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprIntervalString) {
|
|
if (!ctx->STRING_LITERAL()) {
|
|
throw NotImplementedError("Unsupported interval type: missing string literal");
|
|
}
|
|
|
|
// The text should contain something like "5 day", "2 weeks", etc.
|
|
std::string text = parse_string_literal_ctx(ctx->STRING_LITERAL());
|
|
|
|
auto space_pos = text.find(' ');
|
|
if (space_pos == std::string::npos) {
|
|
throw NotImplementedError("Unsupported interval type: must be in the format '<count> <unit>'");
|
|
}
|
|
std::string count_str = text.substr(0, space_pos);
|
|
std::string unit_str = text.substr(space_pos + 1);
|
|
|
|
bool count_valid = !count_str.empty();
|
|
for (char c : count_str) {
|
|
if (!std::isdigit(static_cast<unsigned char>(c))) {
|
|
count_valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!count_valid) {
|
|
throw NotImplementedError(("Unsupported interval count: '" + count_str + "' is not a valid integer").c_str());
|
|
}
|
|
// Datastore stores intervals as Int64, so accept the full Int64 range (stoll, not int32 stoi).
|
|
int64_t countInt;
|
|
try {
|
|
countInt = std::stoll(count_str);
|
|
} catch (const std::out_of_range&) {
|
|
throw NotImplementedError(("Unsupported interval count: '" + count_str + "' is too large").c_str());
|
|
}
|
|
|
|
std::string name;
|
|
if (unit_str == "second" || unit_str == "seconds") {
|
|
name = "toIntervalSecond";
|
|
} else if (unit_str == "minute" || unit_str == "minutes") {
|
|
name = "toIntervalMinute";
|
|
} else if (unit_str == "hour" || unit_str == "hours") {
|
|
name = "toIntervalHour";
|
|
} else if (unit_str == "day" || unit_str == "days") {
|
|
name = "toIntervalDay";
|
|
} else if (unit_str == "week" || unit_str == "weeks") {
|
|
name = "toIntervalWeek";
|
|
} else if (unit_str == "month" || unit_str == "months") {
|
|
name = "toIntervalMonth";
|
|
} else if (unit_str == "quarter" || unit_str == "quarters") {
|
|
name = "toIntervalQuarter";
|
|
} else if (unit_str == "year" || unit_str == "years") {
|
|
name = "toIntervalYear";
|
|
} else {
|
|
throw NotImplementedError(("Unsupported interval unit: " + unit_str).c_str());
|
|
}
|
|
|
|
// Create Call with Constant argument
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
Json constant = Json::object();
|
|
constant["node"] = "Constant";
|
|
constant["value"] = countInt;
|
|
Json args = Json::array();
|
|
args.pushBack(std::move(constant));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprIgnoreNulls) { return visitAsJSON(ctx->columnExprValue()); }
|
|
|
|
VISIT(ColumnExprIsNull) {
|
|
Json json = Json::object();
|
|
json["node"] = "CompareOperation";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->columnExprValue());
|
|
// Create null constant for right side
|
|
Json null_constant = Json::object();
|
|
null_constant["node"] = "Constant";
|
|
null_constant["value"] = nullptr;
|
|
json["right"] = std::move(null_constant);
|
|
json["op"] = ctx->NOT() ? "!=" : "==";
|
|
json["is_null_comparison_style"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprIsDistinctFrom) {
|
|
Json json = Json::object();
|
|
json["node"] = "IsDistinctFrom";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->columnExprValue(0));
|
|
json["right"] = visitAsJSON(ctx->columnExprValue(1));
|
|
json["negated"] = ctx->NOT() != nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNullSafeEq) {
|
|
// MySQL `a <=> b` is sugar for `a IS NOT DISTINCT FROM b`
|
|
Json json = Json::object();
|
|
json["node"] = "IsDistinctFrom";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["left"] = visitAsJSON(ctx->columnExprValue(0));
|
|
json["right"] = visitAsJSON(ctx->columnExprValue(1));
|
|
json["negated"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTrim) {
|
|
const char* name;
|
|
if (ctx->LEADING()) {
|
|
name = "trimLeft";
|
|
} else if (ctx->TRAILING()) {
|
|
name = "trimRight";
|
|
} else if (ctx->BOTH()) {
|
|
name = "trim";
|
|
} else {
|
|
throw ParsingError("Unsupported value of rule ColumnExprTrim");
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
Json args = Json::array();
|
|
args.pushBack(visitAsJSON(ctx->columnExpr()));
|
|
args.pushBack(visitAsJSON(ctx->string()));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTuple) {
|
|
Json json = Json::object();
|
|
json["node"] = "Tuple";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["exprs"] = visitAsJSONOrEmptyArray(ctx->columnExprList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprArrayAccess) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArrayAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["array"] = visitAsJSON(ctx->columnExprValue());
|
|
json["property"] = visitAsJSON(ctx->columnExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprArraySlice) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArraySlice";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["array"] = visitAsJSON(ctx->columnExprValue());
|
|
auto exprs = ctx->columnExpr();
|
|
|
|
Json start_json = Json();
|
|
Json end_json = Json();
|
|
if (exprs.size() > 0) {
|
|
auto colon_index = ctx->COLON()->getSymbol()->getTokenIndex();
|
|
if (exprs[0]->getStart()->getTokenIndex() < colon_index) {
|
|
start_json = visitAsJSON(exprs[0]);
|
|
if (exprs.size() > 1) {
|
|
end_json = visitAsJSON(exprs[1]);
|
|
}
|
|
} else {
|
|
end_json = visitAsJSON(exprs[0]);
|
|
}
|
|
}
|
|
|
|
if (!start_json.isNull()) {
|
|
json["start_expr"] = std::move(start_json);
|
|
}
|
|
if (!end_json.isNull()) {
|
|
json["end_expr"] = std::move(end_json);
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNullArrayAccess) {
|
|
Json json = Json::object();
|
|
json["node"] = "ArrayAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["array"] = visitAsJSON(ctx->columnExprValue());
|
|
json["property"] = visitAsJSON(ctx->columnExpr());
|
|
json["nullish"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprPropertyAccess) {
|
|
string identifier = visitAsString(ctx->identifier());
|
|
// Create constant for property
|
|
Json property = Json::object();
|
|
property["node"] = "Constant";
|
|
property["value"] = identifier;
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "ArrayAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["array"] = visitAsJSON(ctx->columnExprValue());
|
|
json["property"] = std::move(property);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNullPropertyAccess) {
|
|
string identifier = visitAsString(ctx->identifier());
|
|
|
|
// Build property Constant node
|
|
Json property_json = Json::object();
|
|
property_json["node"] = "Constant";
|
|
property_json["value"] = identifier;
|
|
|
|
Json object_json = visitAsJSON(ctx->columnExprValue());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "ArrayAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["array"] = std::move(object_json);
|
|
json["property"] = std::move(property_json);
|
|
json["nullish"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTypeCast) {
|
|
Json json = Json::object();
|
|
json["node"] = "TypeCast";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExprValue());
|
|
json["type_name"] = visitAsJSON(ctx->columnTypeCastExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnTypeCastExprSimple) {
|
|
return Json(to_lower_copy(visitAsString(ctx->columnTypeCastIdentifier())));
|
|
}
|
|
|
|
VISIT(ColumnTypeCastExprWithTimeZone) {
|
|
string result = visitAsString(ctx->columnTypeCastIdentifier());
|
|
result += " with ";
|
|
if (ctx->LOCAL()) {
|
|
result += "local ";
|
|
}
|
|
result += "time zone";
|
|
return Json(to_lower_copy(result));
|
|
}
|
|
|
|
VISIT(ColumnExprBetween) {
|
|
// A lambda or named argument makes no sense as a BETWEEN bound (bounds
|
|
// must be comparable values), yet as the LOW bound the grammar admits
|
|
// them with an ambiguous body extent: the construct's trailing
|
|
// `columnExpr` competes with the bound separator `AND`, and ALL(*)
|
|
// resolves the split adaptively — a resolution a deterministic parser
|
|
// cannot reproduce. Reject the shape outright (through NOT / unary
|
|
// minus wrappers); parenthesizing the bound still works.
|
|
InsightsQLParser::ColumnExprValueContext* low_ctx = ctx->columnExprValue(1);
|
|
while (true) {
|
|
if (auto* not_ctx = dynamic_cast<InsightsQLParser::ColumnExprNotContext*>(low_ctx)) {
|
|
low_ctx = not_ctx->columnExprValue();
|
|
} else if (auto* neg_ctx = dynamic_cast<InsightsQLParser::ColumnExprNegateContext*>(low_ctx)) {
|
|
low_ctx = neg_ctx->columnExprValue();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (dynamic_cast<InsightsQLParser::ColumnExprLambdaContext*>(low_ctx) ||
|
|
dynamic_cast<InsightsQLParser::ColumnExprColonLambdaContext*>(low_ctx) ||
|
|
dynamic_cast<InsightsQLParser::ColumnExprNamedArgContext*>(low_ctx)) {
|
|
throw SyntaxError(
|
|
"A lambda or named argument is not a valid BETWEEN bound", low_ctx->getStart()->getStartIndex(),
|
|
low_ctx->getStop()->getStopIndex() + 1);
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "BetweenExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExprValue(0));
|
|
json["low"] = visitAsJSON(ctx->columnExprValue(1));
|
|
json["high"] = visitAsJSON(ctx->columnExprValue(2));
|
|
json["negated"] = ctx->NOT() != nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprParens) { return visit(ctx->columnExpr()); }
|
|
|
|
VISIT(ColumnExprValuePassthrough) { return visit(ctx->columnExprValue()); }
|
|
|
|
VISIT_UNSUPPORTED(ColumnExprTimestamp)
|
|
|
|
VISIT(ColumnExprAnd) {
|
|
Json left_json = visitAsJSON(ctx->columnExpr(0));
|
|
Json right_json = visitAsJSON(ctx->columnExpr(1));
|
|
|
|
Json exprs = Json::array();
|
|
if (isNodeOfType(left_json, "And")) {
|
|
exprs = left_json["exprs"];
|
|
} else {
|
|
exprs.pushBack(left_json);
|
|
}
|
|
|
|
if (isNodeOfType(right_json, "And")) {
|
|
for (const auto& item : right_json["exprs"].getArray()) {
|
|
exprs.pushBack(item);
|
|
}
|
|
} else {
|
|
exprs.pushBack(right_json);
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "And";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["exprs"] = exprs;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprOr) {
|
|
Json left_json = visitAsJSON(ctx->columnExpr(0));
|
|
Json right_json = visitAsJSON(ctx->columnExpr(1));
|
|
|
|
Json exprs = Json::array();
|
|
if (isNodeOfType(left_json, "Or")) {
|
|
exprs = left_json["exprs"];
|
|
} else {
|
|
exprs.pushBack(left_json);
|
|
}
|
|
|
|
if (isNodeOfType(right_json, "Or")) {
|
|
for (const auto& item : right_json["exprs"].getArray()) {
|
|
exprs.pushBack(item);
|
|
}
|
|
} else {
|
|
exprs.pushBack(right_json);
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Or";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["exprs"] = exprs;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTupleAccess) {
|
|
string index_str = ctx->DECIMAL_LITERAL()->getText();
|
|
int64_t index_value = stoll(index_str);
|
|
Json tuple_json = visitAsJSON(ctx->columnExprValue());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "TupleAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["tuple"] = tuple_json;
|
|
json["index"] = index_value;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNullTupleAccess) {
|
|
string index_str = ctx->DECIMAL_LITERAL()->getText();
|
|
int64_t index_value = stoll(index_str);
|
|
Json tuple_json = visitAsJSON(ctx->columnExprValue());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "TupleAccess";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["tuple"] = tuple_json;
|
|
json["index"] = index_value;
|
|
json["nullish"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprCase) {
|
|
auto column_expr_ctx = ctx->columnExpr();
|
|
size_t columns_size = column_expr_ctx.size();
|
|
vector<Json> columns = visitAsVectorOfJSON(column_expr_ctx);
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
if (ctx->caseExpr) {
|
|
// CASE expr WHEN ... THEN ... ELSE ... END
|
|
// Transform to: transform(expr, [conditions], [results], else_result)
|
|
json["name"] = "transform";
|
|
|
|
Json args = Json::array();
|
|
|
|
// arg_0: the case expression
|
|
args.pushBack(columns[0]);
|
|
|
|
// arg_1: Array of conditions (odd indices from 1 to columns_size-2)
|
|
Json conditions_array = Json::object();
|
|
conditions_array["node"] = "Array";
|
|
Json conditions_exprs = Json::array();
|
|
for (size_t index = 1; index < columns_size - 1; index++) {
|
|
if ((index - 1) % 2 == 0) {
|
|
conditions_exprs.pushBack(columns[index]);
|
|
}
|
|
}
|
|
conditions_array["exprs"] = std::move(conditions_exprs);
|
|
args.pushBack(std::move(conditions_array));
|
|
|
|
// arg_2: Array of results (even indices from 1 to columns_size-2)
|
|
Json results_array = Json::object();
|
|
results_array["node"] = "Array";
|
|
Json results_exprs = Json::array();
|
|
for (size_t index = 1; index < columns_size - 1; index++) {
|
|
if ((index - 1) % 2 == 1) {
|
|
results_exprs.pushBack(columns[index]);
|
|
}
|
|
}
|
|
results_array["exprs"] = std::move(results_exprs);
|
|
args.pushBack(std::move(results_array));
|
|
|
|
// arg_3: else result (last element)
|
|
args.pushBack(columns[columns_size - 1]);
|
|
|
|
json["args"] = args;
|
|
} else {
|
|
// CASE WHEN ... THEN ... ELSE ... END
|
|
json["name"] = columns_size == 3 ? "if" : "multiIf";
|
|
Json args = Json::array();
|
|
for (const auto& col : columns) {
|
|
args.pushBack(col);
|
|
}
|
|
json["args"] = std::move(args);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(ColumnExprDate)
|
|
|
|
VISIT(ColumnExprNot) {
|
|
Json expr_json = visitAsJSON(ctx->columnExprValue());
|
|
Json json = Json::object();
|
|
json["node"] = "Not";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = expr_json;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprWinFunctionTarget) {
|
|
auto column_expr_list_ctx = ctx->columnExprs;
|
|
string name = visitAsString(ctx->identifier(0));
|
|
string over_identifier = visitAsString(ctx->identifier(1));
|
|
Json exprs_json = visitAsJSONOrEmptyArray(column_expr_list_ctx);
|
|
Json args_json = visitAsJSONOrEmptyArray(ctx->columnArgList);
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "WindowFunction";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
json["exprs"] = exprs_json;
|
|
json["args"] = args_json;
|
|
json["over_identifier"] = over_identifier;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprWinFunction) {
|
|
string identifier = visitAsString(ctx->identifier());
|
|
auto column_expr_list_ctx = ctx->columnExprs;
|
|
Json exprs_json = visitAsJSONOrEmptyArray(column_expr_list_ctx);
|
|
Json args_json = visitAsJSONOrEmptyArray(ctx->columnArgList);
|
|
Json over_expr_json = visitAsJSONOrNull(ctx->windowExpr());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "WindowFunction";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = identifier;
|
|
json["exprs"] = exprs_json;
|
|
json["args"] = args_json;
|
|
json["over_expr"] = over_expr_json;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprIdentifier) { return visit(ctx->columnIdentifier()); }
|
|
|
|
VISIT(ColumnExprFunction) {
|
|
string name = visitAsString(ctx->identifier());
|
|
|
|
// if two LPARENs ()(), make sure the first one is at least an empty list
|
|
// FILTER adds an extra LPAREN, so account for it when detecting parametric calls
|
|
int lparen_threshold = ctx->FILTER() ? 2 : 1;
|
|
Json params_json;
|
|
if (ctx->LPAREN(lparen_threshold)) {
|
|
params_json = visitAsJSONOrEmptyArray(ctx->columnExprs);
|
|
} else {
|
|
params_json = visitAsJSONOrNull(ctx->columnExprs);
|
|
}
|
|
|
|
Json args_json = visitAsJSONOrEmptyArray(ctx->columnArgList);
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
json["params"] = params_json;
|
|
json["args"] = args_json;
|
|
json["distinct"] = ctx->DISTINCT() != nullptr;
|
|
auto order_expr_list_ctx = ctx->orderExprList();
|
|
if (order_expr_list_ctx) {
|
|
json["order_by"] = visitAsJSON(order_expr_list_ctx);
|
|
} else {
|
|
json["order_by"] = Json();
|
|
}
|
|
if (ctx->filterExpr) {
|
|
json["filter_expr"] = visitAsJSON(ctx->filterExpr);
|
|
} else {
|
|
json["filter_expr"] = Json();
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprFunctionWithinGroup) {
|
|
string name = visitAsString(ctx->identifier());
|
|
Json params_json = visitAsJSONOrEmptyArray(ctx->columnExprs);
|
|
Json within_group_json = visitAsJSON(ctx->withinGroupClause());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = name;
|
|
json["params"] = params_json;
|
|
json["args"] = Json::array();
|
|
json["distinct"] = false;
|
|
json["within_group"] = within_group_json;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprAsterisk) {
|
|
auto table_identifier_ctx = ctx->tableIdentifier();
|
|
|
|
if (ctx->EXCLUDE()) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
Json exclude = Json::array();
|
|
vector<string> identifiers = any_cast<vector<string>>(visit(ctx->identifierList()));
|
|
for (const auto& ident : identifiers) {
|
|
exclude.pushBack(ident);
|
|
}
|
|
json["exclude"] = std::move(exclude);
|
|
return json;
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Field";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json chain = Json::array();
|
|
|
|
if (table_identifier_ctx) {
|
|
vector<string> table = any_cast<vector<string>>(visit(table_identifier_ctx));
|
|
for (const auto& part : table) {
|
|
chain.pushBack(part);
|
|
}
|
|
chain.pushBack("*");
|
|
} else {
|
|
chain.pushBack("*");
|
|
}
|
|
|
|
json["chain"] = std::move(chain);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsRegex) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["regex"] = parse_string_literal_ctx(ctx->STRING_LITERAL());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsList) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["columns"] = visitAsJSONOrEmptyArray(ctx->columnExprList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsExclude) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
Json exclude = Json::array();
|
|
vector<string> identifiers = any_cast<vector<string>>(visit(ctx->identifierList()));
|
|
for (const auto& ident : identifiers) {
|
|
exclude.pushBack(ident);
|
|
}
|
|
json["exclude"] = std::move(exclude);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsAll) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsReplace) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
json["replace"] = buildColumnsReplaceJson(ctx->columnsReplaceList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsExcludeReplace) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
Json exclude = Json::array();
|
|
vector<string> identifiers = any_cast<vector<string>>(visit(ctx->identifierList()));
|
|
for (const auto& ident : identifiers) {
|
|
exclude.pushBack(ident);
|
|
}
|
|
json["exclude"] = std::move(exclude);
|
|
json["replace"] = buildColumnsReplaceJson(ctx->columnsReplaceList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsQualifiedAll) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsQualifiedExclude) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
Json exclude = Json::array();
|
|
vector<string> identifiers = any_cast<vector<string>>(visit(ctx->identifierList()));
|
|
for (const auto& ident : identifiers) {
|
|
exclude.pushBack(ident);
|
|
}
|
|
json["exclude"] = std::move(exclude);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsQualifiedReplace) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
json["replace"] = buildColumnsReplaceJson(ctx->columnsReplaceList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprColumnsQualifiedExcludeReplace) {
|
|
Json json = Json::object();
|
|
json["node"] = "ColumnsExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["all_columns"] = true;
|
|
Json exclude = Json::array();
|
|
vector<string> identifiers = any_cast<vector<string>>(visit(ctx->identifierList()));
|
|
for (const auto& ident : identifiers) {
|
|
exclude.pushBack(ident);
|
|
}
|
|
json["exclude"] = std::move(exclude);
|
|
json["replace"] = buildColumnsReplaceJson(ctx->columnsReplaceList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprSpreadColumnsRegex) {
|
|
Json json = Json::object();
|
|
json["node"] = "SpreadExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json inner = Json::object();
|
|
inner["node"] = "ColumnsExpr";
|
|
inner["regex"] = parse_string_literal_ctx(ctx->STRING_LITERAL());
|
|
json["expr"] = inner;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprSpreadColumnsList) {
|
|
Json json = Json::object();
|
|
json["node"] = "SpreadExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json inner = Json::object();
|
|
inner["node"] = "ColumnsExpr";
|
|
inner["columns"] = visitAsJSONOrEmptyArray(ctx->columnExprList());
|
|
json["expr"] = inner;
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTagElement) { return visit(ctx->insightsqlxTagElement()); }
|
|
|
|
VISIT(ColumnExprPositional) {
|
|
Json json = Json::object();
|
|
json["node"] = "PositionalRef";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["index"] = static_cast<int64_t>(stoll(ctx->DECIMAL_LITERAL()->getText()));
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprNamedArg) {
|
|
Json json = Json::object();
|
|
json["node"] = Json("NamedArgument");
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
json["value"] = visitAsJSON(ctx->columnExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprLambda) {
|
|
return visit(ctx->columnLambdaExpr());
|
|
}
|
|
|
|
VISIT(ColumnExprColonLambda) {
|
|
vector<string> args_vec = visitAsVectorOfStrings(ctx->identifier());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Lambda";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json args = Json::array();
|
|
for (const auto& arg : args_vec) {
|
|
args.pushBack(arg);
|
|
}
|
|
json["args"] = std::move(args);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ArrowLambda) {
|
|
auto column_expr_ctx = ctx->columnExpr();
|
|
auto block_ctx = ctx->block();
|
|
if (!column_expr_ctx && !block_ctx) {
|
|
throw ParsingError("ArrowLambda must have either a columnExpr or a block");
|
|
}
|
|
|
|
Json expr_json;
|
|
if (column_expr_ctx) {
|
|
expr_json = visitAsJSON(column_expr_ctx);
|
|
} else {
|
|
expr_json = visitAsJSON(block_ctx);
|
|
}
|
|
|
|
vector<string> args_vec = visitAsVectorOfStrings(ctx->identifier());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Lambda";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json args = Json::array();
|
|
for (const auto& arg : args_vec) {
|
|
args.pushBack(arg);
|
|
}
|
|
json["args"] = std::move(args);
|
|
json["expr"] = std::move(expr_json);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColonLambda) {
|
|
vector<string> args_vec = visitAsVectorOfStrings(ctx->identifier());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Lambda";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json args = Json::array();
|
|
for (const auto& arg : args_vec) {
|
|
args.pushBack(arg);
|
|
}
|
|
json["args"] = std::move(args);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT(WithExprList) {
|
|
// Emit CTEs as an array to preserve declaration order.
|
|
Json json = Json::array();
|
|
|
|
for (auto with_expr_ctx : ctx->withExpr()) {
|
|
json.pushBack(visitAsJSON(with_expr_ctx));
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(WithExprSubquery) {
|
|
Json json = Json::object();
|
|
json["node"] = "CTE";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
json["expr"] = visitAsJSON(ctx->selectSetStmt());
|
|
json["cte_type"] = "subquery";
|
|
json["materialized"] = Json::Null();
|
|
if (ctx->MATERIALIZED()) {
|
|
json["materialized"] = ctx->NOT() ? false : true;
|
|
}
|
|
json["columns"] = Json::Null();
|
|
json["using_key"] = Json::Null();
|
|
const auto& columnNameLists = ctx->withExprColumnNameList();
|
|
if (ctx->USING()) {
|
|
// USING KEY present: last list is the key columns
|
|
const auto& usingKeyList = columnNameLists.back();
|
|
json["using_key"] = Json::array();
|
|
for (const auto& ident : usingKeyList->identifier()) {
|
|
json["using_key"].pushBack(visitAsString(ident));
|
|
}
|
|
// If there are two lists, the first is the CTE column names
|
|
if (columnNameLists.size() > 1) {
|
|
json["columns"] = Json::array();
|
|
for (const auto& ident : columnNameLists.front()->identifier()) {
|
|
json["columns"].pushBack(visitAsString(ident));
|
|
}
|
|
}
|
|
} else if (!columnNameLists.empty()) {
|
|
json["columns"] = Json::array();
|
|
for (const auto& ident : columnNameLists.front()->identifier()) {
|
|
json["columns"].pushBack(visitAsString(ident));
|
|
}
|
|
}
|
|
return json;
|
|
}
|
|
|
|
VISIT(WithExprColumn) {
|
|
Json json = Json::object();
|
|
json["node"] = "CTE";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
json["cte_type"] = "column";
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnIdentifier) {
|
|
if (const auto placeholder_ctx = ctx->placeholder()) {
|
|
return visitAsJSON(placeholder_ctx);
|
|
}
|
|
const auto table_identifier_ctx = ctx->tableIdentifier();
|
|
const auto nested_identifier_ctx = ctx->nestedIdentifier();
|
|
vector<string> table =
|
|
table_identifier_ctx ? any_cast<vector<string>>(visit(table_identifier_ctx)) : vector<string>();
|
|
vector<string> nested =
|
|
nested_identifier_ctx ? any_cast<vector<string>>(visit(nested_identifier_ctx)) : vector<string>();
|
|
|
|
if (table.size() == 0 && nested.size() > 0) {
|
|
string text = ctx->getText();
|
|
to_lower(text);
|
|
if (!text.compare("true")) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = true;
|
|
return json;
|
|
}
|
|
if (!text.compare("false")) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = false;
|
|
return json;
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "Field";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json chain = Json::array();
|
|
for (const auto& part : nested) {
|
|
chain.pushBack(part);
|
|
}
|
|
json["chain"] = std::move(chain);
|
|
return json;
|
|
}
|
|
vector<string> table_plus_nested = table;
|
|
table_plus_nested.insert(table_plus_nested.end(), nested.begin(), nested.end());
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Field";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json chain = Json::array();
|
|
for (const auto& part : table_plus_nested) {
|
|
chain.pushBack(part);
|
|
}
|
|
json["chain"] = std::move(chain);
|
|
return json;
|
|
}
|
|
|
|
VISIT(NestedIdentifier) {
|
|
return visitAsVectorOfStrings(ctx->identifier());
|
|
}
|
|
|
|
VISIT(TableExprIdentifier) {
|
|
vector<string> chain_vec = any_cast<vector<string>>(visit(ctx->tableIdentifier()));
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Field";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json chain = Json::array();
|
|
for (const auto& part : chain_vec) {
|
|
chain.pushBack(part);
|
|
}
|
|
json["chain"] = std::move(chain);
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableExprSubquery) { return visit(ctx->selectSetStmt()); }
|
|
|
|
VISIT(TableExprValues) { return visit(ctx->valuesClause()); }
|
|
|
|
VISIT(ValuesClause) {
|
|
Json json = Json::object();
|
|
json["node"] = "ValuesQuery";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
Json rows = Json::array();
|
|
for (auto* row_ctx : ctx->valuesRow()) {
|
|
Json row = Json::array();
|
|
for (auto* expr_ctx : row_ctx->columnExpr()) {
|
|
row.pushBack(visitAsJSON(expr_ctx));
|
|
}
|
|
rows.pushBack(std::move(row));
|
|
}
|
|
json["rows"] = std::move(rows);
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableExprPlaceholder) { return visitAsJSON(ctx->placeholder()); }
|
|
|
|
VISIT(TableExprPivot) {
|
|
Json json = Json::object();
|
|
json["node"] = "PivotExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = visitAsJSON(ctx->tableExpr());
|
|
|
|
// First columnExprList is the aggregates
|
|
auto expr_lists = ctx->columnExprList();
|
|
json["aggregates"] = visitAsJSON(expr_lists[0]);
|
|
|
|
// Pivot columns
|
|
Json columns = Json::array();
|
|
for (auto pivot_col : ctx->pivotColumnList()->pivotColumn()) {
|
|
Json col_json = Json::object();
|
|
col_json["node"] = "PivotColumn";
|
|
|
|
auto tuple_or_single = pivot_col->columnExprTupleOrSingle();
|
|
if (tuple_or_single->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(tuple_or_single->columnExprList());
|
|
col_json["column"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["column"] = visitAsJSON(tuple_or_single->columnExpr());
|
|
}
|
|
|
|
col_json["values"] = visitAsJSON(pivot_col->columnExprList());
|
|
columns.pushBack(std::move(col_json));
|
|
}
|
|
json["columns"] = std::move(columns);
|
|
|
|
// Optional GROUP BY (second columnExprList)
|
|
if (expr_lists.size() > 1) {
|
|
json["group_by"] = visitAsJSON(expr_lists[1]);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableExprUnpivot) {
|
|
Json json = Json::object();
|
|
json["node"] = "UnpivotExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = visitAsJSON(ctx->tableExpr());
|
|
|
|
Json columns = Json::array();
|
|
for (auto unpivot_col : ctx->unpivotColumnList()->unpivotColumn()) {
|
|
Json col_json = Json::object();
|
|
col_json["node"] = "UnpivotColumn";
|
|
|
|
auto tuple_or_singles = unpivot_col->columnExprTupleOrSingle();
|
|
// value_columns
|
|
auto val_ctx = tuple_or_singles[0];
|
|
if (val_ctx->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(val_ctx->columnExprList());
|
|
col_json["value_columns"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["value_columns"] = visitAsJSON(val_ctx->columnExpr());
|
|
}
|
|
// name_columns
|
|
auto name_ctx = tuple_or_singles[1];
|
|
if (name_ctx->columnExprList()) {
|
|
Json tuple_json = Json::object();
|
|
tuple_json["node"] = "Tuple";
|
|
tuple_json["exprs"] = visitAsJSON(name_ctx->columnExprList());
|
|
col_json["name_columns"] = std::move(tuple_json);
|
|
} else {
|
|
col_json["name_columns"] = visitAsJSON(name_ctx->columnExpr());
|
|
}
|
|
// unpivot_values (use only the first IN list; additional groups from DuckDB multi-column syntax are dropped)
|
|
col_json["unpivot_values"] = visitAsJSON(unpivot_col->columnExprList()[0]);
|
|
columns.pushBack(std::move(col_json));
|
|
}
|
|
json["columns"] = std::move(columns);
|
|
json["include_nulls"] = ctx->INCLUDE() != nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableExprAlias) {
|
|
auto alias_ctx = ctx->alias();
|
|
string alias = alias_ctx ? visitAsString(alias_ctx) : visitAsString(ctx->identifier());
|
|
string raw_alias_text = alias_ctx ? alias_ctx->getText() : ctx->identifier()->getText();
|
|
assertValidAlias(RESERVED_KEYWORDS, alias, raw_alias_text);
|
|
|
|
// Parse column aliases if present
|
|
Json column_aliases = nullptr;
|
|
auto column_aliases_ctx = ctx->columnAliases();
|
|
if (column_aliases_ctx) {
|
|
column_aliases = Json::array();
|
|
for (auto ident_ctx : column_aliases_ctx->identifier()) {
|
|
column_aliases.pushBack(any_cast<string>(visit(ident_ctx)));
|
|
}
|
|
}
|
|
|
|
Json table_json = visitAsJSON(ctx->tableExpr());
|
|
|
|
// Check if table is already a JoinExpr
|
|
bool is_table_a_join_expr = isNodeOfType(table_json, "JoinExpr");
|
|
if (is_table_a_join_expr) {
|
|
// Inject alias into the existing JoinExpr
|
|
table_json["alias"] = alias;
|
|
table_json["column_aliases"] = column_aliases;
|
|
return table_json;
|
|
}
|
|
|
|
// Wrap table in a JoinExpr with alias
|
|
// Note: sample/table_final/join_type/constraint will be injected by JoinExprTable/JoinExprOp before the final }
|
|
Json json = Json::object();
|
|
json["node"] = "JoinExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = std::move(table_json);
|
|
json["alias"] = alias;
|
|
json["column_aliases"] = column_aliases;
|
|
json["next_join"] = nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableExprFunction) { return visit(ctx->tableFunctionExpr()); }
|
|
|
|
VISIT(TableExprTag) { return visit(ctx->insightsqlxTagElement()); }
|
|
|
|
VISIT(TableFunctionExpr) {
|
|
string table_name = visitAsString(ctx->identifier());
|
|
auto table_args_ctx = ctx->tableArgList();
|
|
Json table_args_json = table_args_ctx ? visitAsJSON(table_args_ctx) : Json::array();
|
|
|
|
// Build Field node for table name
|
|
Json table_json = Json::object();
|
|
table_json["node"] = "Field";
|
|
Json chain = Json::array();
|
|
chain.pushBack(table_name);
|
|
table_json["chain"] = std::move(chain);
|
|
|
|
// Build JoinExpr wrapping the table with table_args
|
|
Json json = Json::object();
|
|
json["node"] = "JoinExpr";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["table"] = std::move(table_json);
|
|
json["table_args"] = std::move(table_args_json);
|
|
json["next_join"] = nullptr;
|
|
json["alias"] = nullptr;
|
|
return json;
|
|
}
|
|
|
|
VISIT(TableIdentifier) {
|
|
auto nested_identifier_ctx = ctx->nestedIdentifier();
|
|
vector<string> nested =
|
|
nested_identifier_ctx ? any_cast<vector<string>>(visit(nested_identifier_ctx)) : vector<string>();
|
|
|
|
auto database_identifier_ctx = ctx->databaseIdentifier();
|
|
if (database_identifier_ctx) {
|
|
vector<string> database_plus_nested = vector<string>{visitAsString(database_identifier_ctx)};
|
|
database_plus_nested.insert(database_plus_nested.end(), nested.begin(), nested.end());
|
|
return database_plus_nested;
|
|
}
|
|
return nested;
|
|
}
|
|
|
|
VISIT(TableArgList) { return visitJSONArrayOfObjects(ctx->columnExpr()); }
|
|
|
|
VISIT(DatabaseIdentifier) { return visit(ctx->identifier()); }
|
|
|
|
VISIT_UNSUPPORTED(FloatingLiteral)
|
|
|
|
VISIT(NumberLiteral) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
|
|
string text = ctx->getText();
|
|
to_lower(text);
|
|
|
|
// Grammar admits an optional leading sign on every numberLiteral, including INF/NAN.
|
|
// Strip a leading '+' so downstream comparisons (and stoll's sign handling) work uniformly.
|
|
if (!text.empty() && text[0] == '+') {
|
|
text.erase(0, 1);
|
|
}
|
|
|
|
// Hex: route through the integer branch — `stod` would parse "0xfe" as a double and lose precision near 2^53.
|
|
bool is_hex = (text.compare(0, 2, "0x") == 0) ||
|
|
(text.size() > 2 && text[0] == '-' && text.compare(1, 2, "0x") == 0);
|
|
// Binary (`0b…`, BINARY_LITERAL token): strip prefix, parse base 2.
|
|
bool is_binary = (text.compare(0, 2, "0b") == 0) ||
|
|
(text.size() > 2 && text[0] == '-' && text.compare(1, 2, "0b") == 0);
|
|
// `0o…` (Postgres-16 octal, OCTAL_PREFIX_LITERAL token): unsupported, rejected below.
|
|
bool is_postgres_octal = (text.compare(0, 2, "0o") == 0) ||
|
|
(text.size() > 2 && text[0] == '-' && text.compare(1, 2, "0o") == 0);
|
|
if (is_postgres_octal) {
|
|
throw SyntaxError(
|
|
"InsightsQL does not support `0o`-prefixed octal integer literals; got `" +
|
|
text + "`. Use a plain decimal literal instead.");
|
|
}
|
|
|
|
if (text.find("inf") != string::npos || text.find("nan") != string::npos) {
|
|
// INF token matches both `inf` and `infinity`. value_type="number" tells the deserializer to convert.
|
|
if (!text.compare("-inf") || !text.compare("-infinity")) {
|
|
json["value"] = "-Infinity";
|
|
} else if (!text.compare("inf") || !text.compare("infinity")) {
|
|
json["value"] = "Infinity";
|
|
} else {
|
|
json["value"] = "NaN";
|
|
}
|
|
json["value_type"] = "number";
|
|
} else if (!is_hex && !is_binary && (text.find(".") != string::npos || text.find("e") != string::npos)) {
|
|
// `stod` collapses overflow + underflow into the same `out_of_range`; use `strtod` + errno to keep them apart.
|
|
errno = 0;
|
|
const char* c_str = text.c_str();
|
|
char* end = nullptr;
|
|
double value = std::strtod(c_str, &end);
|
|
bool consumed_all = end == c_str + text.size();
|
|
if (!consumed_all) {
|
|
// Malformed input — defer to `stod` so callers see the historical SyntaxError shape.
|
|
json["value"] = Json(stod(text));
|
|
} else if (errno == ERANGE && (value == HUGE_VAL || value == -HUGE_VAL)) {
|
|
json["value"] = (text[0] == '-') ? "-Infinity" : "Infinity";
|
|
json["value_type"] = "number";
|
|
} else {
|
|
// No errno (in range) or `ERANGE` underflow (subnormal / 0) — keep the value.
|
|
json["value"] = Json(value);
|
|
}
|
|
return json;
|
|
} else if (is_binary) {
|
|
// Datastore caps binary literals at 64 bits — magnitude must fit UInt64
|
|
// (positive) or Int64 (negative); wider literals are rejected.
|
|
bool negative = text[0] == '-';
|
|
string digits = negative ? text.substr(3) : text.substr(2);
|
|
uint64_t magnitude;
|
|
try {
|
|
magnitude = stoull(digits, nullptr, 2);
|
|
} catch (const std::out_of_range&) {
|
|
throw SyntaxError("InsightsQL binary integer literals are limited to 64 bits; got `" + text + "`.");
|
|
}
|
|
if (negative) {
|
|
if (magnitude > 9223372036854775808ULL) {
|
|
throw SyntaxError("InsightsQL binary integer literals are limited to 64 bits; got `" + text + "`.");
|
|
}
|
|
json["value"] = (magnitude == 9223372036854775808ULL) ? INT64_MIN : -static_cast<int64_t>(magnitude);
|
|
} else if (magnitude > static_cast<uint64_t>(INT64_MAX)) {
|
|
// Exceeds Int64 but fits UInt64; Json has no unsigned slot, so emit
|
|
// the exact value as a raw JSON number rather than rounding to double.
|
|
json["value"] = Json::raw(std::to_string(magnitude));
|
|
} else {
|
|
json["value"] = static_cast<int64_t>(magnitude);
|
|
}
|
|
return json;
|
|
} else if (is_hex && ctx->floatingLiteral() != nullptr) {
|
|
// Hex-float literal (e.g. `0x1p4`) — route through `stod`; the integer path's `stoll` would drop the exponent.
|
|
json["value"] = Json(stod(text));
|
|
return json;
|
|
} else {
|
|
try {
|
|
// base 10 (not strtoll base 0): leading zeros are no-ops, never octal — "017" → 17, "09" → 9.
|
|
int base = is_hex ? 16 : 10;
|
|
json["value"] = static_cast<int64_t>(stoll(text, nullptr, base)); // Integer
|
|
} catch (const std::out_of_range&) {
|
|
// Beyond Int64 — keep the literal lossless via the `value_type: "number"` string envelope; the deserialiser rebuilds an arbitrary-precision Python int.
|
|
json["value"] = text;
|
|
json["value_type"] = "number";
|
|
}
|
|
return json;
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(Literal) {
|
|
if (ctx->NULL_SQL()) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = nullptr;
|
|
return json;
|
|
}
|
|
if (const auto string_literal_terminal = ctx->STRING_LITERAL()) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = parse_string_literal_ctx(string_literal_terminal);
|
|
return json;
|
|
}
|
|
return visitChildren(ctx);
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(Interval)
|
|
|
|
VISIT_UNSUPPORTED(Keyword)
|
|
|
|
VISIT(KeywordForAlias) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(KeywordForImplicitAlias) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(Identifier) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(ColumnTypeCastIdentifier) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(Alias) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(ImplicitAlias) { return unquoteIdentifierText(ctx->getText()); }
|
|
|
|
VISIT(InsightsqlxTagAttribute) {
|
|
Json json = Json::object();
|
|
json["node"] = "InsightsQLXAttribute";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = visitAsString(ctx->identifier());
|
|
|
|
if (const auto column_expr_ctx = ctx->columnExpr()) {
|
|
json["value"] = visitAsJSON(column_expr_ctx);
|
|
} else {
|
|
if (const auto string_ctx = ctx->string()) {
|
|
json["value"] = visitAsJSON(string_ctx);
|
|
} else {
|
|
// Default to true Constant
|
|
json["value"] = Json::object();
|
|
json["value"]["node"] = "Constant";
|
|
json["value"]["value"] = true;
|
|
}
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
VISIT(InsightsqlxChildElement) {
|
|
if (const auto tag_element_ctx = ctx->insightsqlxTagElement()) {
|
|
return visitAsJSON(tag_element_ctx);
|
|
}
|
|
if (const auto text_element_ctx = ctx->insightsqlxText()) {
|
|
return visitAsJSON(text_element_ctx);
|
|
}
|
|
return visitAsJSON(ctx->columnExpr());
|
|
}
|
|
|
|
VISIT(InsightsqlxText) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = ctx->INSIGHTSQLX_TEXT_TEXT()->getText();
|
|
return json;
|
|
}
|
|
|
|
VISIT(InsightsqlxTagElementClosed) {
|
|
Json json = Json::object();
|
|
json["node"] = "InsightsQLXTag";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["kind"] = visitAsString(ctx->identifier());
|
|
json["attributes"] = visitAsVectorOfJSON(ctx->insightsqlxTagAttribute());
|
|
return json;
|
|
}
|
|
|
|
VISIT(InsightsqlxTagElementNested) {
|
|
std::string opening = visitAsString(ctx->identifier(0));
|
|
std::string closing = visitAsString(ctx->identifier(1));
|
|
if (opening != closing) {
|
|
throw SyntaxError("Opening and closing InsightsQLX tags must match. Got " + opening + " and " + closing);
|
|
}
|
|
|
|
const auto attribute_ctxs = ctx->insightsqlxTagAttribute();
|
|
vector<Json> attributes = visitAsVectorOfJSON(attribute_ctxs);
|
|
|
|
/* ── children ───────────────────────────────────────────── */
|
|
std::vector<Json> kept_children;
|
|
for (const auto child_ctx : ctx->insightsqlxChildElement()) {
|
|
Json child_json = visitAsJSON(child_ctx);
|
|
|
|
/* drop Constant nodes that are only-whitespace *and* contain a line-break */
|
|
if (isNodeOfType(child_json, "Constant")) {
|
|
const auto& obj = child_json.getObject();
|
|
auto value_it = obj.find("value");
|
|
if (value_it != obj.end() && value_it->second.isString()) {
|
|
const string& value_text = value_it->second.getString();
|
|
bool only_ws = true;
|
|
for (char c : value_text) {
|
|
if (!isspace(static_cast<unsigned char>(c))) {
|
|
only_ws = false;
|
|
break;
|
|
}
|
|
}
|
|
bool has_newline = value_text.find('\n') != string::npos || value_text.find('\r') != string::npos;
|
|
if (only_ws && has_newline) {
|
|
continue; // skip it
|
|
}
|
|
}
|
|
}
|
|
|
|
kept_children.emplace_back(std::move(child_json)); // keep it
|
|
}
|
|
|
|
/* if we have child nodes, validate + attach them as attribute "children" */
|
|
if (!kept_children.empty()) {
|
|
// Check if any attribute is named "children"
|
|
for (const auto& attr_json : attributes) {
|
|
if (attr_json.isObject() && attr_json.getObject().at("name").getString() == "children") {
|
|
throw SyntaxError("Can't have a InsightsQLX tag with both children and a 'children' attribute");
|
|
}
|
|
}
|
|
|
|
/* build children attribute */
|
|
Json children_array = Json::array();
|
|
for (const auto& child : kept_children) {
|
|
children_array.pushBack(child);
|
|
}
|
|
|
|
Json children_attr = Json::object();
|
|
children_attr["node"] = "InsightsQLXAttribute";
|
|
children_attr["name"] = "children";
|
|
children_attr["value"] = std::move(children_array);
|
|
attributes.push_back(children_attr);
|
|
}
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "InsightsQLXTag";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["kind"] = opening;
|
|
json["attributes"] = std::move(attributes);
|
|
return json;
|
|
}
|
|
|
|
VISIT(Placeholder) {
|
|
Json json = Json::object();
|
|
json["node"] = "Placeholder";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExpr());
|
|
return json;
|
|
}
|
|
|
|
VISIT_UNSUPPORTED(EnumValue)
|
|
|
|
VISIT(ColumnExprNullish) {
|
|
Json value_json = visitAsJSON(ctx->columnExprValue(0));
|
|
Json fallback_json = visitAsJSON(ctx->columnExprValue(1));
|
|
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = "ifNull";
|
|
Json args = Json::array();
|
|
args.pushBack(value_json);
|
|
args.pushBack(fallback_json);
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprCall) {
|
|
Json json = Json::object();
|
|
json["node"] = "ExprCall";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = visitAsJSON(ctx->columnExprValue());
|
|
json["args"] = visitAsJSONOrEmptyArray(ctx->columnExprList());
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprCallSelect) {
|
|
// 1) Parse the "function expression" from columnExprValue().
|
|
Json expr_json = visitAsJSON(ctx->columnExprValue());
|
|
|
|
// 2) Check if `expr` is a Field node with a chain of length == 1.
|
|
// If so, interpret that chain[0] as the function name, and the SELECT as the function argument.
|
|
bool is_field = isNodeOfType(expr_json, "Field");
|
|
|
|
if (is_field) {
|
|
// Extract chain array from Field
|
|
Json chain_json = expr_json.getObject().at("chain");
|
|
|
|
if (!chain_json.isArray()) {
|
|
throw ParsingError("Expected 'chain' to be an array in Field node");
|
|
}
|
|
|
|
if (chain_json.getArray().size() == 1) {
|
|
// Extract function name
|
|
string func_name = chain_json.getArray()[0].getString();
|
|
|
|
// Build Call(name=func_name, args=[select])
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = func_name;
|
|
Json args = Json::array();
|
|
args.pushBack(visitAsJSON(ctx->selectSetStmt()));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
}
|
|
|
|
// 3) Otherwise, build ExprCall(expr=<expr>, args=[select])
|
|
Json json = Json::object();
|
|
json["node"] = "ExprCall";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["expr"] = expr_json;
|
|
Json args = Json::array();
|
|
args.pushBack(visitAsJSON(ctx->selectSetStmt()));
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(ColumnExprTemplateString) { return visit(ctx->templateString()); }
|
|
|
|
VISIT(String) {
|
|
auto string_literal = ctx->STRING_LITERAL();
|
|
if (string_literal) {
|
|
string text = parse_string_literal_ctx(string_literal);
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = text;
|
|
return json;
|
|
}
|
|
return visit(ctx->templateString());
|
|
}
|
|
|
|
VISIT(TemplateString) {
|
|
auto string_contents = ctx->stringContents();
|
|
|
|
if (string_contents.size() == 0) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = "";
|
|
return json;
|
|
}
|
|
|
|
if (string_contents.size() == 1) {
|
|
return visit(string_contents[0]);
|
|
}
|
|
|
|
vector<Json> args_vec = visitAsVectorOfJSON(string_contents);
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = "concat";
|
|
json["args"] = args_vec;
|
|
return json;
|
|
}
|
|
|
|
VISIT(FullTemplateString) {
|
|
auto string_contents_full = ctx->stringContentsFull();
|
|
|
|
if (string_contents_full.size() == 0) {
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = "";
|
|
return json;
|
|
}
|
|
|
|
if (string_contents_full.size() == 1) {
|
|
return visit(string_contents_full[0]);
|
|
}
|
|
|
|
vector<Json> args_vec = visitAsVectorOfJSON(string_contents_full);
|
|
Json json = Json::object();
|
|
json["node"] = "Call";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["name"] = "concat";
|
|
Json args = Json::array();
|
|
for (const auto& arg : args_vec) {
|
|
args.pushBack(arg);
|
|
}
|
|
json["args"] = std::move(args);
|
|
return json;
|
|
}
|
|
|
|
VISIT(StringContents) {
|
|
auto string_text = ctx->STRING_TEXT();
|
|
if (string_text) {
|
|
string text = parse_string_text_ctx(string_text, true);
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = text;
|
|
return json;
|
|
}
|
|
auto column_expr = ctx->columnExpr();
|
|
if (column_expr) {
|
|
return visit(column_expr);
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = "";
|
|
return json;
|
|
}
|
|
|
|
VISIT(StringContentsFull) {
|
|
auto full_string_text = ctx->FULL_STRING_TEXT();
|
|
if (full_string_text) {
|
|
string text = parse_string_text_ctx(full_string_text, false);
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = text;
|
|
return json;
|
|
}
|
|
auto column_expr = ctx->columnExpr();
|
|
if (column_expr) {
|
|
return visit(column_expr);
|
|
}
|
|
Json json = Json::object();
|
|
json["node"] = "Constant";
|
|
if (!is_internal) addPositionInfo(json, ctx);
|
|
json["value"] = "";
|
|
return json;
|
|
}
|
|
};
|