import type { DbColumn } from '../entity/db-column'; import type { Subquery } from './subquery'; /** * SQL condition types */ export type ConditionOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'notIn' | 'isNull' | 'isNotNull' | 'between'; /** * Field reference - wraps a database column name with type information * TName: The property name (e.g., 'isActive') * TValueType: The TypeScript type of the column value (e.g., boolean) */ export interface FieldRef { readonly __fieldName: TName; readonly __dbColumnName: string; readonly __valueType?: TValueType; } /** * Type that represents anything that can be used as a field in a condition. * This includes FieldRef, DbColumn, or any object with __dbColumnName. * Allows navigation properties (which return EntityQuery>) to work in conditions. */ export type FieldLike = FieldRef | DbColumn | SqlFragment | { __dbColumnName: string; __fieldName?: string; }; /** * Extract the field name from a FieldRef or string */ export type ExtractFieldName = T extends FieldRef ? N : T extends string ? T : never; /** * Extract the value type from a FieldRef */ export type ExtractValueType = T extends FieldRef ? V : any; /** * Forward declaration for SqlFragment (actual class defined later in this file) * Used by UnwrapSqlFragment type */ export interface SqlFragmentLike { mapWith: any; as: any; getMapper: any; getAlias: any; getFieldRefs: any; buildSql: any; /** Phantom property to hold the value type - never actually set */ readonly __valueType?: T; } /** * Unwrap SqlFragment to T, or return T if not a SqlFragment * This is used to extract the actual value type from SQL expressions in selections */ export type UnwrapSqlFragment = T extends SqlFragmentLike ? V : T; /** * Type helper to detect if a type is a class instance (has prototype methods) * vs a plain data object (only has data properties). * * Class instances like Date, Map, Set, RegExp, Error, Promise, typed arrays, * and user-defined classes have inherited methods from prototypes. * Plain objects only have their own enumerable properties. * * We detect this by checking for common method signatures that class instances have. * If an object type has valueOf/toString as actual methods (not just from Object.prototype pattern), * it's likely a class instance. * * This approach works for: * - Built-in types: Date, Map, Set, RegExp, Error, Promise, ArrayBuffer, etc. * - Temporal API types (when available) * - BigInt, Symbol * - User-defined classes with methods * - Third-party library types like Decimal.js, moment, etc. * * EXCLUDES: * - DbColumn - has valueOf but should NOT be treated as a value type * - SqlFragment - has valueOf but should NOT be treated as a value type */ type IsClassInstance = T extends { __isDbColumn: true; } ? false : T extends SqlFragmentLike ? false : T extends { valueOf(): infer V; } ? V extends T ? true : V extends number | string | boolean | bigint | symbol ? true : false : false; /** * Alternative check: if type has constructor signature or known class methods * This catches types that might not have valueOf but are still class instances */ type HasClassMethods = T extends { getTime(): number; } ? true : T extends { size: number; has(value: any): boolean; } ? true : T extends { byteLength: number; } ? true : T extends { then(onfulfilled?: any): any; } ? true : T extends { message: string; name: string; } ? true : T extends { exec(string: string): any; } ? true : false; /** * Combined check for value types that should not be recursively processed */ type IsValueType = IsClassInstance extends true ? true : HasClassMethods extends true ? true : false; /** * Recursively unwrap all SqlFragment types in an object type * Maps { a: SqlFragment, b: string } to { a: number, b: string } * Preserves arrays, functions, primitive types, and class instances without recursing into them * Also unwraps Subquery to TResult */ export type UnwrapSelection = T extends SqlFragment ? V : T extends SqlFragmentLike ? V : T extends DbColumn ? V : T extends Subquery ? R : T extends (infer U)[] ? UnwrapSelection[] : T extends (...args: any[]) => any ? T : T extends object ? IsValueType extends true ? T : { [K in keyof T]: UnwrapSelection; } : T; /** * Context for building SQL with parameter tracking */ export interface SqlBuildContext { paramCounter: number; params: any[]; /** Map of placeholder names to their parameter indices (for prepared statements) */ placeholders?: Map; } /** * Placeholder for named parameters in prepared statements * Used with sql.placeholder() to create reusable parameterized queries * * @example * const query = db.users * .where(u => eq(u.id, sql.placeholder('userId'))) * .prepare('getUserById'); * * await query.execute({ userId: 10 }); */ export declare class Placeholder { readonly name: TName; constructor(name: TName); } /** * Base class for all WHERE conditions with helper methods */ export declare abstract class WhereConditionBase { /** * Build the SQL for this condition */ abstract buildSql(context: SqlBuildContext): string; /** * Get all field references used in this condition. * Used to detect navigation property references that need JOINs. */ getFieldRefs(): FieldRef[]; /** * Helper to check if a value is a FieldRef */ protected isFieldRef(value: any): value is FieldRef; /** * Helper to extract database column name from field reference * Returns the fully qualified column name (with table alias if present) */ protected getDbColumnName(field: FieldRef | T, context?: SqlBuildContext): string; /** * Helper to extract value from a FieldRef or constant */ protected extractValue(value: FieldLike | V): any; /** * Helper to get the right-hand side of a comparison * Returns either a column reference or a parameter placeholder * @param value The value to process (field reference, literal, or placeholder) * @param context The SQL build context * @param sourceField Optional source field that may contain a mapper for toDriver transformation */ protected getRightSide(value: FieldLike | V | Placeholder, context: SqlBuildContext, sourceField?: FieldLike | string): string; } /** * Base class for comparison operations (eq, gt, like, etc.) */ export declare abstract class WhereComparisonBase extends WhereConditionBase { protected field: FieldLike | string; protected value?: (FieldLike | V | Placeholder) | undefined; constructor(field: FieldLike | string, value?: (FieldLike | V | Placeholder) | undefined); /** * Get the comparison operator (e.g., '=', '>', 'LIKE') */ protected abstract getOperator(): string; /** * Get all field references used in this comparison */ getFieldRefs(): FieldRef[]; /** * Build the comparison SQL * Can be overridden for custom behavior */ buildSql(context: SqlBuildContext): string; } /** * Logical condition (AND, OR, NOT) */ export declare class LogicalCondition extends WhereConditionBase { private operator; private conditions; constructor(operator: 'and' | 'or' | 'not', conditions: WhereConditionBase[]); /** * Get all field references from nested conditions */ getFieldRefs(): FieldRef[]; buildSql(context: SqlBuildContext): string; } /** * Raw SQL condition */ export declare class RawSqlCondition extends WhereConditionBase { private sql; private sqlParams; constructor(sql: string, sqlParams?: any[]); buildSql(context: SqlBuildContext): string; } export declare class EqComparison extends WhereComparisonBase { protected getOperator(): string; /** * Override buildSql to handle null values correctly. * In SQL, `column = NULL` never matches anything (returns NULL, not TRUE). * We convert `eq(field, null)` to `field IS NULL` for correct semantics. */ buildSql(context: SqlBuildContext): string; } export declare class NeComparison extends WhereComparisonBase { protected getOperator(): string; /** * Override buildSql to handle null values correctly. * In SQL, `column != NULL` never matches anything (returns NULL, not TRUE). * We convert `ne(field, null)` to `field IS NOT NULL` for correct semantics. */ buildSql(context: SqlBuildContext): string; } export declare class GtComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class GteComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class LtComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class LteComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class LikeComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class ILikeComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class StartsWithComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class RegexMatchesComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class RegexMatchesCaseInsensitiveComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class RegexNoMatchComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class RegexNoMatchCaseInsensitiveComparison extends WhereComparisonBase { protected getOperator(): string; } export declare class IsNullComparison extends WhereComparisonBase { constructor(field: FieldLike | string); protected getOperator(): string; } export declare class IsNotNullComparison extends WhereComparisonBase { constructor(field: FieldLike | string); protected getOperator(): string; } /** * IN comparison - handles array of values */ export declare class InComparison extends WhereComparisonBase { private values; constructor(field: FieldLike | string, values: V[]); buildSql(context: SqlBuildContext): string; protected getOperator(): string; } /** * NOT IN comparison */ export declare class NotInComparison extends WhereComparisonBase { private values; constructor(field: FieldLike | string, values: V[]); buildSql(context: SqlBuildContext): string; protected getOperator(): string; } /** * BETWEEN comparison */ export declare class BetweenComparison extends WhereComparisonBase { private min; private max; constructor(field: FieldLike | string, min: FieldLike | V, max: FieldLike | V); /** * Get all field references including min and max */ getFieldRefs(): FieldRef[]; buildSql(context: SqlBuildContext): string; protected getOperator(): string; } /** * Condition type - can be any WHERE condition */ export type Condition = WhereConditionBase; export declare function eq(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function ne(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function gt(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function gte(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function lt(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function lte(field: FieldLike | T | undefined, value: FieldLike | V | Placeholder | undefined): Condition; export declare function like(field: FieldLike | T | undefined, value: FieldLike | string | Placeholder | undefined): Condition; export declare function ilike(field: FieldLike | T | undefined, value: FieldLike | string | Placeholder | undefined): Condition; export declare function startsWith(field: FieldLike | T | undefined, value: FieldLike | string | Placeholder | undefined): Condition; export declare function regexMatches(field: FieldLike | T | undefined, pattern: FieldLike | string | Placeholder | undefined): Condition; export declare function regexMatchesCaseInsensitive(field: FieldLike | T | undefined, pattern: FieldLike | string | Placeholder | undefined): Condition; export declare function regexNoMatch(field: FieldLike | T | undefined, pattern: FieldLike | string | Placeholder | undefined): Condition; export declare function regexNoMatchCaseInsensitive(field: FieldLike | T | undefined, pattern: FieldLike | string | Placeholder | undefined): Condition; /** * Wrap a field or value in `public.search_normalize(...)`. Usable both inside a * `sql\`\`` template and as a building block for the normalized helpers below. * * @example * db.users.where(u => sql` * ${searchNormalize(u.username)} LIKE ${searchNormalize(containsSearch(query))} * `) */ export declare function searchNormalize(value: FieldLike | string | Placeholder): SqlFragment; /** Build a `%value%` (contains) LIKE pattern. */ export declare function containsSearch(value: string): string; /** Build a `value%` (starts-with) LIKE pattern. */ export declare function startsWithSearch(value: string): string; /** Build a `%value` (ends-with) LIKE pattern. */ export declare function endsWithSearch(value: string): string; /** * Accent/case-insensitive equality: * `search_normalize(field) = search_normalize(value)`. */ export declare function normalizedEq(field: FieldLike | T, value: FieldLike | string | Placeholder): Condition; /** * Accent/case-insensitive `LIKE`. The `pattern` is normalized too, so pass the * wildcards yourself (or build them with `containsSearch` / `startsWithSearch`): * `search_normalize(field) LIKE search_normalize(pattern)`. */ export declare function normalizedLike(field: FieldLike | T, pattern: FieldLike | string | Placeholder): Condition; /** * Accent/case-insensitive prefix match. The wildcard is appended after * normalization, so callers pass a plain prefix: * `search_normalize(field) LIKE search_normalize(value) || '%'`. */ export declare function normalizedStartsWith(field: FieldLike | T, value: FieldLike | string | Placeholder): Condition; export declare function inArray(field: FieldLike | T | undefined, values: V[]): Condition; export declare function notInArray(field: FieldLike | T | undefined, values: V[]): Condition; export declare function isNull(field: FieldLike | T | undefined): Condition; export declare function isNotNull(field: FieldLike | T | undefined): Condition; export declare function between(field: FieldLike | T | undefined, min: FieldLike | V | undefined, max: FieldLike | V | undefined): Condition; export declare function and(...conditions: Condition[]): Condition; export declare function or(...conditions: Condition[]): Condition; export declare function not(condition: Condition): Condition; /** * Extract the underlying value type from a FieldLike or DbColumn */ type ExtractFieldValue = T extends FieldLike ? V : T extends DbColumn ? V : T; /** * COALESCE - returns the first non-null value from the arguments. * Accepts two or more arguments; each may be a column, SqlFragment or literal. * * @example * // Use in select * db.users.select(u => ({ * name: coalesce(u.displayName, u.username), * })) * * @example * // With literal fallback * db.users.select(u => ({ * status: coalesce(u.status, 'unknown'), * })) * * @example * // Use in update with a SqlFragment fallback for JSONB * db.orderItems.where(p => eq(p.id, id)).update({ * integrationInfo: sql`${coalesce(p.integrationInfo, sql`'{}'::jsonb`)} || ${patch}::jsonb`, * }) */ export declare function coalesce(value1: FieldLike | T1, value2: FieldLike | T2): SqlFragment> | ExtractFieldValue>; export declare function coalesce(value1: FieldLike | T1, value2: FieldLike | T2, ...rest: TRest): SqlFragment> | ExtractFieldValue>; /** * JSONB merge helper - emits `COALESCE(target, '{}'::jsonb) || $patch::jsonb`. * * Convenience wrapper for the common pattern of merging a JSONB patch onto a * column that may be null. Safe under concurrent writes because the `||` * operator is evaluated atomically by PostgreSQL per row. * * @param target - The JSONB column (or SqlFragment) to merge onto. May be null. * Accept the column proxy directly (e.g. `p.integrationInfo`) — at * runtime it is a FieldRef even though TypeScript sees the value type. * @param patch - The JSONB object literal (or SqlFragment) to merge in. * * @example * // Atomic JSONB merge in update (note: must use the function form so `p` is * // the column proxy — bare `update({...})` lacks the proxy). * db.orderItems.where(p => eq(p.id, id)).update(p => ({ * integrationInfo: jsonbMerge(p.integrationInfo, patch), * })) */ export declare function jsonbMerge(target: FieldLike | SqlFragment | T | null | undefined, patch: FieldLike | SqlFragment | T): SqlFragment; /** * Creates a SQL condition to check if a flag is set in a numeric column * Uses bitwise AND to check if the specific bit is non-zero * * @param column - The numeric column containing flags * @param flag - The flag value to check for * @returns SqlFragment that evaluates to true if the flag is set * * @example * enum UserStateFlags { * Active = 1, * Verified = 2, * Admin = 4, * } * db.users.where(u => flagHas(u.state, UserStateFlags.Active)) */ export declare function flagHas(column: FieldLike | DbColumn, flag: T): SqlFragment; /** * Creates a SQL condition to check if ALL specified flags are set * Uses bitwise AND and checks if result equals the flags value * * @param column - The numeric column containing flags * @param flags - The combined flag values to check for (use | to combine) * @returns SqlFragment that evaluates to true if all flags are set * * @example * db.users.where(u => flagHasAll(u.state, UserStateFlags.Active | UserStateFlags.Verified)) */ export declare function flagHasAll(column: FieldLike | DbColumn, flags: T): SqlFragment; /** * Creates a SQL condition to check if ANY of the specified flags is set * Uses bitwise AND to check if any of the bits are non-zero * * @param column - The numeric column containing flags * @param flags - The combined flag values to check for (use | to combine) * @returns SqlFragment that evaluates to true if any flag is set * * @example * db.users.where(u => flagHasAny(u.state, UserStateFlags.Slave | UserStateFlags.Unsynced)) */ export declare function flagHasAny(column: FieldLike | DbColumn, flags: T): SqlFragment; /** * Creates a SQL condition to check if a flag is NOT set * Uses bitwise AND to check if the specific bit is zero * * @param column - The numeric column containing flags * @param flag - The flag value to check is not set * @returns SqlFragment that evaluates to true if the flag is not set * * @example * db.users.where(u => flagHasNone(u.state, UserStateFlags.Banned)) */ export declare function flagHasNone(column: FieldLike | DbColumn, flag: T): SqlFragment; /** * JSONB property selector - extracts a property from a JSONB column * Uses the -> operator for JSONB extraction * * @param jsonbField - The JSONB column to extract from * @param key - The property key to extract (typed as keyof TJsonb) * @returns SqlFragment that extracts the property as JSONB * * @example * // Given a JSONB column 'metadata' with structure { priority: number, tags: string[] } * type Metadata = { priority: number; tags: string[] }; * db.tasks.select(t => ({ * priority: jsonbSelect(t.metadata, 'priority'), * })) * * @example * // Use in where clause * db.tasks.where(t => eq(jsonbSelect(t.metadata, 'priority'), 'high')) */ export declare function jsonbSelect(jsonbField: FieldLike | DbColumn | undefined, key: TKey): SqlFragment; /** * JSONB text property selector - extracts a property from a JSONB column as text * Uses the ->> operator for direct text extraction * * @param jsonbField - The JSONB column to extract from * @param key - The property key to extract (typed as keyof TJsonb) * @returns SqlFragment that extracts the property as text (string) * * @example * type Metadata = { priority: string }; * db.tasks.select(t => ({ * priorityText: jsonbSelectText(t.metadata, 'priority'), * })) */ export declare function jsonbSelectText(jsonbField: FieldLike | DbColumn | undefined, key: TKey): SqlFragment; /** * Recursive mapped type for navigating into a JSONB object. * Each property returns a type that can be: * - Used directly as a field in conditions (eq, ne, like, isNotNull, etc.) * - Navigated further for nested objects (e.g. `c.config.token`) * * Values are compared as text (PostgreSQL ->> operator) which works naturally * with string, number, and boolean comparisons via the pg driver's text protocol. */ export type JsonbElement = SqlFragment & { readonly [K in keyof T]-?: JsonbElement>; }; /** * Check if any element in a JSONB array matches a predicate. * Generates an EXISTS subquery with jsonb_array_elements. * * The callback receives a typed proxy where each property access builds * a JSONB path expression (using ->> for text extraction). Use standard * condition functions (eq, ne, like, isNotNull, etc.) on the proxy properties. * * @example * ```typescript * // Check if integrationConfig array has an element with type 'VILLAPRO' and a token * db.products.where(p => * jsonbArraySome(p.integrationConfig, c => * and( * eq(c.type, IntegrationType.VILLAPRO), * isNotNull(c.config.villaproSystToken) * ) * ) * ) * // → WHERE EXISTS (SELECT 1 FROM jsonb_array_elements("product"."integration_config") AS __elem * // WHERE (__elem->>'type' = $1 AND __elem->'config'->>'villaproSystToken' IS NOT NULL)) * ``` */ export declare function jsonbArraySome(jsonbField: FieldLike | FieldLike | DbColumn | DbColumn | undefined, predicate: (element: JsonbElement) => Condition): Condition; /** * Converts a value to its string representation for JSONB text comparisons. * The PostgreSQL ->> operator always returns text, so comparison values must be strings. * * @example * ```typescript * jsonbArraySome(p.integrationConfig, c => * eq(c.type, jsonbConditionUnwrap(IntegrationType.VILLAPRO)) * ) * ``` */ export declare function jsonbConditionUnwrap(value: T): T; /** * SQL Fragment - represents a raw SQL expression that can be used in SELECT or WHERE * Supports embedding FieldRef objects * Extends WhereConditionBase so it can be used directly in WHERE clauses */ export declare class SqlFragment extends WhereConditionBase { private sqlParts; private values; private mapper?; private alias?; constructor(parts: string[], values: any[], mapper?: any, alias?: string); /** * Set custom type mapper for bidirectional transformation * Can accept either: * - A function (value: TDriver) => TData for inline transformations * - A CustomTypeBuilder with full toDriver/fromDriver methods */ mapWith(mapper: ((value: TDriver) => TData) | any): SqlFragment; /** * Set column alias for SELECT clause */ as(alias: string): SqlFragment; /** * Get the type mapper (internal use) */ getMapper(): any | undefined; /** * Get the alias (internal use) */ getAlias(): string | undefined; /** * Get all field references from the fragment values */ getFieldRefs(): FieldRef[]; /** * Check if value is a RawSql instance */ private isRawSql; /** * Check if value is a CTE table reference created by DbCte.as(). * Duck-typed on marker properties to avoid a circular import with cte-builder. */ private isCteTableRef; /** * Build the SQL string with proper parameter placeholders */ buildSql(context: SqlBuildContext): string; /** * Get the SQL string with parameters (for debugging) */ toString(): string; } /** * Marker class for raw SQL strings that should be inserted without parameterization * Used by sql.raw() to inject SQL directly into queries */ export declare class RawSql { readonly value: string; constructor(value: string); } /** * Tagged template literal for creating SQL fragments * Usage: sql`lower(${field})` or sql`${field} = ${value}` */ declare function sqlTemplate(strings: TemplateStringsArray, ...values: any[]): SqlFragment; /** * Create a raw SQL string that will be inserted directly without parameterization * WARNING: Do not use with user input - this can lead to SQL injection! * Use for table names, column names, SQL keywords, or trusted static strings only. * * @example * // Use for dynamic table/column names * sql`SELECT * FROM ${sql.raw(tableName)} WHERE ${sql.raw(columnName)} = ${value}` * * // Use for SQL keywords/operators * sql`SELECT * FROM users ORDER BY name ${sql.raw(sortDirection)}` * * // Use for complex SQL that shouldn't be parameterized * sql`SELECT ${sql.raw('COUNT(*)')} FROM users` */ declare function raw(value: string): RawSql; /** * Join multiple SQL fragments with a separator * * @example * const columns = [sql`name`, sql`email`, sql`age`]; * const selectList = sql.join(columns, sql`, `); * // Result: name, email, age */ declare function join(fragments: SqlFragment[], separator?: SqlFragment): SqlFragment; /** * Create a named placeholder for prepared statements * The placeholder will be replaced with a parameter when the query is executed * * @example * const query = db.users * .where(u => eq(u.id, sql.placeholder('userId'))) * .prepare('getUserById'); * * await query.execute({ userId: 10 }); */ declare function placeholder(name: TName): Placeholder; export declare const sql: typeof sqlTemplate & { raw: typeof raw; empty: SqlFragment; join: typeof join; placeholder: typeof placeholder; }; export declare class ConditionBuilder { build(condition: Condition, startParam?: number, placeholders?: Map): { sql: string; params: any[]; placeholders?: Map; paramCounter: number; }; } export {}; //# sourceMappingURL=conditions.d.ts.map