proto.io 0.0.211 → 0.0.213

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.
Files changed (48) hide show
  1. package/dist/adapters/file/aliyun-oss.d.ts +3 -3
  2. package/dist/adapters/file/database.d.ts +2 -2
  3. package/dist/adapters/file/database.js +1 -1
  4. package/dist/adapters/file/database.js.map +1 -1
  5. package/dist/adapters/file/database.mjs +2 -2
  6. package/dist/adapters/file/database.mjs.map +1 -1
  7. package/dist/adapters/file/filesystem.d.ts +3 -3
  8. package/dist/adapters/file/google-cloud-storage.d.ts +3 -3
  9. package/dist/adapters/storage/progres.d.ts +8 -6
  10. package/dist/adapters/storage/progres.js +148 -80
  11. package/dist/adapters/storage/progres.js.map +1 -1
  12. package/dist/adapters/storage/progres.mjs +149 -81
  13. package/dist/adapters/storage/progres.mjs.map +1 -1
  14. package/dist/client.d.ts +3 -3
  15. package/dist/client.js +1 -1
  16. package/dist/client.mjs +2 -2
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.js +8 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +10 -10
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/internals/{base-BCWOHUaQ.d.ts → base-CZGalGrd.d.ts} +2 -2
  23. package/dist/internals/base-CZGalGrd.d.ts.map +1 -0
  24. package/dist/internals/{chunk-2pKgkO5-.d.ts → chunk-BsT9SYny.d.ts} +3 -3
  25. package/dist/internals/chunk-BsT9SYny.d.ts.map +1 -0
  26. package/dist/internals/{index-lTzbCO8S.d.ts → index-Boxwkqe0.d.ts} +32 -15
  27. package/dist/internals/index-Boxwkqe0.d.ts.map +1 -0
  28. package/dist/internals/{index-DPPLcZx8.mjs → index-DG9HHO_U.mjs} +2 -2
  29. package/dist/internals/{index-DPPLcZx8.mjs.map → index-DG9HHO_U.mjs.map} +1 -1
  30. package/dist/internals/{index-Btxxs0KS.js → index-DfnPpl1I.js} +16 -10
  31. package/dist/internals/index-DfnPpl1I.js.map +1 -0
  32. package/dist/internals/{index-8AdKlZUU.d.ts → index-NF-U_3zG.d.ts} +2 -2
  33. package/dist/internals/index-NF-U_3zG.d.ts.map +1 -0
  34. package/dist/internals/{index-CoeDMG5V.mjs → index-ZPbBr9Db.mjs} +16 -10
  35. package/dist/internals/index-ZPbBr9Db.mjs.map +1 -0
  36. package/dist/internals/{random-ycCeBd0S.mjs → random-CufRbivU.mjs} +121 -19
  37. package/dist/internals/random-CufRbivU.mjs.map +1 -0
  38. package/dist/internals/{random-CyU_Y2Ay.js → random-DzvxbWAc.js} +161 -58
  39. package/dist/internals/random-DzvxbWAc.js.map +1 -0
  40. package/package.json +1 -1
  41. package/dist/internals/base-BCWOHUaQ.d.ts.map +0 -1
  42. package/dist/internals/chunk-2pKgkO5-.d.ts.map +0 -1
  43. package/dist/internals/index-8AdKlZUU.d.ts.map +0 -1
  44. package/dist/internals/index-Btxxs0KS.js.map +0 -1
  45. package/dist/internals/index-CoeDMG5V.mjs.map +0 -1
  46. package/dist/internals/index-lTzbCO8S.d.ts.map +0 -1
  47. package/dist/internals/random-CyU_Y2Ay.js.map +0 -1
  48. package/dist/internals/random-ycCeBd0S.mjs.map +0 -1
@@ -5,9 +5,9 @@ import QueryStream from 'pg-query-stream';
5
5
  import { asyncStream, IteratorPool } from '@o2ter/utils-js';
6
6
  import Decimal from 'decimal.js';
7
7
  import { escapeLiteral, escapeIdentifier } from 'pg/lib/utils';
