Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { RowLike } from "./query/values";
import type { Any } from "./types";
import type { OrderBySpec } from "./query/order-by";
import { compileOrderBy } from "./query/order-by";
import { escapeLiteral } from "pg";

Copy link

Copilot AI Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import of escapeLiteral from the 'pg' package may not be available in all environments or could introduce a dependency that's not needed everywhere. Consider wrapping this in a try-catch or providing a fallback implementation.

Suggested change
// Helper to provide escapeLiteral with fallback if 'pg' is not available
function getEscapeLiteral() {
try {
// Try to require 'pg' dynamically (works in Node.js environments)
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require("pg").escapeLiteral as (str: string) => string;
} catch {
// Fallback: basic SQL literal escaping (not as robust as pg's)
return (str: string) => "'" + str.replace(/'/g, "''") + "'";
}
}
const escapeLiteral = getEscapeLiteral();

Copilot uses AI. Check for mistakes.
export class QueryAlias {
constructor(public name: string) {}
Expand All @@ -12,14 +13,17 @@ export class Context {
// The list of tables in the current context (including aliases for subqueries)
public namespace: Map<QueryAlias, string>;
public usedAliases: Set<string>;
// Whether we are in a DDL context (e.g., CREATE TABLE) where query parameters are not allowed:
public inDdl: boolean;

private constructor(namespace: Map<QueryAlias, string>) {
private constructor(namespace: Map<QueryAlias, string>, inDdl: boolean) {
this.namespace = namespace;
this.usedAliases = new Set(namespace.values());
this.inDdl = inDdl;
}

static new() {
return new Context(new Map());
static new(opts?: { inDdl?: boolean }) {
return new Context(new Map(), opts?.inDdl ?? false);
}

withReference(ref: string) {
Expand All @@ -28,7 +32,7 @@ export class Context {
}
const newNamespace = new Map(this.namespace);
newNamespace.set(new QueryAlias(ref), ref);
return new Context(newNamespace);
return new Context(newNamespace, this.inDdl);
}

withAliases(aliases: QueryAlias[]) {
Expand Down Expand Up @@ -56,7 +60,7 @@ export class Context {
newNamespace.set(alias, aliasName);
}

return new Context(newNamespace);
return new Context(newNamespace, this.inDdl);
}

getAlias(alias: QueryAlias): string {
Expand Down Expand Up @@ -95,8 +99,9 @@ export class LiteralExpression extends Expression {
super();
}

compile() {
return sql`cast(${this.value} as ${sql.raw(this.type)})`;
compile(ctx: Context) {
const value = ctx.inDdl ? sql.raw(escapeLiteral(String(this.value))) : this.value;
return sql`cast(${value} as ${sql.raw(this.type)})`;
}
}

Expand All @@ -105,8 +110,9 @@ export class LiteralUnknownExpression extends Expression {
super();
}

compile() {
return sql`${this.value}`;
compile(ctx: Context) {
const value = ctx.inDdl ? sql.raw(escapeLiteral(String(this.value))) : this.value;
return sql`${value}`;
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/gen/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,18 @@ const main = async () => {
await output.write(`export class ${className}<N extends number> extends AnynonarrayBase<Parsed, N> {\n`);

if (isInstantiatable) {
const stripTypes = (t: string) => t.split(".").slice(1); // remove "Types." prefix
await output.write(
` static new(v: SerializeParam): ${asType(type, {
nullable: false,
})};\n`,
` static new(v: SerializeParam): ${stripTypes(
asType(type, {
nullable: false,
}),
)};\n`,
);
await output.write(` static new(v: null): ${asType(type, { nullable: true })};\n`);
await output.write(` static new(v: Expression): ${asType(type)};\n`);
await output.write(` static new(v: null): ${stripTypes(asType(type, { nullable: true }))};\n`);
await output.write(` static new(v: Expression): ${stripTypes(asType(type))};\n`);
await output.write(
` static new(v: SerializeParam | null | Expression): ${asType(type)} { return new ${asType(type)}(v); }\n`,
` static new(v: SerializeParam | null | Expression): ${stripTypes(asType(type))} { return new this(v); }\n`,
);

if (hasParser) {
Expand Down
Loading
Loading