@kuindji/sql-type-parser
Version:
Type-level SQL parser for TypeScript
660 lines • 40.3 kB
TypeScript
/**
* Type-level SQL SELECT parser
*/
import type { SelectClause, TableRef, TableSource, DerivedTableRef, CTEDefinition, ColumnRef, ColumnRefType, TableColumnRef, UnboundColumnRef, TableWildcard, ComplexExpr, SubqueryExpr, ValidatableColumnRef, WhereExpr, BinaryExpr, JoinClause, JoinType, OrderByItem, AggregateExpr, AggregateFunc, LiteralValue, SelectItem, SQLQuery, UnparsedExpr, UnionClause, UnionClauseAny, UnionOperatorType } from "./ast.js";
import type { NormalizeSQL, NextToken, ExtractUntil, SplitByComma, FromTerminators, WhereTerminators, OrderByTerminators, StartsWith } from "./tokenizer.js";
import type { Trim, ParseError, RemoveQuotes, Increment, Decrement } from "./utils.js";
/**
* Parse a SQL SELECT query string into an AST
*/
export type ParseSQL<T extends string> = ParseQueryOrUnion<NormalizeSQL<T>>;
/**
* Parse a query that may contain UNION/INTERSECT/EXCEPT operators
*/
type ParseQueryOrUnion<T extends string> = ParseSelectQueryWithRest<T> extends infer Result ? Result extends {
query: infer Q extends SelectClause;
rest: infer Rest extends string;
} ? CheckForUnion<Q, Rest> : Result extends SQLQuery<infer Q> ? Result : Result : never;
/**
* Parse a normalized SELECT query and return both the query and remaining string
*/
type ParseSelectQueryWithRest<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "WITH" ? ParseWithAndSelectWithRest<Rest> : First extends "SELECT" ? ParseSelectBodyWithCTEsAndRest<Rest, undefined> : ParseError<`Expected SELECT or WITH, got: ${First}`> : ParseError<"Empty query">;
/**
* Legacy entry point for backward compatibility with subquery parsing
*/
type ParseSelectQuery<T extends string> = ParseSelectQueryWithRest<T> extends infer Result ? Result extends {
query: infer Q extends SelectClause;
rest: infer Rest extends string;
} ? CheckForUnion<Q, Rest> : Result : never;
/**
* Parse WITH clause followed by SELECT, returning both query and rest
*/
type ParseWithAndSelectWithRest<T extends string> = ParseCTEList<T> extends infer CTEResult ? CTEResult extends {
ctes: infer CTEs extends CTEDefinition[];
rest: infer AfterCTEs extends string;
} ? NextToken<AfterCTEs> extends ["SELECT", infer SelectRest extends string] ? ParseSelectBodyWithCTEsAndRest<SelectRest, CTEs> : ParseError<"Expected SELECT after WITH clause"> : CTEResult : never;
/**
* Check if there's a union operator and parse accordingly
*/
type CheckForUnion<Left extends SelectClause, Rest extends string> = Trim<Rest> extends "" ? SQLQuery<Left> : ParseUnionOperator<Rest> extends [infer Op extends UnionOperatorType, infer AfterOp extends string] ? ParseSelectQueryWithRest<AfterOp> extends infer RightResult ? RightResult extends {
query: infer RightQ extends SelectClause;
rest: infer AfterRight extends string;
} ? CheckForMoreUnions<Left, Op, RightQ, AfterRight> : RightResult extends SQLQuery<infer RightQ> ? RightQ extends SelectClause ? SQLQuery<UnionClause<Left, Op, RightQ>> : RightQ extends UnionClauseAny ? SQLQuery<UnionClause<Left, Op, RightQ>> : ParseError<"Invalid right side of union"> : RightResult : never : SQLQuery<Left>;
/**
* Handle more unions on the right side
*/
type CheckForMoreUnions<Left extends SelectClause, Op extends UnionOperatorType, Right extends SelectClause, Rest extends string> = Trim<Rest> extends "" ? SQLQuery<UnionClause<Left, Op, Right>> : ParseUnionOperator<Rest> extends [infer NextOp extends UnionOperatorType, infer AfterNextOp extends string] ? ParseSelectQueryWithRest<AfterNextOp> extends infer NextRightResult ? NextRightResult extends {
query: infer NextRightQ extends SelectClause;
rest: infer AfterNextRight extends string;
} ? CheckForMoreUnions<Left, Op, Right, Rest> extends SQLQuery<infer LeftUnion> ? LeftUnion extends UnionClauseAny ? CheckForMoreUnions<Right, NextOp, NextRightQ, AfterNextRight> extends SQLQuery<infer RightUnion> ? SQLQuery<UnionClause<Left, Op, RightUnion>> : ParseError<"Failed to parse chained union"> : ParseError<"Invalid union chain"> : ParseError<"Failed to parse union chain"> : NextRightResult : never : SQLQuery<UnionClause<Left, Op, Right>>;
/**
* Parse a union operator and return [operator, remaining string]
*/
type ParseUnionOperator<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "UNION" ? NextToken<Rest> extends ["ALL", infer AfterAll extends string] ? ["UNION ALL", AfterAll] : ["UNION", Rest] : First extends "INTERSECT" ? NextToken<Rest> extends ["ALL", infer AfterAll extends string] ? ["INTERSECT ALL", AfterAll] : ["INTERSECT", Rest] : First extends "EXCEPT" ? NextToken<Rest> extends ["ALL", infer AfterAll extends string] ? ["EXCEPT ALL", AfterAll] : ["EXCEPT", Rest] : never : never;
/**
* Parse a comma-separated list of CTEs
*/
type ParseCTEList<T extends string, Acc extends CTEDefinition[] = []> = ParseSingleCTE<T> extends infer CTEResult ? CTEResult extends {
cte: infer CTE extends CTEDefinition;
rest: infer Rest extends string;
} ? NextToken<Rest> extends [",", infer AfterComma extends string] ? ParseCTEList<AfterComma, [...Acc, CTE]> : {
ctes: [...Acc, CTE];
rest: Rest;
} : CTEResult extends ParseError<string> ? CTEResult : ParseError<"Invalid CTE syntax"> : never;
/**
* Parse a single CTE: name AS ( SELECT ... )
*/
type ParseSingleCTE<T extends string> = NextToken<T> extends [
infer Name extends string,
infer AfterName extends string
] ? NextToken<AfterName> extends ["AS", infer AfterAS extends string] ? NextToken<AfterAS> extends ["(", infer AfterParen extends string] ? ExtractCTEQuery<AfterParen> extends [infer QueryStr extends string, infer Rest extends string] ? ParseSelectQuery<QueryStr> extends SQLQuery<infer Query extends SelectClause> ? {
cte: CTEDefinition<RemoveQuotes<Name>, Query>;
rest: Rest;
} : ParseError<"Failed to parse CTE query"> : ParseError<"Invalid CTE query syntax"> : ParseError<"Expected ( after AS in CTE"> : ParseError<"Expected AS after CTE name"> : ParseError<"Expected CTE name">;
/**
* Extract the CTE query from parentheses and return rest
*/
type ExtractCTEQuery<T extends string> = ExtractUntilClosingParen<T, 1, ""> extends [
infer Query extends string,
infer Rest extends string
] ? [Trim<Query>, Trim<Rest>] : never;
/**
* Parse the body of a SELECT statement with optional CTEs, returning both query and rest
*/
type ParseSelectBodyWithCTEsAndRest<T extends string, CTEs extends CTEDefinition[] | undefined> = CheckDistinct<T> extends [
infer IsDistinct extends boolean,
infer AfterDistinct extends string
] ? ExtractUntil<AfterDistinct, "FROM"> extends [
infer ColumnsPart extends string,
infer FromPart extends string
] ? ParseColumns<ColumnsPart> extends infer Columns ? Columns extends ParseError<string> ? Columns : ParseFromClause<FromPart> extends infer FromResult ? FromResult extends ParseError<string> ? FromResult : FromResult extends {
from: infer From extends TableSource;
rest: infer Rest extends string;
} ? BuildSelectClauseWithCTEsAndRest<Columns, From, Rest, IsDistinct, CTEs> : ParseError<"Failed to parse FROM clause"> : never : never : ParseError<"Missing FROM clause"> : never;
/**
* Check for DISTINCT keyword
*/
type CheckDistinct<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "DISTINCT" ? [true, Rest] : [false, T] : [false, T];
/**
* Parse column list
*/
type ParseColumns<T extends string> = Trim<T> extends "*" ? "*" : SplitByComma<Trim<T>> extends infer Parts extends string[] ? ParseColumnList<Parts> : ParseError<"Failed to split columns">;
/**
* Parse a list of columns
*/
type ParseColumnList<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? ParseSingleColumn<First> extends infer Col ? Col extends ParseError<string> ? Col : Rest extends [] ? [Col] : ParseColumnList<Rest> extends infer RestCols ? RestCols extends ParseError<string> ? RestCols : RestCols extends SelectItem[] ? [Col, ...RestCols] : ParseError<"Invalid column list"> : never : never : [];
/**
* Parse a single column (could be aggregate, aliased, or simple)
*/
type ParseSingleColumn<T extends string> = Trim<T> extends "" ? ParseError<"Empty column"> : IsAggregate<Trim<T>> extends true ? ParseAggregateColumn<Trim<T>> : ParseSimpleColumn<Trim<T>>;
/**
* Check if a column is an aggregate function
*/
type IsAggregate<T extends string> = T extends `COUNT ${string}` ? true : T extends `SUM ${string}` ? true : T extends `AVG ${string}` ? true : T extends `MIN ${string}` ? true : T extends `MAX ${string}` ? true : false;
/**
* Parse an aggregate function column
*/
type ParseAggregateColumn<T extends string> = T extends `${infer Func} ( ${infer Arg} ) AS ${infer Alias}` ? Func extends AggregateFunc ? AggregateExpr<Func, ParseAggregateArg<Arg>, RemoveQuotes<Alias>> : ParseError<`Unknown aggregate function: ${Func}`> : T extends `${infer Func} ( ${infer Arg} )` ? Func extends AggregateFunc ? AggregateExpr<Func, ParseAggregateArg<Arg>, `${Func}_result`> : ParseError<`Unknown aggregate function: ${Func}`> : ParseError<`Invalid aggregate syntax: ${T}`>;
/**
* Parse aggregate function argument
*/
type ParseAggregateArg<T extends string> = Trim<T> extends "*" ? "*" : ParseColumnRefType<Trim<T>>;
/**
* Parse a simple column reference with optional alias
* Handles PostgreSQL type casting syntax (::type), complex expressions, and subqueries
*/
type ParseSimpleColumn<T extends string> = IsTableWildcard<T> extends true ? ParseTableWildcard<T> : IsSubqueryExpression<T> extends true ? ParseSubqueryColumn<T> : IsComplexExpression<T> extends true ? ParseComplexColumn<T> : T extends `${infer Col} AS ${infer Alias}` ? ColumnRef<ParseColumnRefType<StripTypeCast<Trim<Col>>>, RemoveQuotes<Alias>> : ColumnRef<ParseColumnRefType<StripTypeCast<T>>, ExtractColumnName<StripTypeCast<T>>>;
/**
* Check if the expression is a scalar subquery (starts with parenthesized SELECT)
*/
type IsSubqueryExpression<T extends string> = Trim<T> extends `( SELECT ${string}` ? true : false;
/**
* Parse a scalar subquery column expression
* Extracts the inner SELECT, parses it, and creates a SubqueryExpr
*/
type ParseSubqueryColumn<T extends string> = T extends `${infer Expr} AS ${infer Alias}` ? ColumnRef<ParseSubqueryExpr<Trim<Expr>>, RemoveQuotes<Alias>> : ColumnRef<ParseSubqueryExpr<T>, "subquery">;
/**
* Parse a subquery expression, extracting the SELECT from parentheses
*/
type ParseSubqueryExpr<T extends string> = ExtractParenthesizedContent<Trim<T>> extends [infer Inner extends string, infer Remainder extends string] ? ParseSelectQuery<Inner> extends SQLQuery<infer Query extends SelectClause> ? SubqueryExpr<Query, ExtractSubqueryCastType<Remainder>> : ParseSelectQuery<Inner> extends ParseError<infer E> ? ComplexExpr<[], undefined> : ComplexExpr<[], undefined> : ComplexExpr<[], undefined>;
/**
* Extract the cast type that may follow a subquery's closing parenthesis
* e.g., ")::text" -> "text"
*/
type ExtractSubqueryCastType<T extends string> = Trim<T> extends `::${infer Type} ${string}` ? ExtractTypeName<Type> : Trim<T> extends `::${infer Type}` ? ExtractTypeName<Type> : undefined;
/**
* Extract content from balanced parentheses
* Returns [inner content, remainder after closing paren]
*/
type ExtractParenthesizedContent<T extends string> = Trim<T> extends `( ${infer Rest}` ? ExtractUntilClosingParen<Rest, 1, ""> : never;
/**
* Extract content until we find the matching closing parenthesis
*/
type ExtractUntilClosingParen<T extends string, Depth extends number, Acc extends string> = Depth extends 0 ? [Trim<Acc>, Trim<T>] : NextToken<T> extends [infer Token extends string, infer Rest extends string] ? Token extends "(" ? ExtractUntilClosingParen<Rest, Increment<Depth>, `${Acc} ${Token}`> : Token extends ")" ? Decrement<Depth> extends 0 ? [Trim<Acc>, Trim<Rest>] : ExtractUntilClosingParen<Rest, Decrement<Depth>, `${Acc} ${Token}`> : ExtractUntilClosingParen<Rest, Depth, `${Acc} ${Token}`> : [Trim<Acc>, ""];
/**
* Check if this is a function call (pattern: identifier ( ... ))
* Excludes aggregate functions which are handled separately
* After normalization, functions look like: funcName ( args )
*/
type IsFunctionCall<T extends string> = Trim<T> extends `${infer Name} ( ${string}` ? Name extends "(" | ")" | "," ? false : true : false;
/**
* Check if the expression is complex (contains JSON operators, function calls, nested parens, type casts, etc.)
*/
type IsComplexExpression<T extends string> = T extends `${string}->${string}` ? true : T extends `${string}->>${string}` ? true : T extends `${string}#>${string}` ? true : T extends `${string}#>>${string}` ? true : T extends `( ${string}` ? true : IsFunctionCall<T> extends true ? true : HasTypeCast<T> extends true ? true : false;
/**
* Check if the expression contains a type cast (::type)
*/
type HasTypeCast<T extends string> = T extends `${string}::${string}` ? true : false;
/**
* Parse a complex column expression
* Extracts base column for validation and final cast type for result type
*/
type ParseComplexColumn<T extends string> = T extends `${infer Expr} AS ${infer Alias}` ? ColumnRef<ParseComplexExpr<Trim<Expr>>, RemoveQuotes<Alias>> : ColumnRef<ParseComplexExpr<T>, ExtractComplexColumnName<T>>;
/**
* Parse a complex expression into ComplexExpr AST
* Extracts all column references for validation and the final cast type
*/
type ParseComplexExpr<T extends string> = ComplexExpr<ExtractAllColumnRefs<T>, ExtractFinalCastType<T>>;
/**
* Extract all column references from a complex expression
* Scans token by token to find all column patterns
*/
type ExtractAllColumnRefs<T extends string> = ScanTokensForColumnRefs<Trim<T>, []>;
/**
* Scan tokens one by one looking for column reference patterns
*/
type ScanTokensForColumnRefs<T extends string, Acc extends ValidatableColumnRef[]> = Trim<T> extends "" ? Acc : NextToken<Trim<T>> extends [infer Token extends string, infer Rest extends string] ? ExtractColumnFromToken<Token> extends infer ColRef ? [ColRef] extends [never] ? ScanTokensForColumnRefs<Rest, Acc> : ColRef extends ValidatableColumnRef ? ScanTokensForColumnRefs<Rest, [...Acc, ColRef]> : ScanTokensForColumnRefs<Rest, Acc> : ScanTokensForColumnRefs<Rest, Acc> : Acc;
/**
* Try to extract a column reference from a single token
* Handles: schema.table.col, alias."col", alias."col"::type, "table"."col", "col", "col"::type
* Also handles unquoted: schema.table.col::type, table.col::type, col::type
*/
type ExtractColumnFromToken<T extends string> = ExtractThreePartColumnRef<T> extends infer ThreePart ? [ThreePart] extends [never] ? ExtractTwoPartOrSimpleColumnRef<T> : ThreePart : never;
/**
* Extract three-part column reference (schema.table.column)
*/
type ExtractThreePartColumnRef<T extends string> = T extends `"${infer Schema}"."${infer Table}"."${infer Col}"::${string}` ? TableColumnRef<Table, Col, Schema> : T extends `"${infer Schema}"."${infer Table}"."${infer Col}"` ? TableColumnRef<Table, Col, Schema> : T extends `${infer Schema}.${infer Table}.${infer Col}::${string}` ? IsSimpleIdentifier<Schema> extends true ? IsSimpleIdentifier<Table> extends true ? TableColumnRef<Table, ExtractBeforeCast<Col>, Schema> : never : never : T extends `${infer Schema}.${infer Table}.${infer Col}` ? IsSimpleIdentifier<Schema> extends true ? IsSimpleIdentifier<Table> extends true ? IsSimpleIdentifier<Col> extends true ? TableColumnRef<Table, Col, Schema> : never : never : never : never;
/**
* Extract two-part (table.column) or simple column reference
*/
type ExtractTwoPartOrSimpleColumnRef<T extends string> = T extends `${infer Alias}."${infer Col}"::${string}` ? IsSimpleIdentifier<Alias> extends true ? TableColumnRef<Alias, Col, undefined> : never : T extends `${infer Alias}."${infer Col}"` ? IsSimpleIdentifier<Alias> extends true ? TableColumnRef<Alias, Col, undefined> : never : T extends `"${infer Table}"."${infer Col}"::${string}` ? TableColumnRef<Table, Col, undefined> : T extends `"${infer Table}"."${infer Col}"` ? TableColumnRef<Table, Col, undefined> : T extends `"${infer Col}"::${string}` ? UnboundColumnRef<Col> : T extends `"${infer Col}"->>${string}` ? UnboundColumnRef<Col> : T extends `"${infer Col}"->${string}` ? UnboundColumnRef<Col> : T extends `${infer Table}.${infer Col}::${string}` ? Col extends `${string}.${string}` ? never : IsSimpleIdentifier<Table> extends true ? TableColumnRef<Table, ExtractBeforeCast<Col>, undefined> : never : T extends `${infer Table}.${infer Col}` ? Col extends `${string}.${string}` ? never : IsSimpleIdentifier<Table> extends true ? IsSimpleIdentifier<Col> extends true ? TableColumnRef<Table, Col, undefined> : never : never : T extends `${infer Col}::${string}` ? IsSimpleIdentifier<ExtractBeforeCast<Col>> extends true ? UnboundColumnRef<ExtractBeforeCast<Col>> : never : never;
/**
* Extract the column name before the :: cast operator
*/
type ExtractBeforeCast<T extends string> = T extends `${infer Name}::${string}` ? Name : T;
/**
* Check if a string is a simple identifier (no spaces, not a special char)
*/
type IsSimpleIdentifier<T extends string> = T extends "" ? false : T extends `${string} ${string}` ? false : T extends "(" | ")" | "," | "/" | "*" | "+" | "-" | "=" ? false : true;
/**
* Extract the final type cast from an expression
* Looks for ::type at the very end (after all parentheses)
* e.g., ( expr ) ::text -> "text"
*/
type ExtractFinalCastType<T extends string> = Trim<T> extends `${string}) ::${infer Type} AS ${string}` ? ExtractTypeName<Type> : Trim<T> extends `${string}) ::${infer Type}` ? ExtractTypeName<Type> : Trim<T> extends `${string}::${infer Type} AS ${string}` ? ExtractTypeName<Type> : Trim<T> extends `${string}::${infer Type}` ? ExtractTypeName<Type> : undefined;
/**
* Extract just the type name from a cast (handles things like varchar(255))
*/
type ExtractTypeName<T extends string> = Trim<T> extends `${infer TypeName} ( ${string}` ? Trim<TypeName> : Trim<T> extends `${infer TypeName}(${string}` ? Trim<TypeName> : Trim<T>;
/**
* Extract column name for complex expressions (for default alias)
*/
type ExtractComplexColumnName<T extends string> = T extends `${string} AS ${infer Alias}` ? RemoveQuotes<Alias> : "expr";
/**
* Strip PostgreSQL type cast syntax (::type) from a column reference
* e.g., "id::text" -> "id", "col::varchar(255)" -> "col"
*/
type StripTypeCast<T extends string> = T extends `${infer Col}::${string}` ? Trim<Col> : T;
/**
* Check if this is a table.* or alias.* or schema.table.* pattern
*/
type IsTableWildcard<T extends string> = Trim<T> extends `${string}.*` ? true : Trim<T> extends `${string}. *` ? true : false;
/**
* Parse a table.* or schema.table.* wildcard into a TableWildcard type
* Note: We use [X] extends [never] pattern due to TypeScript 5.9+ behavior
*/
type ParseTableWildcard<T extends string> = [
ParseSchemaTableWildcard<Trim<T>>
] extends [never] ? ParseSimpleTableWildcard<T> : ParseSchemaTableWildcard<Trim<T>> extends [infer Schema extends string, infer Table extends string] ? TableWildcard<Table, Schema> : ParseSimpleTableWildcard<T>;
/**
* Parse simple table.* pattern (no schema)
*/
type ParseSimpleTableWildcard<T extends string> = Trim<T> extends `${infer Table}.*` ? TableWildcard<RemoveQuotes<Table>, undefined> : Trim<T> extends `${infer Table}. *` ? TableWildcard<RemoveQuotes<Table>, undefined> : never;
/**
* Parse schema.table.* pattern, returns [schema, table] or never
*/
type ParseSchemaTableWildcard<T extends string> = T extends `"${infer Schema}"."${infer Table}".*` ? [Schema, Table] : T extends `"${infer Schema}"."${infer Table}". *` ? [Schema, Table] : T extends `"${infer Schema}".${infer Table}.*` ? IsSimpleIdentifier<Table> extends true ? [Schema, Table] : never : T extends `"${infer Schema}".${infer Table}. *` ? IsSimpleIdentifier<Table> extends true ? [Schema, Table] : never : T extends `${infer Schema}."${infer Table}".*` ? IsSimpleIdentifier<Schema> extends true ? [Schema, Table] : never : T extends `${infer Schema}."${infer Table}". *` ? IsSimpleIdentifier<Schema> extends true ? [Schema, Table] : never : T extends `${infer Part1}.${infer Part2}.*` ? IsSimpleIdentifier<Part1> extends true ? Part2 extends `${string}.${string}` ? never : IsSimpleIdentifier<Part2> extends true ? [Part1, Part2] : never : never : T extends `${infer Part1}.${infer Part2}. *` ? IsSimpleIdentifier<Part1> extends true ? Part2 extends `${string}.${string}` ? never : IsSimpleIdentifier<Part2> extends true ? [Part1, Part2] : never : never : never;
/**
* Extract column name for default alias (removes quotes)
* For three-part identifiers (schema.table.column), extracts just the column
*/
type ExtractColumnName<T extends string> = T extends `${infer _}.${infer _2}.${infer Col}` ? RemoveQuotes<Col> : T extends `${infer _}.${infer Col}` ? RemoveQuotes<Col> : RemoveQuotes<T>;
/**
* Parse a column reference (schema.table.column, table.column, or just column)
* Removes quotes from schema, table, and column names
* Note: We use [X] extends [never] pattern due to TypeScript 5.9+ behavior
*/
type ParseColumnRefType<T extends string> = [
ParseThreePartIdentifier<Trim<T>>
] extends [never] ? ParseTwoOrOnePartIdentifier<T> : ParseThreePartIdentifier<Trim<T>> extends [infer Schema extends string, infer Table extends string, infer Col extends string] ? TableColumnRef<Table, Col, Schema> : ParseTwoOrOnePartIdentifier<T>;
/**
* Parse two-part (table.column) or single-part (column) identifier
*/
type ParseTwoOrOnePartIdentifier<T extends string> = Trim<T> extends `${infer Table}.${infer Col}` ? TableColumnRef<RemoveQuotes<Table>, RemoveQuotes<Col>, undefined> : UnboundColumnRef<RemoveQuotes<T>>;
/**
* Parse a three-part identifier: schema.table.column
* Returns [schema, table, column] or never if not a three-part identifier
*/
type ParseThreePartIdentifier<T extends string> = T extends `"${infer Schema}"."${infer Table}"."${infer Col}"` ? [Schema, Table, Col] : T extends `"${infer Schema}"."${infer Table}".${infer Col}` ? [Schema, Table, RemoveQuotes<Col>] : T extends `"${infer Schema}".${infer Table}."${infer Col}"` ? [Schema, RemoveQuotes<Table>, Col] : T extends `"${infer Schema}".${infer Table}.${infer Col}` ? IsSimpleIdentifier<Table> extends true ? IsSimpleIdentifier<Col> extends true ? [Schema, Table, Col] : never : never : T extends `${infer Schema}."${infer Table}"."${infer Col}"` ? IsSimpleIdentifier<Schema> extends true ? [Schema, Table, Col] : never : T extends `${infer Schema}."${infer Table}".${infer Col}` ? IsSimpleIdentifier<Schema> extends true ? [Schema, Table, RemoveQuotes<Col>] : never : T extends `${infer Schema}.${infer Table}."${infer Col}"` ? IsSimpleIdentifier<Schema> extends true ? IsSimpleIdentifier<Table> extends true ? [Schema, Table, Col] : never : never : T extends `${infer Part1}.${infer Part2}.${infer Part3}` ? IsSimpleIdentifier<Part1> extends true ? IsSimpleIdentifier<Part2> extends true ? IsSimpleIdentifier<Part3> extends true ? [Part1, Part2, Part3] : never : never : never : never;
/**
* Parse FROM clause and return table + remaining query
* Handles both regular tables and derived tables (subqueries)
*/
type ParseFromClause<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "FROM" ? NextToken<Rest> extends ["(", infer AfterParen extends string] ? ParseDerivedTable<AfterParen> : ExtractUntil<Rest, FromTerminators> extends [
infer TablePart extends string,
infer Remaining extends string
] ? {
from: ParseTableRef<TablePart>;
rest: Remaining;
} : {
from: ParseTableRef<Rest>;
rest: "";
} : ParseError<`Expected FROM, got: ${First}`> : ParseError<"Missing FROM clause">;
/**
* Parse a derived table (subquery in FROM clause)
* Pattern: ( SELECT ... ) AS alias
*/
type ParseDerivedTable<T extends string> = ExtractUntilClosingParen<T, 1, ""> extends [infer QueryStr extends string, infer AfterParen extends string] ? ParseSelectQuery<Trim<QueryStr>> extends SQLQuery<infer Query extends SelectClause> ? ParseDerivedTableAlias<Trim<AfterParen>> extends {
alias: infer Alias extends string;
rest: infer Rest extends string;
} ? {
from: DerivedTableRef<Query, Alias>;
rest: Rest;
} : ParseError<"Derived table requires an alias"> : ParseError<"Failed to parse derived table query"> : ParseError<"Invalid derived table syntax">;
/**
* Parse the alias after a derived table's closing parenthesis
*/
type ParseDerivedTableAlias<T extends string> = NextToken<T> extends ["AS", infer AfterAS extends string] ? NextToken<AfterAS> extends [infer Alias extends string, infer Rest extends string] ? {
alias: RemoveQuotes<Alias>;
rest: Rest;
} : ParseError<"Expected alias after AS"> : NextToken<T> extends [infer First extends string, infer Rest extends string] ? First extends FromTerminators ? ParseError<"Derived table requires an alias"> : {
alias: RemoveQuotes<First>;
rest: Rest;
} : ParseError<"Expected alias for derived table">;
/**
* Parse a table reference with optional schema and alias
* Handles: schema.table, table, schema.table AS alias, table AS alias
* Removes quotes from schema, table name, and alias
*/
type ParseTableRef<T extends string> = Trim<T> extends `${infer SchemaOrTable} AS ${infer Alias}` ? ParseSchemaTable<SchemaOrTable> extends [infer Schema extends string | undefined, infer Table extends string] ? TableRef<Table, RemoveQuotes<Alias>, Schema> : TableRef<RemoveQuotes<SchemaOrTable>, RemoveQuotes<Alias>, undefined> : Trim<T> extends `${infer SchemaOrTable} ${infer Alias}` ? Alias extends FromTerminators ? ParseSchemaTable<SchemaOrTable> extends [infer Schema extends string | undefined, infer Table extends string] ? TableRef<Table, Table, Schema> : TableRef<RemoveQuotes<SchemaOrTable>, RemoveQuotes<SchemaOrTable>, undefined> : ParseSchemaTable<SchemaOrTable> extends [infer Schema extends string | undefined, infer Table extends string] ? TableRef<Table, RemoveQuotes<Alias>, Schema> : TableRef<RemoveQuotes<SchemaOrTable>, RemoveQuotes<Alias>, undefined> : ParseSchemaTable<T> extends [infer Schema extends string | undefined, infer Table extends string] ? TableRef<Table, Table, Schema> : TableRef<RemoveQuotes<T>, RemoveQuotes<T>, undefined>;
/**
* Parse schema.table syntax, returns [schema, table] or [undefined, table]
* Handles: "schema"."table", schema.table, "table", table
*/
type ParseSchemaTable<T extends string> = Trim<T> extends `"${infer Schema}"."${infer Table}"` ? [Schema, Table] : Trim<T> extends `${infer Schema}."${infer Table}"` ? IsSimpleIdentifier<Schema> extends true ? [Schema, Table] : [undefined, RemoveQuotes<T>] : Trim<T> extends `"${infer Schema}".${infer Table}` ? IsSimpleIdentifier<Table> extends true ? [Schema, RemoveQuotes<Table>] : [undefined, RemoveQuotes<T>] : Trim<T> extends `${infer Schema}.${infer Table}` ? IsSimpleIdentifier<Schema> extends true ? IsSimpleIdentifier<Table> extends true ? [Schema, Table] : [undefined, RemoveQuotes<T>] : [undefined, RemoveQuotes<T>] : [undefined, RemoveQuotes<T>];
/**
* Build the complete SELECT clause with optional CTEs, returning both query and rest
*/
type BuildSelectClauseWithCTEsAndRest<Columns, From extends TableSource, Rest extends string, Distinct extends boolean, CTEs extends CTEDefinition[] | undefined> = ParseOptionalClausesWithRest<Rest> extends infer OptionalResult ? OptionalResult extends ParseError<string> ? OptionalResult : OptionalResult extends {
joins: infer Joins;
where: infer Where;
groupBy: infer GroupBy;
having: infer Having;
orderBy: infer OrderBy;
limit: infer Limit;
offset: infer Offset;
rest: infer Remaining extends string;
} ? {
query: SelectClause<Columns extends "*" ? "*" : Columns extends SelectItem[] ? Columns : never, From, Joins extends JoinClause[] ? Joins : undefined, Where extends WhereExpr ? Where : undefined, GroupBy extends ColumnRefType[] ? GroupBy : undefined, Having extends WhereExpr ? Having : undefined, OrderBy extends OrderByItem[] ? OrderBy : undefined, Limit extends number ? Limit : undefined, Offset extends number ? Offset : undefined, Distinct, CTEs>;
rest: Remaining;
} : never : never;
/**
* Parse all optional clauses with remaining rest
*/
type ParseOptionalClausesWithRest<T extends string> = ParseJoins<T> extends infer JoinResult ? JoinResult extends {
joins: infer Joins;
rest: infer AfterJoins extends string;
} ? ParseWhereClause<AfterJoins> extends infer WhereResult ? WhereResult extends {
where: infer Where;
rest: infer AfterWhere extends string;
} ? ParseGroupBy<AfterWhere> extends infer GroupByResult ? GroupByResult extends {
groupBy: infer GroupBy;
rest: infer AfterGroupBy extends string;
} ? ParseHaving<AfterGroupBy> extends infer HavingResult ? HavingResult extends {
having: infer Having;
rest: infer AfterHaving extends string;
} ? ParseOrderBy<AfterHaving> extends infer OrderByResult ? OrderByResult extends {
orderBy: infer OrderBy;
rest: infer AfterOrderBy extends string;
} ? ParseLimitOffsetWithRest<AfterOrderBy> extends infer LimitResult ? LimitResult extends {
limit: infer Limit;
offset: infer Offset;
rest: infer AfterLimitOffset extends string;
} ? {
joins: Joins;
where: Where;
groupBy: GroupBy;
having: Having;
orderBy: OrderBy;
limit: Limit;
offset: Offset;
rest: AfterLimitOffset;
} : never : never : never : never : never : never : never : never : never : never : never : never;
/**
* Parse JOIN clauses
*/
type ParseJoins<T extends string> = Trim<T> extends "" ? {
joins: undefined;
rest: "";
} : IsJoinStart<T> extends true ? ParseJoinList<T, []> : {
joins: undefined;
rest: T;
};
/**
* Check if string starts with a JOIN keyword
*/
type IsJoinStart<T extends string> = NextToken<T> extends [
infer First extends string,
infer _
] ? First extends "JOIN" | "INNER" | "LEFT" | "RIGHT" | "FULL" | "CROSS" ? true : false : false;
/**
* Parse a list of JOINs
*/
type ParseJoinList<T extends string, Acc extends JoinClause[]> = IsJoinStart<T> extends true ? ParseSingleJoin<T> extends infer JoinResult ? JoinResult extends {
join: infer J extends JoinClause;
rest: infer Rest extends string;
} ? ParseJoinList<Rest, [...Acc, J]> : JoinResult extends ParseError<string> ? JoinResult : {
joins: Acc extends [] ? undefined : Acc;
rest: T;
} : never : {
joins: Acc extends [] ? undefined : Acc;
rest: T;
};
/**
* Parse a single JOIN clause
* Note: We check [ExtractJoinType<T>] extends [never] first to detect plain JOIN vs INNER/LEFT/etc JOIN
*/
type ParseSingleJoin<T extends string> = [
ExtractJoinType<T>
] extends [never] ? ParsePlainJoin<T> : ParseTypedJoin<T, ExtractJoinType<T>>;
/**
* Parse a plain JOIN (without INNER/LEFT/etc prefix) - treated as INNER JOIN
* Note: ON conditions are not fully parsed since they don't affect type inference
*/
type ParsePlainJoin<T extends string> = NextToken<T> extends ["JOIN", infer AfterJoin extends string] ? ExtractUntil<AfterJoin, "ON" | FromTerminators> extends [
infer TablePart extends string,
infer OnPart extends string
] ? StartsWith<OnPart, "ON"> extends true ? NextToken<OnPart> extends ["ON", infer ConditionPart extends string] ? ExtractUntil<ConditionPart, FromTerminators | "JOIN" | "INNER" | "LEFT" | "RIGHT" | "FULL" | "CROSS"> extends [
infer _Condition extends string,
infer Rest extends string
] ? {
join: JoinClause<"INNER", ParseTableRef<TablePart>, UnparsedExpr>;
rest: Rest;
} : {
join: JoinClause<"INNER", ParseTableRef<TablePart>, UnparsedExpr>;
rest: "";
} : never : {
join: JoinClause<"INNER", ParseTableRef<TablePart>, undefined>;
rest: OnPart;
} : never : ParseError<"Invalid JOIN syntax">;
/**
* Parse a typed JOIN (INNER/LEFT/RIGHT/FULL/CROSS JOIN)
* Note: ON conditions are not fully parsed since they don't affect type inference
*/
type ParseTypedJoin<T extends string, JoinTypeResult> = JoinTypeResult extends [infer JType extends JoinType, infer AfterType extends string] ? NextToken<AfterType> extends ["JOIN", infer AfterJoin extends string] ? ExtractUntil<AfterJoin, "ON" | FromTerminators> extends [
infer TablePart extends string,
infer OnPart extends string
] ? StartsWith<OnPart, "ON"> extends true ? NextToken<OnPart> extends ["ON", infer ConditionPart extends string] ? ExtractUntil<ConditionPart, FromTerminators | "JOIN" | "INNER" | "LEFT" | "RIGHT" | "FULL" | "CROSS"> extends [
infer _Condition extends string,
infer Rest extends string
] ? {
join: JoinClause<JType, ParseTableRef<TablePart>, UnparsedExpr>;
rest: Rest;
} : {
join: JoinClause<JType, ParseTableRef<TablePart>, UnparsedExpr>;
rest: "";
} : never : {
join: JoinClause<JType, ParseTableRef<TablePart>, undefined>;
rest: OnPart;
} : {
join: JoinClause<JType, ParseTableRef<AfterJoin>, undefined>;
rest: "";
} : ParseError<"Expected JOIN keyword"> : never;
/**
* Extract join type from the beginning of the string
*/
type ExtractJoinType<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "INNER" ? ["INNER", Rest] : First extends "LEFT" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["LEFT OUTER", AfterOuter] : ["LEFT", Rest] : First extends "RIGHT" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["RIGHT OUTER", AfterOuter] : ["RIGHT", Rest] : First extends "FULL" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["FULL OUTER", AfterOuter] : ["FULL", Rest] : First extends "CROSS" ? ["CROSS", Rest] : never : never;
/**
* Parse WHERE clause
* Note: We don't fully parse WHERE expressions since they're not used for type inference.
* This significantly reduces recursion depth for complex queries.
*/
type ParseWhereClause<T extends string> = Trim<T> extends "" ? {
where: undefined;
rest: "";
} : NextToken<T> extends ["WHERE", infer Rest extends string] ? ExtractUntil<Rest, WhereTerminators> extends [
infer _WherePart extends string,
infer Remaining extends string
] ? {
where: UnparsedExpr;
rest: Remaining;
} : {
where: UnparsedExpr;
rest: "";
} : {
where: undefined;
rest: T;
};
/**
* Parse a WHERE expression (handles AND/OR)
* Simplified version that doesn't recurse deeply
*/
type ParseWhereExpr<T extends string> = ParseSimpleWhereExpr<Trim<T>>;
/**
* Simplified WHERE expression parser - creates a simple comparison
* without recursively parsing complex AND/OR trees
*/
type ParseSimpleWhereExpr<T extends string> = T extends `${infer Left} = ${infer Right}` ? BinaryExpr<ParseOperand<Left>, "=", ParseOperand<Right>> : BinaryExpr<LiteralValue<true>, "=", LiteralValue<true>>;
/**
* Parse an operand (column reference or literal)
*/
type ParseOperand<T extends string> = Trim<T> extends `'${infer Val}'` ? LiteralValue<Val> : Trim<T> extends `"${infer Val}"` ? LiteralValue<Val> : Trim<T> extends "NULL" ? LiteralValue<null> : Trim<T> extends "TRUE" ? LiteralValue<true> : Trim<T> extends "FALSE" ? LiteralValue<false> : IsNumericString<Trim<T>> extends true ? LiteralValue<Trim<T>> : ParseColumnRefType<Trim<T>>;
/**
* Check if a string looks like a number
*/
type IsNumericString<T extends string> = T extends `${number}` ? true : false;
/**
* Parse GROUP BY clause
*/
type ParseGroupBy<T extends string> = Trim<T> extends "" ? {
groupBy: undefined;
rest: "";
} : NextToken<T> extends ["GROUP", infer Rest extends string] ? NextToken<Rest> extends ["BY", infer AfterBy extends string] ? ExtractUntil<AfterBy, "HAVING" | "ORDER" | "LIMIT" | "OFFSET"> extends [
infer GroupPart extends string,
infer Remaining extends string
] ? {
groupBy: ParseGroupByList<GroupPart>;
rest: Remaining;
} : {
groupBy: ParseGroupByList<AfterBy>;
rest: "";
} : ParseError<"Expected BY after GROUP"> : {
groupBy: undefined;
rest: T;
};
/**
* Parse GROUP BY column list
*/
type ParseGroupByList<T extends string> = SplitByComma<Trim<T>> extends infer Parts extends string[] ? ParseGroupByColumns<Parts> : [];
/**
* Parse list of GROUP BY columns
*/
type ParseGroupByColumns<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [ParseColumnRefType<First>, ...ParseGroupByColumns<Rest>] : [];
/**
* Parse HAVING clause
*/
type ParseHaving<T extends string> = Trim<T> extends "" ? {
having: undefined;
rest: "";
} : NextToken<T> extends ["HAVING", infer Rest extends string] ? ExtractUntil<Rest, "ORDER" | "LIMIT" | "OFFSET"> extends [
infer HavingPart extends string,
infer Remaining extends string
] ? {
having: ParseWhereExpr<HavingPart>;
rest: Remaining;
} : {
having: ParseWhereExpr<Rest>;
rest: "";
} : {
having: undefined;
rest: T;
};
/**
* Parse ORDER BY clause
*/
type ParseOrderBy<T extends string> = Trim<T> extends "" ? {
orderBy: undefined;
rest: "";
} : NextToken<T> extends ["ORDER", infer Rest extends string] ? NextToken<Rest> extends ["BY", infer AfterBy extends string] ? ExtractUntil<AfterBy, OrderByTerminators> extends [
infer OrderPart extends string,
infer Remaining extends string
] ? {
orderBy: ParseOrderByList<OrderPart>;
rest: Remaining;
} : {
orderBy: ParseOrderByList<AfterBy>;
rest: "";
} : ParseError<"Expected BY after ORDER"> : {
orderBy: undefined;
rest: T;
};
/**
* Parse ORDER BY column list
*/
type ParseOrderByList<T extends string> = SplitByComma<Trim<T>> extends infer Parts extends string[] ? ParseOrderByItems<Parts> : [];
/**
* Parse list of ORDER BY items
*/
type ParseOrderByItems<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [ParseOrderByItem<First>, ...ParseOrderByItems<Rest>] : [];
/**
* Parse a single ORDER BY item
*/
type ParseOrderByItem<T extends string> = Trim<T> extends `${infer Col} DESC` ? OrderByItem<ParseColumnRefType<Col>, "DESC"> : Trim<T> extends `${infer Col} ASC` ? OrderByItem<ParseColumnRefType<Col>, "ASC"> : OrderByItem<ParseColumnRefType<Trim<T>>, "ASC">;
/**
* Parse LIMIT and OFFSET clauses, returning remaining rest
*/
type ParseLimitOffsetWithRest<T extends string> = ParseLimit<T> extends {
limit: infer Limit;
rest: infer AfterLimit extends string;
} ? ParseOffset<AfterLimit> extends {
offset: infer Offset;
rest: infer AfterOffset extends string;
} ? {
limit: Limit;
offset: Offset;
rest: AfterOffset;
} : {
limit: Limit;
offset: undefined;
rest: AfterLimit;
} : ParseOffset<T> extends {
offset: infer Offset;
rest: infer AfterOffset extends string;
} ? ParseLimit<AfterOffset> extends {
limit: infer Limit;
rest: infer AfterLimit extends string;
} ? {
limit: Limit;
offset: Offset;
rest: AfterLimit;
} : {
limit: undefined;
offset: Offset;
rest: AfterOffset;
} : {
limit: undefined;
offset: undefined;
rest: T;
};
/**
* Parse LIMIT clause
*/
type ParseLimit<T extends string> = Trim<T> extends "" ? {
limit: undefined;
rest: "";
} : NextToken<T> extends ["LIMIT", infer Rest extends string] ? NextToken<Rest> extends [infer Num extends string, infer Remaining extends string] ? {
limit: ParseNumber<Num>;
rest: Remaining;
} : {
limit: undefined;
rest: T;
} : {
limit: undefined;
rest: T;
};
/**
* Parse OFFSET clause
*/
type ParseOffset<T extends string> = Trim<T> extends "" ? {
offset: undefined;
rest: "";
} : NextToken<T> extends ["OFFSET", infer Rest extends string] ? NextToken<Rest> extends [infer Num extends string, infer Remaining extends string] ? {
offset: ParseNumber<Num>;
rest: Remaining;
} : {
offset: undefined;
rest: T;
} : {
offset: undefined;
rest: T;
};
/**
* Parse a string as a number
*/
type ParseNumber<T extends string> = T extends `${infer N extends number}` ? N : undefined;
export {};
//# sourceMappingURL=parser.d.ts.map