8
- import { a as QueryCoditionalSelector, b as QueryFieldSelector, c as QueryExpressionSelector, d as QueryDistanceExpression, e as QueryCoditionalExpression, f as QueryComparisonExpression, g as QueryNotExpression, h as QueryArrayExpression, i as QueryValueExpression, j as QueryKeyExpression, Q as QuerySelector, F as FieldSelectorExpression } from '../../internals/index-DPPLcZx8.mjs';
8
+ import { a as QueryCoditionalSelector, b as QueryFieldSelector, c as QueryExpressionSelector, d as QueryDistanceExpression, e as QueryKeyExpression, f as QueryValueExpression, g as QueryCoditionalExpression, h as QueryComparisonExpression, i as QueryNotExpression, j as QueryArrayExpression, Q as QuerySelector, F as FieldSelectorExpression } from '../../internals/index-DG9HHO_U.mjs';
9
9
  import '@o2ter/crypto-js';
10
- import { r as resolveColumn, a as resolveDataType$1, g as generateId, Q as QueryValidator } from '../../internals/random-ycCeBd0S.mjs';
10
+ import { r as resolveColumn, a as resolveDataType$1, g as generateId, b as accumulatorKeyTypes, Q as QueryValidator } from '../../internals/random-CufRbivU.mjs';
11
11
  import { c as PROTO_EVENT } from '../../internals/const-Dkp7Nsv5.mjs';
12
12
  import { P as PVK } from '../../internals/private-CNw40LZ7.mjs';
13
13
 
