typeorm-extensions 2.2.0-rc.6 → 2.2.0-rc.7
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/extensions/cursor-pagination.extension.d.ts +2 -1
- package/dist/extensions/order.extension.d.ts +2 -2
- package/dist/extensions/pagination.extension.d.ts +1 -1
- package/dist/extensions/period-filter.extension.d.ts +4 -0
- package/dist/extensions/period-filter.extension.js +2 -0
- package/dist/extensions/typed/index.d.ts +1 -0
- package/dist/extensions/typed/index.js +1 -0
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.d.ts +19 -0
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.js +22 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-root.extension.d.ts +26 -4
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +5 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +13 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +8 -4
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +7 -3
- package/dist/extensions/typed/typed-where/typed-where.extension.js +2 -0
- package/dist/extensions/where-array.extension.d.ts +4 -0
- package/dist/extensions/where-array.extension.js +2 -0
- package/dist/extensions/where-is-any.extension.d.ts +41 -6
- package/dist/extensions/where-is-any.extension.js +2 -0
- package/dist/extensions/where-is-distinct-from.extension.d.ts +4 -0
- package/dist/extensions/where-is-distinct-from.extension.js +2 -0
- package/dist/extensions/where-is-in.extension.d.ts +38 -6
- package/dist/extensions/where-is-in.extension.js +2 -0
- package/dist/extensions/where-json.extension.d.ts +4 -0
- package/dist/extensions/where-json.extension.js +2 -0
- package/dist/extensions/where-relation-exists.extension.d.ts +6 -6
- package/dist/helpers.d.ts +4 -0
- package/dist/helpers.js +11 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/types/extensions/index.d.ts +1 -0
- package/dist/types/extensions/index.js +1 -0
- package/dist/types/extensions/typed-join-and-map.types.d.ts +6 -0
- package/dist/types/extensions/typed-join-and-map.types.js +2 -0
- package/dist/types/modules/typed-root.types.d.ts +11 -9
- package/dist/utils/query-builder.utils.d.ts +3 -2
- package/dist/utils/query-builder.utils.js +6 -4
- package/dist/utils/reserved-qualifier-guard.utils.d.ts +36 -0
- package/dist/utils/reserved-qualifier-guard.utils.js +98 -0
- package/dist/utils/reserved-qualifier.utils.d.ts +33 -0
- package/dist/utils/reserved-qualifier.utils.js +252 -0
- package/dist/utils/table.utils.d.ts +27 -11
- package/dist/utils/table.utils.js +49 -12
- package/package.json +2 -2
|
@@ -19,17 +19,17 @@ type RelatedCondition<Related extends ObjectLiteral> = (subQuery: SelectQueryBui
|
|
|
19
19
|
*/
|
|
20
20
|
export interface WhereRelationExistsMethods<Entity extends ObjectLiteral> {
|
|
21
21
|
/** Sets `WHERE EXISTS (…)` over the relation, replacing the WHERE conditions set before. */
|
|
22
|
-
whereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
22
|
+
whereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
23
23
|
/** Adds `AND EXISTS (…)` over the relation. */
|
|
24
|
-
andWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
24
|
+
andWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
25
25
|
/** Adds `OR EXISTS (…)` over the relation. */
|
|
26
|
-
orWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
26
|
+
orWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
27
27
|
/** Sets `WHERE NOT EXISTS (…)` over the relation, replacing the WHERE conditions set before. */
|
|
28
|
-
whereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
28
|
+
whereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
29
29
|
/** Adds `AND NOT EXISTS (…)` over the relation. */
|
|
30
|
-
andWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
30
|
+
andWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
31
31
|
/** Adds `OR NOT EXISTS (…)` over the relation. */
|
|
32
|
-
orWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
32
|
+
orWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
33
33
|
}
|
|
34
34
|
declare module 'typeorm/query-builder/SelectQueryBuilder' {
|
|
35
35
|
interface SelectQueryBuilder<Entity extends ObjectLiteral> extends WhereRelationExistsMethods<Entity> {
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export * from './utils/table.utils';
|
|
2
2
|
export { escapeLikePattern } from './utils/search.utils';
|
|
3
|
+
export { COCKROACHDB_RESERVED_KEY_WORDS, findBareReservedQualifiers, POSTGRES_RESERVED_KEY_WORDS, } from './utils/reserved-qualifier.utils';
|
|
4
|
+
export type { ReservedQualifier } from './utils/reserved-qualifier.utils';
|
|
5
|
+
export { disableReservedQualifierGuard, enableReservedQualifierGuard, ReservedQualifierError, } from './utils/reserved-qualifier-guard.utils';
|
|
6
|
+
export type { ReservedQualifierGuardOptions } from './utils/reserved-qualifier-guard.utils';
|
package/dist/helpers.js
CHANGED
|
@@ -14,10 +14,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.escapeLikePattern = void 0;
|
|
17
|
+
exports.ReservedQualifierError = exports.enableReservedQualifierGuard = exports.disableReservedQualifierGuard = exports.POSTGRES_RESERVED_KEY_WORDS = exports.findBareReservedQualifiers = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.escapeLikePattern = void 0;
|
|
18
18
|
// Side-effect-free entry point: `typeorm-extensions/helpers`.
|
|
19
|
-
// Exports helpers (tableHelper, escapeLikePattern) only and patches nothing
|
|
20
|
-
// (`typeorm-extensions`) installs every QueryBuilder extension.
|
|
19
|
+
// Exports helpers (tableHelper, escapeLikePattern, the reserved qualifier guard) only and patches nothing on import;
|
|
20
|
+
// importing the package root (`typeorm-extensions`) installs every QueryBuilder extension.
|
|
21
21
|
__exportStar(require("./utils/table.utils"), exports);
|
|
22
22
|
var search_utils_1 = require("./utils/search.utils");
|
|
23
23
|
Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
|
|
24
|
+
var reserved_qualifier_utils_1 = require("./utils/reserved-qualifier.utils");
|
|
25
|
+
Object.defineProperty(exports, "COCKROACHDB_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS; } });
|
|
26
|
+
Object.defineProperty(exports, "findBareReservedQualifiers", { enumerable: true, get: function () { return reserved_qualifier_utils_1.findBareReservedQualifiers; } });
|
|
27
|
+
Object.defineProperty(exports, "POSTGRES_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS; } });
|
|
28
|
+
var reserved_qualifier_guard_utils_1 = require("./utils/reserved-qualifier-guard.utils");
|
|
29
|
+
Object.defineProperty(exports, "disableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.disableReservedQualifierGuard; } });
|
|
30
|
+
Object.defineProperty(exports, "enableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.enableReservedQualifierGuard; } });
|
|
31
|
+
Object.defineProperty(exports, "ReservedQualifierError", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.ReservedQualifierError; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ export type { Json } from './types/modules/proxy-callback.types';
|
|
|
6
6
|
export type { TypedRoot, WithTypedRoot } from './types/modules/typed-root.types';
|
|
7
7
|
export * from './utils/table.utils';
|
|
8
8
|
export { escapeLikePattern } from './utils/search.utils';
|
|
9
|
+
export { COCKROACHDB_RESERVED_KEY_WORDS, findBareReservedQualifiers, POSTGRES_RESERVED_KEY_WORDS, } from './utils/reserved-qualifier.utils';
|
|
10
|
+
export type { ReservedQualifier } from './utils/reserved-qualifier.utils';
|
|
11
|
+
export { disableReservedQualifierGuard, enableReservedQualifierGuard, ReservedQualifierError, } from './utils/reserved-qualifier-guard.utils';
|
|
12
|
+
export type { ReservedQualifierGuardOptions } from './utils/reserved-qualifier-guard.utils';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.escapeLikePattern = void 0;
|
|
17
|
+
exports.ReservedQualifierError = exports.enableReservedQualifierGuard = exports.disableReservedQualifierGuard = exports.POSTGRES_RESERVED_KEY_WORDS = exports.findBareReservedQualifiers = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.escapeLikePattern = void 0;
|
|
18
18
|
__exportStar(require("./extensions"), exports);
|
|
19
19
|
__exportStar(require("./decorators"), exports);
|
|
20
20
|
__exportStar(require("./types/interfaces"), exports);
|
|
@@ -22,3 +22,11 @@ __exportStar(require("./types/extensions"), exports);
|
|
|
22
22
|
__exportStar(require("./utils/table.utils"), exports);
|
|
23
23
|
var search_utils_1 = require("./utils/search.utils");
|
|
24
24
|
Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
|
|
25
|
+
var reserved_qualifier_utils_1 = require("./utils/reserved-qualifier.utils");
|
|
26
|
+
Object.defineProperty(exports, "COCKROACHDB_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS; } });
|
|
27
|
+
Object.defineProperty(exports, "findBareReservedQualifiers", { enumerable: true, get: function () { return reserved_qualifier_utils_1.findBareReservedQualifiers; } });
|
|
28
|
+
Object.defineProperty(exports, "POSTGRES_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS; } });
|
|
29
|
+
var reserved_qualifier_guard_utils_1 = require("./utils/reserved-qualifier-guard.utils");
|
|
30
|
+
Object.defineProperty(exports, "disableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.disableReservedQualifierGuard; } });
|
|
31
|
+
Object.defineProperty(exports, "enableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.enableReservedQualifierGuard; } });
|
|
32
|
+
Object.defineProperty(exports, "ReservedQualifierError", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.ReservedQualifierError; } });
|
|
@@ -26,4 +26,5 @@ __exportStar(require("./search.types"), exports);
|
|
|
26
26
|
__exportStar(require("./typed-having.types"), exports);
|
|
27
27
|
__exportStar(require("./union.types"), exports);
|
|
28
28
|
__exportStar(require("./join-lateral.types"), exports);
|
|
29
|
+
__exportStar(require("./typed-join-and-map.types"), exports);
|
|
29
30
|
__exportStar(require("./query-returning.types"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SelectQueryBuilder } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* What a map join joins, as TypeORM's `leftJoinAndMapOne` and the others take it: an entity class or name, a
|
|
4
|
+
* relation (`'user.photos'`), a table name, or a factory that builds a subquery on the query builder it is given.
|
|
5
|
+
*/
|
|
6
|
+
export type JoinAndMapTarget = Function | string | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { ObjectLiteral } from 'typeorm';
|
|
2
|
-
declare const typedRoot: unique symbol;
|
|
3
2
|
declare class TypedRootMark<Root> {
|
|
4
|
-
|
|
3
|
+
__typedRoot?(): Root;
|
|
5
4
|
}
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
* with an optional
|
|
6
|
+
* A query builder whose typed callbacks take another root, as `typed<Root>()` returns it:
|
|
7
|
+
* `WithTypedRoot<SelectQueryBuilder<Entity>, Root>`, the builder with an optional `__typedRoot` method that only
|
|
8
|
+
* types carry. A builder of a marked entity, `SelectQueryBuilder<WithTypedRoot<Entity, Root>>`, takes the root too.
|
|
9
9
|
*/
|
|
10
|
-
export type WithTypedRoot<
|
|
10
|
+
export type WithTypedRoot<Marked, Root extends ObjectLiteral> = Marked & TypedRootMark<Root>;
|
|
11
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
12
|
+
type BuilderRoot<Builder> = '__typedRoot' extends keyof Builder ? Builder extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : never : never : never;
|
|
13
|
+
type EntityRoot<Entity> = '__typedRoot' extends keyof Entity ? IsAny<Entity['__typedRoot' & keyof Entity]> extends true ? Entity : Entity extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : Entity : Entity : Entity;
|
|
11
14
|
/**
|
|
12
|
-
* The root of a builder's typed callbacks: the one set with `typed<Root>()
|
|
13
|
-
*
|
|
14
|
-
* optional mark too, and its root would be inferred as `unknown`.
|
|
15
|
+
* The root of a builder's typed callbacks: the one set with `typed<Root>()` on the builder (`Builder`, which the
|
|
16
|
+
* typed methods pass as `this`) or on its entity, or else the entity.
|
|
15
17
|
*/
|
|
16
|
-
export type TypedRoot<Entity
|
|
18
|
+
export type TypedRoot<Entity, Builder = unknown> = [BuilderRoot<Builder>] extends [never] ? EntityRoot<Entity> : BuilderRoot<Builder>;
|
|
17
19
|
export {};
|
|
@@ -54,8 +54,9 @@ export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(buil
|
|
|
54
54
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
55
55
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
56
56
|
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
57
|
-
* metadata (a subquery or a CTE in FROM or a join)
|
|
58
|
-
* TypeORM leaves its keys as written, and
|
|
57
|
+
* metadata (a subquery or a CTE in FROM or a join), or one the query still doesn't have when the SQL
|
|
58
|
+
* is built, is quoted unless `useQuotes` is `false`: TypeORM leaves its keys as written, and
|
|
59
|
+
* PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
59
60
|
* Strings are kept as is.
|
|
60
61
|
*/
|
|
61
62
|
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean, columnsFor?: string): string | string[];
|
|
@@ -70,12 +70,13 @@ function deferColumnKey(aliasName, separator, name) {
|
|
|
70
70
|
* quoted without an entity;
|
|
71
71
|
* - an unquoted one (select, order, group by, distinct without `useQuotes`): `alias.property` on an alias with an
|
|
72
72
|
* entity, which TypeORM maps and reads as a column of the entity; quoted on an alias without one (a subquery, a
|
|
73
|
-
* CTE), as on an alias known at the call
|
|
73
|
+
* CTE), as on an alias known at the call, and on an alias the query never has, which only raw SQL adds (a LATERAL
|
|
74
|
+
* subquery in the condition of a raw join): TypeORM can't map it, and PostgreSQL would fold it to lower case.
|
|
74
75
|
*/
|
|
75
76
|
function toDeferredKeySql(alias, aliasName, separator, name, escape) {
|
|
76
77
|
const metadata = (alias === null || alias === void 0 ? void 0 : alias.hasMetadata) ? alias.metadata : undefined;
|
|
77
78
|
if (separator === DEFERRED_UNQUOTED) {
|
|
78
|
-
return metadata
|
|
79
|
+
return metadata ? `${aliasName}.${name}` : `${escape(aliasName)}.${escape(name)}`;
|
|
79
80
|
}
|
|
80
81
|
const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
|
|
81
82
|
const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
|
|
@@ -403,8 +404,9 @@ function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
|
403
404
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
404
405
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
405
406
|
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
406
|
-
* metadata (a subquery or a CTE in FROM or a join)
|
|
407
|
-
* TypeORM leaves its keys as written, and
|
|
407
|
+
* metadata (a subquery or a CTE in FROM or a join), or one the query still doesn't have when the SQL
|
|
408
|
+
* is built, is quoted unless `useQuotes` is `false`: TypeORM leaves its keys as written, and
|
|
409
|
+
* PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
408
410
|
* Strings are kept as is.
|
|
409
411
|
*/
|
|
410
412
|
function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ReservedQualifier } from './reserved-qualifier.utils';
|
|
2
|
+
/** The SQL of a query names a reserved word as a qualifier without quotes; see `enableReservedQualifierGuard`. */
|
|
3
|
+
export declare class ReservedQualifierError extends Error {
|
|
4
|
+
/** The SQL of the query, as the driver gets it. */
|
|
5
|
+
readonly sql: string;
|
|
6
|
+
/** Each reserved word used as a qualifier, in the order of the SQL. */
|
|
7
|
+
readonly qualifiers: ReservedQualifier[];
|
|
8
|
+
constructor(sql: string, qualifiers: ReservedQualifier[]);
|
|
9
|
+
}
|
|
10
|
+
type GuardWordsOption = {
|
|
11
|
+
/**
|
|
12
|
+
* The words to report, instead of the default list of the database (`POSTGRES_RESERVED_KEY_WORDS`,
|
|
13
|
+
* `COCKROACHDB_RESERVED_KEY_WORDS` on CockroachDB). Spread a default list to extend it.
|
|
14
|
+
*/
|
|
15
|
+
words?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
export type ReservedQualifierGuardOptions = GuardWordsOption & ({
|
|
18
|
+
/** `'throw'` (the default): the query rejects with a `ReservedQualifierError` before it runs. */
|
|
19
|
+
mode?: 'throw';
|
|
20
|
+
} | {
|
|
21
|
+
/** `'warn'`: the query runs, and the error is passed to `onViolation`. */
|
|
22
|
+
mode: 'warn';
|
|
23
|
+
/** Reports the error the query would throw in `'throw'` mode; `console.warn` by default. */
|
|
24
|
+
onViolation?: (violation: ReservedQualifierError) => void;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Checks the SQL of every query builder on PostgreSQL and CockroachDB for a reserved word used as a qualifier
|
|
28
|
+
* without quotes, such as `order."userId"` or `user.id` (`findBareReservedQualifiers`), before the query runs.
|
|
29
|
+
* TypeORM quotes the aliases it writes, so such a word comes from SQL written by hand, which PostgreSQL rejects.
|
|
30
|
+
*
|
|
31
|
+
* Off by default. A second call replaces the options. Other databases aren't checked.
|
|
32
|
+
*/
|
|
33
|
+
export declare function enableReservedQualifierGuard(options?: ReservedQualifierGuardOptions): void;
|
|
34
|
+
/** Turns the check of `enableReservedQualifierGuard` off. */
|
|
35
|
+
export declare function disableReservedQualifierGuard(): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disableReservedQualifierGuard = exports.enableReservedQualifierGuard = exports.ReservedQualifierError = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
const reserved_qualifier_utils_1 = require("./reserved-qualifier.utils");
|
|
6
|
+
const sql_utils_1 = require("./sql.utils");
|
|
7
|
+
const NAME_AFTER_DOT = /^\s*\.\s*("(?:[^"]|"")*"|[A-Za-z_][\w$]*|\*)/;
|
|
8
|
+
/** `"order"."userId"` for `order."userId"` / `order.userId`, `"order".*` for `order.*`. */
|
|
9
|
+
function toQuotedReference(sql, { word, index }) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
const name = (_b = (_a = NAME_AFTER_DOT.exec(sql.slice(index + word.length))) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 'column';
|
|
12
|
+
const quotedName = name.startsWith('"') || name === '*' ? name : `"${name}"`;
|
|
13
|
+
return `"${word}".${quotedName}`;
|
|
14
|
+
}
|
|
15
|
+
/** The SQL of a query names a reserved word as a qualifier without quotes; see `enableReservedQualifierGuard`. */
|
|
16
|
+
class ReservedQualifierError extends Error {
|
|
17
|
+
constructor(sql, qualifiers) {
|
|
18
|
+
const [first] = qualifiers;
|
|
19
|
+
const found = qualifiers.map(({ word, snippet }) => `"${word}" in ${snippet}`).join('; ');
|
|
20
|
+
super(`Reserved word used as a qualifier without quotes: ${found}. ` +
|
|
21
|
+
`PostgreSQL reads it as the key word, not as an alias, and rejects the query. ` +
|
|
22
|
+
(first
|
|
23
|
+
? `Quote both halves, ${toQuotedReference(sql, first)}, or build the reference with tableHelper or the typed methods`
|
|
24
|
+
: ''));
|
|
25
|
+
this.name = 'ReservedQualifierError';
|
|
26
|
+
this.sql = sql;
|
|
27
|
+
this.qualifiers = qualifiers;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.ReservedQualifierError = ReservedQualifierError;
|
|
31
|
+
let guard;
|
|
32
|
+
let installed = false;
|
|
33
|
+
let lastScan;
|
|
34
|
+
const reportViolation = (violation) => console.warn(violation);
|
|
35
|
+
function checkQuery(queryBuilder, sql, state) {
|
|
36
|
+
var _a;
|
|
37
|
+
const driver = (0, sql_utils_1.getDriverType)(queryBuilder);
|
|
38
|
+
if (typeof sql !== 'string' || !(0, sql_utils_1.isPostgresLikeDriver)(driver)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const words = (_a = state.words) !== null && _a !== void 0 ? _a : (0, reserved_qualifier_utils_1.toReservedWordSet)(driver === 'cockroachdb' ? reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS : reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS);
|
|
42
|
+
// the same SQL again, as a query run in a loop: one scan
|
|
43
|
+
const qualifiers = lastScan && lastScan.sql === sql && lastScan.words === words
|
|
44
|
+
? lastScan.qualifiers
|
|
45
|
+
: (0, reserved_qualifier_utils_1.scanReservedQualifiers)(sql, words);
|
|
46
|
+
lastScan = { sql, words, qualifiers };
|
|
47
|
+
if (qualifiers.length === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const violation = new ReservedQualifierError(sql, qualifiers);
|
|
51
|
+
if (state.mode === 'throw') {
|
|
52
|
+
throw violation;
|
|
53
|
+
}
|
|
54
|
+
state.onViolation(violation);
|
|
55
|
+
}
|
|
56
|
+
// every query of every builder gets its SQL here before it runs: getMany, getRawMany, getOne, getCount, stream,
|
|
57
|
+
// execute; getSql() too. getQuery() isn't checked: a subquery's SQL is checked as part of the query it is in
|
|
58
|
+
function installGuard() {
|
|
59
|
+
if (installed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
installed = true;
|
|
63
|
+
const getQueryAndParameters = typeorm_1.QueryBuilder.prototype.getQueryAndParameters;
|
|
64
|
+
typeorm_1.QueryBuilder.prototype.getQueryAndParameters = function () {
|
|
65
|
+
const queryAndParameters = getQueryAndParameters.call(this);
|
|
66
|
+
if (guard) {
|
|
67
|
+
checkQuery(this, queryAndParameters[0], guard);
|
|
68
|
+
}
|
|
69
|
+
return queryAndParameters;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Checks the SQL of every query builder on PostgreSQL and CockroachDB for a reserved word used as a qualifier
|
|
74
|
+
* without quotes, such as `order."userId"` or `user.id` (`findBareReservedQualifiers`), before the query runs.
|
|
75
|
+
* TypeORM quotes the aliases it writes, so such a word comes from SQL written by hand, which PostgreSQL rejects.
|
|
76
|
+
*
|
|
77
|
+
* Off by default. A second call replaces the options. Other databases aren't checked.
|
|
78
|
+
*/
|
|
79
|
+
function enableReservedQualifierGuard(options = {}) {
|
|
80
|
+
var _a;
|
|
81
|
+
const mode = (_a = options.mode) !== null && _a !== void 0 ? _a : 'throw';
|
|
82
|
+
if (mode !== 'throw' && mode !== 'warn') {
|
|
83
|
+
throw new TypeError(`Invalid mode ${JSON.stringify(mode)} of the reserved qualifier guard: 'throw' or 'warn'`);
|
|
84
|
+
}
|
|
85
|
+
guard = {
|
|
86
|
+
mode,
|
|
87
|
+
words: options.words === undefined ? undefined : (0, reserved_qualifier_utils_1.toReservedWordSet)(options.words),
|
|
88
|
+
onViolation: (options.mode === 'warn' && options.onViolation) || reportViolation,
|
|
89
|
+
};
|
|
90
|
+
installGuard();
|
|
91
|
+
}
|
|
92
|
+
exports.enableReservedQualifierGuard = enableReservedQualifierGuard;
|
|
93
|
+
/** Turns the check of `enableReservedQualifierGuard` off. */
|
|
94
|
+
function disableReservedQualifierGuard() {
|
|
95
|
+
guard = undefined;
|
|
96
|
+
lastScan = undefined;
|
|
97
|
+
}
|
|
98
|
+
exports.disableReservedQualifierGuard = disableReservedQualifierGuard;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** A reserved word that qualifies a name without quotes, as in `order."userId"`. */
|
|
2
|
+
export interface ReservedQualifier {
|
|
3
|
+
/** The word as the SQL has it: `order`, `ORDER`. */
|
|
4
|
+
word: string;
|
|
5
|
+
/** Offset of the word in the SQL. */
|
|
6
|
+
index: number;
|
|
7
|
+
/** The SQL around the word, on one line. */
|
|
8
|
+
snippet: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The reserved key words of PostgreSQL, "reserved" in Appendix C of its documentation (`SYSTEM_USER` since
|
|
12
|
+
* PostgreSQL 16). None can be a table, an alias or a schema without quotes, so none is followed by a `.` in valid
|
|
13
|
+
* SQL. The words "reserved (can be function or type)" (`LEFT`, `JOIN`, `BINARY`, …) aren't on the list: one may
|
|
14
|
+
* name the schema of a type, as in `CAST(x AS binary.my_type)`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const POSTGRES_RESERVED_KEY_WORDS: readonly string[];
|
|
17
|
+
/** The reserved key words of CockroachDB: PostgreSQL's without `SYSTEM_USER`, with `CONCURRENTLY` and `CURRENT_SCHEMA`. */
|
|
18
|
+
export declare const COCKROACHDB_RESERVED_KEY_WORDS: readonly string[];
|
|
19
|
+
/** The words in lower case; cached for a frozen list, such as the defaults, which can't change afterwards. */
|
|
20
|
+
export declare function toReservedWordSet(words: readonly string[]): ReadonlySet<string>;
|
|
21
|
+
/** `findBareReservedQualifiers` with the words as a set of lower-case words (`toReservedWordSet`). */
|
|
22
|
+
export declare function scanReservedQualifiers(sql: string, words: ReadonlySet<string>): ReservedQualifier[];
|
|
23
|
+
/**
|
|
24
|
+
* Reserved words that qualify a name without quotes, as in `order."userId"` or `user.id`: PostgreSQL reads such a
|
|
25
|
+
* word as the key word, not as an alias, and rejects the statement (`syntax error at or near "order"`).
|
|
26
|
+
*
|
|
27
|
+
* Words in string literals (`E'…'` and dollar-quoted ones too), quoted identifiers, comments and parameters are
|
|
28
|
+
* skipped, and so are words after a dot (`public.order`) and a number after one (`THEN .5`). Case-insensitive;
|
|
29
|
+
* whitespace may come before the dot (`group . name`).
|
|
30
|
+
*
|
|
31
|
+
* @param words the words to look for, `POSTGRES_RESERVED_KEY_WORDS` by default
|
|
32
|
+
*/
|
|
33
|
+
export declare function findBareReservedQualifiers(sql: string, words?: readonly string[]): ReservedQualifier[];
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findBareReservedQualifiers = exports.scanReservedQualifiers = exports.toReservedWordSet = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.POSTGRES_RESERVED_KEY_WORDS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The reserved key words of PostgreSQL, "reserved" in Appendix C of its documentation (`SYSTEM_USER` since
|
|
6
|
+
* PostgreSQL 16). None can be a table, an alias or a schema without quotes, so none is followed by a `.` in valid
|
|
7
|
+
* SQL. The words "reserved (can be function or type)" (`LEFT`, `JOIN`, `BINARY`, …) aren't on the list: one may
|
|
8
|
+
* name the schema of a type, as in `CAST(x AS binary.my_type)`.
|
|
9
|
+
*/
|
|
10
|
+
exports.POSTGRES_RESERVED_KEY_WORDS = Object.freeze([
|
|
11
|
+
'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'BOTH', 'CASE', 'CAST', 'CHECK',
|
|
12
|
+
'COLLATE', 'COLUMN', 'CONSTRAINT', 'CREATE', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_TIME',
|
|
13
|
+
'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT',
|
|
14
|
+
'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IN', 'INITIALLY', 'INTERSECT', 'INTO',
|
|
15
|
+
'LATERAL', 'LEADING', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NOT', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER',
|
|
16
|
+
'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'SELECT', 'SESSION_USER', 'SOME', 'SYMMETRIC', 'SYSTEM_USER',
|
|
17
|
+
'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'WHEN', 'WHERE',
|
|
18
|
+
'WINDOW', 'WITH',
|
|
19
|
+
]);
|
|
20
|
+
/** The reserved key words of CockroachDB: PostgreSQL's without `SYSTEM_USER`, with `CONCURRENTLY` and `CURRENT_SCHEMA`. */
|
|
21
|
+
exports.COCKROACHDB_RESERVED_KEY_WORDS = Object.freeze([
|
|
22
|
+
...exports.POSTGRES_RESERVED_KEY_WORDS.filter((word) => word !== 'SYSTEM_USER'),
|
|
23
|
+
'CONCURRENTLY',
|
|
24
|
+
'CURRENT_SCHEMA',
|
|
25
|
+
]);
|
|
26
|
+
const KEY_WORD = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
27
|
+
const wordSets = new WeakMap();
|
|
28
|
+
/** The words in lower case; cached for a frozen list, such as the defaults, which can't change afterwards. */
|
|
29
|
+
function toReservedWordSet(words) {
|
|
30
|
+
const cached = wordSets.get(words);
|
|
31
|
+
if (cached) {
|
|
32
|
+
return cached;
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(words)) {
|
|
35
|
+
throw new TypeError(`Reserved words must be an array of key words, got ${typeof words}`);
|
|
36
|
+
}
|
|
37
|
+
for (const word of words) {
|
|
38
|
+
if (typeof word !== 'string' || !KEY_WORD.test(word)) {
|
|
39
|
+
throw new TypeError(`Invalid reserved word ${JSON.stringify(word)}: expected a key word, such as "order"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const set = new Set(words.map((word) => word.toLowerCase()));
|
|
43
|
+
if (Object.isFrozen(words)) {
|
|
44
|
+
wordSets.set(words, set);
|
|
45
|
+
}
|
|
46
|
+
return set;
|
|
47
|
+
}
|
|
48
|
+
exports.toReservedWordSet = toReservedWordSet;
|
|
49
|
+
const TAB = 9;
|
|
50
|
+
const CARRIAGE_RETURN = 13;
|
|
51
|
+
const LINE_FEED = 10;
|
|
52
|
+
const SPACE = 32;
|
|
53
|
+
const DOUBLE_QUOTE = 34;
|
|
54
|
+
const DOLLAR = 36;
|
|
55
|
+
const QUOTE = 39;
|
|
56
|
+
const STAR = 42;
|
|
57
|
+
const DASH = 45;
|
|
58
|
+
const DOT = 46;
|
|
59
|
+
const SLASH = 47;
|
|
60
|
+
const COLON = 58;
|
|
61
|
+
const BACKSLASH = 92;
|
|
62
|
+
const isSpace = (code) => code === SPACE || (code >= TAB && code <= CARRIAGE_RETURN);
|
|
63
|
+
const isDigit = (code) => code >= 48 && code <= 57;
|
|
64
|
+
// as PostgreSQL reads identifiers: letters, `_` and every non-ASCII character; then digits and `$` too
|
|
65
|
+
const isIdentifierStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95 || code >= 128;
|
|
66
|
+
const isIdentifierPart = (code) => isIdentifierStart(code) || isDigit(code) || code === DOLLAR;
|
|
67
|
+
/** Index after the string literal whose opening quote is at `start`; `E'…'` strings take backslash escapes. */
|
|
68
|
+
function skipString(sql, start, backslashEscapes) {
|
|
69
|
+
for (let i = start + 1; i < sql.length; i++) {
|
|
70
|
+
const code = sql.charCodeAt(i);
|
|
71
|
+
if (backslashEscapes && code === BACKSLASH) {
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
else if (code === QUOTE) {
|
|
75
|
+
if (sql.charCodeAt(i + 1) !== QUOTE) {
|
|
76
|
+
return i + 1;
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return sql.length;
|
|
82
|
+
}
|
|
83
|
+
function skipQuotedIdentifier(sql, start) {
|
|
84
|
+
for (let i = start + 1; i < sql.length; i++) {
|
|
85
|
+
if (sql.charCodeAt(i) === DOUBLE_QUOTE) {
|
|
86
|
+
if (sql.charCodeAt(i + 1) !== DOUBLE_QUOTE) {
|
|
87
|
+
return i + 1;
|
|
88
|
+
}
|
|
89
|
+
i++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return sql.length;
|
|
93
|
+
}
|
|
94
|
+
function skipLineComment(sql, start) {
|
|
95
|
+
for (let i = start + 2; i < sql.length; i++) {
|
|
96
|
+
const code = sql.charCodeAt(i);
|
|
97
|
+
if (code === LINE_FEED || code === CARRIAGE_RETURN) {
|
|
98
|
+
return i;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return sql.length;
|
|
102
|
+
}
|
|
103
|
+
// PostgreSQL nests block comments: `/* a /* b */ c */` is one comment
|
|
104
|
+
function skipBlockComment(sql, start) {
|
|
105
|
+
let depth = 0;
|
|
106
|
+
let i = start;
|
|
107
|
+
while (i < sql.length) {
|
|
108
|
+
const code = sql.charCodeAt(i);
|
|
109
|
+
const next = sql.charCodeAt(i + 1);
|
|
110
|
+
if (code === SLASH && next === STAR) {
|
|
111
|
+
depth++;
|
|
112
|
+
i += 2;
|
|
113
|
+
}
|
|
114
|
+
else if (code === STAR && next === SLASH) {
|
|
115
|
+
depth--;
|
|
116
|
+
i += 2;
|
|
117
|
+
if (depth === 0) {
|
|
118
|
+
return i;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return sql.length;
|
|
126
|
+
}
|
|
127
|
+
/** Index after the opening tag of a dollar-quoted string at `start` (`$$`, `$tag$`); -1 for a parameter (`$1`). */
|
|
128
|
+
function dollarTagEnd(sql, start) {
|
|
129
|
+
let i = start + 1;
|
|
130
|
+
if (isIdentifierStart(sql.charCodeAt(i))) {
|
|
131
|
+
do {
|
|
132
|
+
i++;
|
|
133
|
+
} while (isIdentifierStart(sql.charCodeAt(i)) || isDigit(sql.charCodeAt(i)));
|
|
134
|
+
}
|
|
135
|
+
return sql.charCodeAt(i) === DOLLAR ? i + 1 : -1;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether the word from `start` to `end` qualifies a name: a dot follows it, after optional whitespace, and not a
|
|
139
|
+
* number (`THEN .5`); and it isn't itself a name after a dot (`public.order.id`), next to a quoted name or an
|
|
140
|
+
* identifier character, or a named parameter (`:order`, but `::` is a cast).
|
|
141
|
+
*/
|
|
142
|
+
function isQualifier(sql, start, end, previous) {
|
|
143
|
+
let next = end;
|
|
144
|
+
while (isSpace(sql.charCodeAt(next))) {
|
|
145
|
+
next++;
|
|
146
|
+
}
|
|
147
|
+
if (sql.charCodeAt(next) !== DOT || isDigit(sql.charCodeAt(next + 1)) || previous === DOT) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const before = sql.charCodeAt(start - 1);
|
|
151
|
+
return (before !== DOUBLE_QUOTE &&
|
|
152
|
+
!isIdentifierPart(before) &&
|
|
153
|
+
!(before === COLON && sql.charCodeAt(start - 2) !== COLON));
|
|
154
|
+
}
|
|
155
|
+
const SNIPPET_CONTEXT = 30;
|
|
156
|
+
// cut at whitespace, not inside a word, when the context has some
|
|
157
|
+
function snippetAt(sql, start, end) {
|
|
158
|
+
let from = Math.max(0, start - SNIPPET_CONTEXT);
|
|
159
|
+
let to = Math.min(sql.length, end + SNIPPET_CONTEXT);
|
|
160
|
+
if (from > 0) {
|
|
161
|
+
const space = sql.slice(from, start).search(/\s/);
|
|
162
|
+
if (space !== -1) {
|
|
163
|
+
from += space + 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (to < sql.length) {
|
|
167
|
+
const space = sql.slice(end, to).search(/\s\S*$/);
|
|
168
|
+
if (space !== -1) {
|
|
169
|
+
to = end + space;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return `${from > 0 ? '…' : ''}${sql.slice(from, to).replace(/\s+/g, ' ')}${to < sql.length ? '…' : ''}`;
|
|
173
|
+
}
|
|
174
|
+
/** `findBareReservedQualifiers` with the words as a set of lower-case words (`toReservedWordSet`). */
|
|
175
|
+
function scanReservedQualifiers(sql, words) {
|
|
176
|
+
const found = [];
|
|
177
|
+
const length = sql.length;
|
|
178
|
+
// the last character outside whitespace and comments
|
|
179
|
+
let previous = -1;
|
|
180
|
+
let i = 0;
|
|
181
|
+
while (i < length) {
|
|
182
|
+
const code = sql.charCodeAt(i);
|
|
183
|
+
if (isSpace(code)) {
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
else if (code === DASH && sql.charCodeAt(i + 1) === DASH) {
|
|
187
|
+
i = skipLineComment(sql, i);
|
|
188
|
+
}
|
|
189
|
+
else if (code === SLASH && sql.charCodeAt(i + 1) === STAR) {
|
|
190
|
+
i = skipBlockComment(sql, i);
|
|
191
|
+
}
|
|
192
|
+
else if (code === QUOTE) {
|
|
193
|
+
i = skipString(sql, i, false);
|
|
194
|
+
previous = code;
|
|
195
|
+
}
|
|
196
|
+
else if (code === DOUBLE_QUOTE) {
|
|
197
|
+
i = skipQuotedIdentifier(sql, i);
|
|
198
|
+
previous = code;
|
|
199
|
+
}
|
|
200
|
+
else if (code === DOLLAR) {
|
|
201
|
+
const tagEnd = dollarTagEnd(sql, i);
|
|
202
|
+
if (tagEnd === -1) {
|
|
203
|
+
do {
|
|
204
|
+
i++;
|
|
205
|
+
} while (isDigit(sql.charCodeAt(i)));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const close = sql.indexOf(sql.slice(i, tagEnd), tagEnd);
|
|
209
|
+
i = close === -1 ? length : close + (tagEnd - i);
|
|
210
|
+
}
|
|
211
|
+
previous = code;
|
|
212
|
+
}
|
|
213
|
+
else if (isIdentifierPart(code)) {
|
|
214
|
+
const start = i;
|
|
215
|
+
do {
|
|
216
|
+
i++;
|
|
217
|
+
} while (isIdentifierPart(sql.charCodeAt(i)));
|
|
218
|
+
// E'…', the string literal with backslash escapes
|
|
219
|
+
if (i - start === 1 && (code === 69 || code === 101) && sql.charCodeAt(i) === QUOTE) {
|
|
220
|
+
i = skipString(sql, i, true);
|
|
221
|
+
previous = QUOTE;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (isIdentifierStart(code) &&
|
|
225
|
+
isQualifier(sql, start, i, previous) &&
|
|
226
|
+
words.has(sql.slice(start, i).toLowerCase())) {
|
|
227
|
+
found.push({ word: sql.slice(start, i), index: start, snippet: snippetAt(sql, start, i) });
|
|
228
|
+
}
|
|
229
|
+
previous = code;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
previous = code;
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return found;
|
|
237
|
+
}
|
|
238
|
+
exports.scanReservedQualifiers = scanReservedQualifiers;
|
|
239
|
+
/**
|
|
240
|
+
* Reserved words that qualify a name without quotes, as in `order."userId"` or `user.id`: PostgreSQL reads such a
|
|
241
|
+
* word as the key word, not as an alias, and rejects the statement (`syntax error at or near "order"`).
|
|
242
|
+
*
|
|
243
|
+
* Words in string literals (`E'…'` and dollar-quoted ones too), quoted identifiers, comments and parameters are
|
|
244
|
+
* skipped, and so are words after a dot (`public.order`) and a number after one (`THEN .5`). Case-insensitive;
|
|
245
|
+
* whitespace may come before the dot (`group . name`).
|
|
246
|
+
*
|
|
247
|
+
* @param words the words to look for, `POSTGRES_RESERVED_KEY_WORDS` by default
|
|
248
|
+
*/
|
|
249
|
+
function findBareReservedQualifiers(sql, words = exports.POSTGRES_RESERVED_KEY_WORDS) {
|
|
250
|
+
return scanReservedQualifiers(sql, toReservedWordSet(words));
|
|
251
|
+
}
|
|
252
|
+
exports.findBareReservedQualifiers = findBareReservedQualifiers;
|