@cheetah.js/orm 0.1.147 → 0.1.150

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.
@@ -5,6 +5,7 @@ const entities_1 = require("./domain/entities");
5
5
  const orm_1 = require("./orm");
6
6
  const value_processor_1 = require("./utils/value-processor");
7
7
  const sql_condition_builder_1 = require("./query/sql-condition-builder");
8
+ const sql_subquery_builder_1 = require("./query/sql-subquery-builder");
8
9
  const model_transformer_1 = require("./query/model-transformer");
9
10
  const sql_column_manager_1 = require("./query/sql-column-manager");
10
11
  const sql_join_manager_1 = require("./query/sql-join-manager");
@@ -27,6 +28,8 @@ class SqlBuilder {
27
28
  return this.joinManager.applyJoin(relationship, value, alias);
28
29
  };
29
30
  this.conditionBuilder = new sql_condition_builder_1.SqlConditionBuilder(this.entityStorage, applyJoinWrapper, this.statements);
31
+ const subqueryBuilder = new sql_subquery_builder_1.SqlSubqueryBuilder(this.entityStorage, () => this.conditionBuilder);
32
+ this.conditionBuilder.setSubqueryBuilder(subqueryBuilder);
30
33
  this.joinManager = new sql_join_manager_1.SqlJoinManager(this.entityStorage, this.statements, this.entity, this.model, this.driver, this.logger, this.conditionBuilder, this.columnManager, this.modelTransformer, () => this.originalColumns, this.getAlias.bind(this));
31
34
  }
