-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
dialect-aware-fn.ts
113 lines (94 loc) · 3.32 KB
/
dialect-aware-fn.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import type { Class } from 'type-fest';
import type { AbstractDialect } from '../abstract-dialect/dialect.js';
import type { EscapeOptions } from '../abstract-dialect/query-generator-typescript.js';
import type { Expression } from '../sequelize.js';
import { BaseSqlExpression } from './base-sql-expression.js';
import { JsonPath } from './json-path.js';
/**
* Unlike {@link Fn}, this class does not accept a function name.
* It must instead be extended by a class that implements the {@link applyForDialect} method, in which
* the function name is provided.
*
* The goal of this class is to allow dialect-specific functions to be used in a cross-dialect way.
* For instance, an extension of this class could be used to represent the `LOWER` function in a cross-dialect way,
* by generating the correct SQL function name based on which dialect is used.
*/
export abstract class DialectAwareFn extends BaseSqlExpression {
readonly args: readonly Expression[];
constructor(...args: DialectAwareFn['args']) {
super();
this.args = args;
if (this.args.length > this.maxArgCount) {
throw new Error(
`Too many arguments provided to ${this.constructor.name} function. Expected ${this.maxArgCount} or less, but got ${this.args.length}.`,
);
}
if (this.args.length < this.minArgCount) {
throw new Error(
`Too few arguments provided to ${this.constructor.name} function. Expected ${this.minArgCount} or more, but got ${this.args.length}.`,
);
}
}
get maxArgCount() {
return Number.POSITIVE_INFINITY;
}
get minArgCount() {
return 0;
}
abstract supportsDialect(dialect: AbstractDialect): boolean;
abstract applyForDialect(dialect: AbstractDialect, options?: EscapeOptions): string;
supportsJavaScript(): boolean {
return false;
}
applyForJavaScript(): unknown {
throw new Error(`JavaScript is not supported by the ${this.constructor.name} function.`);
}
/**
* This getter is designed to be used as an attribute's default value.
* This is useful when the SQL version must be bypassed due to a limitation of the dialect that Sequelize cannot detect,
* such as a missing extension.
*
* ```ts
* const User = sequelize.define('User', {
* uuid: {
* type: DataTypes.UUID,
* defaultValue: sql.uuidV4.asJavaScript,
* },
* });
* ```
*/
get asJavaScript(): () => unknown {
if (!this.supportsJavaScript()) {
throw new Error(`JavaScript is not supported by the ${this.constructor.name} function.`);
}
return () => this.applyForJavaScript();
}
static build<M extends DialectAwareFn>(this: Class<M>, ...args: DialectAwareFn['args']): M {
return new this(...args);
}
}
/**
* Unquotes JSON values.
*/
export class Unquote extends DialectAwareFn {
get maxArgCount() {
return 1;
}
get minArgCount() {
return 1;
}
supportsDialect(dialect: AbstractDialect): boolean {
return dialect.supports.jsonOperations;
}
applyForDialect(dialect: AbstractDialect, options?: EscapeOptions): string {
const arg = this.args[0];
if (arg instanceof JsonPath) {
return dialect.queryGenerator.jsonPathExtractionQuery(
dialect.queryGenerator.escape(arg.expression),
arg.path,
true,
);
}
return dialect.queryGenerator.formatUnquoteJson(arg, options);
}
}