uql-orm 0.2.1 → 0.2.3
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/CHANGELOG.md +52 -8
- package/README.md +46 -4
- package/dist/dialect/abstractSqlDialect.d.ts +16 -2
- package/dist/dialect/abstractSqlDialect.d.ts.map +1 -1
- package/dist/dialect/abstractSqlDialect.js +169 -103
- package/dist/dialect/abstractSqlDialect.js.map +1 -1
- package/dist/mongo/mongoDialect.d.ts +17 -2
- package/dist/mongo/mongoDialect.d.ts.map +1 -1
- package/dist/mongo/mongoDialect.js +151 -113
- package/dist/mongo/mongoDialect.js.map +1 -1
- package/dist/mongo/mongodbQuerier.d.ts +2 -1
- package/dist/mongo/mongodbQuerier.d.ts.map +1 -1
- package/dist/mongo/mongodbQuerier.js +10 -0
- package/dist/mongo/mongodbQuerier.js.map +1 -1
- package/dist/querier/abstractQuerier.d.ts +9 -1
- package/dist/querier/abstractQuerier.d.ts.map +1 -1
- package/dist/querier/abstractQuerier.js +75 -65
- package/dist/querier/abstractQuerier.js.map +1 -1
- package/dist/querier/abstractSqlQuerier.d.ts +2 -1
- package/dist/querier/abstractSqlQuerier.d.ts.map +1 -1
- package/dist/querier/abstractSqlQuerier.js +5 -0
- package/dist/querier/abstractSqlQuerier.js.map +1 -1
- package/dist/type/querier.d.ts +5 -1
- package/dist/type/querier.d.ts.map +1 -1
- package/dist/type/querier.js.map +1 -1
- package/dist/type/query.d.ts +89 -0
- package/dist/type/query.d.ts.map +1 -1
- package/dist/type/query.js.map +1 -1
- package/dist/type/universalQuerier.d.ts +8 -1
- package/dist/type/universalQuerier.d.ts.map +1 -1
- package/dist/util/dialect.util.d.ts +18 -1
- package/dist/util/dialect.util.d.ts.map +1 -1
- package/dist/util/dialect.util.js +19 -0
- package/dist/util/dialect.util.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,27 +3,71 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [0.2.
|
|
6
|
+
## [0.2.3](https://github.com/rogerpadilla/uql/compare/uql-orm@0.2.2...uql-orm@0.2.3) (2026-03-09)
|
|
7
7
|
|
|
8
|
+
**Note:** Version bump only for package uql-orm
|
|
8
9
|
|
|
9
|
-
### Bug Fixes
|
|
10
10
|
|
|
11
|
-
* update changelog version from 0.3.0 to 0.2.1. ([3768858](https://github.com/rogerpadilla/uql/commit/3768858b532454bae5a28777b4313e2bea52aa31))
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
### Features
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
# Changelog
|
|
17
15
|
|
|
16
|
+
All notable changes to this project will be documented in this file. Please add new changes to the top.
|
|
18
17
|
|
|
18
|
+
date format is [yyyy-mm-dd]
|
|
19
19
|
|
|
20
|
+
## [0.2.3] - 2026-03-09
|
|
21
|
+
### Documentation
|
|
22
|
+
- **Aggregate Queries guide**: Added dedicated [aggregate documentation page](https://uql-orm.dev/querying/aggregate) covering `$group`, `$having`, `$where` vs `$having`, sorting/pagination, and `$distinct`.
|
|
23
|
+
- **README**: Added Aggregate Queries to features table, new §4 subsection with code examples and generated SQL, and "Learn more" link.
|
|
24
|
+
- **Querier methods table**: Added `aggregate()` to the website's querier reference.
|
|
25
|
+
- **Simplified tsconfig**: Removed `module`/`target` from recommended config — only decorator flags are UQL-specific. Added Pure ESM note.
|
|
20
26
|
|
|
27
|
+
## [0.2.2] - 2026-03-09
|
|
28
|
+
### New Features
|
|
29
|
+
- **Aggregate Query API**: Added `querier.aggregate()` with full support across all SQL dialects and MongoDB. Includes typed `QueryAggregate<E>`, `QueryGroupMap`, `QueryHavingMap`, and `QueryAggregateOp` types. Supports `$group` (with `$count`, `$sum`, `$avg`, `$min`, `$max`), `$having` (post-aggregation filtering with operator support), `$where` (pre-aggregation filtering), `$sort`, `$skip`, and `$limit`.
|
|
30
|
+
```ts
|
|
31
|
+
const results = await querier.aggregate(Order, {
|
|
32
|
+
$group: { status: true, total: { $sum: 'amount' }, count: { $count: '*' } },
|
|
33
|
+
$having: { count: { $gt: 5 } },
|
|
34
|
+
$sort: { total: -1 },
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
- **SQL**: Generates `SELECT … GROUP BY … HAVING … ORDER BY` with proper escaping and parameterization.
|
|
38
|
+
- **MongoDB**: Generates a full aggregation pipeline (`$match → $group → $project → $match → $sort → $skip → $limit`).
|
|
39
|
+
- **`$distinct` support**: Added `$distinct` option to `Query<E>` for `SELECT DISTINCT` queries.
|
|
21
40
|
|
|
22
|
-
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
- **Sort direction with numeric `-1`**: `SORT_DIRECTION_MAP` only had the string key `'-1'`, not the numeric `-1` from `QuerySortDirection`. Queries using `$sort: { field: -1 }` silently produced ascending order. Now both numeric and string forms work correctly.
|
|
43
|
+
- **MongoDB sort normalization**: Unified sort direction normalization into `sort()` method, ensuring all callers (find queries and aggregate pipelines) normalize string directions (`'asc'`/`'desc'`) to numeric `1`/`-1` for the MongoDB server.
|
|
23
44
|
|
|
24
|
-
|
|
45
|
+
### Type Safety
|
|
46
|
+
- **`QueryAggregateFn` enforces single operation**: Changed from a mapped type (which allowed invalid `{ $count: '*', $sum: 'amount' }`) to a discriminated union that enforces exactly one aggregate op per entry.
|
|
47
|
+
- **HAVING `$in`/`$nin` support**: `havingCondition` now supports `$in` and `$nin` operators (e.g., `HAVING COUNT(*) IN (5, 10)`).
|
|
48
|
+
- **HAVING `$isNull`/`$isNotNull` support**: `havingCondition` now supports null-checking operators (e.g., `HAVING MAX(score) IS NULL`).
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
### Code Quality
|
|
51
|
+
- **`compareFieldOperator` compaction**: Reduced from 142 to 85 lines (−40%) by extracting `COMPARE_OP_MAP` (simple comparison operators), `LIKE_OP_MAP` (8 string/LIKE operators), and unifying `$in`/`$nin` into a single code path.
|
|
52
|
+
- **`saveRelation` split**: Decomposed the 61-line monolith into a dispatcher + 3 focused helpers by cardinality: `saveToMany` (1:M + M:M), `saveOneToOne` (1:1), `saveManyToOne` (M:1).
|
|
53
|
+
- **`buildAggregateStages` complexity reduction**: Extracted `buildHavingFilter()` helper from the MongoDB aggregation pipeline builder, bringing cognitive complexity under the linter threshold.
|
|
54
|
+
- **`deleteMany` DRY**: Eliminated duplicated `emitHook → internalDeleteMany → deleteRelations` logic by reusing the `resolveEntityAndQuery()` pattern.
|
|
55
|
+
- **`directionMap` deduplication**: Extracted the `asc/desc → 1/-1` mapping into a static `SORT_DIRECTION_MAP` constant shared by `sort()` and `aggregateSort()`.
|
|
56
|
+
- **`parseGroupMap` shared utility**: Eliminated `$group` parsing duplication between SQL and MongoDB dialects with a single generator function in `dialect.util.ts`.
|
|
57
|
+
- **`transformOperators` compaction**: Replaced verbose `if/else if` chains with a static `MONGO_COMPARISON_OP_MAP` lookup, and absorbed `$like`/`$ilike` into `REGEX_OP_MAP`.
|
|
58
|
+
- **`putChildrenInParents` simplification**: Simplified child-grouping loop using explicit initialization pattern.
|
|
59
|
+
- **`findManyAndCount` cleanup**: Replaced spread + triple-delete mutation with clean destructuring.
|
|
60
|
+
- **`insertRelations` cleanup**: Replaced `.map()` with implicit undefined return with `.filter().map()` pattern.
|
|
61
|
+
- **Dead code removal**: Removed dead `Array.isArray` branch in `fillToManyRelations` (array-based `$select` was removed in 3.14.0), dead `Promise.resolve()` in async context, unused generic type parameter.
|
|
62
|
+
- **`havingCondition` visibility**: Changed from `private` to `protected` to allow dialect subclass overrides.
|
|
63
|
+
- **`insertRelations` DRY**: Eliminated double `filterPersistableRelationKeys` call per item using a single `.reduce()` pass.
|
|
64
|
+
- **`where()` loop**: Replaced `.reduce()` accumulator in MongoDB `where()` with a cleaner `for...of` loop.
|
|
65
|
+
- **`_id` constant**: Extracted repeated `'_id'` string literal to `MongoDialect.ID_KEY` class constant.
|
|
66
|
+
- **`negateOperatorMap` static**: Promoted per-call `negateOperatorMap` allocation in `compareLogicalOperator` to `static readonly NEGATE_OP_MAP`.
|
|
67
|
+
- **`AGGREGATE_OP_MAP` class-level**: Moved module-level `MONGO_AGGREGATE_OP_MAP` to `MongoDialect.AGGREGATE_OP_MAP` static, consistent with `REGEX_OP_MAP` and `NATIVE_OPS`.
|
|
68
|
+
|
|
69
|
+
### Test Coverage
|
|
70
|
+
- Added comprehensive tests for `aggregate()` (all SQL dialects + MongoDB pipeline stages), HAVING `$in`/`$nin`/`$isNull`/`$isNotNull`, sort with numeric `-1`, mixed sort directions, MongoDB string-to-numeric sort normalization, aggregate pagination, `parseGroupMap` (edge cases), and `deleteMany` dual-API pattern. All coverage thresholds met.
|
|
27
71
|
|
|
28
72
|
## [0.2.1] - 2026-03-08
|
|
29
73
|
### New Features
|
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ const users = await querier.findMany(User, {
|
|
|
30
30
|
| **Thread-Safe by Design** | Centralized task queue and `@Serialized()` decorator prevent race conditions. |
|
|
31
31
|
| **[Declarative Transactions](https://uql-orm.dev/transactions)** | Standard `@Transactional()` and `@InjectQuerier()` decorators for NestJS/DI. |
|
|
32
32
|
| **[Lifecycle Hooks](https://uql-orm.dev/entities/lifecycle-hooks)**| `@BeforeInsert`, `@AfterLoad` and 5 more decorators for validation, timestamps, and computed fields. |
|
|
33
|
+
| **[Aggregate Queries](https://uql-orm.dev/querying/aggregate)** | `GROUP BY`, `HAVING`, `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, and `DISTINCT` across all dialects. |
|
|
33
34
|
| **[Modern & Versatile](https://uql-orm.dev/entities/virtual-fields)** | **Pure ESM**, high-res timing, [Soft-delete](https://uql-orm.dev/entities/soft-delete), and **Vector/JSONB/JSON** support. |
|
|
34
35
|
| **[Database Migrations](https://www.uql-orm.dev/migrations)** | Built-in [Entity-First synchronization](https://uql-orm.dev/migrations#3-entity-first-synchronization-development) and a robust CLI for version-controlled schema evolution. |
|
|
35
36
|
| **[Logging & Monitoring](https://www.uql-orm.dev/logging)** | Professional-grade monitoring with slow-query detection and colored output. |
|
|
@@ -65,14 +66,12 @@ Ensure your `tsconfig.json` is configured to support decorators and metadata:
|
|
|
65
66
|
{
|
|
66
67
|
"compilerOptions": {
|
|
67
68
|
"experimentalDecorators": true,
|
|
68
|
-
"emitDecoratorMetadata": true
|
|
69
|
-
"module": "NodeNext",
|
|
70
|
-
"target": "ESNext"
|
|
69
|
+
"emitDecoratorMetadata": true
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
**Note:**
|
|
74
|
+
**Note:** UQL is Pure ESM — ensure your project's `module` supports ESM imports (e.g., `NodeNext`, `ESNext`, `Bundler`).
|
|
76
75
|
|
|
77
76
|
## 2. Define the Entities
|
|
78
77
|
|
|
@@ -351,6 +350,48 @@ const posts = await querier.findMany(Post, {
|
|
|
351
350
|
|
|
352
351
|
|
|
353
352
|
|
|
353
|
+
### Aggregate Queries
|
|
354
|
+
|
|
355
|
+
Use `querier.aggregate()` for `GROUP BY` analytics with `$count`, `$sum`, `$avg`, `$min`, `$max`, and full `$having` support.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const results = await querier.aggregate(Order, {
|
|
359
|
+
$group: {
|
|
360
|
+
status: true,
|
|
361
|
+
total: { $sum: 'amount' },
|
|
362
|
+
count: { $count: '*' },
|
|
363
|
+
},
|
|
364
|
+
$having: { count: { $gt: 5 } },
|
|
365
|
+
$sort: { total: -1 },
|
|
366
|
+
$limit: 10,
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Generated SQL (PostgreSQL):**
|
|
371
|
+
|
|
372
|
+
```sql
|
|
373
|
+
SELECT "status", SUM("amount") "total", COUNT(*) "count"
|
|
374
|
+
FROM "Order"
|
|
375
|
+
GROUP BY "status"
|
|
376
|
+
HAVING COUNT(*) > $1
|
|
377
|
+
ORDER BY SUM("amount") DESC
|
|
378
|
+
LIMIT 10
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
For `SELECT DISTINCT`, add `$distinct: true` to any find query:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
const names = await querier.findMany(User, {
|
|
385
|
+
$select: { name: true },
|
|
386
|
+
$distinct: true,
|
|
387
|
+
});
|
|
388
|
+
// → SELECT DISTINCT "name" FROM "User"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
> **Learn more**: See the full [Aggregate Queries guide](https://uql-orm.dev/querying/aggregate) for `$having` operators, MongoDB pipeline details, and advanced patterns.
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
354
395
|
### Thread-Safe Transactions
|
|
355
396
|
|
|
356
397
|
UQL is one of the few ORMs with a **centralized serialization engine**. Transactions are guaranteed to be race-condition free.
|
|
@@ -535,6 +576,7 @@ error: Failed to connect to database: Connection timeout
|
|
|
535
576
|
Learn more about UQL at [uql-orm.dev](https://uql-orm.dev) for details on:
|
|
536
577
|
|
|
537
578
|
- [Complex Logical Operators](https://uql-orm.dev/querying/logical-operators)
|
|
579
|
+
- [Aggregate Queries (GROUP BY, HAVING, DISTINCT)](https://uql-orm.dev/querying/aggregate)
|
|
538
580
|
- [Relationship Mapping (1-1, 1-M, M-M)](https://uql-orm.dev/querying/relations)
|
|
539
581
|
- [Lifecycle Hooks](https://uql-orm.dev/entities/lifecycle-hooks)
|
|
540
582
|
- [Soft Deletes & Auditing](https://uql-orm.dev/entities/soft-delete)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonMergeOp, type Query, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelect, type QuerySelectOptions, type QuerySortMap, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationOptions, type SqlDialect, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
|
|
1
|
+
import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonMergeOp, type Query, type QueryAggregate, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelect, type QuerySelectOptions, type QuerySortMap, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationOptions, type SqlDialect, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
|
|
2
2
|
import { type CallbackKey } from '../util/index.js';
|
|
3
3
|
import { AbstractDialect } from './abstractDialect.js';
|
|
4
4
|
export declare abstract class AbstractSqlDialect extends AbstractDialect implements QueryDialect, SqlQueryDialect {
|
|
@@ -14,7 +14,7 @@ export declare abstract class AbstractSqlDialect extends AbstractDialect impleme
|
|
|
14
14
|
returningId<E>(entity: Type<E>): string;
|
|
15
15
|
search<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions): void;
|
|
16
16
|
selectFields<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelect<E> | QueryRaw[] | undefined, opts?: QuerySelectOptions): void;
|
|
17
|
-
select<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelect<E> | QueryRaw[] | undefined, opts?: QueryOptions): void;
|
|
17
|
+
select<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelect<E> | QueryRaw[] | undefined, opts?: QueryOptions, distinct?: boolean): void;
|
|
18
18
|
protected selectRelationFields<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelect<E> | undefined, opts?: {
|
|
19
19
|
prefix?: string;
|
|
20
20
|
}): void;
|
|
@@ -29,6 +29,11 @@ export declare abstract class AbstractSqlDialect extends AbstractDialect impleme
|
|
|
29
29
|
where<E>(ctx: QueryContext, entity: Type<E>, where?: QueryWhere<E>, opts?: QueryWhereOptions): void;
|
|
30
30
|
compare<E>(ctx: QueryContext, entity: Type<E>, key: string, val: unknown, opts?: QueryComparisonOptions): void;
|
|
31
31
|
protected compareLogicalOperator<E>(ctx: QueryContext, entity: Type<E>, key: '$and' | '$or' | '$not' | '$nor', val: QueryWhereArray<E>, opts: QueryComparisonOptions): void;
|
|
32
|
+
/** Simple comparison operators: `getComparisonKey → op → addValue`. */
|
|
33
|
+
private static readonly NEGATE_OP_MAP;
|
|
34
|
+
private static readonly COMPARE_OP_MAP;
|
|
35
|
+
/** String/LIKE operators: wrap the value, optionally lowercase it. */
|
|
36
|
+
private static readonly LIKE_OP_MAP;
|
|
32
37
|
compareFieldOperator<E, K extends keyof QueryWhereFieldOperatorMap<E>>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, op: K, val: QueryWhereFieldOperatorMap<E>[K], opts?: QueryOptions): void;
|
|
33
38
|
protected addValues(ctx: QueryContext, vals: unknown[]): void;
|
|
34
39
|
/**
|
|
@@ -40,6 +45,15 @@ export declare abstract class AbstractSqlDialect extends AbstractDialect impleme
|
|
|
40
45
|
sort<E>(ctx: QueryContext, entity: Type<E>, sort: QuerySortMap<E> | undefined, { prefix }: QueryOptions): void;
|
|
41
46
|
pager(ctx: QueryContext, opts: QueryPager): void;
|
|
42
47
|
count<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): void;
|
|
48
|
+
aggregate<E>(ctx: QueryContext, entity: Type<E>, q: QueryAggregate<E>, opts?: QueryOptions): void;
|
|
49
|
+
/**
|
|
50
|
+
* ORDER BY for aggregate queries — handles both entity-field and alias references.
|
|
51
|
+
*/
|
|
52
|
+
private aggregateSort;
|
|
53
|
+
protected having(ctx: QueryContext, having: QueryHavingMap, aggregateExpressions: Record<string, string>): void;
|
|
54
|
+
private static readonly SORT_DIRECTION_MAP;
|
|
55
|
+
private static readonly havingOpMap;
|
|
56
|
+
protected havingCondition(ctx: QueryContext, expr: string, condition: QueryHavingMap[string]): void;
|
|
43
57
|
find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions): void;
|
|
44
58
|
insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
|
|
45
59
|
update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abstractSqlDialect.d.ts","sourceRoot":"","sources":["../../src/dialect/abstractSqlDialect.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,YAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,KAAK,EACV,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,QAAQ,EACR,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EAEvB,KAAK,YAAY,EAEjB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAGtB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,IAAI,EACT,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGL,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"abstractSqlDialect.d.ts","sourceRoot":"","sources":["../../src/dialect/abstractSqlDialect.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
2
|
import { QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
|
|
3
|
-
import { buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, filterRelationKeys, flatObject, getFieldCallbackValue, getFieldKeys, getKeys, hasKeys, isJsonType, isSelectingRelations, raw, } from '../util/index.js';
|
|
3
|
+
import { buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, filterRelationKeys, flatObject, getFieldCallbackValue, getFieldKeys, getKeys, hasKeys, isJsonType, isSelectingRelations, parseGroupMap, raw, } from '../util/index.js';
|
|
4
4
|
import { AbstractDialect } from './abstractDialect.js';
|
|
5
5
|
import { SqlQueryContext } from './queryContext.js';
|
|
6
6
|
export class AbstractSqlDialect extends AbstractDialect {
|
|
@@ -128,12 +128,12 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
-
select(ctx, entity, select, opts = {}) {
|
|
131
|
+
select(ctx, entity, select, opts = {}, distinct) {
|
|
132
132
|
const meta = getMeta(entity);
|
|
133
133
|
const tableName = this.resolveTableName(entity, meta);
|
|
134
134
|
const mapSelect = Array.isArray(select) ? undefined : select;
|
|
135
135
|
const prefix = (opts.prefix ?? (opts.autoPrefix || isSelectingRelations(meta, mapSelect))) ? tableName : undefined;
|
|
136
|
-
ctx.append('SELECT ');
|
|
136
|
+
ctx.append(distinct ? 'SELECT DISTINCT ' : 'SELECT ');
|
|
137
137
|
this.selectFields(ctx, entity, select, { prefix });
|
|
138
138
|
// Add related fields BEFORE FROM clause
|
|
139
139
|
this.selectRelationFields(ctx, entity, mapSelect, { prefix });
|
|
@@ -313,12 +313,8 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
compareLogicalOperator(ctx, entity, key, val, opts) {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
$nor: '$or',
|
|
319
|
-
};
|
|
320
|
-
const op = negateOperatorMap[key] ?? key;
|
|
321
|
-
const negate = key in negateOperatorMap ? 'NOT' : '';
|
|
316
|
+
const op = AbstractSqlDialect.NEGATE_OP_MAP[key] ?? key;
|
|
317
|
+
const negate = key in AbstractSqlDialect.NEGATE_OP_MAP ? 'NOT' : '';
|
|
322
318
|
const valArr = val ?? [];
|
|
323
319
|
const hasManyItems = valArr.length > 1;
|
|
324
320
|
if ((opts.usePrecedence || negate) && hasManyItems) {
|
|
@@ -348,120 +344,75 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
348
344
|
ctx.append(')');
|
|
349
345
|
}
|
|
350
346
|
}
|
|
347
|
+
/** Simple comparison operators: `getComparisonKey → op → addValue`. */
|
|
348
|
+
static NEGATE_OP_MAP = { $not: '$and', $nor: '$or' };
|
|
349
|
+
static COMPARE_OP_MAP = {
|
|
350
|
+
$gt: ' > ',
|
|
351
|
+
$gte: ' >= ',
|
|
352
|
+
$lt: ' < ',
|
|
353
|
+
$lte: ' <= ',
|
|
354
|
+
$regex: ' REGEXP ',
|
|
355
|
+
};
|
|
356
|
+
/** String/LIKE operators: wrap the value, optionally lowercase it. */
|
|
357
|
+
static LIKE_OP_MAP = {
|
|
358
|
+
$startsWith: { wrap: (v) => `${v}%`, lower: false },
|
|
359
|
+
$istartsWith: { wrap: (v) => `${v.toLowerCase()}%`, lower: false },
|
|
360
|
+
$endsWith: { wrap: (v) => `%${v}`, lower: false },
|
|
361
|
+
$iendsWith: { wrap: (v) => `%${v.toLowerCase()}`, lower: false },
|
|
362
|
+
$includes: { wrap: (v) => `%${v}%`, lower: false },
|
|
363
|
+
$iincludes: { wrap: (v) => `%${v.toLowerCase()}%`, lower: false },
|
|
364
|
+
$like: { wrap: (v) => v, lower: false },
|
|
365
|
+
$ilike: { wrap: (v) => v.toLowerCase(), lower: false },
|
|
366
|
+
};
|
|
351
367
|
compareFieldOperator(ctx, entity, key, op, val, opts = {}) {
|
|
368
|
+
// Simple comparison operators (5 cases)
|
|
369
|
+
const simpleOp = AbstractSqlDialect.COMPARE_OP_MAP[op];
|
|
370
|
+
if (simpleOp) {
|
|
371
|
+
this.getComparisonKey(ctx, entity, key, opts);
|
|
372
|
+
ctx.append(simpleOp);
|
|
373
|
+
ctx.addValue(val);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
// LIKE string operators (8 cases)
|
|
377
|
+
const likeEntry = AbstractSqlDialect.LIKE_OP_MAP[op];
|
|
378
|
+
if (likeEntry) {
|
|
379
|
+
this.getComparisonKey(ctx, entity, key, opts);
|
|
380
|
+
ctx.append(' LIKE ');
|
|
381
|
+
ctx.addValue(likeEntry.wrap(val));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
352
384
|
switch (op) {
|
|
353
385
|
case '$eq':
|
|
354
386
|
this.getComparisonKey(ctx, entity, key, opts);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
ctx.append(' = ');
|
|
387
|
+
ctx.append(val === null ? ' IS NULL' : ' = ');
|
|
388
|
+
if (val !== null)
|
|
360
389
|
ctx.addValue(val);
|
|
361
|
-
}
|
|
362
390
|
break;
|
|
363
391
|
case '$ne':
|
|
364
392
|
this.getComparisonKey(ctx, entity, key, opts);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
ctx.append(' <> ');
|
|
393
|
+
ctx.append(val === null ? ' IS NOT NULL' : ' <> ');
|
|
394
|
+
if (val !== null)
|
|
370
395
|
ctx.addValue(val);
|
|
371
|
-
}
|
|
372
396
|
break;
|
|
373
397
|
case '$not':
|
|
374
398
|
ctx.append('NOT (');
|
|
375
399
|
this.compare(ctx, entity, key, val, opts);
|
|
376
400
|
ctx.append(')');
|
|
377
401
|
break;
|
|
378
|
-
case '$gt':
|
|
379
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
380
|
-
ctx.append(' > ');
|
|
381
|
-
ctx.addValue(val);
|
|
382
|
-
break;
|
|
383
|
-
case '$gte':
|
|
384
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
385
|
-
ctx.append(' >= ');
|
|
386
|
-
ctx.addValue(val);
|
|
387
|
-
break;
|
|
388
|
-
case '$lt':
|
|
389
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
390
|
-
ctx.append(' < ');
|
|
391
|
-
ctx.addValue(val);
|
|
392
|
-
break;
|
|
393
|
-
case '$lte':
|
|
394
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
395
|
-
ctx.append(' <= ');
|
|
396
|
-
ctx.addValue(val);
|
|
397
|
-
break;
|
|
398
|
-
case '$startsWith':
|
|
399
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
400
|
-
ctx.append(' LIKE ');
|
|
401
|
-
ctx.addValue(`${val}%`);
|
|
402
|
-
break;
|
|
403
|
-
case '$istartsWith':
|
|
404
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
405
|
-
ctx.append(' LIKE ');
|
|
406
|
-
ctx.addValue(`${val.toLowerCase()}%`);
|
|
407
|
-
break;
|
|
408
|
-
case '$endsWith':
|
|
409
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
410
|
-
ctx.append(' LIKE ');
|
|
411
|
-
ctx.addValue(`%${val}`);
|
|
412
|
-
break;
|
|
413
|
-
case '$iendsWith':
|
|
414
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
415
|
-
ctx.append(' LIKE ');
|
|
416
|
-
ctx.addValue(`%${val.toLowerCase()}`);
|
|
417
|
-
break;
|
|
418
|
-
case '$includes':
|
|
419
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
420
|
-
ctx.append(' LIKE ');
|
|
421
|
-
ctx.addValue(`%${val}%`);
|
|
422
|
-
break;
|
|
423
|
-
case '$iincludes':
|
|
424
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
425
|
-
ctx.append(' LIKE ');
|
|
426
|
-
ctx.addValue(`%${val.toLowerCase()}%`);
|
|
427
|
-
break;
|
|
428
|
-
case '$ilike':
|
|
429
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
430
|
-
ctx.append(' LIKE ');
|
|
431
|
-
ctx.addValue(val.toLowerCase());
|
|
432
|
-
break;
|
|
433
|
-
case '$like':
|
|
434
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
435
|
-
ctx.append(' LIKE ');
|
|
436
|
-
ctx.addValue(val);
|
|
437
|
-
break;
|
|
438
402
|
case '$in':
|
|
403
|
+
case '$nin': {
|
|
439
404
|
this.getComparisonKey(ctx, entity, key, opts);
|
|
405
|
+
const keyword = op === '$in' ? ' IN ' : ' NOT IN ';
|
|
440
406
|
if (Array.isArray(val) && val.length > 0) {
|
|
441
|
-
ctx.append(
|
|
442
|
-
this.addValues(ctx, val);
|
|
443
|
-
ctx.append(')');
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
ctx.append(' IN (NULL)');
|
|
447
|
-
}
|
|
448
|
-
break;
|
|
449
|
-
case '$nin':
|
|
450
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
451
|
-
if (Array.isArray(val) && val.length > 0) {
|
|
452
|
-
ctx.append(' NOT IN (');
|
|
407
|
+
ctx.append(`${keyword}(`);
|
|
453
408
|
this.addValues(ctx, val);
|
|
454
409
|
ctx.append(')');
|
|
455
410
|
}
|
|
456
411
|
else {
|
|
457
|
-
ctx.append(
|
|
412
|
+
ctx.append(`${keyword}(NULL)`);
|
|
458
413
|
}
|
|
459
414
|
break;
|
|
460
|
-
|
|
461
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
462
|
-
ctx.append(' REGEXP ');
|
|
463
|
-
ctx.addValue(val);
|
|
464
|
-
break;
|
|
415
|
+
}
|
|
465
416
|
case '$between': {
|
|
466
417
|
const [min, max] = val;
|
|
467
418
|
this.getComparisonKey(ctx, entity, key, opts);
|
|
@@ -482,7 +433,6 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
482
433
|
case '$all':
|
|
483
434
|
case '$size':
|
|
484
435
|
case '$elemMatch':
|
|
485
|
-
// Each SQL dialect must provide its own implementation
|
|
486
436
|
throw TypeError(`${op} is not supported in the base SQL dialect - override in dialect subclass`);
|
|
487
437
|
default:
|
|
488
438
|
throw TypeError(`unknown operator: ${op}`);
|
|
@@ -577,13 +527,12 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
577
527
|
}
|
|
578
528
|
const meta = getMeta(entity);
|
|
579
529
|
const flattenedSort = flatObject(sortMap, prefix);
|
|
580
|
-
const directionMap = { 1: '', asc: '', '-1': ' DESC', desc: ' DESC' };
|
|
581
530
|
ctx.append(' ORDER BY ');
|
|
582
531
|
Object.entries(flattenedSort).forEach(([key, sort], index) => {
|
|
583
532
|
if (index > 0) {
|
|
584
533
|
ctx.append(', ');
|
|
585
534
|
}
|
|
586
|
-
const direction =
|
|
535
|
+
const direction = AbstractSqlDialect.SORT_DIRECTION_MAP[sort];
|
|
587
536
|
// Detect JSONB dot-notation: 'column.path'
|
|
588
537
|
const jsonDot = this.resolveJsonDotPath(meta, key);
|
|
589
538
|
if (jsonDot) {
|
|
@@ -609,8 +558,125 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
609
558
|
this.select(ctx, entity, [raw('COUNT(*)', 'count')]);
|
|
610
559
|
this.search(ctx, entity, search, opts);
|
|
611
560
|
}
|
|
561
|
+
aggregate(ctx, entity, q, opts = {}) {
|
|
562
|
+
const meta = getMeta(entity);
|
|
563
|
+
const tableName = this.resolveTableName(entity, meta);
|
|
564
|
+
const groupKeys = [];
|
|
565
|
+
const selectParts = [];
|
|
566
|
+
const aggregateExpressions = {};
|
|
567
|
+
for (const entry of parseGroupMap(q.$group)) {
|
|
568
|
+
if (entry.kind === 'key') {
|
|
569
|
+
const field = meta.fields[entry.alias];
|
|
570
|
+
const columnName = this.resolveColumnName(entry.alias, field);
|
|
571
|
+
const escaped = this.escapeId(columnName);
|
|
572
|
+
groupKeys.push(escaped);
|
|
573
|
+
selectParts.push(columnName !== entry.alias ? `${escaped} ${this.escapeId(entry.alias)}` : escaped);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
const sqlFn = entry.op.slice(1).toUpperCase();
|
|
577
|
+
const sqlArg = entry.fieldRef === '*'
|
|
578
|
+
? '*'
|
|
579
|
+
: this.escapeId(this.resolveColumnName(entry.fieldRef, meta.fields[entry.fieldRef]));
|
|
580
|
+
const expr = `${sqlFn}(${sqlArg})`;
|
|
581
|
+
aggregateExpressions[entry.alias] = expr;
|
|
582
|
+
selectParts.push(`${expr} ${this.escapeId(entry.alias)}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
ctx.append(`SELECT ${selectParts.join(', ')} FROM ${this.escapeId(tableName)}`);
|
|
586
|
+
this.where(ctx, entity, q.$where, opts);
|
|
587
|
+
if (groupKeys.length) {
|
|
588
|
+
ctx.append(` GROUP BY ${groupKeys.join(', ')}`);
|
|
589
|
+
}
|
|
590
|
+
if (q.$having) {
|
|
591
|
+
this.having(ctx, q.$having, aggregateExpressions);
|
|
592
|
+
}
|
|
593
|
+
this.aggregateSort(ctx, q.$sort, aggregateExpressions);
|
|
594
|
+
this.pager(ctx, q);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* ORDER BY for aggregate queries — handles both entity-field and alias references.
|
|
598
|
+
*/
|
|
599
|
+
aggregateSort(ctx, sort, aggregateExpressions) {
|
|
600
|
+
const sortMap = buildSortMap(sort);
|
|
601
|
+
if (!hasKeys(sortMap))
|
|
602
|
+
return;
|
|
603
|
+
ctx.append(' ORDER BY ');
|
|
604
|
+
Object.entries(sortMap).forEach(([key, dir], index) => {
|
|
605
|
+
if (index > 0)
|
|
606
|
+
ctx.append(', ');
|
|
607
|
+
const direction = AbstractSqlDialect.SORT_DIRECTION_MAP[dir];
|
|
608
|
+
const ref = aggregateExpressions[key] ?? this.escapeId(key);
|
|
609
|
+
ctx.append(ref + direction);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
having(ctx, having, aggregateExpressions) {
|
|
613
|
+
const entries = Object.entries(having).filter(([, v]) => v !== undefined);
|
|
614
|
+
if (!entries.length)
|
|
615
|
+
return;
|
|
616
|
+
ctx.append(' HAVING ');
|
|
617
|
+
entries.forEach(([alias, condition], index) => {
|
|
618
|
+
if (index > 0)
|
|
619
|
+
ctx.append(' AND ');
|
|
620
|
+
const expr = aggregateExpressions[alias] ?? this.escapeId(alias);
|
|
621
|
+
this.havingCondition(ctx, expr, condition);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
static SORT_DIRECTION_MAP = Object.assign({ 1: '', asc: '', desc: ' DESC', '-1': ' DESC' }, { [-1]: ' DESC' });
|
|
625
|
+
static havingOpMap = {
|
|
626
|
+
$eq: '=',
|
|
627
|
+
$ne: '<>',
|
|
628
|
+
$gt: '>',
|
|
629
|
+
$gte: '>=',
|
|
630
|
+
$lt: '<',
|
|
631
|
+
$lte: '<=',
|
|
632
|
+
};
|
|
633
|
+
havingCondition(ctx, expr, condition) {
|
|
634
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
635
|
+
ctx.append(`${expr} = `);
|
|
636
|
+
ctx.addValue(condition);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const ops = condition;
|
|
640
|
+
const keys = getKeys(ops);
|
|
641
|
+
keys.forEach((op, i) => {
|
|
642
|
+
if (i > 0)
|
|
643
|
+
ctx.append(' AND ');
|
|
644
|
+
const val = ops[op];
|
|
645
|
+
if (op === '$between') {
|
|
646
|
+
const [min, max] = val;
|
|
647
|
+
ctx.append(`${expr} BETWEEN `);
|
|
648
|
+
ctx.addValue(min);
|
|
649
|
+
ctx.append(' AND ');
|
|
650
|
+
ctx.addValue(max);
|
|
651
|
+
}
|
|
652
|
+
else if (op === '$in' || op === '$nin') {
|
|
653
|
+
const keyword = op === '$in' ? 'IN' : 'NOT IN';
|
|
654
|
+
if (Array.isArray(val) && val.length > 0) {
|
|
655
|
+
ctx.append(`${expr} ${keyword} (`);
|
|
656
|
+
this.addValues(ctx, val);
|
|
657
|
+
ctx.append(')');
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
ctx.append(`${expr} ${keyword} (NULL)`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (op === '$isNull') {
|
|
664
|
+
ctx.append(`${expr}${val ? ' IS NULL' : ' IS NOT NULL'}`);
|
|
665
|
+
}
|
|
666
|
+
else if (op === '$isNotNull') {
|
|
667
|
+
ctx.append(`${expr}${val ? ' IS NOT NULL' : ' IS NULL'}`);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
const sqlOp = AbstractSqlDialect.havingOpMap[op];
|
|
671
|
+
if (!sqlOp)
|
|
672
|
+
throw TypeError(`unsupported HAVING operator: ${op}`);
|
|
673
|
+
ctx.append(`${expr} ${sqlOp} `);
|
|
674
|
+
ctx.addValue(val);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
612
678
|
find(ctx, entity, q = {}, opts) {
|
|
613
|
-
this.select(ctx, entity, q.$select, opts);
|
|
679
|
+
this.select(ctx, entity, q.$select, opts, q.$distinct);
|
|
614
680
|
this.search(ctx, entity, q, opts);
|
|
615
681
|
}
|
|
616
682
|
insert(ctx, entity, payload, opts) {
|