proto.io 0.0.166 → 0.0.168

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.
@@ -287,7 +287,7 @@ class QueryCompiler {
287
287
  const _stages = _.mapValues(context.populates, (populate) => this.dialect.encodePopulate(this, populate));
288
288
  const stages = _.fromPairs(_.flatMap(_.values(_stages), (p) => _.toPairs(p)));
289
289
  const fetchName = `_fetch_$${query.className.toLowerCase()}`;
290
- const parent = { className: query.className, name: fetchName };
290
+ const parent = { className: query.className, name: fetchName, populates: context.populates };
291
291
  const baseFilter = this._encodeFilter(parent, query.filter);
292
292
  const populates = this._selectPopulateMap(context, query.className, fetchName);
293
293
  const joins = _.compact(_.map(populates, ({ join }) => join));
@@ -795,36 +795,8 @@ class SqlStorage {
795
795
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
796
796
  // THE SOFTWARE.
797
797
  //
798
- const foreignFieldType = (schema, className, path) => {
799
- let fields = schema[className].fields;
800
- let last;
801
- let result = false;
802
- for (const key of _.toPath(path)) {
803
- const dataType = fields[key];
804
- if (_.isNil(dataType))
805
- return;
806
- if (isPrimitive(dataType) || isVector(dataType))
807
- return;
808
- if (isShape(dataType)) {
809
- fields = dataType.shape;
810
- continue;
811
- }
812
- if (_.isNil(schema[dataType.target]))
813
- return;
814
- if (dataType.type === 'relation')
815
- result = true;
816
- fields = schema[dataType.target].fields;
817
- last = dataType;
818
- }
819
- if (!last)
820
- return;
821
- return {
822
- target: last.target,
823
- type: result ? 'relation' : last.type,
824
- };
825
- };
826
798
  const _fetchElement = (parent, colname, subpath, dataType) => {
827
- const element = sql `${{ identifier: parent.name }}.${{ identifier: parent.name.startsWith('_expr_$') ? '$' : colname }}`;
799
+ const element = sql `${{ identifier: parent.name }}.${{ identifier: parent.name.startsWith('_doller_expr_$') ? '$' : colname }}`;
828
800
  if (!parent.className) {
829
801
  if (colname !== '$') {
830
802
  return {
@@ -854,7 +826,7 @@ const _fetchElement = (parent, colname, subpath, dataType) => {
854
826
  };
855
827
  }
856
828
  }
857
- if (parent.name.startsWith('_expr_$') && colname !== '$') {
829
+ if (parent.name.startsWith('_doller_expr_$') && colname !== '$') {
858
830
  return {
859
831
  element: sql `jsonb_extract_path(${element}, ${{ quote: colname.startsWith('$') ? `$${colname}` : colname }})`,
860
832
  json: true,
@@ -886,36 +858,37 @@ const resolvePaths = (compiler, className, paths) => {
886
858
  }
887
859
  return { dataType, colname, subpath };
888
860
  };
861
+ const _resolvePopulate = (path, populates) => {
862
+ let [colname, ...subpath] = path;
863
+ while (populates && !_.isEmpty(subpath)) {
864
+ const populate = populates[colname];
865
+ if (populate)
866
+ return _resolvePopulate(subpath, populate.populates);
867
+ const [key, ...remain] = subpath;
868
+ colname = `${colname}.${key}`;
869
+ subpath = remain;
870
+ }
871
+ return populates?.[colname];
872
+ };
889
873
  const fetchElement = (compiler, parent, field) => {
890
874
  if (parent.className) {
891
875
  const { dataType, colname, subpath } = resolvePaths(compiler, parent.className, _.toPath(field));
892
876
  const { element, json } = _fetchElement(parent, colname, subpath, dataType);
893
- if (!_.isEmpty(subpath)) {
894
- const foreignField = foreignFieldType(compiler.schema, parent.className, field);
895
- if (foreignField) {
896
- return {
897
- element,
898
- dataType: foreignField,
899
- relation: {
900
- target: foreignField.target,
901
- sql: (callback) => sql `SELECT
902
- ${callback(sql `UNNEST`)}
903
- FROM UNNEST(${{ identifier: parent.name }}.${{ identifier: colname }})`,
904
- },
905
- };
906
- }
907
- }
908
877
  if (isPointer(dataType))
909
878
  return { element: sql `${{ identifier: parent.name }}.${{ identifier: `${colname}._id` }}`, dataType };
879
+ const populate = isRelation(dataType) && _resolvePopulate(_.toPath(colname), parent.populates);
880
+ if (!populate)
881
+ return { element, dataType: json ? null : dataType };
910
882
  return {
911
883
  element,
912
884
  dataType: json ? null : dataType,
913
- relation: isRelation(dataType) ? {
885
+ relation: {
914
886
  target: dataType.target,
915
- sql: (callback) => sql `SELECT
887
+ populate,
888
+ mapElem: (callback) => sql `SELECT
916
889
  ${callback(sql `${json ? sql `VALUE` : sql `UNNEST`}`)}
917
890
  FROM ${json ? sql `jsonb_array_elements(${element})` : sql `UNNEST(${element})`}`,
918
- } : null,
891
+ },
919
892
  };
920
893
  }
921
894
  const [colname, ...subpath] = _.toPath(field);
@@ -1874,6 +1847,231 @@ const encodeQueryExpression = (compiler, parent, expr) => {
1874
1847
  return encodeBooleanExpression(compiler, parent, expr);
1875
1848
  };
1876
1849
 
1850
+ //
1851
+ // populate.ts
1852
+ //
1853
+ // The MIT License
1854
+ // Copyright (c) 2021 - 2024 O2ter Limited. All rights reserved.
1855
+ //
1856
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1857
+ // of this software and associated documentation files (the "Software"), to deal
1858
+ // in the Software without restriction, including without limitation the rights
1859
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1860
+ // copies of the Software, and to permit persons to whom the Software is
1861
+ // furnished to do so, subject to the following conditions:
1862
+ //
1863
+ // The above copyright notice and this permission notice shall be included in
1864
+ // all copies or substantial portions of the Software.
1865
+ //
1866
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1867
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1868
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1869
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1870
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1871
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1872
+ // THE SOFTWARE.
1873
+ //
1874
+ const resolveSubpaths = (compiler, populate) => {
1875
+ const subpaths = [];
1876
+ for (const [name, type] of _.toPairs(populate.includes)) {
1877
+ if (isPointer(type)) {
1878
+ subpaths.push(..._.map(resolveSubpaths(compiler, populate.populates[name]), ({ path, type }) => ({
1879
+ path: `${name}.${path}`,
1880
+ type,
1881
+ })));
1882
+ }
1883
+ else {
1884
+ subpaths.push({
1885
+ path: name,
1886
+ type,
1887
+ });
1888
+ }
1889
+ }
1890
+ return subpaths;
1891
+ };
1892
+ const _isPointer = (schema, className, path) => {
1893
+ let fields = schema[className].fields;
1894
+ let last;
1895
+ for (const key of _.toPath(path)) {
1896
+ const dataType = fields[key];
1897
+ if (_.isNil(dataType))
1898
+ throw Error(`Invalid path: ${path}`);
1899
+ if (isPrimitive(dataType) || isVector(dataType))
1900
+ throw Error(`Invalid path: ${path}`);
1901
+ if (isShape(dataType)) {
1902
+ fields = dataType.shape;
1903
+ continue;
1904
+ }
1905
+ if (dataType.type !== 'pointer')
1906
+ return false;
1907
+ if (_.isNil(schema[dataType.target]))
1908
+ throw Error(`Invalid path: ${path}`);
1909
+ fields = schema[dataType.target].fields;
1910
+ last = dataType;
1911
+ }
1912
+ return last?.type === 'pointer';
1913
+ };
1914
+ const _selectRelationPopulate = (compiler, parent, populate, field, encode) => {
1915
+ const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
1916
+ const _foreign = (field) => sql `${{ identifier: populate.name }}.${{ identifier: field }}`;
1917
+ const subpaths = resolveSubpaths(compiler, populate);
1918
+ let cond;
1919
+ if (_.isNil(populate.foreignField)) {
1920
+ cond = sql `${sql `(${{ quote: populate.className + '$' }} || ${_foreign('_id')})`} = ANY(${_local(field)})`;
1921
+ }
1922
+ else if (_isPointer(compiler.schema, populate.className, populate.foreignField)) {
1923
+ cond = sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${_foreign(populate.colname)}`;
1924
+ }
1925
+ else {
1926
+ cond = sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ANY(${_foreign(populate.colname)})`;
1927
+ }
1928
+ return sql `
1929
+ SELECT ${_.compact(_.flatMap(subpaths, ({ path, type }) => [
1930
+ encode && _encodePopulateInclude(populate.name, path, type),
1931
+ !encode && sql `${{ identifier: populate.name }}.${{ identifier: path }}`,
1932
+ !encode && isRelation(type) && sql `${{ identifier: populate.name }}.${{ identifier: `$${path}` }}`,
1933
+ ]))}
1934
+ FROM ${{ identifier: populate.name }} WHERE ${cond}
1935
+ ${!_.isEmpty(populate.sort) ? sql `ORDER BY ${compiler._encodeSort(populate.sort, { className: populate.className, name: populate.name })}` : sql ``}
1936
+ ${populate.limit ? sql `LIMIT ${{ literal: `${populate.limit}` }}` : sql ``}
1937
+ ${populate.skip ? sql `OFFSET ${{ literal: `${populate.skip}` }}` : sql ``}
1938
+ ${compiler.selectLock ? compiler.isUpdate ? sql `FOR UPDATE NOWAIT` : sql `FOR SHARE NOWAIT` : sql ``}
1939
+ `;
1940
+ };
1941
+ const selectPopulate = (compiler, parent, populate, field) => {
1942
+ if (populate.type === 'relation') {
1943
+ return {
1944
+ columns: [
1945
+ sql `
1946
+ ARRAY(
1947
+ SELECT to_jsonb(${{ identifier: populate.name }}) FROM (
1948
+ ${_selectRelationPopulate(compiler, parent, populate, field, true)}
1949
+ ) ${{ identifier: populate.name }}
1950
+ ) AS ${{ identifier: field }}
1951
+ `,
1952
+ sql `${{ identifier: parent.name }}.${{ identifier: field }} AS ${{ identifier: `$${field}` }}`
1953
+ ],
1954
+ };
1955
+ }
1956
+ const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
1957
+ const _foreign = (field) => sql `${{ identifier: populate.name }}.${{ identifier: field }}`;
1958
+ const subpaths = resolveSubpaths(compiler, populate);
1959
+ return {
1960
+ columns: _.compact(_.flatMap(subpaths, ({ path, type }) => [
1961
+ sql `${{ identifier: populate.name }}.${{ identifier: path }} AS ${{ identifier: `${field}.${path}` }}`,
1962
+ isRelation(type) && sql `${{ identifier: populate.name }}.${{ identifier: `$${path}` }} AS ${{ identifier: `$${field}.${path}` }}`,
1963
+ ])),
1964
+ join: sql `
1965
+ LEFT JOIN ${{ identifier: populate.name }}
1966
+ ON ${sql `(${{ quote: populate.className + '$' }} || ${_foreign('_id')})`} = ${_local(field)}
1967
+ `,
1968
+ };
1969
+ };
1970
+ const encodeRemix = (parent, remix) => sql `${remix?.className === parent.className ? sql `
1971
+ (SELECT * FROM ${{ identifier: remix.name }} UNION SELECT * FROM ${{ identifier: parent.className }})
1972
+ ` : { identifier: parent.className }}`;
1973
+ const encodeForeignField = (compiler, parent, foreignField, remix) => {
1974
+ const { paths: [colname, ...subpath], dataType } = resolveColumn(compiler.schema, parent.className, foreignField);
1975
+ const tempName = `_populate_$${compiler.nextIdx()}`;
1976
+ const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
1977
+ const _foreign = (field) => sql `${{ identifier: tempName }}.${{ identifier: field }}`;
1978
+ if (_.isEmpty(subpath) && isRelation(dataType) && dataType.foreignField) {
1979
+ const { joins, field, rows, array } = encodeForeignField(compiler, { className: dataType.target, name: tempName }, dataType.foreignField, remix);
1980
+ return {
1981
+ joins: [],
1982
+ field: sql `(
1983
+ SELECT ${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`}
1984
+ FROM ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
1985
+ ${!_.isEmpty(joins) ? { literal: joins, separator: '\n' } : sql ``}
1986
+ WHERE ${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${array || rows ? sql `ANY(${field})` : field}
1987
+ )`,
1988
+ array: false,
1989
+ rows: true,
1990
+ };
1991
+ }
1992
+ if (_.isEmpty(subpath)) {
1993
+ return {
1994
+ joins: [],
1995
+ field: sql `${{ identifier: parent.name }}.${{ identifier: foreignField }}`,
1996
+ array: isRelation(dataType),
1997
+ rows: false,
1998
+ };
1999
+ }
2000
+ if (!isPointer(dataType) && !isRelation(dataType))
2001
+ throw Error(`Invalid path: ${foreignField}`);
2002
+ const { joins, field, rows, array } = encodeForeignField(compiler, { className: dataType.target, name: tempName }, subpath.join('.'), remix);
2003
+ const cond = [];
2004
+ if (compiler.extraFilter) {
2005
+ const filter = compiler.extraFilter(dataType.target);
2006
+ cond.push(compiler._encodeFilter({ className: dataType.target, name: tempName }, filter));
2007
+ }
2008
+ if (isPointer(dataType)) {
2009
+ cond.push(sql `${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`} = ${_local(colname)}`);
2010
+ return {
2011
+ joins: [sql `
2012
+ LEFT JOIN ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
2013
+ ON ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2014
+ `, ...joins],
2015
+ field,
2016
+ array,
2017
+ rows,
2018
+ };
2019
+ }
2020
+ if (_.isNil(dataType.foreignField)) {
2021
+ cond.push(sql `${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`} = ANY(${_local(colname)})`);
2022
+ }
2023
+ else if (_isPointer(compiler.schema, dataType.target, dataType.foreignField)) {
2024
+ cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${_foreign(dataType.foreignField)}`);
2025
+ }
2026
+ else {
2027
+ cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ANY(${_foreign(dataType.foreignField)})`);
2028
+ }
2029
+ return {
2030
+ joins: [],
2031
+ field: sql `(
2032
+ SELECT ${array ? sql `UNNEST(${field})` : field}
2033
+ FROM ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
2034
+ ${!_.isEmpty(joins) ? { literal: joins, separator: '\n' } : sql ``}
2035
+ WHERE ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2036
+ )`,
2037
+ array: false,
2038
+ rows: true,
2039
+ };
2040
+ };
2041
+ const encodePopulate = (compiler, parent, remix) => {
2042
+ const _filter = _.compact([
2043
+ parent.filter && compiler._encodeFilter(parent, parent.filter),
2044
+ compiler.extraFilter && compiler._encodeFilter(parent, compiler.extraFilter(parent.className)),
2045
+ ]);
2046
+ const _populates = _.map(parent.populates, (populate, field) => selectPopulate(compiler, parent, populate, field));
2047
+ const _joins = _.compact(_.map(_populates, ({ join }) => join));
2048
+ const _includes = _.pickBy(parent.includes, v => isPrimitive(v));
2049
+ const { joins: _joins2 = [], field: _foreignField = undefined, rows = false, } = parent.foreignField ? encodeForeignField(compiler, {
2050
+ className: parent.className,
2051
+ name: parent.name,
2052
+ }, parent.foreignField, remix) : {};
2053
+ return _.reduce(parent.populates, (acc, populate) => ({
2054
+ ...encodePopulate(compiler, populate, remix),
2055
+ ...acc,
2056
+ }), {
2057
+ [parent.name]: sql `
2058
+ SELECT * FROM (
2059
+ SELECT
2060
+ ${{
2061
+ literal: [
2062
+ ..._.map(_.keys(_includes), colname => sql `${{ identifier: parent.name }}.${{ identifier: colname }}`),
2063
+ ..._.flatMap(_populates, ({ columns: column }) => column),
2064
+ ..._foreignField ? [sql `${rows ? sql `ARRAY(${_foreignField})` : _foreignField} AS ${{ identifier: parent.colname }}`] : [],
2065
+ ], separator: ',\n'
2066
+ }}
2067
+ FROM ${encodeRemix(parent, remix)} AS ${{ identifier: parent.name }}
2068
+ ${!_.isEmpty(_joins) || !_.isEmpty(_joins2) ? { literal: [..._joins, ..._joins2], separator: '\n' } : sql ``}
2069
+ ) AS ${{ identifier: parent.name }}
2070
+ ${!_.isEmpty(_filter) ? sql `WHERE ${{ literal: _.map(_.compact(_filter), x => sql `(${x})`), separator: ' AND ' }}` : sql ``}
2071
+ `,
2072
+ });
2073
+ };
2074
+
1877
2075
  //
1878
2076
  // selectors.ts
1879
2077
  //
@@ -2060,12 +2258,12 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2060
2258
  if (dataType === 'array' || (!_.isString(dataType) && dataType?.type === 'array')) {
2061
2259
  return sql `${element} <@ ${{ value: _encodeValue(expr.value) }}`;
2062
2260
  }
2063
- else if (relation) {
2261
+ if (relation) {
2064
2262
  if (!_.every(expr.value, x => x instanceof TObject && x.objectId))
2065
2263
  break;
2066
- return sql `ARRAY(${relation.sql((v) => sql `${v} ->> '_id'`)}) <@ ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2264
+ return sql `ARRAY(${relation.mapElem((v) => sql `${v} ->> '_id'`)}) <@ ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2067
2265
  }
2068
- else if (!dataType) {
2266
+ if (!dataType) {
2069
2267
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND ${element} <@ ${_encodeJsonValue(_encodeValue(expr.value))}`;
2070
2268
  }
2071
2269
  }
@@ -2078,12 +2276,12 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2078
2276
  if (dataType === 'array' || (!_.isString(dataType) && dataType?.type === 'array')) {
2079
2277
  return sql `${element} @> ${{ value: _encodeValue(expr.value) }}`;
2080
2278
  }
2081
- else if (relation) {
2279
+ if (relation) {
2082
2280
  if (!_.every(expr.value, x => x instanceof TObject && x.objectId))
2083
2281
  break;
2084
- return sql `ARRAY(${relation.sql((v) => sql `${v} ->> '_id'`)}) @> ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2282
+ return sql `ARRAY(${relation.mapElem((v) => sql `${v} ->> '_id'`)}) @> ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2085
2283
  }
2086
- else if (!dataType) {
2284
+ if (!dataType) {
2087
2285
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND ${element} @> ${_encodeJsonValue(_encodeValue(expr.value))}`;
2088
2286
  }
2089
2287
  }
@@ -2096,12 +2294,12 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2096
2294
  if (dataType === 'array' || (!_.isString(dataType) && dataType?.type === 'array')) {
2097
2295
  return sql `NOT ${element} && ${{ value: _encodeValue(expr.value) }}`;
2098
2296
  }
2099
- else if (relation) {
2297
+ if (relation) {
2100
2298
  if (!_.every(expr.value, x => x instanceof TObject && x.objectId))
2101
2299
  break;
2102
- return sql `NOT ARRAY(${relation.sql((v) => sql `${v} ->> '_id'`)}) && ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2300
+ return sql `NOT ARRAY(${relation.mapElem((v) => sql `${v} ->> '_id'`)}) && ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2103
2301
  }
2104
- else if (!dataType) {
2302
+ if (!dataType) {
2105
2303
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND NOT ${element} && ${_encodeJsonValue(_encodeValue(expr.value))}`;
2106
2304
  }
2107
2305
  }
@@ -2114,12 +2312,12 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2114
2312
  if (dataType === 'array' || (!_.isString(dataType) && dataType?.type === 'array')) {
2115
2313
  return sql `${element} && ${{ value: _encodeValue(expr.value) }}`;
2116
2314
  }
2117
- else if (relation) {
2315
+ if (relation) {
2118
2316
  if (!_.every(expr.value, x => x instanceof TObject && x.objectId))
2119
2317
  break;
2120
- return sql `ARRAY(${relation.sql((v) => sql `${v} ->> '_id'`)}) && ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2318
+ return sql `ARRAY(${relation.mapElem((v) => sql `${v} ->> '_id'`)}) && ARRAY[${_.map(expr.value, (x) => sql `${{ value: x.objectId }}`)}]`;
2121
2319
  }
2122
- else if (!dataType) {
2320
+ if (!dataType) {
2123
2321
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND ${element} && ${_encodeJsonValue(_encodeValue(expr.value))}`;
2124
2322
  }
2125
2323
  }
@@ -2135,7 +2333,7 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2135
2333
  if (_.isString(expr.value)) {
2136
2334
  return sql `${element} LIKE ${{ value: `%${expr.value.replace(/([\\_%])/g, '\\$1')}%` }}`;
2137
2335
  }
2138
- else if (_.isRegExp(expr.value)) {
2336
+ if (_.isRegExp(expr.value)) {
2139
2337
  if (expr.value.ignoreCase)
2140
2338
  return sql `${element} ~* ${{ value: expr.value.source }}`;
2141
2339
  return sql `${element} ~ ${{ value: expr.value.source }}`;
@@ -2145,7 +2343,7 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2145
2343
  if (_.isString(expr.value)) {
2146
2344
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'string' AND (${element} #>> '{}') LIKE ${{ value: `%${expr.value.replace(/([\\_%])/g, '\\$1')}%` }}`;
2147
2345
  }
2148
- else if (_.isRegExp(expr.value)) {
2346
+ if (_.isRegExp(expr.value)) {
2149
2347
  if (expr.value.ignoreCase)
2150
2348
  return sql `${element} ~* ${{ value: expr.value.source }}`;
2151
2349
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'string' AND (${element} #>> '{}') ~ ${{ value: expr.value.source }}`;
@@ -2159,7 +2357,7 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2159
2357
  if (dataType === 'string' || (!_.isString(dataType) && dataType?.type === 'string')) {
2160
2358
  return sql `${element} LIKE ${{ value: `${expr.value.replace(/([\\_%])/g, '\\$1')}%` }}`;
2161
2359
  }
2162
- else if (!dataType) {
2360
+ if (!dataType) {
2163
2361
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'string' AND (${element} #>> '{}') LIKE ${{ value: `${expr.value.replace(/([\\_%])/g, '\\$1')}%` }}`;
2164
2362
  }
2165
2363
  }
@@ -2170,7 +2368,7 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2170
2368
  if (dataType === 'string' || (!_.isString(dataType) && dataType?.type === 'string')) {
2171
2369
  return sql `${element} LIKE ${{ value: `%${expr.value.replace(/([\\_%])/g, '\\$1')}` }}`;
2172
2370
  }
2173
- else if (!dataType) {
2371
+ if (!dataType) {
2174
2372
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'string' AND (${element} #>> '{}') LIKE ${{ value: `%${expr.value.replace(/([\\_%])/g, '\\$1')}` }}`;
2175
2373
  }
2176
2374
  }
@@ -2181,10 +2379,10 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2181
2379
  if (dataType === 'string' || (!_.isString(dataType) && dataType?.type === 'string')) {
2182
2380
  return sql `COALESCE(length(${element}), 0) = ${{ value: expr.value }}`;
2183
2381
  }
2184
- else if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector' || dataType?.type === 'relation'))) {
2382
+ if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector' || dataType?.type === 'relation'))) {
2185
2383
  return sql `COALESCE(array_length(${element}, 1), 0) = ${{ value: expr.value }}`;
2186
2384
  }
2187
- else if (!dataType) {
2385
+ if (!dataType) {
2188
2386
  return sql `(
2189
2387
  CASE jsonb_typeof(${element})
2190
2388
  WHEN 'array' THEN jsonb_array_length(${element}) = ${{ value: expr.value }}
@@ -2201,10 +2399,10 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2201
2399
  if (dataType === 'string' || (!_.isString(dataType) && dataType?.type === 'string')) {
2202
2400
  return sql `COALESCE(length(${element}), 0) ${{ literal: expr.value ? '=' : '<>' }} 0`;
2203
2401
  }
2204
- else if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector' || dataType?.type === 'relation'))) {
2402
+ if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector' || dataType?.type === 'relation'))) {
2205
2403
  return sql `COALESCE(array_length(${element}, 1), 0) ${{ literal: expr.value ? '=' : '<>' }} 0`;
2206
2404
  }
2207
- else if (!dataType) {
2405
+ if (!dataType) {
2208
2406
  return sql `(
2209
2407
  CASE jsonb_typeof(${element})
2210
2408
  WHEN 'array' THEN jsonb_array_length(${element}) ${{ literal: expr.value ? '=' : '<>' }} 0
@@ -2218,23 +2416,34 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2218
2416
  {
2219
2417
  if (!(expr.value instanceof QuerySelector))
2220
2418
  break;
2221
- const tempName = `_expr_$${compiler.nextIdx()}`;
2222
- const filter = compiler._encodeFilter({ name: tempName, className: relation?.target }, expr.value);
2223
- if (!filter)
2224
- break;
2225
- if (relation) {
2419
+ if (relation?.populate && parent.className) {
2420
+ const tempName = `_populate_expr_$${compiler.nextIdx()}`;
2421
+ const filter = compiler._encodeFilter({
2422
+ name: tempName,
2423
+ className: relation.target,
2424
+ populates: relation.populate.populates,
2425
+ }, expr.value);
2426
+ if (!filter)
2427
+ break;
2226
2428
  return sql `NOT EXISTS(
2227
- SELECT * FROM (${relation.sql((v) => sql `${v} AS "$"`)}) AS ${{ identifier: tempName }}
2429
+ SELECT * FROM (${_selectRelationPopulate(compiler, {
2430
+ className: parent.className,
2431
+ name: parent.name,
2432
+ }, relation.populate, `$${field}`, false)}) AS ${{ identifier: tempName }}
2228
2433
  WHERE NOT (${filter})
2229
2434
  )`;
2230
2435
  }
2231
- else if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector'))) {
2436
+ const tempName = `_doller_expr_$${compiler.nextIdx()}`;
2437
+ const filter = compiler._encodeFilter({ name: tempName, className: relation?.target }, expr.value);
2438
+ if (!filter)
2439
+ break;
2440
+ if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector'))) {
2232
2441
  return sql `NOT EXISTS(
2233
2442
  SELECT * FROM (SELECT UNNEST AS "$" FROM UNNEST(${element})) AS ${{ identifier: tempName }}
2234
2443
  WHERE NOT (${filter})
2235
2444
  )`;
2236
2445
  }
2237
- else if (!dataType) {
2446
+ if (!dataType) {
2238
2447
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND NOT EXISTS(
2239
2448
  SELECT * FROM (SELECT value AS "$" FROM jsonb_array_elements(${element})) AS ${{ identifier: tempName }}
2240
2449
  WHERE NOT (${filter})
@@ -2245,23 +2454,34 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2245
2454
  {
2246
2455
  if (!(expr.value instanceof QuerySelector))
2247
2456
  break;
2248
- const tempName = `_expr_$${compiler.nextIdx()}`;
2249
- const filter = compiler._encodeFilter({ name: tempName, className: relation?.target }, expr.value);
2250
- if (!filter)
2251
- break;
2252
- if (relation) {
2457
+ if (relation?.populate && parent.className) {
2458
+ const tempName = `_populate_expr_$${compiler.nextIdx()}`;
2459
+ const filter = compiler._encodeFilter({
2460
+ name: tempName,
2461
+ className: relation.target,
2462
+ populates: relation.populate.populates,
2463
+ }, expr.value);
2464
+ if (!filter)
2465
+ break;
2253
2466
  return sql `EXISTS(
2254
- SELECT * FROM (${relation.sql((v) => sql `${v} AS "$"`)}) AS ${{ identifier: tempName }}
2467
+ SELECT * FROM (${_selectRelationPopulate(compiler, {
2468
+ className: parent.className,
2469
+ name: parent.name,
2470
+ }, relation.populate, `$${field}`, false)}) AS ${{ identifier: tempName }}
2255
2471
  WHERE ${filter}
2256
2472
  )`;
2257
2473
  }
2258
- else if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector'))) {
2474
+ const tempName = `_doller_expr_$${compiler.nextIdx()}`;
2475
+ const filter = compiler._encodeFilter({ name: tempName, className: relation?.target }, expr.value);
2476
+ if (!filter)
2477
+ break;
2478
+ if (dataType === 'array' || (!_.isString(dataType) && (dataType?.type === 'array' || dataType?.type === 'vector'))) {
2259
2479
  return sql `EXISTS(
2260
2480
  SELECT * FROM (SELECT UNNEST AS "$" FROM UNNEST(${element})) AS ${{ identifier: tempName }}
2261
2481
  WHERE ${filter}
2262
2482
  )`;
2263
2483
  }
2264
- else if (!dataType) {
2484
+ if (!dataType) {
2265
2485
  return sql `jsonb_typeof(${element}) ${nullSafeEqual()} 'array' AND EXISTS(
2266
2486
  SELECT * FROM (SELECT value AS "$" FROM jsonb_array_elements(${element})) AS ${{ identifier: tempName }}
2267
2487
  WHERE ${filter}
@@ -2272,215 +2492,6 @@ const encodeFieldExpression = (compiler, parent, field, expr) => {
2272
2492
  throw Error('Invalid expression');
2273
2493
  };
2274
2494
 
2275
- //
2276
- // populate.ts
2277
- //
2278
- // The MIT License
2279
- // Copyright (c) 2021 - 2024 O2ter Limited. All rights reserved.
2280
- //
2281
- // Permission is hereby granted, free of charge, to any person obtaining a copy
2282
- // of this software and associated documentation files (the "Software"), to deal
2283
- // in the Software without restriction, including without limitation the rights
2284
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2285
- // copies of the Software, and to permit persons to whom the Software is
2286
- // furnished to do so, subject to the following conditions:
2287
- //
2288
- // The above copyright notice and this permission notice shall be included in
2289
- // all copies or substantial portions of the Software.
2290
- //
2291
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2292
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2293
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2294
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2295
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2296
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2297
- // THE SOFTWARE.
2298
- //
2299
- const resolveSubpaths = (compiler, populate) => {
2300
- const subpaths = [];
2301
- for (const [name, type] of _.toPairs(populate.includes)) {
2302
- if (isPointer(type)) {
2303
- subpaths.push(..._.map(resolveSubpaths(compiler, populate.populates[name]), ({ path, type }) => ({
2304
- path: `${name}.${path}`,
2305
- type,
2306
- })));
2307
- }
2308
- else {
2309
- subpaths.push({
2310
- path: name,
2311
- type,
2312
- });
2313
- }
2314
- }
2315
- return subpaths;
2316
- };
2317
- const _isPointer = (schema, className, path) => {
2318
- let fields = schema[className].fields;
2319
- let last;
2320
- for (const key of _.toPath(path)) {
2321
- const dataType = fields[key];
2322
- if (_.isNil(dataType))
2323
- throw Error(`Invalid path: ${path}`);
2324
- if (isPrimitive(dataType) || isVector(dataType))
2325
- throw Error(`Invalid path: ${path}`);
2326
- if (isShape(dataType)) {
2327
- fields = dataType.shape;
2328
- continue;
2329
- }
2330
- if (dataType.type !== 'pointer')
2331
- return false;
2332
- if (_.isNil(schema[dataType.target]))
2333
- throw Error(`Invalid path: ${path}`);
2334
- fields = schema[dataType.target].fields;
2335
- last = dataType;
2336
- }
2337
- return last?.type === 'pointer';
2338
- };
2339
- const selectPopulate = (compiler, parent, populate, field) => {
2340
- const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
2341
- const _foreign = (field) => sql `${{ identifier: populate.name }}.${{ identifier: field }}`;
2342
- const subpaths = resolveSubpaths(compiler, populate);
2343
- const cond = [];
2344
- if (compiler.extraFilter) {
2345
- const filter = compiler.extraFilter(populate.className);
2346
- cond.push(compiler._encodeFilter(populate, filter));
2347
- }
2348
- if (populate.type === 'pointer') {
2349
- cond.push(sql `${sql `(${{ quote: populate.className + '$' }} || ${_foreign('_id')})`} = ${_local(field)}`);
2350
- return {
2351
- columns: _.map(subpaths, ({ path }) => sql `${{ identifier: populate.name }}.${{ identifier: path }} AS ${{ identifier: `${field}.${path}` }}`),
2352
- join: sql `
2353
- LEFT JOIN ${{ identifier: populate.name }}
2354
- ON ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2355
- `,
2356
- };
2357
- }
2358
- if (_.isNil(populate.foreignField)) {
2359
- cond.push(sql `${sql `(${{ quote: populate.className + '$' }} || ${_foreign('_id')})`} = ANY(${_local(field)})`);
2360
- }
2361
- else if (_isPointer(compiler.schema, populate.className, populate.foreignField)) {
2362
- cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${_foreign(populate.colname)}`);
2363
- }
2364
- else {
2365
- cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ANY(${_foreign(populate.colname)})`);
2366
- }
2367
- return {
2368
- columns: [sql `
2369
- ARRAY(
2370
- SELECT to_jsonb(${{ identifier: populate.name }}) FROM (
2371
- SELECT ${_.map(subpaths, ({ path, type }) => _encodePopulateInclude(populate.name, path, type))}
2372
- FROM ${{ identifier: populate.name }} WHERE ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2373
- ${!_.isEmpty(populate.sort) ? sql `ORDER BY ${compiler._encodeSort(populate.sort, { className: populate.className, name: populate.name })}` : sql ``}
2374
- ${populate.limit ? sql `LIMIT ${{ literal: `${populate.limit}` }}` : sql ``}
2375
- ${populate.skip ? sql `OFFSET ${{ literal: `${populate.skip}` }}` : sql ``}
2376
- ${compiler.selectLock ? compiler.isUpdate ? sql `FOR UPDATE NOWAIT` : sql `FOR SHARE NOWAIT` : sql ``}
2377
- ) ${{ identifier: populate.name }}
2378
- ) AS ${{ identifier: field }}
2379
- `],
2380
- };
2381
- };
2382
- const encodeRemix = (parent, remix) => sql `${remix?.className === parent.className ? sql `
2383
- (SELECT * FROM ${{ identifier: remix.name }} UNION SELECT * FROM ${{ identifier: parent.className }})
2384
- ` : { identifier: parent.className }}`;
2385
- const encodeForeignField = (compiler, parent, foreignField, remix) => {
2386
- const { paths: [colname, ...subpath], dataType } = resolveColumn(compiler.schema, parent.className, foreignField);
2387
- const tempName = `_populate_$${compiler.nextIdx()}`;
2388
- const _local = (field) => sql `${{ identifier: parent.name }}.${{ identifier: field }}`;
2389
- const _foreign = (field) => sql `${{ identifier: tempName }}.${{ identifier: field }}`;
2390
- if (_.isEmpty(subpath) && isRelation(dataType) && dataType.foreignField) {
2391
- const { joins, field, rows, array } = encodeForeignField(compiler, { className: dataType.target, name: tempName }, dataType.foreignField, remix);
2392
- return {
2393
- joins: [],
2394
- field: sql `(
2395
- SELECT ${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`}
2396
- FROM ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
2397
- ${!_.isEmpty(joins) ? { literal: joins, separator: '\n' } : sql ``}
2398
- WHERE ${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${array || rows ? sql `ANY(${field})` : field}
2399
- )`,
2400
- array: false,
2401
- rows: true,
2402
- };
2403
- }
2404
- if (_.isEmpty(subpath)) {
2405
- return {
2406
- joins: [],
2407
- field: sql `${{ identifier: parent.name }}.${{ identifier: foreignField }}`,
2408
- array: isRelation(dataType),
2409
- rows: false,
2410
- };
2411
- }
2412
- if (!isPointer(dataType) && !isRelation(dataType))
2413
- throw Error(`Invalid path: ${foreignField}`);
2414
- const { joins, field, rows, array } = encodeForeignField(compiler, { className: dataType.target, name: tempName }, subpath.join('.'), remix);
2415
- const cond = [];
2416
- if (compiler.extraFilter) {
2417
- const filter = compiler.extraFilter(dataType.target);
2418
- cond.push(compiler._encodeFilter({ className: dataType.target, name: tempName }, filter));
2419
- }
2420
- if (isPointer(dataType)) {
2421
- cond.push(sql `${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`} = ${_local(colname)}`);
2422
- return {
2423
- joins: [sql `
2424
- LEFT JOIN ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
2425
- ON ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2426
- `, ...joins],
2427
- field,
2428
- array,
2429
- rows,
2430
- };
2431
- }
2432
- if (_.isNil(dataType.foreignField)) {
2433
- cond.push(sql `${sql `(${{ quote: dataType.target + '$' }} || ${_foreign('_id')})`} = ANY(${_local(colname)})`);
2434
- }
2435
- else if (_isPointer(compiler.schema, dataType.target, dataType.foreignField)) {
2436
- cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ${_foreign(dataType.foreignField)}`);
2437
- }
2438
- else {
2439
- cond.push(sql `${sql `(${{ quote: parent.className + '$' }} || ${_local('_id')})`} = ANY(${_foreign(dataType.foreignField)})`);
2440
- }
2441
- return {
2442
- joins: [],
2443
- field: sql `(
2444
- SELECT ${array ? sql `UNNEST(${field})` : field}
2445
- FROM ${encodeRemix({ className: dataType.target }, remix)} AS ${{ identifier: tempName }}
2446
- ${!_.isEmpty(joins) ? { literal: joins, separator: '\n' } : sql ``}
2447
- WHERE ${{ literal: _.map(_.compact(cond), x => sql `(${x})`), separator: ' AND ' }}
2448
- )`,
2449
- array: false,
2450
- rows: true,
2451
- };
2452
- };
2453
- const encodePopulate = (compiler, parent, remix) => {
2454
- const _filter = parent.filter && compiler._encodeFilter(parent, parent.filter);
2455
- const _populates = _.map(parent.populates, (populate, field) => selectPopulate(compiler, parent, populate, field));
2456
- const _joins = _.compact(_.map(_populates, ({ join }) => join));
2457
- const _includes = _.pickBy(parent.includes, v => isPrimitive(v));
2458
- const { joins: _joins2 = [], field: _foreignField = undefined, rows = false, } = parent.foreignField ? encodeForeignField(compiler, {
2459
- className: parent.className,
2460
- name: parent.name,
2461
- }, parent.foreignField, remix) : {};
2462
- return _.reduce(parent.populates, (acc, populate) => ({
2463
- ...encodePopulate(compiler, populate, remix),
2464
- ...acc,
2465
- }), {
2466
- [parent.name]: sql `
2467
- SELECT * FROM (
2468
- SELECT
2469
- ${{
2470
- literal: [
2471
- ..._.map(_.keys(_includes), colname => sql `${{ identifier: parent.name }}.${{ identifier: colname }}`),
2472
- ..._.flatMap(_populates, ({ columns: column }) => column),
2473
- ..._foreignField ? [sql `${rows ? sql `ARRAY(${_foreignField})` : _foreignField} AS ${{ identifier: parent.colname }}`] : [],
2474
- ], separator: ',\n'
2475
- }}
2476
- FROM ${encodeRemix(parent, remix)} AS ${{ identifier: parent.name }}
2477
- ${!_.isEmpty(_joins) || !_.isEmpty(_joins2) ? { literal: [..._joins, ..._joins2], separator: '\n' } : sql ``}
2478
- ) AS ${{ identifier: parent.name }}
2479
- ${_filter ? sql `WHERE ${_filter}` : sql ``}
2480
- `,
2481
- });
2482
- };
2483
-
2484
2495
  //
2485
2496
  // relation.ts
2486
2497
  //