UNPKG

@kuindji/sql-type-parser

Version:
373 lines 20.1 kB
/** * Type-level SQL UPDATE parser * * This module handles parsing of UPDATE queries specifically. * It uses shared utilities from common/ but maintains its own * execution tree for TypeScript performance. */ import type { UpdateClause, SetClause, SetAssignment, UpdateFromClause, UpdateReturningClause, ReturningItem, QualifiedColumnRef, QualifiedWildcard, SQLUpdateQuery } from "./ast.js"; import type { TableRef, TableSource, UnboundColumnRef, ValidatableColumnRef, TableColumnRef, ParsedCondition, WhereExpr, JoinClause, JoinType, CTEDefinition, SubquerySelectClause } from "../common/ast.js"; import type { NormalizeSQL, NextToken, ExtractUntil, SplitByComma } from "../common/tokenizer.js"; import type { Trim, ParseError, RemoveQuotes, Increment, Decrement } from "../common/utils.js"; /** * Parse a SQL UPDATE query string into an AST * Supports: UPDATE, WITH ... UPDATE */ export type ParseUpdateSQL<T extends string> = ParseUpdateQuery<NormalizeSQL<T>>; /** * Parse a normalized UPDATE query */ type ParseUpdateQuery<T extends string> = NextToken<T> extends [ infer First extends string, infer Rest extends string ] ? First extends "WITH" ? ParseWithAndUpdate<Rest> : First extends "UPDATE" ? ParseUpdateBodyWithCTEs<Rest, undefined> : ParseError<`Expected UPDATE or WITH, got: ${First}`> : ParseError<"Empty query">; /** * Parse WITH clause followed by UPDATE */ type ParseWithAndUpdate<T extends string> = ParseCTEList<T> extends infer CTEResult ? CTEResult extends { ctes: infer CTEs extends CTEDefinition[]; rest: infer AfterCTEs extends string; } ? NextToken<AfterCTEs> extends ["UPDATE", infer UpdateRest extends string] ? ParseUpdateBodyWithCTEs<UpdateRest, CTEs> : ParseError<"Expected UPDATE after WITH clause"> : CTEResult : never; /** * Parse a list of CTEs: name AS (SELECT ...), name2 AS (SELECT ...) */ 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 InParen extends string] ? ExtractParenContent<InParen> extends [ infer Content extends string, infer AfterParen extends string ] ? { cte: CTEDefinition<Name, SubquerySelectClause>; rest: AfterParen; } : ParseError<"Invalid CTE: missing closing parenthesis"> : ParseError<"Invalid CTE: expected ( after AS"> : ParseError<"Invalid CTE: expected AS after name"> : ParseError<"Invalid CTE: expected name">; /** * Extract content within parentheses, handling nested parens */ type ExtractParenContent<T extends string, Depth extends number = 1, Acc extends string = ""> = Depth extends 0 ? [Acc, T] : T extends `(${infer Rest}` ? ExtractParenContent<Rest, Increment<Depth>, `${Acc}(`> : T extends `)${infer Rest}` ? Depth extends 1 ? [Acc, Rest] : ExtractParenContent<Rest, Decrement<Depth>, `${Acc})`> : T extends `${infer Char}${infer Rest}` ? ExtractParenContent<Rest, Depth, `${Acc}${Char}`> : ParseError<"Unclosed parenthesis in CTE">; /** * Parse UPDATE table_name SET ... with optional CTEs */ type ParseUpdateBodyWithCTEs<T extends string, CTEs extends CTEDefinition[] | undefined> = ExtractUntil<T, "SET"> extends [infer TablePart extends string, infer Rest extends string] ? ParseTableRef<Trim<TablePart>> extends infer Table extends TableRef ? NextToken<Rest> extends ["SET", infer AfterSet extends string] ? ParseSetClauseWithCTEs<AfterSet, Table, CTEs> : ParseError<"Expected SET clause"> : ParseError<"Invalid table reference"> : ParseError<"Expected SET after table name">; /** * Parse SET clause and remaining parts with optional CTEs */ type ParseSetClauseWithCTEs<T extends string, Table extends TableRef, CTEs extends CTEDefinition[] | undefined> = ExtractUntil<T, UpdateTerminators> extends [ infer SetPart extends string, infer Rest extends string ] ? ParseSetAssignments<Trim<SetPart>> extends infer Assignments extends SetAssignment[] ? BuildUpdateClauseWithCTEs<Table, SetClause<Assignments>, Rest, CTEs> : ParseError<"Failed to parse SET assignments"> : ParseSetAssignments<Trim<T>> extends infer Assignments extends SetAssignment[] ? BuildUpdateClauseWithCTEs<Table, SetClause<Assignments>, "", CTEs> : ParseError<"Failed to parse SET assignments">; /** * Keywords that terminate the SET clause */ type UpdateTerminators = "FROM" | "WHERE" | "RETURNING"; /** * Parse SET assignments: col1 = val1, col2 = val2, ... */ type ParseSetAssignments<T extends string> = SplitByComma<T> extends infer Parts extends string[] ? ParseAssignmentList<Parts> : []; /** * Parse list of assignments */ type ParseAssignmentList<T extends string[]> = T extends [ infer First extends string, ...infer Rest extends string[] ] ? ParseSingleAssignment<Trim<First>> extends infer Assignment extends SetAssignment ? [Assignment, ...ParseAssignmentList<Rest>] : [] : []; /** * Parse a single assignment: column = value */ type ParseSingleAssignment<T extends string> = T extends `${infer Col} = ${infer Val}` ? SetAssignment<RemoveQuotes<Trim<Col>>, ParseSetValue<Trim<Val>>> : never; /** * Parse a SET value */ type ParseSetValue<T extends string> = T extends "DEFAULT" ? { readonly type: "Default"; } : T extends "NULL" ? { readonly type: "Null"; } : T extends "TRUE" ? { readonly type: "Literal"; readonly value: true; } : T extends "FALSE" ? { readonly type: "Literal"; readonly value: false; } : T extends `'${infer Val}'` ? { readonly type: "Literal"; readonly value: Val; } : T extends `$${infer Num}` ? { readonly type: "Param"; readonly name: Num; } : T extends `:${infer Name}` ? { readonly type: "Param"; readonly name: Name; } : IsNumericString<T> extends true ? { readonly type: "Literal"; readonly value: T; } : T extends `${infer Table}.${infer Col}` ? { readonly type: "ColumnRef"; readonly column: RemoveQuotes<Col>; readonly table: RemoveQuotes<Table>; } : IsSimpleIdentifier<T> extends true ? { readonly type: "ColumnRef"; readonly column: RemoveQuotes<T>; } : { readonly type: "Expression"; readonly expr: T; }; /** * Check if a string looks like a number */ type IsNumericString<T extends string> = T extends `${number}` ? true : false; /** * Build the complete UPDATE clause by parsing remaining optional parts with CTEs */ type BuildUpdateClauseWithCTEs<Table extends TableRef, Set extends SetClause, Rest extends string, CTEs extends CTEDefinition[] | undefined> = ParseFromWithJoins<Rest> extends infer FromResult ? FromResult extends { from: infer From; joins: infer Joins; rest: infer AfterFrom extends string; } ? ParseWhere<AfterFrom> extends infer WhereResult ? WhereResult extends { where: infer Where; rest: infer AfterWhere extends string; } ? ParseReturning<AfterWhere> extends infer ReturnResult ? ReturnResult extends { returning: infer Returning; rest: infer _AfterReturn extends string; } ? SQLUpdateQuery<UpdateClause<Table, Set, From extends TableSource[] ? UpdateFromClause<From, Joins extends JoinClause[] ? Joins : undefined> : undefined, Where extends WhereExpr ? Where : undefined, Returning extends UpdateReturningClause ? Returning : undefined, CTEs>> : never : never : never : never : never : never; /** * Parse FROM clause with JOIN support */ type ParseFromWithJoins<T extends string> = Trim<T> extends "" ? { from: undefined; joins: undefined; rest: ""; } : NextToken<T> extends ["FROM", infer Rest extends string] ? ParseFromTablesWithJoins<Rest> : { from: undefined; joins: undefined; rest: T; }; /** * Parse tables and JOINs in FROM clause */ type ParseFromTablesWithJoins<T extends string> = ExtractFirstTable<T> extends [ infer FirstTable extends string, infer AfterFirst extends string ] ? ParseJoinsOrContinue<AfterFirst, [ParseTableRef<Trim<FirstTable>>]> : { from: [ParseTableRef<Trim<T>>]; joins: undefined; rest: ""; }; /** * Extract the first table (until comma, JOIN, WHERE, or RETURNING) */ type ExtractFirstTable<T extends string> = ExtractUntil<T, "," | JoinKeywords | "WHERE" | "RETURNING"> extends [infer TablePart extends string, infer Rest extends string] ? [TablePart, Rest] : [T, ""]; /** * JOIN keywords to detect */ type JoinKeywords = "JOIN" | "INNER" | "LEFT" | "RIGHT" | "FULL" | "CROSS"; /** * Parse JOINs or continue with comma-separated tables */ type ParseJoinsOrContinue<T extends string, Tables extends TableSource[]> = NextToken<Trim<T>> extends [infer Token extends string, infer Rest extends string] ? Token extends "," ? ExtractFirstTable<Rest> extends [ infer NextTable extends string, infer AfterNext extends string ] ? ParseJoinsOrContinue<AfterNext, [...Tables, ParseTableRef<Trim<NextTable>>]> : { from: Tables; joins: undefined; rest: T; } : Token extends JoinKeywords ? ParseJoinClauses<T, []> extends infer JoinResult ? JoinResult extends { joins: infer Joins extends JoinClause[]; rest: infer AfterJoins extends string; } ? { from: Tables; joins: Joins; rest: AfterJoins; } : { from: Tables; joins: undefined; rest: T; } : { from: Tables; joins: undefined; rest: T; } : { from: Tables; joins: undefined; rest: T; } : { from: Tables; joins: undefined; rest: T; }; /** * Parse multiple JOIN clauses */ type ParseJoinClauses<T extends string, Acc extends JoinClause[]> = ParseSingleJoin<Trim<T>> extends infer JoinResult ? JoinResult extends { join: infer J extends JoinClause; rest: infer Rest extends string; } ? NextToken<Trim<Rest>> extends [infer Token extends string, infer _] ? Token extends JoinKeywords ? ParseJoinClauses<Rest, [...Acc, J]> : { joins: [...Acc, J]; rest: Rest; } : { joins: [...Acc, J]; rest: Rest; } : { joins: Acc; rest: T; } : { joins: Acc; rest: T; }; /** * Parse a single JOIN clause */ type ParseSingleJoin<T extends string> = ParseJoinType<T> extends [ infer JType extends JoinType, infer AfterType extends string ] ? NextToken<AfterType> extends ["JOIN", infer AfterJoin extends string] ? ExtractUntil<AfterJoin, "ON" | JoinKeywords | "WHERE" | "RETURNING"> extends [ infer TablePart extends string, infer Rest extends string ] ? NextToken<Rest> extends ["ON", infer AfterOn extends string] ? ExtractUntil<AfterOn, JoinKeywords | "WHERE" | "RETURNING"> extends [ infer Condition extends string, infer FinalRest extends string ] ? { join: JoinClause<JType, ParseTableRef<Trim<TablePart>>, ParsedCondition<ScanTokensForColumnRefs<Trim<Condition>, []>>>; rest: FinalRest; } : { join: JoinClause<JType, ParseTableRef<Trim<TablePart>>, ParsedCondition<ScanTokensForColumnRefs<Trim<AfterOn>, []>>>; rest: ""; } : JType extends "CROSS" ? { join: JoinClause<JType, ParseTableRef<Trim<TablePart>>, undefined>; rest: Rest; } : never : never : T extends `JOIN ${infer AfterJoin}` ? ExtractUntil<AfterJoin, "ON" | JoinKeywords | "WHERE" | "RETURNING"> extends [ infer TablePart extends string, infer Rest extends string ] ? NextToken<Rest> extends ["ON", infer AfterOn extends string] ? ExtractUntil<AfterOn, JoinKeywords | "WHERE" | "RETURNING"> extends [ infer Condition extends string, infer FinalRest extends string ] ? { join: JoinClause<"INNER", ParseTableRef<Trim<TablePart>>, ParsedCondition<ScanTokensForColumnRefs<Trim<Condition>, []>>>; rest: FinalRest; } : { join: JoinClause<"INNER", ParseTableRef<Trim<TablePart>>, ParsedCondition<ScanTokensForColumnRefs<Trim<AfterOn>, []>>>; rest: ""; } : never : never : never : never; /** * Parse JOIN type (INNER, LEFT, RIGHT, FULL, CROSS) */ type ParseJoinType<T extends string> = NextToken<T> extends [ infer Token extends string, infer Rest extends string ] ? Token extends "INNER" ? ["INNER", Rest] : Token extends "LEFT" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["LEFT", AfterOuter] : ["LEFT", Rest] : Token extends "RIGHT" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["RIGHT", AfterOuter] : ["RIGHT", Rest] : Token extends "FULL" ? NextToken<Rest> extends ["OUTER", infer AfterOuter extends string] ? ["FULL", AfterOuter] : ["FULL", Rest] : Token extends "CROSS" ? ["CROSS", Rest] : Token extends "JOIN" ? ["INNER", T] : never : never; /** * Parse WHERE clause */ type ParseWhere<T extends string> = Trim<T> extends "" ? { where: undefined; rest: ""; } : NextToken<T> extends ["WHERE", infer Rest extends string] ? ExtractUntil<Rest, "RETURNING"> extends [ infer WherePart extends string, infer Remaining extends string ] ? { where: ParsedCondition<ScanTokensForColumnRefs<Trim<WherePart>, []>>; rest: Remaining; } : { where: ParsedCondition<ScanTokensForColumnRefs<Trim<Rest>, []>>; rest: ""; } : { where: undefined; rest: T; }; /** * Scan tokens for column references */ 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; /** * Extract the base column from a JSON operator expression (recursively) */ type ExtractBaseColumnFromJsonExpr<T extends string> = T extends `${infer Base}->>${string}` ? ExtractBaseColumnFromJsonExpr<Base> : T extends `${infer Base}->${string}` ? ExtractBaseColumnFromJsonExpr<Base> : T extends `${infer Base}#>>${string}` ? ExtractBaseColumnFromJsonExpr<Base> : T extends `${infer Base}#>${string}` ? ExtractBaseColumnFromJsonExpr<Base> : T; /** * Check if the token contains a JSON operator */ type HasJsonOperator<T extends string> = T extends `${string}->>${string}` ? true : T extends `${string}->${string}` ? true : T extends `${string}#>>${string}` ? true : T extends `${string}#>${string}` ? true : false; /** * Try to extract a column reference from a token */ type ExtractColumnFromToken<T extends string> = T extends `${infer Table}.${infer Rest}` ? HasJsonOperator<Rest> extends true ? IsSimpleIdentifier<Table> extends true ? ExtractBaseColumnFromJsonExpr<Rest> extends infer BaseCol extends string ? IsSimpleIdentifier<BaseCol> extends true ? TableColumnRef<RemoveQuotes<Table>, RemoveQuotes<BaseCol>, undefined> : never : never : never : IsSimpleIdentifier<Table> extends true ? IsSimpleIdentifier<Rest> extends true ? TableColumnRef<RemoveQuotes<Table>, RemoveQuotes<Rest>, undefined> : never : never : T extends `"${infer Table}"."${infer Col}"` ? TableColumnRef<Table, Col, undefined> : HasJsonOperator<T> extends true ? ExtractBaseColumnFromJsonExpr<T> extends infer BaseCol extends string ? IsSimpleIdentifier<BaseCol> extends true ? IsKeywordOrOperator<BaseCol> extends true ? never : UnboundColumnRef<RemoveQuotes<BaseCol>> : never : never : IsSimpleIdentifier<T> extends true ? IsKeywordOrOperator<T> extends true ? never : UnboundColumnRef<RemoveQuotes<T>> : T extends `"${infer Col}"` ? UnboundColumnRef<Col> : never; /** * Check if a string is a simple identifier */ type IsSimpleIdentifier<T extends string> = T extends "" ? false : T extends `${string} ${string}` ? false : T extends "(" | ")" | "," | "/" | "*" | "+" | "-" | "=" | "'" ? false : true; /** * Check if a token is a SQL keyword or operator */ type IsKeywordOrOperator<T extends string> = T extends `'${string}'` ? true : T extends "SELECT" | "FROM" | "WHERE" | "AND" | "OR" | "NOT" | "IN" | "IS" | "NULL" | "TRUE" | "FALSE" | "LIKE" | "ILIKE" | "BETWEEN" | "EXISTS" | "UPDATE" | "SET" | "RETURNING" | "DEFAULT" | "=" | "!=" | "<>" | "<" | ">" | "<=" | ">=" ? true : T extends `$${number}` | `$${string}` | `:${string}` ? true : T extends `${number}` ? true : false; /** * Parse RETURNING clause * Supports: RETURNING *, RETURNING col, RETURNING OLD.*, RETURNING NEW.col, etc. */ type ParseReturning<T extends string> = Trim<T> extends "" ? { returning: undefined; rest: ""; } : NextToken<T> extends ["RETURNING", infer Rest extends string] ? Trim<Rest> extends "*" ? { returning: UpdateReturningClause<"*">; rest: ""; } : ParseReturningItems<Rest> extends infer Result ? Result extends { items: infer Items extends ReturningItem[]; rest: infer AfterItems extends string; } ? { returning: UpdateReturningClause<Items>; rest: AfterItems; } : Result : never : { returning: undefined; rest: T; }; /** * Parse RETURNING items list (columns, OLD/NEW references) */ type ParseReturningItems<T extends string> = SplitByComma<Trim<T>> extends infer Parts extends string[] ? { items: ParseReturningItemList<Parts>; rest: ""; } : { items: []; rest: ""; }; /** * Parse list of RETURNING items */ type ParseReturningItemList<T extends string[]> = T extends [ infer First extends string, ...infer Rest extends string[] ] ? [ParseSingleReturningItem<Trim<First>>, ...ParseReturningItemList<Rest>] : []; /** * Parse a single RETURNING item * Handles: column, OLD.column, NEW.column, OLD.*, NEW.* */ type ParseSingleReturningItem<T extends string> = T extends "OLD.*" ? QualifiedWildcard<"OLD"> : T extends "NEW.*" ? QualifiedWildcard<"NEW"> : T extends `OLD.${infer Col}` ? QualifiedColumnRef<RemoveQuotes<Col>, "OLD"> : T extends `NEW.${infer Col}` ? QualifiedColumnRef<RemoveQuotes<Col>, "NEW"> : UnboundColumnRef<RemoveQuotes<T>>; /** * Parse a table reference with optional schema and alias */ type ParseTableRef<T extends string> = Trim<T> extends `${infer SchemaTable} AS ${infer Alias}` ? ParseSchemaTable<SchemaTable> extends [ infer Schema extends string | undefined, infer Table extends string ] ? TableRef<Table, RemoveQuotes<Alias>, Schema> : TableRef<RemoveQuotes<SchemaTable>, RemoveQuotes<Alias>, undefined> : Trim<T> extends `${infer SchemaTable} ${infer Alias}` ? Alias extends UpdateTerminators | "SET" ? ParseSchemaTable<SchemaTable> extends [ infer Schema extends string | undefined, infer Table extends string ] ? TableRef<Table, Table, Schema> : TableRef<RemoveQuotes<SchemaTable>, RemoveQuotes<SchemaTable>, undefined> : ParseSchemaTable<SchemaTable> extends [ infer Schema extends string | undefined, infer Table extends string ] ? TableRef<Table, RemoveQuotes<Alias>, Schema> : TableRef<RemoveQuotes<SchemaTable>, 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 */ 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>]; export {}; //# sourceMappingURL=parser.d.ts.map