@kuindji/sql-type-parser
Version:
Type-level SQL parser for TypeScript
360 lines • 15.8 kB
TypeScript
/**
* Type-level SQL INSERT parser
*
* This module handles parsing of INSERT queries specifically.
* It uses shared utilities from common/ but maintains its own
* execution tree for TypeScript performance.
*/
import type { InsertClause, InsertColumnList, InsertColumnRef, InsertValuesClause, InsertSelectClause, InsertValueRow, InsertValue, InsertSource, ReturningClause, OnConflictClause, ConflictTarget, ConflictAction, ConflictUpdateSet, SQLInsertQuery } from "./ast.js";
import type { TableRef, UnboundColumnRef, 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 INSERT query string into an AST
*/
export type ParseInsertSQL<T extends string> = ParseInsertQuery<NormalizeSQL<T>>;
/**
* Parse a normalized INSERT query
*/
type ParseInsertQuery<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "INSERT" ? ParseInsertBody<Rest> : ParseError<`Expected INSERT, got: ${First}`> : ParseError<"Empty query">;
/**
* Parse INSERT INTO table_name ...
*/
type ParseInsertBody<T extends string> = NextToken<T> extends [
infer First extends string,
infer Rest extends string
] ? First extends "INTO" ? ParseTableAndColumns<Rest> : ParseError<`Expected INTO after INSERT, got: ${First}`> : ParseError<"Expected INTO after INSERT">;
/**
* Parse table name and optional column list
*/
type ParseTableAndColumns<T extends string> = NextToken<T> extends [
infer TablePart extends string,
infer Rest extends string
] ? ParseTableRef<TablePart> extends infer Table extends TableRef ? CheckForColumns<Rest, Table> : ParseError<"Invalid table reference"> : ParseError<"Expected table name">;
/**
* Check if there's a column list (parentheses) or go directly to VALUES/SELECT
*/
type CheckForColumns<T extends string, Table extends TableRef> = NextToken<T> extends ["(", infer AfterParen extends string] ? ParseColumnList<AfterParen> extends infer ColResult ? ColResult extends {
columns: infer Cols extends InsertColumnList;
rest: infer AfterCols extends string;
} ? ParseInsertSource<AfterCols, Table, Cols> : ColResult : never : ParseInsertSource<T, Table, undefined>;
/**
* Parse column list inside parentheses
*/
type ParseColumnList<T extends string> = ExtractUntilClosingParen<T, 1, ""> extends [
infer ColsPart extends string,
infer Rest extends string
] ? SplitByComma<Trim<ColsPart>> extends infer Parts extends string[] ? ParseColumnNames<Parts> extends infer Cols extends InsertColumnRef[] ? {
columns: InsertColumnList<Cols>;
rest: Trim<Rest>;
} : ParseError<"Failed to parse column names"> : ParseError<"Failed to split column list"> : ParseError<"Invalid column list syntax">;
/**
* Parse column names from string array
*/
type ParseColumnNames<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [InsertColumnRef<RemoveQuotes<Trim<First>>>, ...ParseColumnNames<Rest>] : [];
/**
* Parse the source of INSERT data (VALUES or SELECT)
*/
type ParseInsertSource<T extends string, Table extends TableRef, Columns extends InsertColumnList | undefined> = NextToken<T> extends [infer First extends string, infer Rest extends string] ? First extends "VALUES" ? ParseValuesClause<Rest, Table, Columns> : First extends "SELECT" ? ParseInsertSelect<T, Table, Columns> : First extends "DEFAULT" ? NextToken<Rest> extends ["VALUES", infer AfterValues extends string] ? BuildInsertClause<Table, Columns, InsertValuesClause<[InsertValueRow<[{
type: "Default";
}]>]>, AfterValues> : ParseError<"Expected VALUES after DEFAULT"> : ParseError<`Expected VALUES or SELECT, got: ${First}`> : ParseError<"Expected VALUES or SELECT">;
/**
* Parse VALUES clause with one or more rows
*/
type ParseValuesClause<T extends string, Table extends TableRef, Columns extends InsertColumnList | undefined> = ParseValueRows<T, []> extends infer Result ? Result extends {
rows: infer Rows extends InsertValueRow[];
rest: infer Rest extends string;
} ? BuildInsertClause<Table, Columns, InsertValuesClause<Rows>, Rest> : Result : never;
/**
* Parse multiple value rows separated by commas
*/
type ParseValueRows<T extends string, Acc extends InsertValueRow[]> = NextToken<T> extends [
"(",
infer AfterParen extends string
] ? ParseSingleValueRow<AfterParen> extends infer RowResult ? RowResult extends {
row: infer Row extends InsertValueRow;
rest: infer Rest extends string;
} ? NextToken<Rest> extends [",", infer AfterComma extends string] ? ParseValueRows<AfterComma, [...Acc, Row]> : {
rows: [...Acc, Row];
rest: Rest;
} : RowResult : never : Acc extends [] ? ParseError<"Expected ( to start value row"> : {
rows: Acc;
rest: T;
};
/**
* Parse a single row of values inside parentheses
*/
type ParseSingleValueRow<T extends string> = ExtractUntilClosingParen<T, 1, ""> extends [
infer ValuesPart extends string,
infer Rest extends string
] ? SplitByComma<Trim<ValuesPart>> extends infer Parts extends string[] ? ParseValues<Parts> extends infer Values extends InsertValue[] ? {
row: InsertValueRow<Values>;
rest: Trim<Rest>;
} : ParseError<"Failed to parse values"> : ParseError<"Failed to split values"> : ParseError<"Invalid value row syntax">;
/**
* Parse individual values from string array
*/
type ParseValues<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [ParseSingleValue<Trim<First>>, ...ParseValues<Rest>] : [];
/**
* Parse a single value
*/
type ParseSingleValue<T extends string> = T extends "DEFAULT" ? {
readonly type: "Default";
} : T extends "NULL" ? {
readonly type: "Literal";
readonly value: 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;
} : {
readonly type: "Expression";
readonly expr: T;
};
/**
* Check if a string looks like a number
*/
type IsNumericString<T extends string> = T extends `${number}` ? true : false;
/**
* Parse INSERT ... SELECT
*/
type ParseInsertSelect<T extends string, Table extends TableRef, Columns extends InsertColumnList | undefined> = ExtractSelectQuery<T> extends infer SelectResult ? SelectResult extends {
query: infer Query extends SubquerySelectClause;
rest: infer Rest extends string;
} ? BuildInsertClause<Table, Columns, InsertSelectClause<Query>, Rest> : SelectResult : never;
/**
* Placeholder select clause for INSERT ... SELECT
* Full SELECT validation would require integrating the SELECT parser
*/
type PlaceholderSelectClause = {
readonly type: "SelectClause";
readonly columns: unknown;
readonly from: TableRef;
readonly joins: undefined;
readonly where: undefined;
readonly groupBy: undefined;
readonly having: undefined;
readonly orderBy: undefined;
readonly limit: undefined;
readonly offset: undefined;
readonly distinct: false;
readonly ctes: undefined;
};
/**
* Extract SELECT query for INSERT ... SELECT
* This is a simplified extraction that finds the SELECT portion
*/
type ExtractSelectQuery<T extends string> = ExtractUntil<T, InsertTerminators> extends [
infer SelectPart extends string,
infer Rest extends string
] ? {
query: PlaceholderSelectClause;
rest: Rest;
} : {
query: PlaceholderSelectClause;
rest: "";
};
/**
* Keywords that terminate the INSERT source
*/
type InsertTerminators = "ON" | "RETURNING";
/**
* Build the complete INSERT clause by parsing remaining optional parts
*/
type BuildInsertClause<Table extends TableRef, Columns extends InsertColumnList | undefined, Source extends InsertSource, Rest extends string> = ParseOnConflict<Rest> extends infer ConflictResult ? ConflictResult extends {
onConflict: infer OnConflict;
rest: infer AfterConflict extends string;
} ? ParseReturning<AfterConflict> extends infer ReturnResult ? ReturnResult extends {
returning: infer Returning;
rest: infer _AfterReturn extends string;
} ? SQLInsertQuery<InsertClause<Table, Columns, Source, OnConflict extends OnConflictClause ? OnConflict : undefined, Returning extends ReturningClause ? Returning : undefined>> : never : never : never : never;
/**
* Parse ON CONFLICT clause
*/
type ParseOnConflict<T extends string> = Trim<T> extends "" ? {
onConflict: undefined;
rest: "";
} : NextToken<T> extends ["ON", infer Rest extends string] ? NextToken<Rest> extends ["CONFLICT", infer AfterConflict extends string] ? ParseConflictBody<AfterConflict> : {
onConflict: undefined;
rest: T;
} : {
onConflict: undefined;
rest: T;
};
/**
* Parse the body of ON CONFLICT
*/
type ParseConflictBody<T extends string> = ParseConflictTarget<T> extends infer TargetResult ? TargetResult extends {
target: infer Target;
rest: infer AfterTarget extends string;
} ? ParseConflictAction<AfterTarget> extends infer ActionResult ? ActionResult extends {
action: infer Action extends ConflictAction;
updates: infer Updates;
rest: infer AfterAction extends string;
} ? {
onConflict: OnConflictClause<Target extends ConflictTarget ? Target : undefined, Action, Updates extends ConflictUpdateSet[] ? Updates : undefined, undefined>;
rest: AfterAction;
} : ActionResult : never : TargetResult : never;
/**
* Parse conflict target (columns or constraint)
*/
type ParseConflictTarget<T extends string> = NextToken<T> extends [
"(",
infer AfterParen extends string
] ? ExtractUntilClosingParen<AfterParen, 1, ""> extends [
infer ColsPart extends string,
infer Rest extends string
] ? {
target: ConflictTarget<SplitByCommaSimple<Trim<ColsPart>>, undefined>;
rest: Trim<Rest>;
} : {
target: undefined;
rest: T;
} : NextToken<T> extends ["ON", infer Rest extends string] ? NextToken<Rest> extends ["CONSTRAINT", infer AfterConstraint extends string] ? NextToken<AfterConstraint> extends [
infer ConstraintName extends string,
infer AfterName extends string
] ? {
target: ConflictTarget<undefined, RemoveQuotes<ConstraintName>>;
rest: AfterName;
} : {
target: undefined;
rest: T;
} : {
target: undefined;
rest: T;
} : {
target: undefined;
rest: T;
};
/**
* Simple split by comma (for conflict target columns)
*/
type SplitByCommaSimple<T extends string> = SplitByComma<T> extends infer Parts extends string[] ? CleanColumnNames<Parts> : [];
/**
* Clean column names (trim and remove quotes)
*/
type CleanColumnNames<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [RemoveQuotes<Trim<First>>, ...CleanColumnNames<Rest>] : [];
/**
* Parse conflict action (DO NOTHING or DO UPDATE)
*/
type ParseConflictAction<T extends string> = NextToken<T> extends [
"DO",
infer AfterDo extends string
] ? NextToken<AfterDo> extends ["NOTHING", infer AfterNothing extends string] ? {
action: "DO NOTHING";
updates: undefined;
rest: AfterNothing;
} : NextToken<AfterDo> extends ["UPDATE", infer AfterUpdate extends string] ? NextToken<AfterUpdate> extends ["SET", infer AfterSet extends string] ? ParseUpdateSets<AfterSet> extends infer SetResult ? SetResult extends {
updates: infer Updates extends ConflictUpdateSet[];
rest: infer AfterSets extends string;
} ? {
action: "DO UPDATE";
updates: Updates;
rest: AfterSets;
} : SetResult : never : ParseError<"Expected SET after DO UPDATE"> : ParseError<"Expected NOTHING or UPDATE after DO"> : ParseError<"Expected DO after ON CONFLICT target">;
/**
* Parse SET clauses for ON CONFLICT DO UPDATE
*/
type ParseUpdateSets<T extends string, Acc extends ConflictUpdateSet[] = []> = ExtractUntil<T, "RETURNING" | "WHERE"> extends [infer SetsPart extends string, infer Rest extends string] ? SplitByComma<Trim<SetsPart>> extends infer Parts extends string[] ? ParseSetAssignments<Parts> extends infer Sets extends ConflictUpdateSet[] ? {
updates: Sets;
rest: Rest;
} : {
updates: Acc;
rest: Rest;
} : {
updates: Acc;
rest: Rest;
} : {
updates: Acc;
rest: "";
};
/**
* Parse SET assignments
*/
type ParseSetAssignments<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? Trim<First> extends `${infer Col} = ${infer Val}` ? [
ConflictUpdateSet<RemoveQuotes<Trim<Col>>, ParseSetValue<Trim<Val>>>,
...ParseSetAssignments<Rest>
] : ParseSetAssignments<Rest> : [];
/**
* Parse SET value (including EXCLUDED.column syntax)
*/
type ParseSetValue<T extends string> = T extends `EXCLUDED . ${infer Col}` ? "EXCLUDED" : T extends `EXCLUDED.${infer Col}` ? "EXCLUDED" : ParseSingleValue<T>;
/**
* Parse RETURNING clause
*/
type ParseReturning<T extends string> = Trim<T> extends "" ? {
returning: undefined;
rest: "";
} : NextToken<T> extends ["RETURNING", infer Rest extends string] ? Trim<Rest> extends "*" ? {
returning: ReturningClause<"*">;
rest: "";
} : ParseReturningColumns<Rest> extends infer Result ? Result extends {
columns: infer Cols extends UnboundColumnRef[];
rest: infer AfterCols extends string;
} ? {
returning: ReturningClause<Cols>;
rest: AfterCols;
} : Result : never : {
returning: undefined;
rest: T;
};
/**
* Parse RETURNING column list
*/
type ParseReturningColumns<T extends string> = SplitByComma<Trim<T>> extends infer Parts extends string[] ? {
columns: ParseColumnRefs<Parts>;
rest: "";
} : {
columns: [];
rest: "";
};
/**
* Parse column references for RETURNING
*/
type ParseColumnRefs<T extends string[]> = T extends [
infer First extends string,
...infer Rest extends string[]
] ? [UnboundColumnRef<RemoveQuotes<Trim<First>>>, ...ParseColumnRefs<Rest>] : [];
/**
* Parse a table reference with optional schema
*/
type ParseTableRef<T extends string> = Trim<T> extends `${infer Schema}.${infer Table}` ? IsSimpleIdentifier<Schema> extends true ? IsSimpleIdentifier<Table> extends true ? TableRef<RemoveQuotes<Table>, RemoveQuotes<Table>, RemoveQuotes<Schema>> : TableRef<RemoveQuotes<T>, RemoveQuotes<T>, undefined> : TableRef<RemoveQuotes<T>, RemoveQuotes<T>, undefined> : Trim<T> extends `"${infer Schema}"."${infer Table}"` ? TableRef<Table, Table, Schema> : TableRef<RemoveQuotes<Trim<T>>, RemoveQuotes<Trim<T>>, undefined>;
/**
* 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;
/**
* 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>, ""];
export {};
//# sourceMappingURL=parser.d.ts.map