@ruiapp/rapid-core 0.2.2 → 0.2.4

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @ruiapp/rapid-core
2
+
3
+ ## 0.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 146a99d: add between filterMode, range is deprecated
@@ -2,12 +2,13 @@ export type DataAccessPgColumnTypes = "int4" | "int8" | "float4" | "float8" | "d
2
2
  export type RowFilterRelationalOperators = "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "contains" | "notContains" | "containsCS" | "notContainsCS" | "startsWith" | "notStartsWith" | "endsWith" | "notEndsWith";
3
3
  export type RowFilterArrayOperators = "arrayContains" | "arrayOverlap";
4
4
  export type RowFilterSetOperators = "in" | "notIn";
5
+ export type RowFilterRangeOperators = "between";
5
6
  export type RowFilterLogicalOperators = "or" | "and";
6
7
  export type RowFilterUnaryOperators = "null" | "notNull";
7
8
  export type RowFilterExistenceOperators = "exists" | "notExists";
8
- export type RowFilterOperators = RowFilterRelationalOperators | RowFilterArrayOperators | RowFilterSetOperators | RowFilterLogicalOperators | RowFilterUnaryOperators | RowFilterExistenceOperators;
9
- export type RowFilterOptions = FindRowRelationalFilterOptions | FindRowArrayFilterOptions | FindRowSetFilterOptions | FindRowLogicalFilterOptions | FindRowUnaryFilterOptions | FindRowExistenceFilterOptions;
10
- export type RowNonRelationPropertyFilterOptions = FindRowRelationalFilterOptions | FindRowArrayFilterOptions | FindRowSetFilterOptions | FindRowUnaryFilterOptions;
9
+ export type RowFilterOperators = RowFilterRelationalOperators | RowFilterArrayOperators | RowFilterSetOperators | RowFilterRangeOperators | RowFilterLogicalOperators | RowFilterUnaryOperators | RowFilterExistenceOperators;
10
+ export type RowFilterOptions = FindRowRelationalFilterOptions | FindRowArrayFilterOptions | FindRowSetFilterOptions | FindRowRangeFilterOptions | FindRowLogicalFilterOptions | FindRowUnaryFilterOptions | FindRowExistenceFilterOptions;
11
+ export type RowNonRelationPropertyFilterOptions = FindRowRelationalFilterOptions | FindRowArrayFilterOptions | FindRowSetFilterOptions | FindRowRangeFilterOptions | FindRowUnaryFilterOptions;
11
12
  export type ColumnSelectOptions = string | ColumnNameWithTableName;
