Member Junction
    Preparing search index...

    Unified parser for MemberJunction's SQL superset.

    Handles standard SQL, Nunjucks template expressions ({{ var | filter }}), Nunjucks block tags ({% if %}...{% endif %}), composition tokens ({{query:"Path/Name(params)"}}), and template comments ({# ... #}).

    Two-tier API:

    • Instance (new SQLParser(sql, dialect)) — parses once (with a preprocessing fallback) and exposes typed, dialect-neutral AST inspection/mutation (StatementKind, OuterCap, SetOuterCap, ToSQL) plus extraction helpers (ExtractCTEs, ExtractTableRefs, ExtractColumnRefs, ExtractSelectColumns).
    • Static utilities — pure string/token operations that need no parsed state (StripComments, Tokenize, Analyze, ParseSQL, SqlifyAST, HasUnwrappableTrailingClause, the MJ-template helpers, etc.).
    Index

    Constructors

    • Parse SQL into an instance exposing dialect-neutral AST inspection, mutation, and extraction.

      On a direct-parse failure, applies preprocessing fallbacks — splitting a trailing OPTION (...) clause and aliasing bracket-quoted identifiers whose interior contains parser-defeating characters ([Active People], [my-cte]) — so a wider class of SQL becomes AST-addressable. ToSQL transparently restores both transforms.

      Never throws on unparseable SQL — check IsValid.

      Parameters

      Returns SQLParser

    Accessors

    • get HasWriteStatement(): boolean

      Whether ANY top-level statement is a write — a DML mutation (INSERT/UPDATE/DELETE/MERGE/REPLACE), DDL (DROP/CREATE/ALTER/TRUNCATE/ RENAME), or EXEC/CALL/GRANT/REVOKE/USE.

      Unlike StatementKind (which classifies only the first statement), this inspects EVERY top-level statement, so a stacked payload such as SELECT 1; DROP TABLE x is detected. Benign session prefixes (SET, DECLARE) and SELECTs are not writes. Matching is on the AST statement type, so the REPLACE() string function inside a SELECT is never mistaken for a REPLACE statement.

      Returns boolean

    • get OuterCap(): RowCapInfo | null

      The outermost row cap on the parsed SELECT, dialect-neutral, or null when none is present. Backed by the dialect's ASTDialectAdapter, so the caller never needs to know whether the cap was a TOP or LIMIT.

      Returns RowCapInfo | null

    Methods

    • Removes the top-level ORDER BY from the parsed statement, following the set-op (UNION / INTERSECT / EXCEPT) chain so an ORDER BY on any branch is cleared. No-op when there is none. Dialect-universal — the orderby field has the same shape across dialects.

      Used by the count-SQL builder: ORDER BY is irrelevant for a COUNT and is illegal inside a SQL Server CTE without TOP.

      Returns void

    • Removes the outermost row cap (TOP on SQL Server, LIMIT on PostgreSQL) from the parsed SELECT. No-op when the root isn't a SELECT.

      Used by the count-SQL builder (queryPagingEngine.stripCountBody) so a paged query's count reflects the full set rather than the capped subset. It clears both forms, so a PostgreSQL count drops an explicit LIMIT — consistent with how SQL Server's TOP is dropped.

      Returns void

    • Sets the outermost row cap. The adapter writes the dialect's form (TOP N on SQL Server, LIMIT N elsewhere), preserving any existing OFFSET. No-op when the root isn't a SELECT.

      Parameters

      • cap: number

      Returns void

    • Serialize the (possibly mutated) AST back to SQL, restoring any preprocessing transforms applied at construction (bracket-identifier aliases, then the trailing OPTION (...) clause).

      Throws if the SQL was not parseable (check IsValid first).

      Returns string

    • Convert a single AST expression node to a SQL string. Useful for extracting ORDER BY terms, column expressions, etc.

      node-sql-parser's exprToSQL always produces backtick-quoted identifiers regardless of dialect. This method converts backticks to the dialect's native identifier quoting via dialect.QuoteIdentifier().

      Parameters

      Returns string

    • Extract CTE definitions from SQL starting with a WITH clause.

      Given: WITH A AS (SELECT 1), B AS (SELECT 2) SELECT A.x, B.y FROM A, B Returns: CTEDefinitions: ["A AS (...)", "B AS (...)"], MainStatement: "SELECT ..."

      Uses AST parsing first (produces bracket-quoted identifiers for SQL Server), falls back to paren-depth scanning if AST fails (e.g., Nunjucks-templated SQL).

      Parameters

      Returns SQLCTEExtraction | null

    • Extract parameter metadata from template expressions.

      Walks tokens with a lexical-scope stack so that loop-local variables introduced by {% for X in Y %} blocks are NOT registered as query parameters (they're rebound on each iteration; callers can't supply them). The iterable side of {% for %} (Y) IS registered as a parameter — typically array and required, unless wrapped in {% if Y %} which makes it optional.

      Skip-Brain Bug B: previously this function treated {{ kw }} inside {% for kw in OrgKeywords %} as a required parameter and failed to register OrgKeywords at all. See __tests__/extract-parameter-info-loops.test.ts and SKIP-QUERY-RENDERING-BUGS.md (Bug B) at the repo root.

      Infers type from filters, isRequired from conditional block context.

      Parameters

      • sql: string

      Returns MJParameterInfo[]

    • Extracts the SELECT clause columns with their output names, source columns, and table qualifiers.

      Handles:

      • Simple columns: u.Name → { OutputName: "Name", SourceColumn: "Name", TableQualifier: "u" }
      • AS aliases: e.Name AS EntityName → { OutputName: "EntityName", SourceColumn: "Name", TableQualifier: "e" }
      • Expressions: COUNT(*) → { OutputName: "COUNT()", SourceColumn: "COUNT()", IsExpression: true }
      • MJ template tokens are replaced with placeholders before AST parsing.

      Parameters

      Returns SQLSelectColumn[]

    • Token-aware detection of stacked statements: a statement-separating semicolon that has real content after it (outside string literals, quoted identifiers, and comments). A single trailing semicolon — or a run of them followed only by whitespace/comments — is allowed.

      Unlike an AST check, this fires even when the trailing payload makes the SQL unparseable (SELECT 1; EXEC xp_cmdshell '…', SELECT 1; WAITFOR DELAY '…'), which is exactly the stacked-injection class an AST scan misses (the whole string fails to parse, so the AST is null). A rendered read query must be a single statement, so any internal ; is rejected.

      Parameters

      Returns boolean

    • Token-aware scan for SQL clauses that cannot legally appear inside a derived table — wrapping a query that contains one of these in SELECT ... FROM (<sql>) AS t would produce invalid SQL.

      Detects (case-insensitive, outside string literals and quoted identifiers):

      • FOR JSON …
      • FOR XML …
      • OPTION (…)

      The dialect determines which identifier quoting styles are recognized ([…] for SQL Server, `…` for MySQL, "…" always).

      Parameters

      Returns boolean

    • Renames a template variable in all {{ variable | filters }} expressions throughout the SQL. Preserves the filter chain and whitespace formatting.

      Example: RenameTemplateVariable("WHERE x = {{ region | sqlString }}", "region", "userRegion")"WHERE x = {{ userRegion | sqlString }}"

      Uses MJLexer for deterministic token identification — no regex guessing.

      Parameters

      • sql: string
      • oldName: string
      • newName: string

      Returns string

    • Reconstruct MJ SQL from an Astify result.

      For plain SQL: uses node-sql-parser's sqlify for normalized output. For MJ SQL: reconstructs from tokens, preserving all Nunjucks and composition syntax verbatim.

      Parameters

      Returns string

    • Strip line and block comments from SQL, preserving content inside string literals and quoted identifiers. Block comments support nesting.

      The dialect determines which identifier quoting styles are recognized: SQL Server uses […], PostgreSQL uses "…", MySQL uses `…`. Double-quoted identifiers are honored on every dialect.

      Parameters

      Returns string

    • Substitutes a template variable with a literal value in all {{ variable | filters }} expressions. The entire expression (including filters) is replaced with the literal value, since filters are irrelevant when injecting a concrete value.

      Example: SubstituteTemplateVariable("WHERE x = {{ region | sqlString }}", "region", "'West'")"WHERE x = 'West'"

      Uses MJLexer for deterministic token identification — no regex guessing.

      Parameters

      • sql: string
      • variableName: string
      • literalValue: string

      Returns string

    • Tokenize MJ SQL into an ordered list of tokens. Returns MJ tokens (template expressions, composition refs, block tags, comments) interleaved with SQL_TEXT tokens for the plain SQL segments.

      Parameters

      • sql: string

      Returns MJToken[]

    • Walk the AST from an Astify result and annotate where MJ placeholders appear.

      Returns an MJASTWalkResult with annotations mapping each placeholder to its SQL clause context (SELECT, WHERE, ORDER BY, etc.) and the resolved MJ node.

      This enables queries like:

      • "Which template expressions appear in the WHERE clause?"
      • "What composition ref is in the FROM clause?"
      • "Does the ORDER BY reference an MJ token?"

      Parameters

      Returns MJASTWalkResult

      Walk result with annotations, or empty result if AST parsing failed