• 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

87.5
/packages/uql-orm/src/dialect/mysqlLikeSqlDialect.ts
1
import SqlString from 'sqlstring';
2
import type { DialectFeatures, QueryContext, QuerySizeComparisonOps } from '../type/index.js';
3
import type { DialectOptions } from './abstractDialect.js';
4
import { AbstractSqlDialect } from './abstractSqlDialect.js';
5
import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
6

7
/**
8
 * Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
9
 *
10
 * Both dialects support the MySQL-compatible JSON functions/operators used by:
11
 * - `$size` (JSON_LENGTH)
12
 * - `$all` (JSON_CONTAINS)
13
 * - `$elemMatch` (JSON_TABLE, or fast JSON_CONTAINS for the simple case)
14
 */
15
export abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
16
  /** Default {@link DialectFeatures} for MySQL-compatible SQL dialects. */
17
  static readonly defaultDialectFeatures: DialectFeatures = {
37✔
18
    explicitJsonCast: false,
19
    nativeArrays: false,
20
    supportsJsonb: false,
21
    returning: false,
22
    ifNotExists: true,
23
    indexIfNotExists: false,
24
    dropTableCascade: false,
25
    renameColumn: true,
26
    foreignKeyAlter: true,
27
    columnComment: true,
28
    vectorIndexStyle: 'inline',
29
    vectorSupportsLength: false,
30
    supportsTimestamptz: false,
31
    defaultStringAsText: false,
32
  };
33

34
  override readonly quoteChar = '`';
73✔
35

36
  override readonly tableOptions = 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4';
73✔
37

38
  override readonly beginTransactionCommand = 'START TRANSACTION';
73✔
39

40
  override readonly commitTransactionCommand = 'COMMIT';
73✔
41

42
  override readonly rollbackTransactionCommand = 'ROLLBACK';
73✔
43

44
  override readonly isolationLevelStrategy = 'set-before';
73✔
45

46
  override readonly dropForeignKeySyntax = 'DROP FOREIGN KEY';
73✔
47

48
  override readonly dropIndexSyntax = 'on-table';
73✔
49

50
  override readonly renameTableSyntax = 'rename-table';
73✔
51

52
  override readonly alterColumnSyntax = 'MODIFY COLUMN';
73✔
53

54
  override readonly booleanLiteral = 'integer';
73✔
55

56
  override readonly insertIdStrategy = 'first';
73✔
57

58
  constructor(options: DialectOptions = {}) {
73✔
59
    super(MysqlLikeSqlDialect.defaultDialectFeatures, options);
73✔
60
  }
61

62
  override escape(value: unknown): string {
63
    return SqlString.escape(value);
2✔
64
  }
65

66
  protected override numericCast(expr: string): string {
67
    return `CAST(${expr} AS DECIMAL)`;
8✔
68
  }
69

70
  protected override ilikeExpr(f: string, ph: string): string {
71
    return `${f} LIKE ${ph}`;
30✔
72
  }
73

74
  protected override neExpr(field: string, ph: string): string {
75
    // MySQL/MariaDB null-safe inequality: true when values differ or one side is NULL.
76
    return `NOT (${field} <=> ${ph})`;
32✔
77
  }
78

79
  protected override jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string {
80
    return `JSON_CONTAINS(${jsonField}, ${this.addValue(ctx.values, JSON.stringify(value))})`;
2✔
81
  }
82

83
  protected override jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string {
84
    const tmpCtx = this.createContext();
8✔
85
    this.buildSizeComparison(tmpCtx, () => tmpCtx.append(`JSON_LENGTH(${jsonField})`), value);
10✔
86
    ctx.pushValue(...tmpCtx.values);
8✔
87
    return tmpCtx.sql;
8✔
88
  }
89

90
  protected override jsonElemMatch(ctx: QueryContext, jsonField: string, match: Record<string, unknown>): string {
91
    const isPrimitiveElement = Object.keys(match).some((k) => k.startsWith('$'));
38✔
92

93
    if (isPrimitiveElement) {
8!
94
      const ops = Object.entries(match);
×
95
      const conditions = ops.map(([op, opVal]) =>
×
96
        this.buildJsonFieldCondition(ctx, () => 'jt.elem_text', '', op, opVal),
×
97
      );
98
      return `EXISTS (SELECT 1 FROM JSON_TABLE(${jsonField}, '$[*]' COLUMNS (elem_text TEXT PATH '$')) AS jt WHERE ${conditions.join(
×
99
        ' AND ',
100
      )})`;
101
    }
102

103
    const hasOperators = Object.values(match).some(
8✔
104
      (v) => v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).some((k) => k.startsWith('$')),
8✔
105
    );
106

107
    if (!hasOperators) {
8✔
108
      return `JSON_CONTAINS(${jsonField}, ${this.addValue(ctx.values, JSON.stringify([match]))})`;
2✔
109
    }
110

111
    const fields = Object.keys(match);
6✔
112
    const columns = fields.map((f) => `${this.escapeId(f, true)} TEXT PATH '$.${this.escapeJsonKey(f)}'`).join(', ');
36✔
113

114
    const conditions = buildElemMatchConditions(
6✔
115
      match,
116
      (field, op, opVal) => this.buildJsonFieldCondition(ctx, (f) => `jt.${this.escapeId(f, true)}`, field, op, opVal),
36✔
117
      (field, val) => `jt.${this.escapeId(field, true)} = ${this.addValue(ctx.values, val)}`,
×
118
    );
119

120
    return `EXISTS (SELECT 1 FROM JSON_TABLE(${jsonField}, '$[*]' COLUMNS (${columns})) AS jt WHERE ${conditions.join(
6✔
121
      ' AND ',
122
    )})`;
123
  }
124
}
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