@@ -214,12 +214,12 @@ class QueryCompiler {
214
214
  _encodeIncludes(query) {
215
215
  const names = {};
216
216
  const populates = {};
217
- const countMatches = [];
217
+ const groupMatches = {};
218
218
  for (const include of query.includes) {
219
219
  const { paths: [colname, ...subpath], dataType } = resolveColumn(this.schema, query.className, include);
220
220
  names[colname] = dataType;
221
- if (isRelation(dataType) && _.includes(query.countMatches, colname))
222
- countMatches.push(colname);
221
+ if (isRelation(dataType) && !_.isNil(query.groupMatches[colname]))
222
+ groupMatches[colname] = query.groupMatches[colname];
223
223
  if (isPointer(dataType) || isRelation(dataType)) {
224
224
  if (_.isEmpty(subpath))
225
225
  throw Error(`Invalid path: ${include}`);
@@ -250,21 +250,26 @@ class QueryCompiler {
250
250
  }
251
251
  for (const [colname, populate] of _.toPairs(populates)) {
252
252
  const _matches = query.matches[colname];
253
- const { includes, populates, countMatches } = this._encodeIncludes({
253
+ const { includes, populates, groupMatches } = this._encodeIncludes({
254
254
  className: populate.className,
255
255
  includes: populate.subpaths,
256
256
  matches: _matches.matches,
257
- countMatches: [
258
- ..._.filter(query.countMatches, x => _.startsWith(x, `${colname}.`)).map(x => x.slice(colname.length + 1)),
259
- ..._matches.countMatches ?? [],
260
- ],
257
+ groupMatches: {
258
+ ..._.mapKeys(_.pickBy(query.groupMatches, (x, k) => _.startsWith(k, `${colname}.`)), (x, k) => k.slice(colname.length + 1)),
259
+ ..._matches.groupMatches ?? {},
260
+ },
261
261
  });
262
262
  populate.sort = _encodeSorting(includes, populates, _matches.sort);
263
263
  populate.includes = includes;
264
264
  populate.populates = populates;
265
- populate.countMatches = countMatches;
265
+ populate.groupMatches = groupMatches;
266
266
  }
267
- return { className: query.className, includes: names, populates, countMatches: _.uniq(countMatches) };
267
+ return {
268
+ className: query.className,
269
+ includes: names,
270
+ populates,
271
+ groupMatches,
272
+ };
268
273
  }
269
274
  _baseSelectQuery(query, options) {
270
275
  const fetchName = `_fetch_$${query.className.toLowerCase()}`;
@@ -300,7 +305,11 @@ class QueryCompiler {
300
305
  ) AS ${{ identifier: fetchName }}
301
306
  ${!_.isEmpty(filter) ? sql `WHERE ${{ literal: _.map(filter, x => sql `(${x})`), separator: ' AND ' }}` : sql ``}
302
307
  ${_options?.sort ? _options?.sort : sql ``}
303
- ${!_.isEmpty(query.sort) ? sql `ORDER BY ${this._encodeSort(query.sort, { className: query.className, name: fetchName })}` : sql ``}
308
+ ${!_.isEmpty(query.sort) ? sql `ORDER BY ${this._encodeSort(query.sort, {
309
+ name: fetchName,
310
+ className: query.className,
311
+ groupMatches: query.groupMatches,
312
+ })}` : sql ``}
304
313
  ${query.limit ? sql `LIMIT ${{ literal: `${query.limit}` }}` : sql ``}
305
314
  ${query.skip ? sql `OFFSET ${{ literal: `${query.skip}` }}` : sql ``}
306
315
  `,
@@ -405,7 +414,7 @@ class QueryCompiler {
405
414
  }
406
415
  _selectIncludes(className, includes) {
407
416
  const _includes = _.pickBy(includes, v => _.isString(v) || (v.type !== 'pointer' && v.type !== 'relation'));
408
- return _.map(_includes, (dataType, colname) => {
417
+ return _.flatMap(_includes, (dataType, colname) => {
409
418
  if (!_.isString(dataType) && isPrimitive(dataType) && !_.isNil(dataType.default)) {
410
419
  return sql `COALESCE(${{ identifier: className }}.${{ identifier: colname }}, ${{ value: dataType.default }}) AS ${{ identifier: colname }}`;
411
420
  }
@@ -426,7 +435,7 @@ class QueryCompiler {
426
435
  `)}`;
427
436
  }
428
437
  _selectPopulateMap(context) {
429
- return _.map(context.populates, (populate, field) => this.dialect.selectPopulate(this, context, populate, field, _.includes(context.countMatches, field)));
438
+ return _.map(context.populates, (populate, field) => this.dialect.selectPopulate(this, context, populate, field));
430
439
  }
431
440
  insert(options, values) {
432
441
  const _values = _.map(values, attr => ({
@@ -564,9 +573,15 @@ class SqlStorage {
564
573
  const { query, values } = sql.compile(this.dialect);
565
574
  return this._query(query, values);
566
575
  }
567
- _decodeShapedObject(dataType, value) {
576
+ _decodeMatchTypes(value, matchType) {
577
+ if (!_.isPlainObject(value))
578
+ return;
579
+ return _.mapValues(value, (v, k) => this.dialect.decodeType(matchType[k], v));
580
+ }
581
+ _decodeShapedObject(dataType, value, matchesType) {
568
582
  const result = {};
569
583
  for (const { path, type } of shapePaths(dataType)) {
584
+ const matchType = _.get(matchesType, path) ?? {};
570
585
  if (_.isString(type)) {
571
586
  const _value = this.dialect.decodeType(type, _.get(value, path));
572
587
  if (!_.isNil(_value))
@@ -575,17 +590,17 @@ class SqlStorage {
575
590
  else if (isPointer(type)) {
576
591
  const _value = _.get(value, path);
577
592
  if (_.isPlainObject(_value)) {
578
- const decoded = this._decodeObject(type.target, _value);
593
+ const decoded = this._decodeObject(type.target, _value, matchType);
579
594
  if (decoded.objectId)
580
595
  _.set(result, path, decoded);
581
596
  }
582
597
  }
583
598
  else if (isRelation(type)) {
584
599
  const _value = _.get(value, path);
585
- if (_.isString(_value) && _value.match(/^\d+$/g))
586
- _.set(result, path, parseInt(_value));
587
- else if (_.isArray(_value))
588
- _.set(result, path, _value.map(x => this._decodeObject(type.target, x)));
600
+ if (_.isArray(_value))
601
+ _.set(result, path, _value.map(x => this._decodeObject(type.target, x, matchType)));
602
+ else if (_.isPlainObject(_value))
603
+ _.set(result, path, this._decodeMatchTypes(_value, matchType));
589
604
  }
590
605
  else {
591
606
  const _value = this.dialect.decodeType(type.type, _.get(value, path)) ?? type.default;
@@ -595,7 +610,7 @@ class SqlStorage {
595
610
  }
596
611
  return result;
597
612
  }
598
- _decodeObject(className, attrs) {
613
+ _decodeObject(className, attrs, matchesType) {
599
614
  const fields = this.schema[className].fields;
600
615
  const obj = new TObject(className);
601
616
  const _attrs = {};
@@ -603,6 +618,7 @@ class SqlStorage {
603
618
  _.set(_attrs, key, value);
604
619
  }
605
620
  for (const [key, value] of _.toPairs(_attrs)) {
621
+ const matchType = matchesType[key] ?? {};
606
622
  const dataType = fields[key];
607
623
  if (!dataType)
608
624
  continue;
@@ -610,20 +626,20 @@ class SqlStorage {
610
626
  obj[PVK].attributes[key] = this.dialect.decodeType(dataType, value);
611
627
  }
612
628
  else if (isShape(dataType)) {
613
- obj[PVK].attributes[key] = this._decodeShapedObject(dataType, value);
629
+ obj[PVK].attributes[key] = this._decodeShapedObject(dataType, value, matchType);
614
630
  }
615
631
  else if (isPointer(dataType)) {
616
632
  if (_.isPlainObject(value)) {
617
- const decoded = this._decodeObject(dataType.target, value);
633
+ const decoded = this._decodeObject(dataType.target, value, matchType);
618
634
  if (decoded.objectId)
619
635
  obj[PVK].attributes[key] = decoded;
620
636
  }
621
637
  }
622
638
  else if (isRelation(dataType)) {
623
- if (_.isString(value) && value.match(/^\d+$/g))
624
- obj[PVK].attributes[key] = parseInt(value);
625
- else if (_.isArray(value))
626
- obj[PVK].attributes[key] = value.map(x => this._decodeObject(dataType.target, x));
639
+ if (_.isArray(value))
640
+ obj[PVK].attributes[key] = value.map(x => this._decodeObject(dataType.target, x, matchType));
641
+ else if (_.isPlainObject(value))
642
+ obj[PVK].attributes[key] = this._decodeMatchTypes(value, matchType);
627
643
  }
628
644
  else {
629
645
  obj[PVK].attributes[key] = this.dialect.decodeType(dataType.type, value) ?? dataType.default;
@@ -652,6 +668,18 @@ class SqlStorage {
652
668
  const count = parseInt(_count);
653
669
  return _.isFinite(count) ? count : 0;
654
670
  }
671
+ _matchesType(options) {
672
+ const types = {};
673
+ for (const [key, match] of _.entries(options.matches)) {
674
+ types[key] = this._matchesType(match);
675
+ }
676
+ for (const [key, group] of _.entries(options.groupMatches)) {
677
+ for (const [field, expr] of _.entries(group)) {
678
+ _.set(types, `${key}.${field}`, accumulatorKeyTypes[expr.type]);
679
+ }
680
+ }
681
+ return types;
682
+ }
655
683
  find(query) {
656
684
  const self = this;
657
685
  const compiler = self._makeCompiler(false, query.extraFilter);
@@ -659,7 +687,7 @@ class SqlStorage {
659
687
  return (async function* () {
660
688
  const objects = self.query(_query);
661
689
  for await (const object of objects) {
662
- yield self._decodeObject(query.className, object);
690
+ yield self._decodeObject(query.className, object, self._matchesType(query));
663
691
  }
664
692
  })();
665
693
  }
@@ -672,7 +700,7 @@ class SqlStorage {
672
700
  return (async function* () {
673
701
  const objects = self.query(_query);
674
702
  for await (const object of objects) {
675
- yield self._decodeObject(query.className, object);
703
+ yield self._decodeObject(query.className, object, self._matchesType(query));
676
704
  }
677
705
  })();
678
706
  }
@@ -686,7 +714,7 @@ class SqlStorage {
686
714
  return (async function* () {
687
715
  const objects = self.query(query);
688
716
  for await (const { _class, ...object } of objects) {
689
- yield self._decodeObject(_class, object);
717
+ yield self._decodeObject(_class, object, {});
690
718
  }
691
719
  })();
692
720
  }
@@ -701,29 +729,29 @@ class SqlStorage {
701
729
  return (async function* () {
702
730
  const objects = self.query(_query);
703
731
  for await (const object of objects) {
704
- yield self._decodeObject(query.className, object);
732
+ yield self._decodeObject(query.className, object, self._matchesType(query));
705
733
  }
706
734
  })();
707
735
  }
708
736
  async insert(options, values) {
709
737
  const compiler = this._makeCompiler(true);
710
738
  const result = await this.query(compiler.insert(options, values));
711
- return _.map(result, x => this._decodeObject(options.className, x));
739
+ return _.map(result, x => this._decodeObject(options.className, x, this._matchesType(options)));
712
740
  }
713
741
  async update(query, update) {
714
742
  const compiler = this._makeCompiler(true, query.extraFilter);
715
743
  const updated = await this.query(compiler.update(query, update));
716
- return _.map(updated, x => this._decodeObject(query.className, x));
744
+ return _.map(updated, x => this._decodeObject(query.className, x, this._matchesType(query)));
717
745
  }
718
746
  async upsert(query, update, setOnInsert) {
719
747
  const compiler = this._makeCompiler(true, query.extraFilter);
720
748
  const upserted = await this.query(compiler.upsert(query, update, setOnInsert));
721
- return _.map(upserted, x => this._decodeObject(query.className, x));
749
+ return _.map(upserted, x => this._decodeObject(query.className, x, this._matchesType(query)));
722
750
  }
723
751
  async delete(query) {
724
752
  const compiler = this._makeCompiler(true, query.extraFilter);
725
753
  const deleted = await this.query(compiler.delete(query));
726
- return _.map(deleted, x => this._decodeObject(query.className, x));
754
+ return _.map(deleted, x => this._decodeObject(query.className, x, this._matchesType(query)));
727
755
  }
728
756
  }
729
757
 
@@ -769,7 +797,15 @@ const _fetchElement = (parent, colname, subpath, dataType) => {
769
797
  }
770
798
  else if (!_.isEmpty(subpath)) {
771
799
  const _subpath = sql `${_.map(subpath, x => sql `${{ quote: x.startsWith('$') ? `$${x}` : x }}`)}`;
772
- if (dataType && _isTypeof(dataType, ['array', 'string[]', 'relation'])) {
800
+ const match = parent.groupMatches?.[colname]?.[subpath[0]];
801
+ if (dataType && isRelation(dataType) && subpath.length === 1 && match) {
802
+ return {
803
+ element: sql `${{ identifier: parent.name }}.${{ identifier: `${colname}.${subpath[0]}` }}`,
804
+ json: false,
805
+ dataType: accumulatorKeyTypes[match.type],
806
+ };
807
+ }
808
+ else if (dataType && _isTypeof(dataType, ['array', 'string[]', 'relation'])) {
773
809
  return {
774
810
  element: sql `jsonb_extract_path(to_jsonb(${element}), ${_subpath})`,
775
811
  json: true,
@@ -829,7 +865,7 @@ const _resolvePopulate = (path, populates) => {
829
865
  const fetchElement = (compiler, parent, field) => {
830
866
  if (parent.className) {
831
867
  const { dataType, colname, subpath } = resolvePaths(compiler, parent.className, _.toPath(field));
832
- const { element, json } = _fetchElement(parent, colname, subpath, dataType);
868
+ const { element, json, dataType: _dataType } = _fetchElement(parent, colname, subpath, dataType);
833
869
  if (isPointer(dataType))
834
870
  return { element: sql `${{ identifier: parent.name }}.${{ identifier: `${colname}._id` }}`, dataType };
835
871
  const populate = isRelation(dataType) && _resolvePopulate(_.toPath(colname), parent.populates);
@@ -837,7 +873,7 @@ const fetchElement = (compiler, parent, field) => {
837
873
  return { element, dataType: json ? null : dataType };
838
874
  return {
839
875
  element,
840
- dataType: json ? null : dataType,
876
+ dataType: json ? null : _dataType ?? dataType,
841
877
  relation: {
842
878
  colname,
843
879
  target: dataType.target,
@@ -1177,7 +1213,7 @@ const _encodeJsonValue = (value) => {
1177
1213
  return sql `jsonb_build_object(${_.map(value, (v, k) => sql `${{ value: k }}, ${_encodeJsonValue(v)}`)})`;
1178
1214
  return sql `to_jsonb(${{ value }})`;
1179
1215
  };
1180
- const _encodePopulateInclude = (className, colname, dataType) => {
1216
+ const _jsonPopulateInclude = (className, colname, dataType) => {
1181
1217
  switch (_typeof(dataType)) {
1182
1218
  case 'decimal':
1183
1219
  return sql `jsonb_build_object(
@@ -1892,45 +1928,83 @@ const _selectRelationPopulate = (compiler, parent, populate, field, encode) => {
1892
1928
  cond = sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ANY(${_foreign(populate.colname)})`;
1893
1929
  }
1894
1930
  return sql `
1895
- SELECT ${_.compact(_.flatMap(subpaths, ({ path, type }) => [
1896
- encode && _encodePopulateInclude(populate.name, path, type),
1897
- !encode && sql `${{ identifier: populate.name }}.${{ identifier: path }}`,
1898
- !encode && isRelation(type) && sql `${{ identifier: populate.name }}.${{ identifier: `$${path}` }}`,
1931
+ SELECT ${_.compact(_.flatMap(subpaths, ({ path, type }) => encode ? [
1932
+ _jsonPopulateInclude(populate.name, path, type)
1933
+ ] : [
1934
+ ...(populate.groupMatches[path] ? _.map(_.keys(populate.groupMatches[path]), k => sql `${{ identifier: populate.name }}.${{ identifier: `${path}.${k}` }}`) : [
1935
+ sql `${{ identifier: populate.name }}.${{ identifier: path }}`
1936
+ ]),
1937
+ isRelation(type) && sql `${{ identifier: populate.name }}.${{ identifier: `$${path}` }}`,
1899
1938
  ]))}
1900
1939
  FROM ${{ identifier: populate.name }} WHERE ${cond}
1901
- ${!_.isEmpty(populate.sort) ? sql `ORDER BY ${compiler._encodeSort(populate.sort, { className: populate.className, name: populate.name })}` : sql ``}
1940
+ ${!_.isEmpty(populate.sort) ? sql `ORDER BY ${compiler._encodeSort(populate.sort, populate)}` : sql ``}
1902
1941
  ${populate.limit ? sql `LIMIT ${{ literal: `${populate.limit}` }}` : sql ``}
1903
1942
  ${populate.skip ? sql `OFFSET ${{ literal: `${populate.skip}` }}` : sql ``}
1904
1943
  ${compiler.selectLock ? compiler.isUpdate ? sql `FOR UPDATE NOWAIT` : sql `FOR SHARE NOWAIT` : sql ``}
1905
1944
  `;
1906
1945
  };
1907
- const selectPopulate = (compiler, parent, populate, field, countMatches) => {
1946
+ const selectPopulate = (compiler, parent, populate, field) => {
1908
1947
  if (populate.type === 'relation') {
1909
- return {
1910
- columns: [
1911
- countMatches ? sql `
1912
- (
1913
- SELECT COUNT(*) FROM (
1914
- ${_selectRelationPopulate(compiler, parent, populate, field, false)}
1915
- ) ${{ identifier: populate.name }}
1916
- ) AS ${{ identifier: field }}
1917
- ` : sql `
1918
- ARRAY(
1919
- SELECT to_jsonb(${{ identifier: populate.name }}) FROM (
1920
- ${_selectRelationPopulate(compiler, parent, populate, field, true)}
1921
- ) ${{ identifier: populate.name }}
1922
- ) AS ${{ identifier: field }}
1923
- `,
1924
- sql `${{ identifier: parent.name }}.${{ identifier: field }} AS ${{ identifier: `$${field}` }}`
1925
- ],
1926
- };
1948
+ const { groupMatches } = parent;
1949
+ const columns = [
1950
+ sql `${{ identifier: parent.name }}.${{ identifier: field }} AS ${{ identifier: `$${field}` }}`,
1951
+ ];
1952
+ if (!_.isEmpty(groupMatches?.[field])) {
1953
+ for (const [key, { type, expr }] of _.entries(groupMatches[field])) {
1954
+ switch (type) {
1955
+ case '$count':
1956
+ columns.push(sql `
1957
+ (
1958
+ SELECT COUNT(*) FROM (
1959
+ ${_selectRelationPopulate(compiler, parent, populate, field, false)}
1960
+ ) ${{ identifier: populate.name }}
1961
+ ) AS ${{ identifier: `${field}.${key}` }}
1962
+ `);
1963
+ break;
1964
+ case '$avg':
1965
+ case '$sum':
1966
+ {
1967
+ const op = {
1968
+ '$avg': 'AVG',
1969
+ '$sum': 'SUM',
1970
+ }[type];
1971
+ if (!expr)
1972
+ throw Error('Invalid expression');
1973
+ const exprs = encodeTypedQueryExpression(compiler, populate, expr);
1974
+ const { sql: value } = (_.includes(['$avg'], type) ? _.find(exprs, e => e.type === 'number') : _.first(exprs)) ?? {};
1975
+ if (!value)
1976
+ throw Error('Invalid expression');
1977
+ columns.push(sql `
1978
+ (
1979
+ SELECT ${{ literal: op }}(${value}) FROM (
1980
+ ${_selectRelationPopulate(compiler, parent, populate, field, false)}
1981
+ ) ${{ identifier: populate.name }}
1982
+ ) AS ${{ identifier: `${field}.${key}` }}
1983
+ `);
1984
+ }
1985
+ break;
1986
+ }
1987
+ }
1988
+ }
1989
+ else {
1990
+ columns.push(sql `
1991
+ ARRAY(
1992
+ SELECT to_jsonb(${{ identifier: populate.name }}) FROM (
1993
+ ${_selectRelationPopulate(compiler, parent, populate, field, true)}
1994
+ ) ${{ identifier: populate.name }}
1995
+ ) AS ${{ identifier: field }}
1996
+ `);
1997
+ }
1998
+ return { columns };
1927
1999
  }
1928
2000
  const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
1929
2001
  const _foreign = (field) => sql `${{ identifier: populate.name }}.${{ identifier: field }}`;
1930
2002
  const subpaths = resolveSubpaths(compiler, populate);
1931
2003
  return {
1932
2004
  columns: _.compact(_.flatMap(subpaths, ({ path, type }) => [
1933
- sql `${{ identifier: populate.name }}.${{ identifier: path }} AS ${{ identifier: `${field}.${path}` }}`,
2005
+ ...populate.groupMatches[path] ? _.map(_.keys(populate.groupMatches[path]), k => sql `${{ identifier: populate.name }}.${{ identifier: `${path}.${k}` }} AS ${{ identifier: `${field}.${path}.${k}` }}`) : [
2006
+ sql `${{ identifier: populate.name }}.${{ identifier: path }} AS ${{ identifier: `${field}.${path}` }}`
2007
+ ],
1934
2008
  isRelation(type) && sql `${{ identifier: populate.name }}.${{ identifier: `$${path}` }} AS ${{ identifier: `$${field}.${path}` }}`,
1935
2009
  ])),
1936
2010
  join: sql `
@@ -2015,9 +2089,8 @@ const encodePopulate = (compiler, parent, remix) => {
2015
2089
  parent.filter && compiler._encodeFilter(parent, parent.filter),
2016
2090
  compiler.extraFilter && compiler._encodeFilter(parent, compiler.extraFilter(parent.className)),
2017
2091
  ]);
2018
- const _populates = _.map(parent.populates, (populate, field) => selectPopulate(compiler, parent, populate, field, _.includes(parent.countMatches, field)));
2092
+ const _populates = _.map(parent.populates, (populate, field) => selectPopulate(compiler, parent, populate, field));
2019
2093
  const _joins = _.compact(_.map(_populates, ({ join }) => join));
2020
- const _includes = _.pickBy(parent.includes, v => isPrimitive(v));
2021
2094
  const { joins: _joins2 = [], field: _foreignField = undefined, rows = false, } = parent.foreignField ? encodeForeignField(compiler, {
2022
2095
  className: parent.className,
2023
2096
  name: parent.name,
@@ -2031,7 +2104,7 @@ const encodePopulate = (compiler, parent, remix) => {
2031
2104
  SELECT
2032
2105
  ${{
2033
2106
  literal: [
2034
- ..._.map(_.keys(_includes), colname => sql `${{ identifier: parent.name }}.${{ identifier: colname }}`),
2107
+ ...compiler._selectIncludes(parent.name, parent.includes),
2035
2108
  ..._.flatMap(_populates, ({ columns: column }) => column),
2036
2109
  ..._foreignField ? [sql `${rows ? sql `ARRAY(${_foreignField})` : _foreignField} AS ${{ identifier: parent.colname }}`] : [],
2037
2110
  ], separator: ',\n'
@@ -2084,9 +2157,6 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2084
2157
  break;
2085
2158
  return sql `${element} ${nullSafeEqual()} ${{ value: expr.value.objectId }}`;
2086
2159
  }
2087
- if (relation && _.includes(parent.countMatches, relation.colname)) {
2088
- return sql `${element} ${nullSafeEqual()} ${encodeType(colname, 'number', expr.value)}`;
2089
- }
2090
2160
  return sql `${element} ${nullSafeEqual()} ${encodeValue(expr.value)}`;
2091
2161
  }
2092
2162
  case '$ne':
@@ -2100,9 +2170,6 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2100
2170
  break;
2101
2171
  return sql `${element} ${nullSafeNotEqual()} ${{ value: expr.value.objectId }}`;
2102
2172
  }
2103
- if (relation && _.includes(parent.countMatches, relation.colname)) {
2104
- return sql `${element} ${nullSafeNotEqual()} ${encodeType(colname, 'number', expr.value)}`;
2105
- }
2106
2173
  return sql `${element} ${nullSafeNotEqual()} ${encodeValue(expr.value)}`;
2107
2174
  }
2108
2175
  case '$gt':
@@ -2146,9 +2213,6 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2146
2213
  else if (!_.isString(dataType) && dataType?.type === 'pointer' && expr.value instanceof TObject && expr.value.objectId) {
2147
2214
  return sql `${element} ${{ literal: op }} ${{ value: expr.value.objectId }}`;
2148
2215
  }
2149
- else if (relation && _.includes(parent.countMatches, relation.colname)) {
2150
- return sql `${element} ${{ literal: op }} ${encodeType(colname, 'number', expr.value)}`;
2151
- }
2152
2216
  else if (!dataType) {
2153
2217
  if (expr.value instanceof Decimal || _.isNumber(expr.value)) {
2154
2218
  return sql `(
@@ -2339,8 +2403,10 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2339
2403
  if (dataType && _isTypeof(dataType, 'string')) {
2340
2404
  return sql `COALESCE(length(${element}), 0) = ${{ value: expr.value }}`;
2341
2405
  }
2342
- if (relation && _.includes(parent.countMatches, relation.colname)) {
2343
- return sql `${element} = ${{ value: expr.value }}`;
2406
+ if (relation && parent.className && parent.groupMatches?.[colname]) {
2407
+ const tempName = `_populate_expr_$${compiler.nextIdx()}`;
2408
+ const populate = _selectRelationPopulate(compiler, { className: parent.className, name: parent.name }, relation.populate, `$${field}`, false);
2409
+ return sql `(SELECT COUNT(*) FROM (${populate}) AS ${{ identifier: tempName }}) = ${{ value: expr.value }}`;
2344
2410
  }
2345
2411
  if (dataType && _isTypeof(dataType, ['array', 'string[]', 'vector', 'relation'])) {
2346
2412
  return sql `COALESCE(array_length(${element}, 1), 0) = ${{ value: expr.value }}`;
@@ -2363,8 +2429,10 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2363
2429
  if (dataType && _isTypeof(dataType, 'string')) {
2364
2430
  return sql `COALESCE(length(${element}), 0) ${{ literal: expr.value ? '=' : '<>' }} 0`;
2365
2431
  }
2366
- if (relation && _.includes(parent.countMatches, relation.colname)) {
2367
- return sql `${element} ${{ literal: expr.value ? '=' : '<>' }} 0`;
2432
+ if (relation && parent.className && parent.groupMatches?.[colname]) {
2433
+ const tempName = `_populate_expr_$${compiler.nextIdx()}`;
2434
+ const populate = _selectRelationPopulate(compiler, { className: parent.className, name: parent.name }, relation.populate, `$${field}`, false);
2435
+ return sql `${{ literal: expr.value ? 'NOT EXISTS' : 'EXISTS' }}(SELECT * FROM (${populate}) AS ${{ identifier: tempName }})`;
2368
2436
  }
2369
2437
  if (dataType && _isTypeof(dataType, ['array', 'string[]', 'vector', 'relation'])) {
2370
2438
  return sql `COALESCE(array_length(${element}, 1), 0) ${{ literal: expr.value ? '=' : '<>' }} 0`;