@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 +7 -0
- package/dist/dataAccess/dataAccessTypes.d.ts +10 -3
- package/dist/helpers/filterHelper.d.ts +4 -1
- package/dist/helpers/metaHelper.d.ts +2 -0
- package/dist/index.js +149 -43
- package/package.json +1 -1
- package/src/dataAccess/dataAccessTypes.ts +12 -0
- package/src/helpers/filterHelper.ts +89 -1
- package/src/helpers/metaHelper.ts +9 -0
- package/src/plugins/metaManage/MetaManagePlugin.ts +4 -1
- package/src/queryBuilder/queryBuilder.ts +32 -4
package/CHANGELOG.md
ADDED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
@@ -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 {
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|