linkgress-orm 0.4.12 → 0.4.14

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.
@@ -479,6 +479,7 @@ class SelectQueryBuilder {
479
479
  this.manualJoins = [];
480
480
  this.joinCounter = 0;
481
481
  this.isDistinct = false;
482
+ this._includeCountOver = false;
482
483
  this.ctes = []; // Track CTEs attached to this query
483
484
  this.schema = schema;
484
485
  this.client = client;
@@ -1053,6 +1054,57 @@ class SelectQueryBuilder {
1053
1054
  : await this.client.query(sql, params);
1054
1055
  return parseInt(result.rows[0]?.count ?? '0', 10);
1055
1056
  }
1057
+ /**
1058
+ * Execute query and return results with total count using COUNT(*) OVER().
1059
+ * Useful for pagination - gets data and total count in a single query.
1060
+ */
1061
+ async countOver() {
1062
+ this._includeCountOver = true;
1063
+ try {
1064
+ const options = this.executor?.getOptions();
1065
+ const tracer = new db_context_1.TimeTracer(options?.traceTime ?? false, options?.logger);
1066
+ tracer.startPhase('queryBuild');
1067
+ const context = tracer.trace('createContext', () => ({
1068
+ ctes: new Map(),
1069
+ cteCounter: 0,
1070
+ paramCounter: 1,
1071
+ allParams: [],
1072
+ collectionStrategy: this.collectionStrategy,
1073
+ executor: this.executor,
1074
+ }));
1075
+ const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
1076
+ const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
1077
+ const { sql, params, nestedPaths } = tracer.trace('buildQuery', () => this.buildQuery(selectionResult, context));
1078
+ tracer.endPhase();
1079
+ tracer.startPhase('queryExecution');
1080
+ const result = await tracer.traceAsync('executeQuery', async () => this.executor
1081
+ ? await this.executor.query(sql, params)
1082
+ : await this.client.query(sql, params), { rowCount: 'pending' });
1083
+ tracer.endPhase();
1084
+ // Extract totalCount from first raw row before transformation
1085
+ // PostgreSQL COUNT() OVER() returns bigint (string in pg driver)
1086
+ const totalCount = result.rows.length > 0 ? parseInt(result.rows[0].__countOver ?? '0', 10) : 0;
1087
+ // Strip __countOver from raw rows before processing
1088
+ for (const row of result.rows) {
1089
+ delete row.__countOver;
1090
+ }
1091
+ if (this.executor?.getOptions().rawResult) {
1092
+ return { data: result.rows, totalCount };
1093
+ }
1094
+ tracer.startPhase('resultProcessing');
1095
+ let rows = result.rows;
1096
+ if (nestedPaths.size > 0) {
1097
+ rows = tracer.trace('reconstructNestedObjects', () => rows.map(row => this.reconstructNestedObjects(row, nestedPaths)), { rowCount: rows.length });
1098
+ }
1099
+ const data = tracer.trace('transformResults', () => this.transformResults(rows, selectionResult), { rowCount: rows.length });
1100
+ tracer.endPhase();
1101
+ tracer.logSummary(data.length);
1102
+ return { data: data, totalCount };
1103
+ }
1104
+ finally {
1105
+ this._includeCountOver = false;
1106
+ }
1107
+ }
1056
1108
  /**
1057
1109
  * Check if any rows match the query
1058
1110
  * More efficient than count() > 0 as it can stop after finding one row
@@ -2712,14 +2764,22 @@ ${joinClauses.join('\n')}`;
2712
2764
  // If the value is already a FieldRef
2713
2765
  if (value && typeof value === 'object' && '__fieldName' in value && '__dbColumnName' in value) {
2714
2766
  if (preserveOriginal) {
2715
- // For WHERE: preserve original column name, table alias, and mapper
2767
+ // For WHERE: preserve original column name, table alias, mapper, and navigation aliases
2716
2768
  // This ensures WHERE references the actual database column with proper type conversion
2717
- return {
2769
+ // and can resolve intermediate JOINs for multi-level navigation
2770
+ const fieldRef = {
2718
2771
  __fieldName: prop,
2719
2772
  __dbColumnName: value.__dbColumnName,
2720
2773
  __tableAlias: value.__tableAlias,
2721
2774
  __mapper: value.__mapper, // Preserve mapper for toDriver in conditions
2722
2775
  };
2776
+ if (value.__navigationAliases) {
2777
+ fieldRef.__navigationAliases = value.__navigationAliases;
2778
+ }
2779
+ if (value.__sourceTable) {
2780
+ fieldRef.__sourceTable = value.__sourceTable;
2781
+ }
2782
+ return fieldRef;
2723
2783
  }
2724
2784
  else {
2725
2785
  // For ORDER BY: use the alias (property name) as the column name
@@ -3009,7 +3069,7 @@ ${joinClauses.join('\n')}`;
3009
3069
  if (tableAlias && tableAlias !== this.schema.name) {
3010
3070
  allTableAliases.add(tableAlias);
3011
3071
  }
3012
- // Also collect intermediate navigation aliases for multi-level navigation
3072
+ // Also collect intermediate navigation aliases for multi-level navigation (e.g., task.level.name)
3013
3073
  if ('__navigationAliases' in value && Array.isArray(value.__navigationAliases)) {
3014
3074
  for (const navAlias of value.__navigationAliases) {
3015
3075
  if (navAlias && navAlias !== this.schema.name) {
@@ -3028,6 +3088,7 @@ ${joinClauses.join('\n')}`;
3028
3088
  allTableAliases.add(tableAlias);
3029
3089
  }
3030
3090
  }
3091
+ // Also collect intermediate navigation aliases for multi-level navigation
3031
3092
  if ('__navigationAliases' in fieldRef && Array.isArray(fieldRef.__navigationAliases)) {
3032
3093
  for (const navAlias of fieldRef.__navigationAliases) {
3033
3094
  if (navAlias && navAlias !== this.schema.name) {
@@ -3611,6 +3672,10 @@ ${joinClauses.join('\n')}`;
3611
3672
  fromClause += `\n${joinClause}`;
3612
3673
  }
3613
3674
  }
3675
+ // Add COUNT(*) OVER() for countOver() support
3676
+ if (this._includeCountOver) {
3677
+ selectParts.push('COUNT(*) OVER() as "__countOver"');
3678
+ }
3614
3679
  // Add DISTINCT if needed
3615
3680
  const distinctClause = this.isDistinct ? 'DISTINCT ' : '';
3616
3681
  finalQuery += `SELECT ${distinctClause}${selectParts.join(', ')}\n${fromClause}\n${whereClause}\n${orderByClause}\n${limitClause}`.trim();
@@ -3967,10 +4032,10 @@ ${joinClauses.join('\n')}`;
3967
4032
  fieldConfigs.push({ key, type: 7 /* FieldType.FIELD_REF_NO_MAPPER */, value });
3968
4033
  }
3969
4034
  else {
3970
- // Not in base table schema - check if FieldRef carries its own mapper (navigation property)
3971
- const directMapper = value.__mapper;
3972
- if (directMapper && typeof directMapper.fromDriver === 'function') {
3973
- fieldConfigs.push({ key, type: 6 /* FieldType.FIELD_REF_MAPPER */, value, mapper: directMapper });
4035
+ // Not in root schema cache check FieldRef's own mapper (from navigation properties)
4036
+ const fieldMapper = value.__mapper;
4037
+ if (fieldMapper && typeof fieldMapper.fromDriver === 'function') {
4038
+ fieldConfigs.push({ key, type: 6 /* FieldType.FIELD_REF_MAPPER */, value, mapper: fieldMapper });
3974
4039
  }
3975
4040
  else {
3976
4041
  fieldConfigs.push({ key, type: 8 /* FieldType.SIMPLE */, value });