• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rogerpadilla / uql / 23874861559

01 Apr 2026 10:56PM UTC coverage: 94.915% (-0.03%) from 94.943%
23874861559

push

github

rogerpadilla
refactor: streamline Bun SQL Postgres update calls in tests for improved readability

2976 of 3299 branches covered (90.21%)

Branch coverage included in aggregate %.

5256 of 5374 relevant lines covered (97.8%)

346.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.49
/packages/uql-orm/src/postgres/postgresDialect.ts
1
import type { DialectOptions } from '../dialect/abstractDialect.js';
2
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
3
import { buildElemMatchConditions } from '../dialect/jsonArrayElemMatchUtils.js';
4
import { getMeta } from '../entity/index.js';
5
import {
6
  type DialectFeatures,
7
  type EntityMeta,
8
  type FieldOptions,
9
  type JsonColumnType,
10
  type JsonUpdateOp,
11
  type QueryComparisonOptions,
12
  type QueryConflictPaths,
13
  type QueryContext,
14
  type QueryOptions,
15
  QueryRaw,
16
  type QuerySizeComparisonOps,
17
  type QueryTextSearchOptions,
18
  type QueryVectorSearch,
19
  type SqlDialectName,
20
  type Type,
21
  type VectorDistance,
22
} from '../type/index.js';
23
import { escapeAnsiSqlLiteral } from '../util/ansiSqlLiteral.js';
24
import { hasKeys, isJsonType } from '../util/index.js';
25

26
/**
27
 * PostgreSQL dialect (AST, quoting, BIGINT IDENTITY PKs).
28
 * For node-pg use PgDialect. Neon, Bun SQL, and Cockroach use driver-specific subclasses.
29
 */
30
export class PostgresDialect extends AbstractSqlDialect {
31
  /** Default {@link DialectFeatures} before wire/driver-specific patches (e.g. {@link PgDialect}). */
32
  static readonly defaultDialectFeatures: DialectFeatures = {
37✔
33
    explicitJsonCast: false,
34
    nativeArrays: true,
35
    supportsJsonb: true,
36
    returning: true,
37
    ifNotExists: true,
38
    indexIfNotExists: true,
39
    dropTableCascade: true,
40
    renameColumn: true,
41
    foreignKeyAlter: true,
42
    columnComment: false,
43
    vectorIndexStyle: 'create',
44
    vectorSupportsLength: true,
45
    supportsTimestamptz: true,
46
    defaultStringAsText: true,
47
  };
48

49
  override readonly dialectName: SqlDialectName = 'postgres';
188✔
50

51
  override readonly quoteChar = '"';
188✔
52
  override readonly serialPrimaryKey: string = 'BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY';
188✔
53
  override readonly tableOptions = '';
188✔
54
  override readonly beginTransactionCommand = 'BEGIN';
188✔
55
  override readonly commitTransactionCommand = 'COMMIT';
188✔
56
  override readonly rollbackTransactionCommand = 'ROLLBACK';
188✔
57
  override readonly alterColumnStrategy = 'separate-clauses';
188✔
58
  override readonly insertIdStrategy = 'last';
188✔
59

60
  constructor(options: DialectOptions = {}) {
188✔
61
    super(PostgresDialect.defaultDialectFeatures, options);
188✔
62
  }
63

64
  override readonly vectorOpsClass: Readonly<Record<VectorDistance, string>> | undefined = {
188✔
65
    cosine: 'vector_cosine_ops',
66
    l2: 'vector_l2_ops',
67
    inner: 'vector_ip_ops',
68
    l1: 'vector_l1_ops',
69
    hamming: 'bit_hamming_ops',
70
  };
71

72
  override readonly vectorExtension: string | undefined = 'vector';
188✔
73

74
  override normalizeValue(value: unknown): unknown {
75
    if (value != null && typeof value === 'object' && Array.isArray(value)) {
1,645✔
76
      return this.features.nativeArrays ? value : toPgArray(value);
202✔
77
    }
78
    return super.normalizeValue(value);
1,443✔
79
  }
80

81
  override placeholder(index: number): string {
82
    return `$${index}`;
699✔
83
  }
84

85
  override insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void {
86
    super.insert(ctx, entity, payload, opts);
82✔
87
    ctx.append(' ' + this.returningId(entity));
82✔
88
  }
89

90
  override upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void {
91
    const meta = getMeta(entity);
14✔
92
    const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, (name) => `EXCLUDED.${name}`);
14✔
93
    const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
14✔
94
    const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
14✔
95
    super.insert(ctx, entity, payload);
14✔
96
    // xmax system column is 0 for newly inserted rows, non-zero for updated rows (MVCC).
97
    ctx.append(
14✔
98
      ` ON CONFLICT (${keysStr}) ${onConflict} ${this.returningId(entity)}, (xmax = 0) AS ${this.escapeId('_created')}`,
99
    );
100
  }
