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