pqb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3630 -0
- package/dist/index.esm.js +4587 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +4691 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/rollup.config.js +35 -0
- package/src/adapter.test.ts +10 -0
- package/src/adapter.ts +171 -0
- package/src/columnSchema/array.ts +21 -0
- package/src/columnSchema/boolean.ts +10 -0
- package/src/columnSchema/columnType.test.ts +129 -0
- package/src/columnSchema/columnType.ts +77 -0
- package/src/columnSchema/columnTypes.ts +145 -0
- package/src/columnSchema/columnsSchema.test.ts +32 -0
- package/src/columnSchema/columnsSchema.ts +100 -0
- package/src/columnSchema/commonMethods.ts +130 -0
- package/src/columnSchema/dateTime.ts +104 -0
- package/src/columnSchema/enum.ts +13 -0
- package/src/columnSchema/index.ts +11 -0
- package/src/columnSchema/json/array.ts +55 -0
- package/src/columnSchema/json/discriminatedUnion.ts +91 -0
- package/src/columnSchema/json/enum.ts +29 -0
- package/src/columnSchema/json/instanceOf.ts +16 -0
- package/src/columnSchema/json/intersection.ts +23 -0
- package/src/columnSchema/json/lazy.ts +22 -0
- package/src/columnSchema/json/literal.ts +12 -0
- package/src/columnSchema/json/map.ts +29 -0
- package/src/columnSchema/json/nativeEnum.ts +30 -0
- package/src/columnSchema/json/nullable.ts +33 -0
- package/src/columnSchema/json/nullish.ts +30 -0
- package/src/columnSchema/json/object.ts +206 -0
- package/src/columnSchema/json/optional.ts +28 -0
- package/src/columnSchema/json/record.ts +40 -0
- package/src/columnSchema/json/scalarTypes.ts +117 -0
- package/src/columnSchema/json/set.ts +34 -0
- package/src/columnSchema/json/tuple.ts +40 -0
- package/src/columnSchema/json/typeBase.ts +202 -0
- package/src/columnSchema/json/union.ts +16 -0
- package/src/columnSchema/json.ts +64 -0
- package/src/columnSchema/number.ts +122 -0
- package/src/columnSchema/string.ts +222 -0
- package/src/columnSchema/utils.ts +27 -0
- package/src/common.ts +86 -0
- package/src/db.test.ts +67 -0
- package/src/db.ts +212 -0
- package/src/errors.ts +7 -0
- package/src/index.ts +18 -0
- package/src/operators.test.ts +608 -0
- package/src/operators.ts +177 -0
- package/src/query.ts +292 -0
- package/src/queryDataUtils.ts +50 -0
- package/src/queryMethods/aggregate.test.ts +583 -0
- package/src/queryMethods/aggregate.ts +878 -0
- package/src/queryMethods/callbacks.test.ts +69 -0
- package/src/queryMethods/callbacks.ts +55 -0
- package/src/queryMethods/clear.test.ts +64 -0
- package/src/queryMethods/clear.ts +58 -0
- package/src/queryMethods/columnInfo.test.ts +45 -0
- package/src/queryMethods/columnInfo.ts +67 -0
- package/src/queryMethods/delete.test.ts +135 -0
- package/src/queryMethods/delete.ts +50 -0
- package/src/queryMethods/for.test.ts +57 -0
- package/src/queryMethods/for.ts +99 -0
- package/src/queryMethods/from.test.ts +66 -0
- package/src/queryMethods/from.ts +58 -0
- package/src/queryMethods/get.test.ts +66 -0
- package/src/queryMethods/get.ts +88 -0
- package/src/queryMethods/having.test.ts +247 -0
- package/src/queryMethods/having.ts +99 -0
- package/src/queryMethods/insert.test.ts +555 -0
- package/src/queryMethods/insert.ts +453 -0
- package/src/queryMethods/join.test.ts +150 -0
- package/src/queryMethods/join.ts +508 -0
- package/src/queryMethods/json.test.ts +398 -0
- package/src/queryMethods/json.ts +259 -0
- package/src/queryMethods/log.test.ts +172 -0
- package/src/queryMethods/log.ts +123 -0
- package/src/queryMethods/queryMethods.test.ts +629 -0
- package/src/queryMethods/queryMethods.ts +428 -0
- package/src/queryMethods/select.test.ts +479 -0
- package/src/queryMethods/select.ts +249 -0
- package/src/queryMethods/then.ts +236 -0
- package/src/queryMethods/transaction.test.ts +66 -0
- package/src/queryMethods/transaction.ts +66 -0
- package/src/queryMethods/union.test.ts +59 -0
- package/src/queryMethods/union.ts +89 -0
- package/src/queryMethods/update.test.ts +417 -0
- package/src/queryMethods/update.ts +350 -0
- package/src/queryMethods/upsert.test.ts +56 -0
- package/src/queryMethods/upsert.ts +43 -0
- package/src/queryMethods/where.test.ts +1594 -0
- package/src/queryMethods/where.ts +450 -0
- package/src/queryMethods/window.test.ts +66 -0
- package/src/queryMethods/window.ts +108 -0
- package/src/queryMethods/with.test.ts +191 -0
- package/src/queryMethods/with.ts +92 -0
- package/src/quote.ts +36 -0
- package/src/relations.ts +194 -0
- package/src/sql/aggregate.ts +80 -0
- package/src/sql/columnInfo.ts +22 -0
- package/src/sql/common.ts +42 -0
- package/src/sql/delete.ts +41 -0
- package/src/sql/distinct.ts +19 -0
- package/src/sql/fromAndAs.ts +51 -0
- package/src/sql/having.ts +140 -0
- package/src/sql/index.ts +2 -0
- package/src/sql/insert.ts +102 -0
- package/src/sql/join.ts +242 -0
- package/src/sql/orderBy.ts +41 -0
- package/src/sql/select.ts +153 -0
- package/src/sql/toSql.ts +153 -0
- package/src/sql/truncate.ts +13 -0
- package/src/sql/types.ts +355 -0
- package/src/sql/update.ts +62 -0
- package/src/sql/where.ts +314 -0
- package/src/sql/window.ts +38 -0
- package/src/sql/with.ts +32 -0
- package/src/test-utils.ts +172 -0
- package/src/utils.ts +140 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { WithOptions } from '../sql';
|
|
2
|
+
import { expectQueryNotMutated, expectSql, User } from '../test-utils';
|
|
3
|
+
import { columnTypes, NumberColumn } from '../columnSchema';
|
|
4
|
+
import { raw } from '../common';
|
|
5
|
+
|
|
6
|
+
describe('with', () => {
|
|
7
|
+
const options: (
|
|
8
|
+
| undefined
|
|
9
|
+
| (Omit<WithOptions, 'columns'> & { columns?: boolean | string[] })
|
|
10
|
+
)[] = [
|
|
11
|
+
undefined,
|
|
12
|
+
{ columns: true },
|
|
13
|
+
{ columns: ['custom', 'columns', 'list'] },
|
|
14
|
+
{ recursive: true },
|
|
15
|
+
{ materialized: true },
|
|
16
|
+
{ notMaterialized: true },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const getExpectedWithSql = (
|
|
20
|
+
sql: string,
|
|
21
|
+
columns: string[],
|
|
22
|
+
opts: typeof options[number],
|
|
23
|
+
) => {
|
|
24
|
+
return `
|
|
25
|
+
WITH${opts?.recursive ? ' RECURSIVE' : ''} "withAlias"${
|
|
26
|
+
opts?.columns
|
|
27
|
+
? `(${(opts.columns === true ? columns : opts.columns)
|
|
28
|
+
.map((column) => `"${column}"`)
|
|
29
|
+
.join(', ')})`
|
|
30
|
+
: ''
|
|
31
|
+
} AS ${
|
|
32
|
+
opts?.materialized
|
|
33
|
+
? 'MATERIALIZED '
|
|
34
|
+
: opts?.notMaterialized
|
|
35
|
+
? 'NOT MATERIALIZED '
|
|
36
|
+
: ''
|
|
37
|
+
} (
|
|
38
|
+
${sql}
|
|
39
|
+
)
|
|
40
|
+
SELECT *
|
|
41
|
+
FROM "withAlias"
|
|
42
|
+
`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const columnShape = { one: columnTypes.integer(), two: columnTypes.text() };
|
|
46
|
+
|
|
47
|
+
it('accepts raw parameter preceded by columns shape', () => {
|
|
48
|
+
const q = User.all();
|
|
49
|
+
|
|
50
|
+
options.forEach((options) => {
|
|
51
|
+
const args: Parameters<typeof q.with> = [
|
|
52
|
+
'withAlias',
|
|
53
|
+
columnShape,
|
|
54
|
+
raw(`(VALUES (1, 'two')) t(one, two)`),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
if (options) {
|
|
58
|
+
(args as unknown[]).splice(1, 0, options);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
expectSql(
|
|
62
|
+
q
|
|
63
|
+
.with(...args)
|
|
64
|
+
.from('withAlias')
|
|
65
|
+
.toSql(),
|
|
66
|
+
getExpectedWithSql(
|
|
67
|
+
`(VALUES (1, 'two')) t(one, two)`,
|
|
68
|
+
['one', 'two'],
|
|
69
|
+
options,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expectQueryNotMutated(q);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('accepts query', () => {
|
|
78
|
+
const q = User.all();
|
|
79
|
+
|
|
80
|
+
options.forEach((options) => {
|
|
81
|
+
const args: Parameters<typeof q.with> = ['withAlias', User.all()];
|
|
82
|
+
|
|
83
|
+
if (options) {
|
|
84
|
+
(args as unknown[]).splice(1, 0, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
expectSql(
|
|
88
|
+
q
|
|
89
|
+
.with(...args)
|
|
90
|
+
.from('withAlias')
|
|
91
|
+
.toSql(),
|
|
92
|
+
getExpectedWithSql(
|
|
93
|
+
'SELECT * FROM "user"',
|
|
94
|
+
Object.keys(User.shape),
|
|
95
|
+
options,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expectQueryNotMutated(q);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('accepts callback for query builder', () => {
|
|
104
|
+
const q = User.all();
|
|
105
|
+
|
|
106
|
+
options.forEach((options) => {
|
|
107
|
+
const args: Parameters<typeof q.with> = [
|
|
108
|
+
'withAlias',
|
|
109
|
+
(qb) => qb.select({ one: raw<NumberColumn>('1') }),
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
if (options) {
|
|
113
|
+
(args as unknown[]).splice(1, 0, options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expectSql(
|
|
117
|
+
q
|
|
118
|
+
.with(...args)
|
|
119
|
+
.from('withAlias')
|
|
120
|
+
.toSql(),
|
|
121
|
+
getExpectedWithSql(
|
|
122
|
+
`SELECT 1 AS "one"`,
|
|
123
|
+
// columns: true will produce empty columns list because there is no way to get it from query builder result
|
|
124
|
+
[],
|
|
125
|
+
options,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expectQueryNotMutated(q);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('can be referenced in .join', () => {
|
|
134
|
+
const q = User.all();
|
|
135
|
+
|
|
136
|
+
const received1 = q
|
|
137
|
+
.with('withAlias', User.all())
|
|
138
|
+
.join('withAlias', 'id', '=', 'user.id')
|
|
139
|
+
.select('withAlias.id')
|
|
140
|
+
.toSql();
|
|
141
|
+
|
|
142
|
+
const received2 = q
|
|
143
|
+
.with('withAlias', User.all())
|
|
144
|
+
.join('withAlias', 'withAlias.id', '=', 'user.id')
|
|
145
|
+
.select('withAlias.id')
|
|
146
|
+
.toSql();
|
|
147
|
+
|
|
148
|
+
const received3 = q
|
|
149
|
+
.with('withAlias', User.all())
|
|
150
|
+
.join('withAlias', raw(`"withAlias"."id" = "user"."id"`))
|
|
151
|
+
.select('withAlias.id')
|
|
152
|
+
.toSql();
|
|
153
|
+
|
|
154
|
+
const received4 = q
|
|
155
|
+
.with('withAlias', User.all())
|
|
156
|
+
.join('withAlias', (qb) => qb.on('withAlias.id', '=', 'user.id'))
|
|
157
|
+
.select('withAlias.id')
|
|
158
|
+
.toSql();
|
|
159
|
+
|
|
160
|
+
const expected = `
|
|
161
|
+
WITH "withAlias" AS (
|
|
162
|
+
SELECT * FROM "user"
|
|
163
|
+
)
|
|
164
|
+
SELECT "withAlias"."id" FROM "user"
|
|
165
|
+
JOIN "withAlias" ON "withAlias"."id" = "user"."id"
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
expectSql(received1, expected);
|
|
169
|
+
expectSql(received2, expected);
|
|
170
|
+
expectSql(received3, expected);
|
|
171
|
+
expectSql(received4, expected);
|
|
172
|
+
|
|
173
|
+
expectQueryNotMutated(q);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('can be used in .from', () => {
|
|
177
|
+
const q = User.all();
|
|
178
|
+
|
|
179
|
+
expectSql(
|
|
180
|
+
q.with('withAlias', User.all()).from('withAlias').select('id').toSql(),
|
|
181
|
+
`
|
|
182
|
+
WITH "withAlias" AS (
|
|
183
|
+
SELECT * FROM "user"
|
|
184
|
+
)
|
|
185
|
+
SELECT "withAlias"."id" FROM "withAlias"
|
|
186
|
+
`,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expectQueryNotMutated(q);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { WithOptions } from '../sql';
|
|
2
|
+
import { ColumnShapeOutput, ColumnsShape, ColumnTypes } from '../columnSchema';
|
|
3
|
+
import { EMPTY_OBJECT, isRaw, RawExpression } from '../common';
|
|
4
|
+
import { AddQueryWith, Query } from '../query';
|
|
5
|
+
import { Db } from '../db';
|
|
6
|
+
import { pushQueryValue, setQueryObjectValue } from '../queryDataUtils';
|
|
7
|
+
|
|
8
|
+
type WithArgsOptions = Omit<WithOptions, 'columns'> & {
|
|
9
|
+
columns?: boolean | string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type WithArgs =
|
|
13
|
+
| [string, ColumnsShape, RawExpression]
|
|
14
|
+
| [string, WithArgsOptions, ColumnsShape, RawExpression]
|
|
15
|
+
| [string, Query | ((qb: Db) => Query)]
|
|
16
|
+
| [string, WithArgsOptions, Query | ((qb: Db) => Query)];
|
|
17
|
+
|
|
18
|
+
type WithShape<Args extends WithArgs> = Args[1] extends Query
|
|
19
|
+
? Args[1]['result']
|
|
20
|
+
: Args[1] extends (qb: Db) => Query
|
|
21
|
+
? ReturnType<Args[1]>['result']
|
|
22
|
+
: Args[2] extends Query
|
|
23
|
+
? Args[2]['result']
|
|
24
|
+
: Args[2] extends (qb: Db) => Query
|
|
25
|
+
? ReturnType<Args[2]>['result']
|
|
26
|
+
: Args[1] extends ColumnsShape
|
|
27
|
+
? Args[1]
|
|
28
|
+
: Args[2] extends ColumnsShape
|
|
29
|
+
? Args[2]
|
|
30
|
+
: Args[2] extends (t: ColumnTypes) => ColumnsShape
|
|
31
|
+
? ReturnType<Args[2]>
|
|
32
|
+
: never;
|
|
33
|
+
|
|
34
|
+
type WithResult<
|
|
35
|
+
T extends Query,
|
|
36
|
+
Args extends WithArgs,
|
|
37
|
+
Shape extends ColumnsShape,
|
|
38
|
+
> = AddQueryWith<
|
|
39
|
+
T,
|
|
40
|
+
{
|
|
41
|
+
table: Args[0];
|
|
42
|
+
shape: Shape;
|
|
43
|
+
type: ColumnShapeOutput<Shape>;
|
|
44
|
+
}
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
export class With {
|
|
48
|
+
with<
|
|
49
|
+
T extends Query,
|
|
50
|
+
Args extends WithArgs,
|
|
51
|
+
Shape extends ColumnsShape = WithShape<Args>,
|
|
52
|
+
>(this: T, ...args: Args): WithResult<T, Args, Shape> {
|
|
53
|
+
return this.clone()._with<T, Args, Shape>(...args);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_with<
|
|
57
|
+
T extends Query,
|
|
58
|
+
Args extends WithArgs,
|
|
59
|
+
Shape extends ColumnsShape = WithShape<Args>,
|
|
60
|
+
>(this: T, ...args: Args): WithResult<T, Args, Shape> {
|
|
61
|
+
let options =
|
|
62
|
+
(args.length === 3 && !isRaw(args[2])) || args.length === 4
|
|
63
|
+
? (args[1] as WithArgsOptions | WithOptions)
|
|
64
|
+
: undefined;
|
|
65
|
+
|
|
66
|
+
const last = args[args.length - 1] as
|
|
67
|
+
| Query
|
|
68
|
+
| ((qb: Db) => Query)
|
|
69
|
+
| RawExpression;
|
|
70
|
+
|
|
71
|
+
const query = typeof last === 'function' ? last(this.queryBuilder) : last;
|
|
72
|
+
|
|
73
|
+
const shape =
|
|
74
|
+
args.length === 4 ? (args[2] as ColumnsShape) : (query as Query).shape;
|
|
75
|
+
|
|
76
|
+
if (options?.columns === true) {
|
|
77
|
+
options = {
|
|
78
|
+
...options,
|
|
79
|
+
columns: Object.keys(shape),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pushQueryValue(this, 'with', [args[0], options || EMPTY_OBJECT, query]);
|
|
84
|
+
|
|
85
|
+
return setQueryObjectValue(
|
|
86
|
+
this,
|
|
87
|
+
'withShapes',
|
|
88
|
+
args[0],
|
|
89
|
+
shape,
|
|
90
|
+
) as unknown as WithResult<T, Args, Shape>;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/quote.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const singleQuoteRegex = /'/g;
|
|
2
|
+
const doubleQuoteRegex = /"/g;
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line
|
|
5
|
+
type Value = any
|
|
6
|
+
|
|
7
|
+
const quoteValue = (value: Value): string => {
|
|
8
|
+
const type = typeof value;
|
|
9
|
+
if (type === 'number') return String(value);
|
|
10
|
+
else if (type === 'string')
|
|
11
|
+
return `"${(value as string)
|
|
12
|
+
.replace(doubleQuoteRegex, '\\"')
|
|
13
|
+
.replace(singleQuoteRegex, "''")}"`;
|
|
14
|
+
else if (type === 'boolean') return value ? 'true' : 'false';
|
|
15
|
+
else if (value instanceof Date) return `"${value.toISOString()}"`;
|
|
16
|
+
else if (Array.isArray(value)) return quoteArray(value);
|
|
17
|
+
else if (type === null || type === undefined) return 'NULL';
|
|
18
|
+
else
|
|
19
|
+
return `"${JSON.stringify(value)
|
|
20
|
+
.replace(doubleQuoteRegex, '\\"')
|
|
21
|
+
.replace(singleQuoteRegex, "''")}"`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const quoteArray = (array: Value[]) => `'{${array.map(quoteValue).join(',')}}'`;
|
|
25
|
+
|
|
26
|
+
export const quote = (value: Value): string => {
|
|
27
|
+
const type = typeof value;
|
|
28
|
+
if (type === 'number') return `${value}`;
|
|
29
|
+
else if (type === 'string')
|
|
30
|
+
return `'${(value as string).replace(singleQuoteRegex, "''")}'`;
|
|
31
|
+
else if (type === 'boolean') return value ? 'true' : 'false';
|
|
32
|
+
else if (value instanceof Date) return `'${value.toISOString()}'`;
|
|
33
|
+
else if (Array.isArray(value)) return quoteArray(value);
|
|
34
|
+
else if (value === null || value === undefined) return 'NULL';
|
|
35
|
+
else return `'${JSON.stringify(value).replace(singleQuoteRegex, "''")}'`;
|
|
36
|
+
};
|
package/src/relations.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { defaultsKey, Query, QueryBase, QueryWithTable } from './query';
|
|
2
|
+
import { WhereArg } from './queryMethods/where';
|
|
3
|
+
import { MaybeArray } from './utils';
|
|
4
|
+
import { UpdateData } from './queryMethods/update';
|
|
5
|
+
|
|
6
|
+
export type NestedInsertOneItem = {
|
|
7
|
+
create?: Record<string, unknown>;
|
|
8
|
+
connect?: WhereArg<QueryBase>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type NestedInsertManyItems = {
|
|
12
|
+
create?: Record<string, unknown>[];
|
|
13
|
+
connect?: WhereArg<QueryBase>[];
|
|
14
|
+
connectOrCreate?: {
|
|
15
|
+
where: WhereArg<QueryBase>;
|
|
16
|
+
create: Record<string, unknown>;
|
|
17
|
+
}[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type NestedInsertItem = NestedInsertOneItem | NestedInsertManyItems;
|
|
21
|
+
|
|
22
|
+
export type BelongsToNestedInsert = (
|
|
23
|
+
query: Query,
|
|
24
|
+
relationData: NestedInsertOneItem[],
|
|
25
|
+
) => Promise<Record<string, unknown>[]>;
|
|
26
|
+
|
|
27
|
+
export type HasOneNestedInsert = (
|
|
28
|
+
query: Query,
|
|
29
|
+
data: [
|
|
30
|
+
selfData: Record<string, unknown>,
|
|
31
|
+
relationData: NestedInsertOneItem,
|
|
32
|
+
][],
|
|
33
|
+
) => Promise<void>;
|
|
34
|
+
|
|
35
|
+
export type HasManyNestedInsert = (
|
|
36
|
+
query: Query,
|
|
37
|
+
data: [
|
|
38
|
+
selfData: Record<string, unknown>,
|
|
39
|
+
relationData: NestedInsertManyItems,
|
|
40
|
+
][],
|
|
41
|
+
) => Promise<void>;
|
|
42
|
+
|
|
43
|
+
export type NestedUpdateOneItem = {
|
|
44
|
+
disconnect?: boolean;
|
|
45
|
+
set?: WhereArg<QueryBase>;
|
|
46
|
+
delete?: boolean;
|
|
47
|
+
update?: UpdateData<Query>;
|
|
48
|
+
upsert?: {
|
|
49
|
+
update: UpdateData<Query>;
|
|
50
|
+
create: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
create: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type NestedUpdateManyItems = {
|
|
56
|
+
disconnect?: MaybeArray<WhereArg<QueryBase>>;
|
|
57
|
+
set?: MaybeArray<WhereArg<QueryBase>>;
|
|
58
|
+
delete?: MaybeArray<WhereArg<QueryBase>>;
|
|
59
|
+
update?: {
|
|
60
|
+
where: MaybeArray<WhereArg<QueryBase>>;
|
|
61
|
+
data: UpdateData<Query>;
|
|
62
|
+
};
|
|
63
|
+
create: Record<string, unknown>[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type NestedUpdateItem = NestedUpdateOneItem | NestedUpdateManyItems;
|
|
67
|
+
|
|
68
|
+
export type BelongsToNestedUpdate = (
|
|
69
|
+
q: Query,
|
|
70
|
+
update: Record<string, unknown>,
|
|
71
|
+
params: NestedUpdateOneItem,
|
|
72
|
+
state: {
|
|
73
|
+
updateLater?: Record<string, unknown>;
|
|
74
|
+
updateLaterPromises?: Promise<void>[];
|
|
75
|
+
},
|
|
76
|
+
) => boolean;
|
|
77
|
+
|
|
78
|
+
export type HasOneNestedUpdate = (
|
|
79
|
+
query: Query,
|
|
80
|
+
data: Record<string, unknown>[],
|
|
81
|
+
relationData: NestedUpdateOneItem,
|
|
82
|
+
) => Promise<void>;
|
|
83
|
+
|
|
84
|
+
export type HasManyNestedUpdate = (
|
|
85
|
+
query: Query,
|
|
86
|
+
data: Record<string, unknown>[],
|
|
87
|
+
relationData: NestedUpdateManyItems,
|
|
88
|
+
) => Promise<void>;
|
|
89
|
+
|
|
90
|
+
export type BaseRelation = {
|
|
91
|
+
type: string;
|
|
92
|
+
key: string;
|
|
93
|
+
model: QueryWithTable;
|
|
94
|
+
joinQuery: Query;
|
|
95
|
+
nestedCreateQuery: Query;
|
|
96
|
+
nestedInsert?:
|
|
97
|
+
| BelongsToNestedInsert
|
|
98
|
+
| HasOneNestedInsert
|
|
99
|
+
| HasManyNestedInsert;
|
|
100
|
+
nestedUpdate?:
|
|
101
|
+
| BelongsToNestedUpdate
|
|
102
|
+
| HasOneNestedUpdate
|
|
103
|
+
| HasManyNestedUpdate;
|
|
104
|
+
primaryKey: string;
|
|
105
|
+
options: {
|
|
106
|
+
scope?(q: QueryWithTable): QueryWithTable;
|
|
107
|
+
required?: boolean;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export interface BelongsToRelation extends BaseRelation {
|
|
112
|
+
type: 'belongsTo';
|
|
113
|
+
returns: 'one';
|
|
114
|
+
options: BaseRelation['options'] & {
|
|
115
|
+
primaryKey: string;
|
|
116
|
+
foreignKey: string;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface HasOneRelation extends BaseRelation {
|
|
121
|
+
type: 'hasOne';
|
|
122
|
+
returns: 'one';
|
|
123
|
+
options: BaseRelation['options'] &
|
|
124
|
+
(
|
|
125
|
+
| {
|
|
126
|
+
primaryKey: string;
|
|
127
|
+
foreignKey: string;
|
|
128
|
+
}
|
|
129
|
+
| {
|
|
130
|
+
through: string;
|
|
131
|
+
source: string;
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface HasManyRelation extends BaseRelation {
|
|
137
|
+
type: 'hasMany';
|
|
138
|
+
returns: 'many';
|
|
139
|
+
options: BaseRelation['options'] &
|
|
140
|
+
(
|
|
141
|
+
| {
|
|
142
|
+
primaryKey: string;
|
|
143
|
+
foreignKey: string;
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
through: string;
|
|
147
|
+
source: string;
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface HasAndBelongsToManyRelation extends BaseRelation {
|
|
153
|
+
type: 'hasAndBelongsToMany';
|
|
154
|
+
returns: 'many';
|
|
155
|
+
options: BaseRelation['options'] & {
|
|
156
|
+
primaryKey: string;
|
|
157
|
+
foreignKey: string;
|
|
158
|
+
associationPrimaryKey: string;
|
|
159
|
+
associationForeignKey: string;
|
|
160
|
+
joinTable: string;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type Relation =
|
|
165
|
+
| BelongsToRelation
|
|
166
|
+
| HasOneRelation
|
|
167
|
+
| HasManyRelation
|
|
168
|
+
| HasAndBelongsToManyRelation;
|
|
169
|
+
|
|
170
|
+
export type RelationsBase = Record<never, Relation>;
|
|
171
|
+
|
|
172
|
+
export type relationQueryKey = typeof relationQueryKey;
|
|
173
|
+
export const relationQueryKey = Symbol('relationQuery');
|
|
174
|
+
|
|
175
|
+
export type isRequiredRelationKey = typeof isRequiredRelationKey;
|
|
176
|
+
export const isRequiredRelationKey = Symbol('isRequiredRelation');
|
|
177
|
+
|
|
178
|
+
export type RelationQueryBase = Query & {
|
|
179
|
+
[relationQueryKey]: string;
|
|
180
|
+
[isRequiredRelationKey]: boolean;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export type RelationQuery<
|
|
184
|
+
RelationName extends PropertyKey = string,
|
|
185
|
+
Params extends Record<string, unknown> = never,
|
|
186
|
+
Populate extends string = never,
|
|
187
|
+
T extends Query = Query,
|
|
188
|
+
Required extends boolean = boolean,
|
|
189
|
+
Q extends RelationQueryBase = Omit<T, 'tableAlias'> & {
|
|
190
|
+
tableAlias: RelationName extends string ? RelationName : never;
|
|
191
|
+
[isRequiredRelationKey]: Required;
|
|
192
|
+
[relationQueryKey]: string;
|
|
193
|
+
},
|
|
194
|
+
> = ((params: Params) => Q & { [defaultsKey]: Pick<T['type'], Populate> }) & Q;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { AggregateItem } from './types';
|
|
2
|
+
import { addValue, expressionToSql, q } from './common';
|
|
3
|
+
import { EMPTY_OBJECT, Expression, getRaw, isRaw } from '../common';
|
|
4
|
+
import { windowToSql } from './window';
|
|
5
|
+
import { pushOrderBySql } from './orderBy';
|
|
6
|
+
import { whereToSql } from './where';
|
|
7
|
+
import { Query } from '../query';
|
|
8
|
+
|
|
9
|
+
export const aggregateToSql = (
|
|
10
|
+
model: Pick<
|
|
11
|
+
Query,
|
|
12
|
+
'whereQueryBuilder' | 'onQueryBuilder' | 'as' | 'shape' | 'relations'
|
|
13
|
+
>,
|
|
14
|
+
values: unknown[],
|
|
15
|
+
item: AggregateItem,
|
|
16
|
+
quotedAs?: string,
|
|
17
|
+
) => {
|
|
18
|
+
const sql: string[] = [`${item.function}(`];
|
|
19
|
+
|
|
20
|
+
const options = item.options || EMPTY_OBJECT;
|
|
21
|
+
|
|
22
|
+
if (options.distinct && !options.withinGroup) sql.push('DISTINCT ');
|
|
23
|
+
|
|
24
|
+
if (typeof item.arg === 'object') {
|
|
25
|
+
if (Array.isArray(item.arg)) {
|
|
26
|
+
sql.push(
|
|
27
|
+
`${expressionToSql(item.arg[0], values, quotedAs)}, ${addValue(
|
|
28
|
+
values,
|
|
29
|
+
item.arg[1],
|
|
30
|
+
)}`,
|
|
31
|
+
);
|
|
32
|
+
} else if (isRaw(item.arg)) {
|
|
33
|
+
sql.push(getRaw(item.arg, values));
|
|
34
|
+
} else {
|
|
35
|
+
const args: string[] = [];
|
|
36
|
+
for (const key in item.arg) {
|
|
37
|
+
args.push(
|
|
38
|
+
// ::text is needed to bypass "could not determine data type of parameter" postgres error
|
|
39
|
+
`${addValue(values, key)}::text, ${expressionToSql(
|
|
40
|
+
item.arg[key as keyof typeof item.arg] as unknown as Expression,
|
|
41
|
+
values,
|
|
42
|
+
quotedAs,
|
|
43
|
+
)}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
sql.push(args.join(', '));
|
|
47
|
+
}
|
|
48
|
+
} else if (item.arg) {
|
|
49
|
+
sql.push(expressionToSql(item.arg, values, quotedAs));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.withinGroup) sql.push(') WITHIN GROUP (');
|
|
53
|
+
else if (options.order) sql.push(' ');
|
|
54
|
+
|
|
55
|
+
if (options.order) pushOrderBySql(sql, values, quotedAs, options.order);
|
|
56
|
+
|
|
57
|
+
sql.push(')');
|
|
58
|
+
|
|
59
|
+
if (options.filter || options.filterOr) {
|
|
60
|
+
const whereSql = whereToSql(
|
|
61
|
+
model,
|
|
62
|
+
{
|
|
63
|
+
and: options.filter ? [options.filter] : undefined,
|
|
64
|
+
or: options.filterOr?.map((item) => [item]),
|
|
65
|
+
},
|
|
66
|
+
values,
|
|
67
|
+
quotedAs,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
sql.push(` FILTER (WHERE ${whereSql})`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (options.over) {
|
|
74
|
+
sql.push(` OVER ${windowToSql(options.over, values, quotedAs)}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (options.as) sql.push(` AS ${q(options.as)}`);
|
|
78
|
+
|
|
79
|
+
return sql.join('');
|
|
80
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ColumnInfoQueryData } from './types';
|
|
2
|
+
import { addValue } from './common';
|
|
3
|
+
|
|
4
|
+
export const pushColumnInfoSql = (
|
|
5
|
+
sql: string[],
|
|
6
|
+
values: unknown[],
|
|
7
|
+
table: string,
|
|
8
|
+
query: ColumnInfoQueryData,
|
|
9
|
+
) => {
|
|
10
|
+
sql.push(
|
|
11
|
+
`SELECT * FROM information_schema.columns WHERE table_name = ${addValue(
|
|
12
|
+
values,
|
|
13
|
+
table,
|
|
14
|
+
)} AND table_catalog = current_database() AND table_schema = ${
|
|
15
|
+
query.schema || 'current_schema()'
|
|
16
|
+
}`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (query.column) {
|
|
20
|
+
sql.push(`AND column_name = ${addValue(values, query.column)}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// quote table or column
|
|
2
|
+
import { Query } from '../query';
|
|
3
|
+
import { Expression, getRaw, isRaw } from '../common';
|
|
4
|
+
|
|
5
|
+
export const q = (sql: string) => `"${sql}"`;
|
|
6
|
+
|
|
7
|
+
// quote column with table or as
|
|
8
|
+
export const qc = (column: string, quotedAs?: string) =>
|
|
9
|
+
quotedAs ? `${quotedAs}.${q(column)}` : column;
|
|
10
|
+
|
|
11
|
+
export const quoteFullColumn = (fullColumn: string, quotedAs?: string) => {
|
|
12
|
+
const index = fullColumn.indexOf('.');
|
|
13
|
+
if (index !== -1) {
|
|
14
|
+
return `${q(fullColumn.slice(0, index))}.${q(fullColumn.slice(index + 1))}`;
|
|
15
|
+
} else if (quotedAs) {
|
|
16
|
+
return `${quotedAs}.${q(fullColumn)}`;
|
|
17
|
+
} else {
|
|
18
|
+
return q(fullColumn);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const expressionToSql = <T extends Query>(
|
|
23
|
+
expr: Expression<T>,
|
|
24
|
+
values: unknown[],
|
|
25
|
+
quotedAs?: string,
|
|
26
|
+
) => {
|
|
27
|
+
return typeof expr === 'object' && isRaw(expr)
|
|
28
|
+
? getRaw(expr, values)
|
|
29
|
+
: quoteFullColumn(expr as string, quotedAs);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const quoteSchemaAndTable = (
|
|
33
|
+
schema: string | undefined,
|
|
34
|
+
table: string,
|
|
35
|
+
) => {
|
|
36
|
+
return schema ? `${q(schema)}.${q(table)}` : q(table);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const addValue = (values: unknown[], value: unknown) => {
|
|
40
|
+
values.push(value);
|
|
41
|
+
return `$${values.length}`;
|
|
42
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Query } from '../query';
|
|
2
|
+
import { DeleteQueryData } from './types';
|
|
3
|
+
import { pushWhereSql } from './where';
|
|
4
|
+
import { pushReturningSql } from './insert';
|
|
5
|
+
import { processJoinItem } from './join';
|
|
6
|
+
|
|
7
|
+
export const pushDeleteSql = (
|
|
8
|
+
sql: string[],
|
|
9
|
+
values: unknown[],
|
|
10
|
+
model: Query,
|
|
11
|
+
query: DeleteQueryData,
|
|
12
|
+
quotedAs: string,
|
|
13
|
+
) => {
|
|
14
|
+
sql.push(`DELETE FROM ${quotedAs}`);
|
|
15
|
+
|
|
16
|
+
let conditions: string | undefined;
|
|
17
|
+
if (query.join?.length) {
|
|
18
|
+
const items = query.join.map((item) =>
|
|
19
|
+
processJoinItem(model, query, values, item.args, quotedAs),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
sql.push(`USING ${items.map((item) => item.target).join(', ')}`);
|
|
23
|
+
|
|
24
|
+
conditions = items
|
|
25
|
+
.map((item) => item.conditions)
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(' AND ');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pushWhereSql(sql, model, query, values, quotedAs);
|
|
31
|
+
|
|
32
|
+
if (conditions?.length) {
|
|
33
|
+
if (query.and?.length || query.or?.length) {
|
|
34
|
+
sql.push('AND', conditions);
|
|
35
|
+
} else {
|
|
36
|
+
sql.push('WHERE', conditions);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pushReturningSql(sql, model, query, values, quotedAs);
|
|
41
|
+
};
|