101

102
  override compare<E>(
103
    ctx: QueryContext,
104
    entity: Type<E>,
105
    key: string,
106
    val: unknown,
107
    opts: QueryComparisonOptions = {},
344✔
108
  ): void {
109
    if (key === '$text') {
344✔
110
      const meta = getMeta(entity);
2✔
111
      const search = val as QueryTextSearchOptions<E>;
2✔
112
      const fields = (search.$fields ?? [])
2!
113
        .map((fKey) => {
114
          const field = meta.fields[fKey];
3✔
115
          const columnName = this.resolveColumnName(fKey, field!);
3✔
116
          return this.escapeId(columnName);
3✔
117
        })
118
        .join(` || ' ' || `);
119
      ctx.append(`to_tsvector(${fields}) @@ to_tsquery(`);
2✔
120
      ctx.addValue(search.$value);
2✔
121
      ctx.append(')');
2✔
122
      return;
2✔
123
    }
124
    super.compare(ctx, entity, key, val, opts);
342✔
125
  }
126

127
  protected override jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string {
128
    return `${jsonField} @> ${this.jsonVal(ctx, value)}`;
2✔
129
  }
130

131
  protected override jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string {
132
    const tmpCtx = this.createContext();
5✔
133
    this.buildSizeComparison(tmpCtx, () => tmpCtx.append(`jsonb_array_length(${jsonField})`), value);
6✔
134
    ctx.pushValue(...tmpCtx.values);
5✔
135
    return tmpCtx.sql;
5✔
136
  }
137

138
  protected override jsonElemMatch(ctx: QueryContext, jsonField: string, match: Record<string, unknown>): string {
139
    // Primitive element match: keys are operators (e.g. { $startsWith: 'ad' } on a string[])
140
    const isPrimitiveElement = Object.keys(match).some((k) => k.startsWith('$'));
21✔
141
    if (isPrimitiveElement) {
10✔
142
      const conditions = Object.entries(match).map(([op, opVal]) =>
1✔
143
        this.buildJsonFieldCondition(ctx, () => 'elem', '', op, opVal),
1✔
144
      );
145
      return `EXISTS (SELECT 1 FROM jsonb_array_elements_text(${jsonField}) AS elem WHERE ${conditions.join(' AND ')})`;
1✔
146
    }
147

148
    const hasOperators = Object.values(match).some(
9✔
149
      (v) => v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).some((k) => k.startsWith('$')),
11✔
150
    );
151

152
    if (!hasOperators) {
9✔
153
      return `${jsonField} @> ${this.jsonVal(ctx, [match])}`;
1✔
154
    }
155

156
    const conditions = buildElemMatchConditions(
8✔
157
      match,
158
      (field, op, opVal) =>
159
        this.buildJsonFieldCondition(ctx, (f) => `elem->>'${this.escapeJsonKey(f)}'`, field, op, opVal),
17✔
160
      (field, val) => `elem->>'${this.escapeJsonKey(field)}' = ${this.addValue(ctx.values, val)}`,
1✔
161
    );
162

163
    return `EXISTS (SELECT 1 FROM jsonb_array_elements(${jsonField}) AS elem WHERE ${conditions.join(' AND ')})`;
8✔
164
  }
165

166
  protected override get regexpOp(): string {
167
    return '~';
2✔
168
  }
169

170
  protected override ilikeExpr(f: string, ph: string): string {
171
    return `${f} ILIKE ${ph}`;
13✔
172
  }
173

174
  protected override get neOp(): string {
175
    return 'IS DISTINCT FROM';
7✔
176
  }
177

178
  protected override formatIn(ctx: QueryContext, values: unknown[], negate: boolean): string {
179
    if (values.length === 0) return negate ? ' NOT IN (NULL)' : ' IN (NULL)';
103!
180
    const ph = this.addValue(ctx.values, values);
103✔
181
    return negate ? ` <> ALL(${ph})` : ` = ANY(${ph})`;
103✔
182
  }
183

184
  protected override numericCast(expr: string): string {
185
    return `(${expr})::numeric`;
7✔
186
  }
187