32
35
  initializeCacheManager() {
@@ -248,6 +248,8 @@ export type OperatorMap<T> = {
248
248
  $lt?: ExpandScalar<T>;
249
249
  $lte?: ExpandScalar<T>;
250
250
  $like?: string;
251
+ $exists?: FilterQuery<ExpandProperty<T>>;
252
+ $nexists?: FilterQuery<ExpandProperty<T>>;
251
253
  };
252
254
  export type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? never : K extends symbol ? never : K;
253
255
  export type Scalar = boolean | number | string | bigint | symbol | Date | RegExp | Uint8Array | {
@@ -1,5 +1,6 @@
1
1
  import { FilterQuery, Relationship, Statement } from '../driver/driver.interface';
2
2
  import { EntityStorage } from '../domain/entities';
3
+ import { SqlSubqueryBuilder } from './sql-subquery-builder';
3
4
  type ApplyJoinCallback = (relationship: Relationship<any>, value: FilterQuery<any>, alias: string) => string;
4
5
  export declare class SqlConditionBuilder<T> {
5
6
  private entityStorage;
@@ -7,10 +8,13 @@ export declare class SqlConditionBuilder<T> {
7
8
  private statements;
8
9
  private readonly OPERATORS;
9
10
  private lastKeyNotOperator;
11
+ private subqueryBuilder?;
10
12
  constructor(entityStorage: EntityStorage, applyJoinCallback: ApplyJoinCallback, statements: Statement<T>);
13
+ setSubqueryBuilder(subqueryBuilder: SqlSubqueryBuilder): void;
11
14
  build(condition: FilterQuery<T>, alias: string, model: Function): string;
12
15
  private processConditions;
13
16
  private processEntry;
17
+ private hasExistsOperator;
14
18
  private handleRelationship;
15
19
  private handleScalarValue;
16
20
  private handleObjectValue;
@@ -39,6 +43,8 @@ export declare class SqlConditionBuilder<T> {
39
43
  private isLogicalOperator;
40
44
  private extractLogicalOperator;
41
45
  private trackLastNonOperatorKey;
46
+ private buildExistsCondition;
47
+ private buildTopLevelExistsCondition;
42
48
  private resolveColumnName;
43
49
  private resolveRelationColumn;
44
50
  }
@@ -8,15 +8,19 @@ class SqlConditionBuilder {
8
8
  this.entityStorage = entityStorage;
9
9
  this.applyJoinCallback = applyJoinCallback;
10
10
  this.statements = statements;
11
- this.OPERATORS = ['$eq', '$ne', '$in', '$nin', '$like', '$gt', '$gte', '$lt', '$lte', '$and', '$or'];
11
+ this.OPERATORS = ['$eq', '$ne', '$in', '$nin', '$like', '$gt', '$gte', '$lt', '$lte', '$and', '$or', '$exists', '$nexists'];
12
12
  this.lastKeyNotOperator = '';
13
13
  }
14
+ setSubqueryBuilder(subqueryBuilder) {
15
+ this.subqueryBuilder = subqueryBuilder;
16
+ }
14
17
  build(condition, alias, model) {
15
18
  const sqlParts = this.processConditions(condition, alias, model);
16
19
  if (sqlParts.length === 0) {
17
20
  return '';
18
21
  }
19
- return this.wrapWithLogicalOperator(sqlParts, 'AND');
22
+ const result = this.wrapWithLogicalOperator(sqlParts, 'AND');
23
+ return result;
20
24
  }
21
25
  processConditions(condition, alias, model) {
22
26
  const sqlParts = [];
@@ -32,7 +36,7 @@ class SqlConditionBuilder {
32
36
  processEntry(key, value, alias, model) {
33
37
  this.trackLastNonOperatorKey(key, model);
34
38
  const relationship = this.findRelationship(key, model);
35
- if (relationship) {
39
+ if (relationship && !this.hasExistsOperator(value)) {
36
40
  return this.handleRelationship(relationship, value, alias);
37
41
  }
38
42
  if (this.isScalarValue(value)) {
@@ -43,6 +47,9 @@ class SqlConditionBuilder {
43
47
  }
44
48
  return this.handleObjectValue(key, value, alias, model);
45
49
  }
50
+ hasExistsOperator(value) {
51
+ return typeof value === 'object' && value !== null && ('$exists' in value || '$nexists' in value);
52
+ }
46
53
  handleRelationship(relationship, value, alias) {
47
54
  const sql = this.applyJoinCallback(relationship, value, alias);
48
55
  if (this.statements.strategy === 'joined') {
@@ -60,6 +67,9 @@ class SqlConditionBuilder {
60
67
  if (this.isLogicalOperator(key)) {
61
68
  return this.buildLogicalOperatorCondition(key, value, alias, model);
62
69
  }
70
+ if (key === '$exists' || key === '$nexists') {
71
+ return this.buildTopLevelExistsCondition(key, value, alias, model);
72
+ }
63
73
  return this.buildOperatorConditions(key, value, alias, model);
64
74
  }
65
75
  buildLogicalOperatorCondition(key, value, alias, model) {
@@ -100,6 +110,10 @@ class SqlConditionBuilder {
100
110
  case '$and':
101
111
  case '$or':
102
112
  return this.buildNestedLogicalCondition(operator, value, alias, model);
113
+ case '$exists':
114
+ return this.buildExistsCondition(key, value, alias, model, false);
115
+ case '$nexists':
116
+ return this.buildExistsCondition(key, value, alias, model, true);
103
117
  default:
104
118
  return '';
105
119
  }
@@ -202,6 +216,31 @@ class SqlConditionBuilder {
202
216
  this.lastKeyNotOperator = key;
203
217
  }
204
218
  }
219
+ buildExistsCondition(key, filters, alias, model, negate) {
220
+ const relationship = this.findRelationship(key, model);
221
+ if (!relationship) {
222
+ const entity = this.entityStorage.get(model);
223
+ const availableRelations = entity?.relations
224
+ ?.map((r) => r.propertyKey)
225
+ .join(', ') || 'none';
226
+ throw new Error(`Cannot use $${negate ? 'nexists' : 'exists'} on non-relationship field '${key}'. ` +
227
+ `Available relationships: ${availableRelations}`);
228
+ }
229
+ if (!this.subqueryBuilder) {
230
+ throw new Error('SqlSubqueryBuilder not initialized. This is an internal error.');
231
+ }
232
+ return this.subqueryBuilder.buildExistsSubquery(relationship, filters, alias, negate, model);
233
+ }
234
+ buildTopLevelExistsCondition(operator, value, alias, model) {
235
+ const negate = operator === '$nexists';
236
+ const conditions = [];
237
+ for (const [relationshipKey, filters] of Object.entries(value)) {
238
+ const condition = this.buildExistsCondition(relationshipKey, filters, alias, model, negate);
239
+ conditions.push(condition);
240
+ }
241
+ const result = this.wrapWithLogicalOperator(conditions, 'AND');
242
+ return result;
243
+ }
205
244
  resolveColumnName(property, model) {
206
245
  if (property.startsWith('$')) {
207
246
  return property;
@@ -0,0 +1,20 @@
1
+ import { FilterQuery, Relationship } from '../driver/driver.interface';
2
+ import { EntityStorage } from '../domain/entities';
3
+ import { SqlConditionBuilder } from './sql-condition-builder';
4
+ export declare class SqlSubqueryBuilder {
5
+ private entityStorage;
6
+ private getConditionBuilder;
7
+ private aliasCounter;
8
+ constructor(entityStorage: EntityStorage, getConditionBuilder: () => SqlConditionBuilder<any>);
9
+ buildExistsSubquery(relationship: Relationship<any>, filters: FilterQuery<any>, outerAlias: string, negate: boolean, outerModel?: Function): string;
10
+ private buildSubquery;
11
+ private buildWhereClause;
12
+ private buildCorrelation;
13
+ private getOuterPrimaryKey;
14
+ private buildFilterConditions;
15
+ private combineConditions;
16
+ private resolveTableName;
17
+ private generateAlias;
18
+ private getFkKey;
19
+ private getRelatedPrimaryKey;
20
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqlSubqueryBuilder = void 0;
4
+ class SqlSubqueryBuilder {
5
+ constructor(entityStorage, getConditionBuilder) {
6
+ this.entityStorage = entityStorage;
7
+ this.getConditionBuilder = getConditionBuilder;
8
+ this.aliasCounter = 1;
9
+ }
10
+ buildExistsSubquery(relationship, filters, outerAlias, negate, outerModel) {
11
+ const prefix = negate ? 'NOT EXISTS' : 'EXISTS';
12
+ const subquery = this.buildSubquery(relationship, filters, outerAlias, outerModel);
13
+ return `${prefix} (${subquery})`;
14
+ }
15
+ buildSubquery(relationship, filters, outerAlias, outerModel) {
16
+ const subqueryAlias = this.generateAlias();
17
+ const tableName = this.resolveTableName(relationship);
18
+ const whereClause = this.buildWhereClause(relationship, filters, outerAlias, subqueryAlias, outerModel);
19
+ return `SELECT 1 FROM ${tableName} ${subqueryAlias} WHERE ${whereClause}`;
20
+ }
21
+ buildWhereClause(relationship, filters, outerAlias, subqueryAlias, outerModel) {
22
+ const correlation = this.buildCorrelation(relationship, outerAlias, subqueryAlias, outerModel);
23
+ const filterSql = this.buildFilterConditions(filters, subqueryAlias, relationship);
24
+ return this.combineConditions(correlation, filterSql);
25
+ }
26
+ buildCorrelation(relationship, outerAlias, subqueryAlias, outerModel) {
27
+ const fkKey = this.getFkKey(relationship);
28
+ const outerPkKey = this.getOuterPrimaryKey(relationship, outerModel);
29
+ const relatedPkKey = this.getRelatedPrimaryKey(relationship);
30
+ if (relationship.relation === 'one-to-many') {
31
+ return `${subqueryAlias}."${fkKey}" = ${outerAlias}."${outerPkKey}"`;
32
+ }
33
+ const outerFk = relationship.columnName;
34
+ return `${outerAlias}."${outerFk}" = ${subqueryAlias}."${relatedPkKey}"`;
35
+ }
36
+ getOuterPrimaryKey(relationship, outerModel) {
37
+ if (!outerModel) {
38
+ return 'id';
39
+ }
40
+ const entity = this.entityStorage.get(outerModel);
41
+ if (!entity) {
42
+ return 'id';
43
+ }
44
+ for (const prop in entity.properties) {
45
+ if (entity.properties[prop].options.isPrimary) {
46
+ return prop;
47
+ }
48
+ }
49
+ return 'id';
50
+ }
51
+ buildFilterConditions(filters, alias, relationship) {
52
+ if (!filters || Object.keys(filters).length === 0) {
53
+ return '';
54
+ }
55
+ const conditionBuilder = this.getConditionBuilder();
56
+ const entity = relationship.entity();
57
+ return conditionBuilder.build(filters, alias, entity);
58
+ }
59
+ combineConditions(correlation, filterSql) {
60
+ if (!filterSql) {
61
+ return correlation;
62
+ }
63
+ return `${correlation} AND ${filterSql}`;
64
+ }
65
+ resolveTableName(relationship) {
66
+ const entity = this.entityStorage.get(relationship.entity());
67
+ if (!entity) {
68
+ const name = relationship.entity().name.toLowerCase();
69
+ return `public.${name}`;
70
+ }
71
+ const schema = entity.schema || 'public';
72
+ const tableName = entity.tableName || relationship.entity().name.toLowerCase();
73
+ return `${schema}."${tableName}"`;
74
+ }
75
+ generateAlias() {
76
+ const alias = `sq${this.aliasCounter}`;
77
+ this.aliasCounter++;
78
+ return alias;
79
+ }
80
+ getFkKey(relationship) {
81
+ if (typeof relationship.fkKey === 'undefined') {
82
+ return 'id';
83
+ }
84
+ if (typeof relationship.fkKey === 'string') {
85
+ return relationship.fkKey;
86
+ }
87
+ const match = /\.(?<propriedade>[\w]+)/.exec(relationship.fkKey.toString());
88
+ const propertyKey = match ? match.groups.propriedade : '';
89
+ const entity = this.entityStorage.get(relationship.entity());
90
+ if (!entity) {
91
+ throw new Error(`Entity not found in storage for relationship. ` +
92
+ `Make sure the entity ${relationship.entity().name} is decorated with @Entity()`);
93
+ }
94
+ const property = Object.entries(entity.properties).find(([key, _value]) => key === propertyKey)?.[1];
95
+ if (property) {
96
+ return property.options.columnName;
97
+ }
98
+ const relation = entity.relations.find((rel) => rel.propertyKey === propertyKey);
99
+ if (relation && relation.columnName) {
100
+ return relation.columnName;
101
+ }
102
+ throw new Error(`Property or relation "${propertyKey}" not found in entity "${entity.tableName}". ` +
103
+ `Available properties: ${Object.keys(entity.properties).join(', ')}. ` +
104
+ `Available relations: ${entity.relations.map((r) => r.propertyKey).join(', ')}`);
105
+ }
106
+ getRelatedPrimaryKey(relationship) {
107
+ const entity = this.entityStorage.get(relationship.entity());
108
+ if (!entity) {
109
+ return 'id';
110
+ }
111
+ for (const prop in entity.properties) {
112
+ if (entity.properties[prop].options.isPrimary) {
113
+ return prop;
114
+ }
115
+ }
116
+ return 'id';
117
+ }
118
+ }
119
+ exports.SqlSubqueryBuilder = SqlSubqueryBuilder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheetah.js/orm",
3
- "version": "0.1.147",
3
+ "version": "0.1.150",
4
4
  "description": "A simple ORM for Cheetah.js.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -55,5 +55,5 @@
55
55
  "bun",
56
56
  "value-object"
57
57
  ],
58
- "gitHead": "9efbea28c5bb4bdc91e8aa6ba03a634f0a4ab00f"
58
+ "gitHead": "f8214d31758bc38e63d8cc2e4db4c5572db28201"
59
59
  }