12
13
  export type ColumnNameWithTableName = {
13
14
  name: string;
@@ -38,6 +39,12 @@ export interface FindRowSetFilterOptions {
38
39
  value: any[];
39
40
  itemType?: string;
40
41
  }
42
+ export interface FindRowRangeFilterOptions {
43
+ field: ColumnSelectOptions;
44
+ operator: RowFilterRangeOperators;
45
+ value: any[];
46
+ itemType?: string;
47
+ }
41
48
  export interface FindRowLogicalFilterOptions {
42
49
  operator: RowFilterLogicalOperators;
43
50
  filters: RowFilterOptions[];
@@ -1,2 +1,5 @@
1
- import { EntityFilterOptions } from "../types";
1
+ import { EntityFilterOptions, RpdDataModel, RpdDataModelIndexOptions } from "../types";
2
+ import { RowFilterOptions } from "../dataAccess/dataAccessTypes";
2
3
  export declare function removeFiltersWithNullValue(filters?: EntityFilterOptions[]): EntityFilterOptions[];
4
+ export declare function convertModelIndexConditionsToRowFilterOptions(model: RpdDataModel, filters: RpdDataModelIndexOptions[]): RowFilterOptions[];
5
+ export declare function replaceModelIndexConditionEntityPropertyWithTableColumn(model: RpdDataModel, filter: RpdDataModelIndexOptions): RowFilterOptions;
@@ -7,4 +7,6 @@ export declare function getEntityProperties(server: IRpdServer, model: RpdDataMo
7
7
  export declare function getEntityPropertiesIncludingBase(server: IRpdServer, model: RpdDataModel): RpdDataModelProperty[];
8
8
  export declare function getEntityPropertyByCode(server: IRpdServer, model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
9
9
  export declare function getEntityProperty(server: IRpdServer, model: RpdDataModel, predicate: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty | undefined;
10
+ export declare function getEntityOwnPropertyByCode(model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined;
11
+ export declare function getEntityOwnProperty(model: RpdDataModel, predicate: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty | undefined;
10
12
  export declare function getEntityPropertyByFieldName(server: IRpdServer, model: RpdDataModel, fieldName: string): RpdDataModelProperty | undefined;
package/dist/index.js CHANGED
@@ -202,8 +202,8 @@ const pgPropertyTypeColumnMap = {
202
202
  "image[]": "jsonb",
203
203
  };
204
204
 
205
- const objLeftQuoteChar = '"';
206
- const objRightQuoteChar = '"';
205
+ const objLeftQuoteChar = "\"";
206
+ const objRightQuoteChar = "\"";
207
207
  const relationalOperatorsMap = new Map([
208
208
  ["eq", "="],
209
209
  ["ne", "<>"],
@@ -369,7 +369,7 @@ class QueryBuilder {
369
369
  paramToLiteral: false,
370
370
  };
371
371
  let { filters } = options;
372
- let command = 'SELECT COUNT(*)::int as "count" FROM ';
372
+ let command = "SELECT COUNT(*)::int as \"count\" FROM ";
373
373
  command += this.quoteTable(model);
374
374
  if (filters && filters.length) {
375
375
  command += " WHERE ";
@@ -389,7 +389,7 @@ class QueryBuilder {
389
389
  paramToLiteral: false,
390
390
  };
391
391
  let { filters } = options;
392
- let command = 'SELECT COUNT(*)::int as "count" FROM ';
392
+ let command = "SELECT COUNT(*)::int as \"count\" FROM ";
393
393
  command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(baseModel.tableName)}.id`;
394
394
  if (filters && filters.length) {
395
395
  command += " WHERE ";
@@ -535,6 +535,9 @@ function buildFilterQuery(level, ctx, filter) {
535
535
  else if (operator === "in" || operator === "notIn") {
536
536
  return buildInFilterQuery(ctx, filter);
537
537
  }
538
+ else if (operator === "between") {
539
+ return buildRangeFilterQuery(ctx, filter);
540
+ }
538
541
  else if (operator === "contains") {
539
542
  return buildContainsFilterQuery(ctx, filter);
540
543
  }
@@ -607,6 +610,24 @@ function buildInFilterQuery(ctx, filter) {
607
610
  }
608
611
  return command;
609
612
  }
613
+ function buildRangeFilterQuery(ctx, filter) {
614
+ let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
615
+ if (filter.operator === "between") {
616
+ if (filter.value.length != 2) {
617
+ throw new Error(`Filter operator '${filter.operator}' need two values.`);
618
+ }
619
+ command += " BETWEEN ";
620
+ ctx.params.push(filter.value[0]);
621
+ command += `$${ctx.params.length}`;
622
+ command += " AND ";
623
+ ctx.params.push(filter.value[1]);
624
+ command += `$${ctx.params.length}`;
625
+ }
626
+ else {
627
+ throw new Error(`Filter operator '${filter.operator}' is not supported.`);
628
+ }
629
+ return command;
630
+ }
610
631
  function buildContainsFilterQuery(ctx, filter) {
611
632
  let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
612
633
  command += " LIKE ";
@@ -2064,6 +2085,13 @@ function getEntityProperty(server, model, predicate) {
2064
2085
  }
2065
2086
  return property;
2066
2087
  }
2088
+ function getEntityOwnPropertyByCode(model, propertyCode) {
2089
+ return getEntityOwnProperty(model, (e) => e.code === propertyCode);
2090
+ }
2091
+ function getEntityOwnProperty(model, predicate) {
2092
+ let property = model.properties.find(predicate);
2093
+ return property;
2094
+ }
2067
2095
  function getEntityPropertyByFieldName(server, model, fieldName) {
2068
2096
  let property = getEntityPropertyByCode(server, model, fieldName);
2069
2097
  if (!property) {
@@ -4150,6 +4178,120 @@ var getMetaModelDetail = /*#__PURE__*/Object.freeze({
4150
4178
  handler: handler$r
4151
4179
  });
4152
4180
 
4181
+ function removeFiltersWithNullValue(filters) {
4182
+ const result = [];
4183
+ if (!filters) {
4184
+ return result;
4185
+ }
4186
+ for (const filter of filters) {
4187
+ const { operator } = filter;
4188
+ switch (operator) {
4189
+ case "null":
4190
+ case "notNull":
4191
+ result.push(filter);
4192
+ break;
4193
+ case "exists":
4194
+ case "notExists":
4195
+ case "and":
4196
+ case "or":
4197
+ const transformedFilter = transformFilterWithSubFilters(filter);
4198
+ if (transformedFilter !== null) {
4199
+ result.push(transformedFilter);
4200
+ }
4201
+ break;
4202
+ default:
4203
+ if (!isNullOrUndefined(filter.value)) {
4204
+ result.push(filter);
4205
+ }
4206
+ }
4207
+ }
4208
+ return result;
4209
+ }
4210
+ function transformFilterWithSubFilters(filter) {
4211
+ const subFilters = removeFiltersWithNullValue(filter.filters);
4212
+ if (!subFilters.length) {
4213
+ return null;
4214
+ }
4215
+ filter.filters = subFilters;
4216
+ return filter;
4217
+ }
4218
+ function convertModelIndexConditionsToRowFilterOptions(model, filters) {
4219
+ if (!filters || !filters.length) {
4220
+ return [];
4221
+ }
4222
+ const replacedFilters = [];
4223
+ for (const filter of filters) {
4224
+ const { operator } = filter;
4225
+ if (operator === "and" || operator === "or") {
4226
+ replacedFilters.push({
4227
+ operator: operator,
4228
+ filters: convertModelIndexConditionsToRowFilterOptions(model, filter.filters),
4229
+ });
4230
+ }
4231
+ else {
4232
+ replacedFilters.push(replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter));
4233
+ }
4234
+ }
4235
+ return replacedFilters;
4236
+ }
4237
+ function replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter) {
4238
+ const { operator } = filter;
4239
+ const filterField = filter.field;
4240
+ let property = getEntityOwnPropertyByCode(model, filterField);
4241
+ let filterValue = filter.value;
4242
+ let columnName = "";
4243
+ if (property) {
4244
+ if (isOneRelationProperty(property)) {
4245
+ columnName = property.targetIdColumnName;
4246
+ if (lodash.isPlainObject(filterValue)) {
4247
+ filterValue = filterValue.id;
4248
+ }
4249
+ }
4250
+ else if (isManyRelationProperty(property)) {
4251
+ throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
4252
+ }
4253
+ else {
4254
+ columnName = property.columnName || property.code;
4255
+ }
4256
+ }
4257
+ else if (operator === "exists" || operator === "notExists") {
4258
+ throw new Error(`"exists" and "notExists" operators are not supported in index conditions.`);
4259
+ }
4260
+ else {
4261
+ property = getEntityOwnProperty(model, (property) => {
4262
+ return property.columnName === filterField;
4263
+ });
4264
+ if (property) {
4265
+ columnName = property.columnName;
4266
+ }
4267
+ else {
4268
+ // may be relation property.
4269
+ property = getEntityOwnProperty(model, (property) => {
4270
+ return property.targetIdColumnName === filterField;
4271
+ });
4272
+ if (property) {
4273
+ if (isManyRelationProperty(property)) {
4274
+ throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
4275
+ }
4276
+ columnName = property.targetIdColumnName;
4277
+ if (lodash.isPlainObject(filterValue)) {
4278
+ filterValue = filterValue.id;
4279
+ }
4280
+ }
4281
+ else {
4282
+ throw new Error(`Unknown field "${filterField}" in index conditions.`);
4283
+ }
4284
+ }
4285
+ }
4286
+ // TODO: do not use `any` here
4287
+ return {
4288
+ operator: filter.operator,
4289
+ field: columnName,
4290
+ value: filterValue,
4291
+ itemType: filter.itemType,
4292
+ };
4293
+ }
4294
+
4153
4295
  /**
4154
4296
  * Meta manager plugin
4155
4297
  */
@@ -4431,6 +4573,7 @@ async function syncDatabaseSchema(server, applicationConfig) {
4431
4573
  if (!model.indexes || !model.indexes.length) {
4432
4574
  continue;
4433
4575
  }
4576
+ logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
4434
4577
  for (const index of model.indexes) {
4435
4578
  const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
4436
4579
  await server.tryQueryDatabaseObject(sqlCreateIndex, []);
@@ -4508,7 +4651,8 @@ function generateTableIndexDDL(queryBuilder, server, model, index) {
4508
4651
  tableName: model.tableName,
4509
4652
  })} (${indexColumns.join(", ")})`;
4510
4653
  if (index.conditions) {
4511
- ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, index.conditions)}`;
4654
+ const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(model, index.conditions);
4655
+ ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
4512
4656
  }
4513
4657
  return ddl;
4514
4658
  }
@@ -4537,44 +4681,6 @@ async function runCollectionEntityActionHandler(ctx, options, code, handleEntity
4537
4681
  }
4538
4682
  }
4539
4683
 
4540
- function removeFiltersWithNullValue(filters) {
4541
- const result = [];
4542
- if (!filters) {
4543
- return result;
4544
- }
4545
- for (const filter of filters) {
4546
- const { operator } = filter;
4547
- switch (operator) {
4548
- case "null":
4549
- case "notNull":
4550
- result.push(filter);
4551
- break;
4552
- case "exists":
4553
- case "notExists":
4554
- case "and":
4555
- case "or":
4556
- const transformedFilter = transformFilterWithSubFilters(filter);
4557
- if (transformedFilter !== null) {
4558
- result.push(transformedFilter);
4559
- }
4560
- break;
4561
- default:
4562
- if (!isNullOrUndefined(filter.value)) {
4563
- result.push(filter);
4564
- }
4565
- }
4566
- }
4567
- return result;
4568
- }
4569
- function transformFilterWithSubFilters(filter) {
4570
- const subFilters = removeFiltersWithNullValue(filter.filters);
4571
- if (!subFilters.length) {
4572
- return null;
4573
- }
4574
- filter.filters = subFilters;
4575
- return filter;
4576
- }
4577
-
4578
4684
  const code$q = "findCollectionEntities";
4579
4685
  async function handler$q(plugin, ctx, options) {
4580
4686
  await runCollectionEntityActionHandler(ctx, options, code$q, async (entityManager, input) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,6 +32,8 @@ export type RowFilterArrayOperators = "arrayContains" | "arrayOverlap";
32
32
 
33
33
  export type RowFilterSetOperators = "in" | "notIn";
34
34
 
35
+ export type RowFilterRangeOperators = "between";
36
+
35
37
  export type RowFilterLogicalOperators = "or" | "and";
36
38
 
37
39
  export type RowFilterUnaryOperators = "null" | "notNull";
@@ -42,6 +44,7 @@ export type RowFilterOperators =
42
44
  | RowFilterRelationalOperators
43
45
  | RowFilterArrayOperators
44
46
  | RowFilterSetOperators
47
+ | RowFilterRangeOperators
45
48
  | RowFilterLogicalOperators
46
49
  | RowFilterUnaryOperators
47
50
  | RowFilterExistenceOperators;
@@ -50,6 +53,7 @@ export type RowFilterOptions =
50
53
  | FindRowRelationalFilterOptions
51
54
  | FindRowArrayFilterOptions
52
55
  | FindRowSetFilterOptions
56
+ | FindRowRangeFilterOptions
53
57
  | FindRowLogicalFilterOptions
54
58
  | FindRowUnaryFilterOptions
55
59
  | FindRowExistenceFilterOptions;
@@ -58,6 +62,7 @@ export type RowNonRelationPropertyFilterOptions =
58
62
  | FindRowRelationalFilterOptions
59
63
  | FindRowArrayFilterOptions
60
64
  | FindRowSetFilterOptions
65
+ | FindRowRangeFilterOptions
61
66
  | FindRowUnaryFilterOptions;
62
67
 
63
68
  export type ColumnSelectOptions = string | ColumnNameWithTableName;
@@ -98,6 +103,13 @@ export interface FindRowSetFilterOptions {
98
103
  itemType?: string;
99
104
  }
100
105
 
106
+ export interface FindRowRangeFilterOptions {
107
+ field: ColumnSelectOptions;
108
+ operator: RowFilterRangeOperators;
109
+ value: any[];
110
+ itemType?: string;
111
+ }
112
+
101
113
  export interface FindRowLogicalFilterOptions {
102
114
  operator: RowFilterLogicalOperators;
103
115
  filters: RowFilterOptions[];
@@ -1,5 +1,17 @@
1
- import { EntityFilterOptions, FindEntityExistenceFilterOptions, FindEntityLogicalFilterOptions } from "~/types";
1
+ import {
2
+ EntityFilterOptions,
3
+ EntityNonRelationPropertyFilterOptions,
4
+ FindEntityExistenceFilterOptions,
5
+ FindEntityLogicalFilterOptions,
6
+ RpdDataModel,
7
+ RpdDataModelIndexOptions,
8
+ RpdDataModelProperty,
9
+ } from "~/types";
2
10
  import { isNullOrUndefined } from "~/utilities/typeUtility";
11
+ import { getEntityOwnProperty, getEntityOwnPropertyByCode, isManyRelationProperty, isOneRelationProperty } from "./metaHelper";
12
+ import { IRpdServer } from "~/core/server";
13
+ import { isPlainObject } from "lodash";
14
+ import { RowFilterOptions } from "~/dataAccess/dataAccessTypes";
3
15
 
4
16
  export function removeFiltersWithNullValue(filters?: EntityFilterOptions[]) {
5
17
  const result: EntityFilterOptions[] = [];
@@ -45,3 +57,79 @@ function transformFilterWithSubFilters(
45
57
  filter.filters = subFilters;
46
58
  return filter;
47
59
  }
60
+
61
+ export function convertModelIndexConditionsToRowFilterOptions(model: RpdDataModel, filters: RpdDataModelIndexOptions[]) {
62
+ if (!filters || !filters.length) {
63
+ return [];
64
+ }
65
+
66
+ const replacedFilters: RowFilterOptions[] = [];
67
+ for (const filter of filters) {
68
+ const { operator } = filter;
69
+ if (operator === "and" || operator === "or") {
70
+ replacedFilters.push({
71
+ operator: operator,
72
+ filters: convertModelIndexConditionsToRowFilterOptions(model, filter.filters),
73
+ });
74
+ } else {
75
+ replacedFilters.push(replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter));
76
+ }
77
+ }
78
+ return replacedFilters;
79
+ }
80
+ export function replaceModelIndexConditionEntityPropertyWithTableColumn(model: RpdDataModel, filter: RpdDataModelIndexOptions): RowFilterOptions {
81
+ const { operator } = filter;
82
+ const filterField = (filter as EntityNonRelationPropertyFilterOptions).field;
83
+ let property: RpdDataModelProperty = getEntityOwnPropertyByCode(model, filterField);
84
+
85
+ let filterValue = (filter as any).value;
86
+
87
+ let columnName = "";
88
+ if (property) {
89
+ if (isOneRelationProperty(property)) {
90
+ columnName = property.targetIdColumnName;
91
+ if (isPlainObject(filterValue)) {
92
+ filterValue = filterValue.id;
93
+ }
94
+ } else if (isManyRelationProperty(property)) {
95
+ throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
96
+ } else {
97
+ columnName = property.columnName || property.code;
98
+ }
99
+ } else if ((operator as any) === "exists" || (operator as any) === "notExists") {
100
+ throw new Error(`"exists" and "notExists" operators are not supported in index conditions.`);
101
+ } else {
102
+ property = getEntityOwnProperty(model, (property) => {
103
+ return property.columnName === filterField;
104
+ });
105
+
106
+ if (property) {
107
+ columnName = property.columnName;
108
+ } else {
109
+ // may be relation property.
110
+ property = getEntityOwnProperty(model, (property) => {
111
+ return property.targetIdColumnName === filterField;
112
+ });
113
+
114
+ if (property) {
115
+ if (isManyRelationProperty(property)) {
116
+ throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
117
+ }
118
+ columnName = property.targetIdColumnName;
119
+ if (isPlainObject(filterValue)) {
120
+ filterValue = filterValue.id;
121
+ }
122
+ } else {
123
+ throw new Error(`Unknown field "${filterField}" in index conditions.`);
124
+ }
125
+ }
126
+ }
127
+
128
+ // TODO: do not use `any` here
129
+ return {
130
+ operator: filter.operator,
131
+ field: columnName,
132
+ value: filterValue,
133
+ itemType: (filter as any).itemType,
134
+ } as RowFilterOptions;
135
+ }
@@ -66,6 +66,15 @@ export function getEntityProperty(
66
66
  return property;
67
67
  }
68
68
 
69
+ export function getEntityOwnPropertyByCode(model: RpdDataModel, propertyCode: string): RpdDataModelProperty | undefined {
70
+ return getEntityOwnProperty(model, (e) => e.code === propertyCode);
71
+ }
72
+
73
+ export function getEntityOwnProperty(model: RpdDataModel, predicate: (item: RpdDataModelProperty) => boolean): RpdDataModelProperty | undefined {
74
+ let property = model.properties.find(predicate);
75
+ return property;
76
+ }
77
+
69
78
  export function getEntityPropertyByFieldName(server: IRpdServer, model: RpdDataModel, fieldName: string): RpdDataModelProperty | undefined {
70
79
  let property = getEntityPropertyByCode(server, model, fieldName);
71
80
  if (!property) {
@@ -30,6 +30,7 @@ import { find, isString, map } from "lodash";
30
30
  import { getEntityPropertiesIncludingBase, getEntityPropertyByCode, isOneRelationProperty, isRelationProperty } from "~/helpers/metaHelper";
31
31
  import { DataAccessPgColumnTypes } from "~/dataAccess/dataAccessTypes";
32
32
  import { pgPropertyTypeColumnMap } from "~/dataAccess/columnTypeMapper";
33
+ import { convertModelIndexConditionsToRowFilterOptions } from "~/helpers/filterHelper";
33
34
 
34
35
  class MetaManager implements RapidPlugin {
35
36
  get code(): string {
@@ -388,6 +389,7 @@ async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdAppl
388
389
  continue;
389
390
  }
390
391
 
392
+ logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
391
393
  for (const index of model.indexes) {
392
394
  const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
393
395
  await server.tryQueryDatabaseObject(sqlCreateIndex, []);
@@ -493,7 +495,8 @@ function generateTableIndexDDL(queryBuilder: IQueryBuilder, server: IRpdServer,
493
495
  })} (${indexColumns.join(", ")})`;
494
496
 
495
497
  if (index.conditions) {
496
- ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, index.conditions)}`;
498
+ const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(model, index.conditions);
499
+ ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
497
500
  }
498
501
 
499
502
  return ddl;
@@ -15,11 +15,12 @@ import {
15
15
  ColumnNameWithTableName,
16
16
  DataAccessPgColumnTypes,
17
17
  FindRowArrayFilterOptions,
18
+ FindRowRangeFilterOptions,
18
19
  } from "~/dataAccess/dataAccessTypes";
19
20
  import { pgPropertyTypeColumnMap } from "~/dataAccess/columnTypeMapper";
20
21
 
21
- const objLeftQuoteChar = '"';
22
- const objRightQuoteChar = '"';
22
+ const objLeftQuoteChar = "\"";
23
+ const objRightQuoteChar = "\"";
23
24
 
24
25
  const relationalOperatorsMap = new Map<RowFilterRelationalOperators, string>([
25
26
  ["eq", "="],
@@ -222,7 +223,7 @@ export default class QueryBuilder {
222
223
  paramToLiteral: false,
223
224
  };
224
225
  let { filters } = options;
225
- let command = 'SELECT COUNT(*)::int as "count" FROM ';
226
+ let command = "SELECT COUNT(*)::int as \"count\" FROM ";
226
227
 
227
228
  command += this.quoteTable(model);
228
229
 
@@ -246,7 +247,7 @@ export default class QueryBuilder {
246
247
  paramToLiteral: false,
247
248
  };
248
249
  let { filters } = options;
249
- let command = 'SELECT COUNT(*)::int as "count" FROM ';
250
+ let command = "SELECT COUNT(*)::int as \"count\" FROM ";
250
251
 
251
252
  command += `${this.quoteTable(derivedModel)} LEFT JOIN ${this.quoteTable(baseModel)} ON ${this.quoteObject(derivedModel.tableName)}.id = ${this.quoteObject(
252
253
  baseModel.tableName,
@@ -413,6 +414,8 @@ function buildFilterQuery(level: number, ctx: BuildQueryContext, filter: RowFilt
413
414
  return buildUnaryFilterQuery(ctx, filter);
414
415
  } else if (operator === "in" || operator === "notIn") {
415
416
  return buildInFilterQuery(ctx, filter);
417
+ } else if (operator === "between") {
418
+ return buildRangeFilterQuery(ctx, filter);
416
419
  } else if (operator === "contains") {
417
420
  return buildContainsFilterQuery(ctx, filter);
418
421
  } else if (operator === "notContains") {
@@ -482,6 +485,31 @@ function buildInFilterQuery(ctx: BuildQueryContext, filter: FindRowSetFilterOpti
482
485
  return command;
483
486
  }
484
487
 
488
+ function buildRangeFilterQuery(ctx: BuildQueryContext, filter: FindRowRangeFilterOptions) {
489
+ let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
490
+
491
+ if (filter.operator === "between") {
492
+ if (filter.value.length != 2) {
493
+ throw new Error(`Filter operator '${filter.operator}' need two values.`);
494
+ }
495
+
496
+ command += " BETWEEN ";
497
+
498
+ ctx.params.push(filter.value[0]);
499
+ command += `$${ctx.params.length}`;
500
+
501
+ command += " AND "
502
+
503
+ ctx.params.push(filter.value[1]);
504
+ command += `$${ctx.params.length}`;
505
+
506
+ } else {
507
+ throw new Error(`Filter operator '${filter.operator}' is not supported.`);
508
+ }
509
+
510
+ return command;
511
+ }
512
+
485
513
  function buildContainsFilterQuery(ctx: BuildQueryContext, filter: FindRowRelationalFilterOptions) {
486
514
  let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
487
515