SQL
SQL is another way into the same operator pipeline. The parser compiles SQL to a QueryDescriptor, which builds the same pull-based operator chain as the DataFrame API.
import { QueryMode } from "querymode/local"
const qm = QueryMode.local()
const results = await qm .sql("SELECT region, SUM(amount) AS total FROM orders GROUP BY region ORDER BY total DESC") .collect()SQL + DataFrame compose
Section titled “SQL + DataFrame compose”.sql() returns a DataFrame, so you can chain DataFrame methods after it:
const results = await qm .sql("SELECT * FROM events WHERE created_at > '2026-01-01'") .filter("country", "eq", "US") .sort("amount", "desc") .limit(50) .collect()Supported syntax
Section titled “Supported syntax”SELECT
Section titled “SELECT”SELECT *SELECT col1, col2SELECT col1 AS aliasSELECT DISTINCT col1, col2SELECT COUNT(*), SUM(amount), AVG(score)WHERE age > 25WHERE status = 'active' AND amount >= 100WHERE dept = 'eng' OR age > 30WHERE name LIKE '%alice%'WHERE id IN (1, 2, 3)WHERE id NOT IN (4, 5)WHERE amount BETWEEN 100 AND 500WHERE email IS NULLWHERE email IS NOT NULLWHERE NOT (status = 'deleted')GROUP BY / HAVING
Section titled “GROUP BY / HAVING”GROUP BY regionGROUP BY region, categoryHAVING SUM(amount) > 1000ORDER BY
Section titled “ORDER BY”ORDER BY amount DESCORDER BY region ASC, amount DESCLIMIT / OFFSET
Section titled “LIMIT / OFFSET”LIMIT 100LIMIT 100 OFFSET 50Expressions
Section titled “Expressions”SELECT salary / 1000 AS salary_kSELECT CASE WHEN age > 30 THEN 'senior' ELSE 'junior' END AS levelSELECT CAST(age AS text) AS age_strAggregate functions
Section titled “Aggregate functions”COUNT, SUM, AVG, MIN, MAX, COUNT(DISTINCT col).
SELECT * FROM orders JOIN users ON orders.user_id = users.idSELECT * FROM orders LEFT JOIN users ON orders.user_id = users.idHow it works
Section titled “How it works”SQL string → lexer → parser → AST → compiler → QueryDescriptor → operator pipelineThe SQL frontend is a recursive descent parser written in TypeScript. It produces an AST that the compiler maps to the same QueryDescriptor the DataFrame API uses. Features that can be expressed as FilterOp[] (simple AND conditions with eq/gt/lt/etc) are pushed down to the inner executor. Features that can’t (OR, LIKE, NOT IN, HAVING, multi-column ORDER BY, CASE/CAST/arithmetic) are handled by SqlWrappingExecutor, which applies them on the result rows.
Programmatic access
Section titled “Programmatic access”If you need the parsed AST or compiled descriptor directly:
import { parse, compile, compileFull, sqlToDescriptor } from "querymode/sql"
// Parse SQL to ASTconst ast = parse("SELECT * FROM users WHERE age > 25")
// Compile AST to QueryDescriptorconst descriptor = compile(ast)
// Or do both in one stepconst descriptor = sqlToDescriptor("SELECT * FROM users WHERE age > 25")
// compileFull returns additional metadata for the wrapping executorconst result = compileFull(ast)// result.descriptor — QueryDescriptor// result.whereExpr — full WHERE expression (when OR/LIKE/NOT IN present)// result.havingExpr — HAVING expression// result.allOrderBy — multi-column ORDER BY// result.computedExprs — CASE/CAST/arithmetic in SELECT