188
  protected override formatPersistableValue<E>(ctx: QueryContext, field: FieldOptions, value: unknown): void {
189
    if (value instanceof QueryRaw) {
430✔
190
      super.formatPersistableValue(ctx, field, value);
1✔
191
      return;
1✔
192
    }
193
    if (isJsonType(field.type)) {
429✔
194
      ctx.append(this.jsonVal(ctx, value, field.type as JsonColumnType));
4✔
195
      return;
4✔
196
    }
197
    if (field.type === 'vector' && Array.isArray(value)) {
425✔
198
      ctx.addValue(`[${value.join(',')}]`);
15✔
199
      ctx.append('::vector');
15✔
200
      return;
15✔
201
    }
202
    super.formatPersistableValue(ctx, field, value);
410✔
203
  }
204

205
  /** pgvector distance operators. */
206
  private static readonly VECTOR_OPS: Record<VectorDistance, string> = {
37✔
207
    cosine: '<=>',
208
    l2: '<->',
209
    inner: '<#>',
210
    l1: '<+>',
211
    hamming: '<~>',
212
  };
213

214
  /** Emit a pgvector distance expression: `"col" <op> $N::<vectorType>`. */
215
  protected override appendVectorSort<E>(
216
    ctx: QueryContext,
217
    meta: EntityMeta<E>,
218
    key: string,
219
    search: QueryVectorSearch,
220
  ): void {
221
    const { colName, distance, vectorCast } = this.resolveVectorSortParams(meta, key, search);
13✔
222
    const op = PostgresDialect.VECTOR_OPS[distance];
13✔
223
    ctx.append(`${this.escapeId(colName)} ${op} `);
13✔
224
    ctx.addValue(`[${search.$vector.join(',')}]`);
13✔
225
    ctx.append(`::${vectorCast}`);
13✔
226
  }
227

228
  protected override formatJsonUpdate<E>(ctx: QueryContext, escapedCol: string, value: JsonUpdateOp<E>): void {
229
    let expr = escapedCol;
17✔
230
    if (hasKeys(value.$merge)) {
17✔
231
      expr = `COALESCE(${escapedCol}, '{}'::jsonb) || ${this.jsonVal(ctx, value.$merge)}`;
8✔
232
    }
233
    if (hasKeys(value.$push)) {
17✔
234
      const push = value.$push as Record<string, unknown>;
10✔
235
      for (const [key, v] of Object.entries(push)) {
10✔
236
        const currentExpr = expr;
10✔
237
        const ph = this.jsonVal(ctx, v);
10✔
238
        expr = `jsonb_set(${currentExpr}, '{${this.escapeJsonKey(key)}}', COALESCE((${currentExpr})->'${this.escapeJsonKey(
10✔
239
          key,
240
        )}', '[]'::jsonb) || jsonb_build_array(${ph}))`;
241
      }
242
    }
243
    if (value.$unset?.length) {
17✔
244
      for (const key of value.$unset) {
6✔
245
        expr = `(${expr}) - '${this.escapeJsonKey(key)}'`;
7✔
246
      }
247
    }
248
    ctx.append(`${escapedCol} = ${expr}`);
17✔
249
  }
250

251
  /**
252
   * Helper to add a JSON value to context with appropriate stringification and cast.
253
   */
254
  private jsonVal(ctx: QueryContext, value: unknown, type: JsonColumnType = 'jsonb'): string {
25✔
255
    if (value instanceof QueryRaw) return this.addValue(ctx.values, value);
25!
256
    if (value == null) return `${this.addValue(ctx.values, null)}::${type}`;
25!
257

258
    const json = JSON.stringify(value);
25✔
259
    const ph = this.addValue(ctx.values, json);
25✔
260
    return this.features.explicitJsonCast ? `(${ph}::text)::${type}` : `${ph}::${type}`;
25✔
261
  }
262

263
  override escape(value: unknown): string {
264
    return escapeAnsiSqlLiteral(value);
1✔
265
  }
266
}
267

268
/**
269
 * Converts a JS array to a Postgres array literal string: `{"val1","val2"}`.
270
 * Safely handles nesting and escaping of special characters.
271
 */
272
function toPgArray(arr: unknown[]): string {
273
  const elements = arr.map((val) => {
4✔
274
    if (val == null) return 'NULL';
9✔
275
    if (Array.isArray(val)) return toPgArray(val);
8✔
276
    if (typeof val === 'boolean') return val ? 'true' : 'false';
7!
277
    if (val instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(val))) {
7!
278
      const hex = Array.from(val)
×
279
        .map((b) => b.toString(16).padStart(2, '0'))
×
280
        .join('');
281
      return `"\\\\x${hex}"`;
×
282
    }
283
    const str = String(val);
7✔
284
    const escaped = str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
7✔
285
    return `"${escaped}"`;
7✔
286
  });
287
  return `{${elements.join(',')}}`;
4✔
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc