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

rogerpadilla / nukak / #520

29 Dec 2025 12:41PM UTC coverage: 86.536% (-10.7%) from 97.192%
#520

push

web-flow
Merge pull request #70 from rogerpadilla/feat/migrations-issue-38

feat: add support for database migrations

669 of 915 branches covered (73.11%)

Branch coverage included in aggregate %.

135 of 276 new or added lines in 9 files covered. (48.91%)

56 existing lines in 6 files now uncovered.

2294 of 2509 relevant lines covered (91.43%)

146.06 hits per line

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

78.0
/packages/postgres/src/postgresDialect.ts
1
import { AbstractSqlDialect } from 'nukak/dialect';
1✔
2
import { getMeta } from 'nukak/entity';
1✔
3
import {
4
  type FieldKey,
5
  type FieldOptions,
6
  type QueryComparisonOptions,
7
  type QueryConflictPaths,
8
  type QueryContext,
9
  type QueryOptions,
10
  QueryRaw,
11
  type QueryTextSearchOptions,
12
  type QueryWhereFieldOperatorMap,
13
  type QueryWhereMap,
14
  type Type,
15
} from 'nukak/type';
1✔
16
import sqlstring from 'sqlstring-sqlite';
1✔
17

18
export class PostgresDialect extends AbstractSqlDialect {
1✔
19
  constructor() {
20
    super('"', 'BEGIN TRANSACTION');
1✔
21
  }
22

23
  override addValue(values: unknown[], value: unknown): string {
24
    values.push(value);
63✔
25
    return this.placeholder(values.length);
63✔
26
  }
27

28
  override placeholder(index: number): string {
29
    return `$${index}`;
63✔
30
  }
31

32
  override insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void {
33
    super.insert(ctx, entity, payload, opts);
3✔
34
    ctx.append(' ' + this.returningId(entity));
3✔
35
  }
36

37
  override upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): void {
38
    const meta = getMeta(entity);
8✔
39
    const update = this.getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, (name) => `EXCLUDED.${name}`);
11✔
40
    const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
8✔
41
    const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
8✔
42
    super.insert(ctx, entity, payload);
8✔
43
    ctx.append(` ON CONFLICT (${keysStr}) ${onConflict} ${this.returningId(entity)}`);
8✔
44
  }
45

46
  override compare<E, K extends keyof QueryWhereMap<E>>(
47
    ctx: QueryContext,
48
    entity: Type<E>,
49
    key: K,
50
    val: QueryWhereMap<E>[K],
51
    opts: QueryComparisonOptions = {},
×
52
  ): void {
53
    if (key === '$text') {
16✔
54
      const meta = getMeta(entity);
2✔
55
      const search = val as QueryTextSearchOptions<E>;
2✔
56
      const fields = search.$fields
2✔
57
        .map((field) => this.escapeId(meta.fields[field]?.name ?? field))
3!
58
        .join(` || ' ' || `);
59
      ctx.append(`to_tsvector(${fields}) @@ to_tsquery(`);
2✔
60
      ctx.addValue(search.$value);
2✔
61
      ctx.append(')');
2✔
62
      return;
2✔
63
    }
64
    super.compare(ctx, entity, key, val, opts);
14✔
65
  }
66

67
  override compareFieldOperator<E, K extends keyof QueryWhereFieldOperatorMap<E>>(
68
    ctx: QueryContext,
69
    entity: Type<E>,
70
    key: FieldKey<E>,
71
    op: K,
72
    val: QueryWhereFieldOperatorMap<E>[K],
73
    opts: QueryOptions = {},
×
74
  ): void {
75
    switch (op) {
18!
76
      case '$istartsWith':
77
        this.getComparisonKey(ctx, entity, key, opts);
2✔
78
        ctx.append(' ILIKE ');
2✔
79
        ctx.addValue(`${val}%`);
2✔
80
        break;
2✔
81
      case '$iendsWith':
82
        this.getComparisonKey(ctx, entity, key, opts);
2✔
83
        ctx.append(' ILIKE ');
2✔
84
        ctx.addValue(`%${val}`);
2✔
85
        break;
2✔
86
      case '$iincludes':
87
        this.getComparisonKey(ctx, entity, key, opts);
2✔
88
        ctx.append(' ILIKE ');
2✔
89
        ctx.addValue(`%${val}%`);
2✔
90
        break;
2✔
91
      case '$ilike':
92
        this.getComparisonKey(ctx, entity, key, opts);
2✔
93
        ctx.append(' ILIKE ');
2✔
94
        ctx.addValue(val);
2✔
95
        break;
2✔
96
      case '$in':
UNCOV
97
        this.getComparisonKey(ctx, entity, key, opts);
×
UNCOV
98
        ctx.append(' = ANY(');
×
UNCOV
99
        ctx.addValue(val);
×
UNCOV
100
        ctx.append(')');
×
UNCOV
101
        break;
×
102
      case '$nin':
UNCOV
103
        this.getComparisonKey(ctx, entity, key, opts);
×
UNCOV
104
        ctx.append(' <> ALL(');
×
UNCOV
105
        ctx.addValue(val);
×
UNCOV
106
        ctx.append(')');
×
UNCOV
107
        break;
×
108
      case '$regex':
109
        this.getComparisonKey(ctx, entity, key, opts);
1✔
110
        ctx.append(' ~ ');
1✔
111
        ctx.addValue(val);
1✔
112
        break;
1✔
113
      default:
114
        super.compareFieldOperator(ctx, entity, key, op, val, opts);
9✔
115
    }
116
  }
117

118
  protected override formatPersistableValue<E>(ctx: QueryContext, field: FieldOptions, value: unknown): void {
119
    if (value instanceof QueryRaw) {
44✔
120
      super.formatPersistableValue(ctx, field, value);
1✔
121
      return;
1✔
122
    }
123
    if (field.type === 'json' || field.type === 'jsonb') {
43✔
124
      ctx.addValue(value ? JSON.stringify(value) : null);
1!
125
      ctx.append(`::${field.type}`);
1✔
126
      return;
1✔
127
    }
128
    if (field.type === 'vector' && Array.isArray(value)) {
42!
UNCOV
129
      ctx.addValue(`[${value.join(',')}]`);
×
UNCOV
130
      ctx.append('::vector');
×
UNCOV
131
      return;
×
132
    }
133
    super.formatPersistableValue(ctx, field, value);
42✔
134
  }
135

136
  override escape(value: unknown): string {
UNCOV
137
    return sqlstring.escape(value);
×
138
  }
139
}
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

© 2025 Coveralls, Inc